传统的8051系列单片机一般都配备一个串口,而STC 89C52RC增强型单片机也不例外,只有一个串口可供使用,这样就出问题了,假如当前单片机系统要求二个串口或多个串口进行同时通信,8051系列单片机只有一个串口可供通信就显得十分尴尬,但是在实际的应用中,有两种方法可以选择。
方法1:使用能够支持多串口通信的单片机,不过通过更换其他单片机来代替8051系列单片机,这样就会直接导致成本的增加,优点就是编程简单,而且通信稳定可靠。
方法2:在IO资源比较充足的情况下,可以通过IO来模拟串口的通信,虽然这样会增加编程的难度,模拟串口的波特率会比真正的串口通信低一个层次,但是唯一优点就是成本上得到控制,而且通过不同的IO组合可以实现更加之多的模拟串口,在实际应用中往往会采用模拟串口的方法来实现多串口通信。
普遍使用串口通信的数据流都是1位起始位、8位数据位、1位停止位的格式的,如表1。
表1
起始位 |
8位数据位 |
停止位 |
0 |
Bit0 |
Bit1 |
Bit2 |
Bit3 |
Bit4 |
Bit5 |
Bit6 |
Bit7 |
1 |
|
|
|
|
|
|
|
|
|
|
要注意的是,起始位作为识别是否有数据到来,停止位标志数据已经发送完毕。起始位固定值为0,停止位固定值为1,那么为什么起始位要是0,停止位要是1呢?这个很好理解,假设停止位固定值为1,为了更加易识别数据的到来,电平的跳变最为简单也最容易识别,那么当有数据来的时候,只要在规定的时间内检测到发送过来的第一位的电平是否0值,就可以确定是否有数据到来;另外停止位为1的作用就是当没有收发数据之后引脚置为高电平起到抗干扰的作用。
在平时使用红外无线收发数据时,一般都采用模拟串口来实现的,但是有个问题要注意,波特率越高,传输距离越近;波特率越低,传输距离越远。对于这些通过模拟串口进行数据传输,波特率适宜为1200b/s来进行数据传输。
例子:在使用单片机的串口接收数据实验当中,使用串口调试助手发送16字节数据,单片机采用模拟串口的方法将接收到的数据返发到PC机。
模拟串口实验代码:
1 #include "stc.h"
2
3 #define RXD P3_0 //宏定义:接收数据的引脚
4 #define TXD P3_1 //宏定义:发送数据的引脚
5 #define RECEIVE_MAX_BYTES 16//宏定义:最大接收字节数
6
7 #define TIMER_ENABLE() {TL0=TH0;TR0=1;fTimeouts=0;}//使能T/C
8 #define TIMER_DISABLE() {TR0=0;fTimeouts=0;}//禁止T/C
9 #define TIMER_WAIT() {while(!fTimeouts);fTimeouts=0;}//等待T/C超时
10
11
12 unsigned char fTimeouts=0;//T/C超时溢出标志位
13 unsigned char RecvBuf[16];//接收数据缓冲区
14 unsigned char RecvCount=0;//接收数据计数器
15
16
17
23 void SendByte(unsigned char b)
24 {
25 unsigned char i=8;
26
27 TXD=0;
28
29 TIMER_ENABLE();
30 TIMER_WAIT();
31
32
33 while(i--)
34 {
35 if(b&1)TXD=1;
36 else TXD=0;
37
38 TIMER_WAIT();
39
40 b>>=1;
41
42 }
43
44
45 TXD=1;
46
47 TIMER_WAIT();
48 TIMER_DISABLE();
49 }
50
56 unsigned char RecvByte(void)
57 {
58 unsigned char i;
59 unsigned char b=0;
60
61 TIMER_ENABLE();
62 TIMER_WAIT();
63
64 for(i=0;i<8;i++)
65 {
66 if(RXD)b|=(1<
67
68 TIMER_WAIT();
69 }
70
71 TIMER_WAIT(); //等待结束位
72 TIMER_DISABLE();
73
74 return b;
75
76 }
77
83 void PrintfStr(char * pstr)
84 {
85 while(pstr && *pstr)
86 {
87 SendByte(*pstr++);
88 }
89 }
90
96 void TimerInit(void)
97 {
98 TMOD=0x02;
99 TR0=0;
100 TF0=0;
101 TH0=(256-99);
102 TL0=TH0;
103 ET0=1;
104 EA=1;
105 }
106
112 unsigned char StartBitCome(void)
113 {
114 return (RXD==0);
115 }
116
122 void main(void)
123 {
124 unsigned char i;
125
126 TimerInit();
127
128 PrintfStr("Hello 8051rn");
129
130 while(1)
131 {
132 if(StartBitCome())
133 {
134 RecvBuf[RecvCount++]=RecvByte();
135
136 if(RecvCount>=RECEIVE_MAX_BYTES)
137 {
138 RecvCount=0;
139
140 for(i=0;i
141 {
142 SendByte(RecvBuf[i]);
143 }
144 }
145 }
146
147 }
148 }
149
155 void Timer0IRQ(void) interrupt 1 using 0
156 {
157 fTimeouts=1;
158 }
159
代码分析
在模拟串口实验代码中,宏的使用占用了相当的一部分。
#define RXD P3_0 //宏定义:接收数据的引脚
#define TXD P3_1 //宏定义:发送数据的引脚
#define TIMER_ENABLE() {TL0=TH0;TR0=1;fTimeouts=0;}//使能T/C
#define TIMER_DISABLE() {TR0=0;fTimeouts=0;}//禁止T/C
#define TIMER_WAIT() {while(!fTimeouts);fTimeouts=0;}//等待T/C超时
模拟串口接收引脚为P3.0,发送引脚为P3.1。为了达到精确的定时,减少模拟串口时收发数据的累积误差,有必要通过对T/C进行频繁的使能和禁止等操作。例如宏TIMER_ENABLE为使能T/C,宏TIMER_DISABLE禁止T/C,宏TIMER_WAIT等待T/C超时。
模拟串口的工作波特率为9600b/s,在串口收发的数据流当中,每一位的时间为1/9600≈104us,
若单片机工作在12MHz频率下,使用T/C0工作在方式2,那么为了达到104us的定时时间,TH0、TL0的初值为256-104=152,在实际的模拟串口中,往往出现收发数据不正确的现象。原因就在于TH0、TL0的初值,或许很多人会疑惑,按道理来说,计算T/C0的初值是没有错的。对,是没有错,但是在SendByte和Recv的函数当中,执行每一行代码都要消耗一定的时间,这就是所谓的“累积误差”导致收发数据出现问题,因此我们必须通过实际测试得到TH0、TL0的初值,最佳值256-99=157。那么在T/C初始化TimerInit函数中,TH0、TL0的初值不能够按照常规来计算得到,实际初值在正常初值附近,可以通过实际测试得到。
模拟串口主要复杂在模拟串口发送与接收,具体实现函数在SendByte和RecvByte函数,这两个函数必须要遵循“1位起始位、8位数据位、1位停止位”的数据流。
SendByte函数用于模拟串口发送数据,以起始位“0”作为移位传输的起始标志,然后将要发送的自己从低字节到高字节移位传输,最后以停止位“1”作为移位传输的结束标志。
RecvByte函数用于模拟串口接收数据,一旦检测到起始位“0”,就立刻将接收到的每一位移位存储,最后以判断停止位“1”结束当前数据的接收。
main函数完成T/C的初始化,在while(1)死循环以检测起始位“0”为目的,当接收到的数据达到宏RECEIVE_MAX_BYTES的个数时,将接收到的数据返发到外设。
波特率的研究
通常情况下,8051系列单片机外接晶振频率一般是12MHz、24MHz、48MHz如图7-6-1,为什么会这样选取呢?从前面的章节已经介绍8051系列单片机的每12个时钟周期为一个指令周期,当8051系列单片机外接12MHz晶振时,指令周期=12/12MHz=1us;若外接24MHz晶振时,指令周期=12/24MHz=0.5us;若外接48MHz晶振时,指令周期=12/48MHz=0.25us。8051系列单片机外接能够被除尽的晶振,在使用单片机内部的定时器/计数器资源时作定时器使用时能够得到精确定时应用;当使用汇编语言编程时,可以清楚知道当前每一行代码执行的时间。
8051系列单片机外接能够被除尽的晶振即12MHz、24MHz、48MHz这些晶振时,波特率的精确性就得不到保证。
假若现在单片机外接的晶振为12MHz时,以T/C2作波特率发生器,根据波特率公式:
波特率=Fosc/2x16x(65536-t)
9600=12MHz/2x16x(65536-t)
t=65496.9375
“65496.9375”不是一个整数值,是一个带有小数点的数值。对于常用的8位、9位、11位一帧的数据接收与传输,最大的允许误差分别是6.25%、5.56%、4.5%。虽然波特率允许误差,但是这样通信时便会产生积累误差,进而影响数据的正确性。唯一的解决办法就是更改单片机外接的晶振频率,更改为常用于产生精确波特率的晶振如11.0592MHz、22.1184MHz。
假若现在单片机外接的晶振为11.0592MHz时,以T/C2作波特率发生器,根据波特率公式:
波特率=Fosc/2x16x(65536-t)
9600=11.0592MHz/2x16x(65536-t)
t=65500=0xFFDC
虽然使用11.0592MHz、22.1184MHz的晶振能够产生精确的波特率,但是用于系统精确的定时服务不是十分的理想。例如单片机外接11.0592MHz晶振时,指令周期=12/11.0592MHz≈1.085us,是一个无限循环的小数。当单片机外接22.1184MHz晶振时,指令周期=12/22.1184MHz≈0.5425us,也是一个无限循环的小数。
串口工作在方式1时分别采用T/C1和T/C2产生常用波特率初值表如下。
波特率
(11.0592MHz) |
初值 |
波特率
(12MHz) |
初值 |
TH1、TL1
(SMOD=0) |
TH1、TL1
(SMOD=1) |
TH1、TL1
(SMOD=0) |
TH1、TL1
(SMOD=1) |
1200 |
0xE7 |
0xD0 |
1200 |
0xE5 |
0xCB |
2400 |
0xF3 |
0xE7 |
2400 |
0xF2 |
0xE5 |
4800 |
0xF9 |
0xF3 |
4800 |
0xF9 |
0xF2 |
9600 |
0xFC |
0xF9 |
9600 |
0xFC |
0xF9 |
14400 |
0xFD |
0xFB |
14400 |
0xFD |
0xFB |
19200 |
0xFE |
0xFC |
19200 |
0xFE |
0xFC |
波特率
(11.0592MHz) |
初值 |
波特率
(12MHz) |
初值 |
RCAL2H |
RCAL2L |
RCAL2H |
RCAL2L |
1200 |
0xFE |
0xE0 |
1200 |
0xFE |
0xC8 |
2400 |
0xFF |
0x70 |
2400 |
0xFF |
0x64 |
4800 |
0xFF |
0xD8 |
4800 |
0xFF |
0xB2 |
9600 |
0xFF |
0xDC |
9600 |
0xFF |
0xD9 |
14400 |
0xFF |
0xE8 |
14400 |
0xFF |
0xE6 |
19200 |
0xFF |
0xEE |
19200 |
0xFF |
0xED |
如果大家想通过设置不同的晶振获取更加多的波特率的值,可以下载以下工具进行计算:
『本文转载自网络,版权归原作者所有,如有侵权请联系删除』