×
嵌入式 > 技术百科 > 详情

单片机模拟串口发送和波特率问题

发布时间:2020-06-06 发布时间:
|
传统的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"

3  #define RXD P3_0 //宏定义:接收数据的引脚
4  #define TXD P3_1 //宏定义:发送数据的引脚
5  #define RECEIVE_MAX_BYTES 16//宏定义:最大接收字节数

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
 
如果大家想通过设置不同的晶振获取更加多的波特率的值,可以下载以下工具进行计算:
 

『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
分拣机器人的工作原理是什么