江科大51单片机学习笔记

单片机

​ 单片机不是完成某一个逻辑功能的芯片,而是把一个计算机系统集成到一个芯片上。相当于一个微型的计算机。

命名规则:

51单片机

 51单片机是对所有兼容Intel8031指令系统的单片机的统称,这一系列的单片机的始祖是Intel的8031单片机,后来随着flash ROM技术的发展,8031单片机取得了长足的进展成为了应用最广泛的8bit单片机之一,他的代表型号就是ATMEL公司的AT89系列。

芯片

8051是一种8位元的单芯片微控制器,属于MCS-51单芯片的一种。8051单芯片是同步式的顺序逻辑系统,整个系统的工作完全是依赖系统内部的时脉信号,用以来产生各种动作周期及同步信号。

  • 看门狗:在单片机运行中,可能会遇到电磁场等恶劣环境干扰失控,造成程序进入死循环,程序跑飞(死机)等意外故障,程序的正常运行被打断,系统无法继续运行,陷入停滞状态,发生不可预料的后果。此时可通过按下复位按钮,强制系统复位。但更理想的实现是通过一套监控系统,实时监视MCU运行状态,在运行异常时,使系统摆脱故障状态。看门狗(Watchdog)技术能够解决这一问题。
单片机管脚图

引脚说明:

  • 主电源引脚

    VCC:电源输入,接+5V电源
    GND:接地线

  • 外接晶振引脚
    XTAL1,XTAL2

  • 控制引脚
    RST、PSEN、ALE/PROG、EA/Vpp

  • 可编程输入/输出引脚
    51单片机有4组8位的可编程I/O口,分别为P0、P1、P2、P3口,每个口有8位(8根引脚)(单片机为八位),共32根。

单片机最小系统

单片机最小系统也称为单片机最小应用系统,是指使用最少的原件组成单片机可以工作的系统

单片机最小系统的三要素为:电源电路晶振电路复位电路

小圆坑或者小标记所对应的引脚就是这个芯片的第 1脚,然后逆时针方向数下去,即 1 到最后一个引脚。

  • 电源:供电
  • 晶振:晶振,又叫晶体振荡器,从这个名字上就可以看出,它注定一生都要不停的振荡。它起到的作用是为单片机系统提供基准时钟信号。电容的作用是帮助晶振起振,并维持振荡信号的稳定
  • 复位电路:分为外部RST复位,软件复位,掉电复位/上电复位,看门狗复位。
  • 寄存器是连接软硬件的媒介
  • 在单片机中寄存器就是一段特殊的RAM存储器,一方面,寄存器可以存储和读取数据,另一方面,每一个寄存器背后都连接了一根导线,控制着电路的连接方式

  • 寄存器相当于一个复杂机器的“操作按钮”

51单片机与stm32单片机的区别

  • 51是8bit单片机,stm32是32bit单片机
  • 51单片机采用冯·诺依曼结构(von Neumann architecture),stm32单片机采用普林斯顿体系结构(Princetion architecture)称作冯诺依曼体系
  • ……

冯·诺依曼体系和哈佛总线体系二者的区别就是程序空间和数据空间是否是一体的。

冯·诺依曼结构的处理器使用同一个存储器,经由同一个总线传输。

哈佛结构是一种将程序指令存储数据存储分开的存储器结构,可以使指令和数据有不同的数据宽度

哈佛结构设计复杂,但效率高。冯诺依曼结构则比较简单,但也比较慢。

详细可查看此文章:哈佛结构和冯诺依曼结构 - 知乎 (zhihu.com)

  • GPIO

GPIO(general purpose intput output)是通用输入输出端口的简称,可以通过软件来控制其输入和输出。51 单片机芯片的 GPIO 引脚与外部设备连接起来,从而实现与外部通讯、控制以及数据采集的功能。

  • 特殊的数据类型
  • sfr(special function register):特殊功能寄存器声明

    例:sfr P0 = 0x80;

    声明P0口寄存器,物理地址为0x80

  • sbit(special bit):特殊位声明

    例:sbit P0_1 = 0x81; 或 sbit P0_1 = P0^1;

    声明P0寄存器的第1位

  • 可位寻址/不可位寻址:说白了就是可位寻址允许对寄存器某位操作,不可位寻址只能对某个寄存器进行整体赋值操作
  • 不可位寻址的寄存器,若要只操作其中一位而不影响其它位时,可用“&=”、“|=”、“^=”的方法进行位操作
  1. &=,101 & 011 = 001,可用于置0,也可用于修改特定位
  2. |=,101 | 011 = 111,可用于置1
  3. ^=,101 ^ 011 = 110,取反
  • 可位寻址的寄存器,可直接赋值,如P1_4=1。

LED

发光二极管(Light Emitting Diode),它具有单向导电性.

在单片机中,1代表高电平,0代表低电平。由图可知, LED 采用共阳接法,即所有LED 阳极管脚接电源 VCC,当p2口为低电平时,产生电势差,LED发光。

图中电阻显示为102,实际值为1000。

电阻值为除最后一位+最后一位值*0.

由于单片机需要不停运行,查看汇编可知,在汇编代码结束前会返回到main函数开始处,因此单片机不会停止。

我们需要在main最后加个无限循环以保证单片机的正常运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include "reg52.h"
#include "intrins.h"
#define LED P2
sbit LED1=P2^0; //只修改LED1
void Delay500ms(void) //@12.000MHz 可以设置参数调整时间为500ms的整数倍
{
unsigned char data i, j, k;

_nop_();
i = 4;
j = 205;
k = 187;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}


void main()
{
int n=2;
int i=0;
while(n--)//8个LED灯闪烁
{
LED=0xFF;
Delay500ms();
LED=0x00;
Delay500ms();

}
n=2;
while(n--)//流水灯方式一
{
for(i=0;i<8;i++)
{
LED=~(0x01<<i);
Delay500ms();
}
}

LED=~0x01;//流水灯方式二
Delay500ms();
n=2;
while(n--)
{
for(i=0;i<7;i++)
{
LED=_crol_(LED,1);//_crol_函数相当于一个队列内循环移动
Delay500ms();
}
for(i=0;i<7;i++)
{
LED=_cror_(LED,1);
Delay500ms();
}
}
while(1)
LED=0x00; //LED全亮
}

独立按键控制

按键是一种电子开关,使用时轻轻按开关按钮就可使开关接通,当松开手时, 开关断开。实现原理是通过轻触按键内部的金属弹片受力弹动来实现接通和断开。

可以看到我们需要修改P3。 - 对于机械开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开,所以在开关闭合及断开的瞬间会伴随一连串的抖动。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
#include <REGX52.H> 	//此头文件包含单独对每个寄存器模块的控制
#include "intrins.h"
#define LED P2

void Delay10ms(unsigned int n) //delay
{
unsigned char data i, j;
while(n--){


i = 117;
j = 184;
do
{
while (--j);
} while (--i);


}
}


void main()
{
int n=3;
int i=0;
unsigned char LEDNum=0;
while(1) //不断对寄存器进行检测
{
if(P3_1==0) //LED流水线,运行中不断加快
{
LED=~0x01;
Delay10ms(n*5);
while(n--)
{
for(i=0;i<7;i++)
{
LED=_crol_(LED,1);
Delay10ms(n*5);
}
for(i=0;i<7;i++)
{
LED=_cror_(LED,1);
Delay10ms(n*5);
}
}
LED=0xFF;
}


if(P3_0==0) //LED按键控制全部LED灯
{
Delay10ms(2);//消抖
while(P3_0==0);
Delay10ms(2);
LED=~LED;
}


if(P3_2==0) //实现二进制加法
{
Delay10ms(2);
while(P3_2==0);
Delay10ms(2);
LED=~(LEDNum++);
}



if(P3_3==0) //LED左移与右移
{
Delay10ms(2);
while(P3_2==0);
Delay10ms(2);
LED=0xFE;
while(1)
{
if(P3_0==0) //左移
{
Delay10ms(2);
while(P3_0==0);
Delay10ms(2);
LED=_cror_(LED,1);
}


if(P3_2==0) //右移
{
Delay10ms(2);
while(P3_2==0);
Delay10ms(2);
LED=_crol_(LED,1);
}


if(P3_1==0) //退出
{
Delay10ms(2);
while(P3_1==0);
Delay10ms(2);
break;
}

}
LED=0xFF;
}


}
}

数码管

  • LED数码管:数码管是一种简单、廉价的显示器,是由多个发光二极管封装在一起组成“8”字型的器件

通过控制LED管来控制数码管的显示。

多个数码管共用引脚。

74HC245是一种三态输出、八路信号收发器,主要应用于大屏显示,以及其它的消费类电子产品中增加驱动。

74HC138是常用的数字芯片,是一种3入8出译码器,三位表示八位。

这也是为什么LED采用共阳极接法,因为P2口输入的电流不够。LED灯采用共阴极接法亮度会小于共阴极接法。

数码管的消隐(消音):位选与段选的执行间的影响

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <REGX52.H> 	

unsigned char NixieTable[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71,0x00};//段码数据

void Delay(unsigned int xms) //delay
{
unsigned char data i, j;
while(xms--)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
}

void Nixie(unsigned char Location,Number)
{
switch(Location) //位选
{
case 1: P2_4=1;P2_3=1;P2_2=1; break;
case 2: P2_4=1;P2_3=1;P2_2=0; break;
case 3: P2_4=1;P2_3=0;P2_2=1; break;
case 4: P2_4=1;P2_3=0;P2_2=0; break;
case 5: P2_4=0;P2_3=1;P2_2=1; break;
case 6: P2_4=0;P2_3=1;P2_2=0; break;
case 7: P2_4=0;P2_3=0;P2_2=1; break;
case 8: P2_4=0;P2_3=0;P2_2=0; break;
}
P0=NixieTable[Number]; //传送段选数据
Delay(1); //延时一段时间,等待显示稳定
P0=0x00;; //消音
}

void main()
{

while(1)
{
Nixie(1,1);
Nixie(2,1);
Nixie(3,4);
Nixie(4,5);
Nixie(5,1);
Nixie(6,4);
}
}
  • 单片机直接扫描:硬件设备简单,但会耗费大量的单片机CPU时间
  • 专用驱动芯片:内部自带显存、扫描电路,单片机只需告诉它显示什么即可

模块化编程

  • .c文件:函数、变量的定义

  • .h文件:可被外部调用的函数、变量的声明

  • 任何自定义的变量、函数在调用前必须有定义或声明(同一个.c)

  • 使用到的自定义函数的.c文件必须添加到工程参与编译

  • 使用到的.h文件必须要放在编译器可寻找到的地方(工程文件夹根目录、安装目录、自定义)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*			Delay.h				*/
#ifndef __DELAY_H__
#define __DELAY_H__
void Delay(unsigned int xms);
#endif
/* Delay.h */


/* Delay.c */
void Delay(unsigned int xms) //delay
{
unsigned char data i, j;
while(xms--)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
}
/* Delay.c */
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/*			Nixie.h				*/
#ifndef __Nixie_H__
#define __Nixie_H__

void Nixie(unsigned char Location,Number);

#endif
/* Nixie.h */



/* Nixie.c */
#include <REGX52.H>
#include "Delay.h"
unsigned char NixieTable[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71,0x00};//段码数据
void Nixie(unsigned char Location,Number)
{
switch(Location) //位选
{
case 1: P2_4=1;P2_3=1;P2_2=1; break;
case 2: P2_4=1;P2_3=1;P2_2=0; break;
case 3: P2_4=1;P2_3=0;P2_2=1; break;
case 4: P2_4=1;P2_3=0;P2_2=0; break;
case 5: P2_4=0;P2_3=1;P2_2=1; break;
case 6: P2_4=0;P2_3=1;P2_2=0; break;
case 7: P2_4=0;P2_3=0;P2_2=1; break;
case 8: P2_4=0;P2_3=0;P2_2=0; break;
}
P0=NixieTable[Number]; //传送段选数据
Delay(1); //延时一段时间,等待显示稳定
P0=0x00;; //消音
}

/* Nixie.c */

LCD1602 液晶屏

  • LCD1602可以作为调试窗口
函数 作用
LCD_Init(); 初始化
LCD_ShowChar(1,1,’A’); 显示一个字符
LCD_ShowString(1,3,”Hello”); 显示字符串
LCD_ShowNum(1,9,123,3); 显示十进制数字
LCD_ShowSignedNum(1,13,-66,2); 显示有符号十进制数字
LCD_ShowHexNum(2,1,0xA8,2); 显示十六进制数字
LCD_ShowBinNum(2,4,0xAA,8); 显示二进制数字
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

#include <REGX52.H>
#include "Delay.h"
#include "lcd1602.h"
void main()
{
int result;
LCD_Init();
LCD_ShowChar(1,1,'a');
LCD_ShowString(1,3,"hello");

LCD_ShowSignedNum(1,13,-66,2);
LCD_ShowHexNum(2,1,0xA8,2);
LCD_ShowBinNum(2,4,0xAA,8);
while(1)
{
result++;
Delay(1000);
LCD_ShowNum(1,9,result,3);
}
}

矩阵键盘

  • 在键盘中按键数量较多时,为了减少I/O口的占用,通常将按键排列成矩阵形式

  • 采用逐行或逐列的“扫描”,就可以读出任何位置按键的状态

扫描的概念

  • 数码管扫描(输出扫描)

    原理:显示第1位→显示第2位→显示第3位→……,然后快速循环这个过程,最终实现所有数码管同时显示的效果

  • 矩阵键盘扫描(输入扫描)

    原理:读取第1行(列)→读取第2行(列) →读取第3行(列) → ……,然后快速循环这个过程,最终实现所有按键被同时检测的假象

  • 以上两种扫描方式的共性:节省I/O口

采用逐行扫描,这里就直接copy了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/*							MatrixKey.c							*/
#include <REGX52.H>
#include "Delay.h"
unsigned char MatrixKey()
{
unsigned char KeyNumber=0;

P1=0xFF;
P1_3=0;
if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=1;}
if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=5;}
if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=9;}
if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=13;}

P1=0xFF;
P1_2=0;
if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=2;}
if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=6;}
if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=10;}
if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=14;}

P1=0xFF;
P1_1=0;
if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=3;}
if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=7;}
if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=11;}
if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=15;}

P1=0xFF;
P1_0=0;
if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=4;}
if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=8;}
if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=12;}
if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=16;}

return KeyNumber;
}
/* MatrixKey.c */

/* MatrixKey.h */
#ifndef __MATRIXKEY_H__
#define __MATRIXKEY_H__
unsigned char MatrixKey();
#endif
/* MatrixKey.h */

矩阵键盘密码锁(8位)

由于我们最大定义int整型,因此要采用两个password实现。然后每四位显示相隔,可以采用LCD_ShowChar()解决

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "MatrixKey.h"

void main()
{
unsigned char KeyNum;
unsigned int Password1,Password2,Count;

LCD_Init();
LCD_ShowString(1,1,"PASSWORD:");
while(1)
{

KeyNum=MatrixKey();
if(KeyNum)
{
if(KeyNum<=10) //输入密码
{
if(Count<4) //password1
{
Password1*=10;
Password1+=KeyNum%10;
Count++;
}
LCD_ShowNum(2,1,Password1,4);

if(Count>4&&Count<=8) //password2
{
Password2*=10;
Password2+=KeyNum%10;
Count++;
}
LCD_ShowNum(2,5,Password2,4);
if(Count==4)
Count++;
}

if(KeyNum==11) //确认按钮
{if(Password1==1234&&Password2==5678)
{
LCD_ShowString(1,14,"OK ");
Password1=0;
Password2=0;
Count=0;
LCD_ShowNum(2,1,Password1,4);
LCD_ShowNum(2,5,Password2,4);
}
else
{
LCD_ShowString(1,14,"ERR");
Password1=0;
Password2=0;
Count=0;
LCD_ShowNum(2,1,Password1,4);
LCD_ShowNum(2,5,Password2,4);
}
}
if(KeyNum==12) //重置按钮
{
Password1=0;
Password2=0;
Count=0;
LCD_ShowNum(2,1,Password1,4);
LCD_ShowNum(2,5,Password2,4);
}

}

}

}

定时器

  • 定时器介绍:51单片机的定时器属于单片机的内部资源,其电路的连接和运转均在单片机内部完成。
  • 定时器个数2个(T0、T1、T2),不同单片机个数不同。
  • SYSclk:系统时钟,即晶振周期,本开发板上的晶振为12MHz

CPU 时序的有关知识

  • 在计算机体系结构和微处理器设计中,有几个与时间周期相关的概念,它们是:
    • 振荡周期(Oscillation Cycle):
      • 振荡周期也称为时钟周期,是指时钟振荡器产生的一个完整的周期的时间。它是处理器内部操作的基本时间单位。
    • 状态周期(State Cycle):
      • 状态周期是处理器内部操作的一个更细分的时序单位。
    • 机器周期(Machine Cycle):
      • 机器周期是执行一个基本机器指令所需的时间。
    • 指令周期(Instruction Cycle):
      • 指令周期是处理器从取指令到执行完该指令所需的全部时间。
  • 例如:外接晶振为 12MHz 时,51 单片机相关周期的具体值为:
    振荡周期=1/12us;
    状态周期=1/6us;
    机器周期=1us;
    指令周期=1~4us;

中断系统

  • STC89C52中断资源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/*									timer0.c									*/		
void Timer0Init(void)
{
TMOD|=0X01;//选择为定时器 0 模式,工作方式 1
TH0=0XFC; //给定时器赋初值,定时 1ms
TL0=0X18; //给定时器赋初值,定时 1ms
ET0=1;//打开定时器 0 中断允许
EA=1;//打开总中断
TR0=1;//打开定时器
}
//详看普中51单片机开发攻略第21章与19章
/* timer0.c */



/* main.c */
#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "Timer0.h"

unsigned char Sec=14,Min=45,Hour=11;

void main()
{
LCD_Init();
Timer0Init();

LCD_ShowString(1,1,"Clock:");
LCD_ShowString(1,8," : :");

while(1)
{
LCD_ShowNum(1,8,Hour,2);
LCD_ShowNum(1,11,Min,2);
LCD_ShowNum(1,14,Sec,2);
}
}
void Timer0_Routine() interrupt 1
{
static unsigned int T0Count;
TL0 = 0x18;
TH0 = 0xFC; //重新设置定时初值
T0Count++;
if(T0Count>=1000) //定时器分频,1s
{
T0Count=0;
Sec++; //1秒到,Sec自增
if(Sec>=60)
{
Sec=0; //60秒到,Sec清0,Min自增
Min++;
if(Min>=60)
{
Min=0; //60分钟到,Min清0,Hour自增
Hour++;
if(Hour>=24)
{
Hour=0; //24小时到,Hour清0
}
}
}
}
}
/* main.c */

串口

  • 串口是一种应用十分广泛的通讯接口,串口成本低、容易使用、通信线路简单,可实现两个设备的互相通信。
  • 串行通信是指使用一条数据线,将数据一位一位地依次传输,每一位数据占据一个固定的时间长度。

电平标准

  • 电平标准是数据1和数据0的表达方式,是传输线缆中人为规定的电压与数据的对应关系,串口常用的电平标准有如下三种:

  • TTL电平:+5V表示1,0V表示0

  • RS232电平:-3-15V表示1,+3~+15V表示0

  • RS485电平:两线压差+2+6V表示1,-2-6V表示0(差分信号)

通信术语

  • 全双工:通信双方可以在同一时刻互相传输数据

  • 半双工:通信双方可以互相传输数据,但必须分时复用一根数据线

  • 单工:通信只能有一方发送到另一方,不能反向传输

  • 异步:通信双方各自约定通信速率

  • 同步:通信双方靠一根时钟线来约定通信速率

  • 总线:连接各个设备的数据传输线路(类似于一条马路,把路边各住户连接起来,使住户可以相互交流)

常见通信接口

名称 引脚定义 通信方式 特点
UART TXD、RXD 全双工、异步 点对点通信
I²C SCL、SDA 半双工、同步 可挂载多个设备
SPI SCLK、MOSI、MISO、CS 全双工、同步 可挂载多个设备
1-Wire DQ 半双工、异步 可挂载多个设备
  • 波特率:串口通信的速率(发送和接收各数据位的间隔时间)

  • 检验位:用于数据验证

  • 停止位:用于数据帧间隔

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/*									main.c									*/
#include <REGX52.H>
#include "Delay.h"
#include "UART.h"

void main()
{
UART_Init();

while(1)
{
}
}

void UART_Routine() interrupt 4
{
if(RI == 1) //判断接收
{
P2=SBUF;
UART_SendByte(SBUF);
RI=0;
}
}
/* main.c */




/* UART.c */
#include <REGX52.H>
void UART_Init(void) //4800bps@12.000MHz
{
PCON |= 0x80; //使能波特率倍速位SMOD
SCON = 0x50; //8位数据,可变波特率
TMOD &= 0x0F; //设置定时器模式
TMOD |= 0x20; //设置定时器模式
TL1 = 0xF4; //设置定时初始值
TH1 = 0xF4; //设置定时重载值
ET1 = 0; //禁止定时器中断
TR1 = 1; //定时器1开始计时
EA = 1; //开放中断,接受数据开启
ES = 1; //允许串行口中断,接受数据开启
}

void UART_SendByte(unsigned char Byte)
{
SBUF = Byte; //写入寄存器
while(TI == 0);
TI = 0; //重置接收中断请求标志位。
}

//模板
/*
void UART_Routine() interrupt 4
{
if(RI == 1) //判断接收
{
P2=SBUF;
RI=0;
}
}
*/
/* UART.c */