CSDN其他博主的博文(自用)嵌入式学习笔记9-51单片机UART串口通信_51uart串口通讯-CSDN博客
CSDN其他博主的博文写的蛮好,如果你想了解51单片机UART串口可以点进去看看:
UART全称Universal Asynchronous Receiver/Transmitter即通用异步收发器
异步通信是指通信中两个字符(8位)之间的时间间隔是不固定的,而在一个字符内各位的时间间隔是固定的。
UART串行通信是单片机最常用的的一种通信技术,通常用于单片机和计算机之间以及单片机和单片机之间的通信。
通信按照基本类型可以分为并行通信和串行通信,并行通信时数据的各个位同时传送,可以实现以字节为单位通信,但是通信线多占用资源多,成本高。 比如前边用到的P0 = 0xFF;一次给P0的8个IO口分别赋值,同时进行信号输出,类似于有8个车道同时可以过去8辆车。这种形式就是并行的,习惯上称P0 、P1、P2和P3为51单片机的4组并行总线。
而串行通信就如同一条车道,一次只能过一辆车。如果一个0xFF这样一个字节的数据要传输过去的话,假设一辆车拉着1位的数据,那么它就需要8辆车以相同的时间间隔依次穿过同一车道。
同步通信与异步通信区别:
1、同步通信要求接收端时钟频率和发送端时钟频率一致,发送端发送连续的比特流;异步通信时不要求接收端时钟和发送端时钟同步,发送端发送完一个字节后,可经过任意长的时间间隔再发送下一个字节。
2、同步通信效率高,异步通信效率较低。
3、同步通信较复杂,双方时钟的允许误差较小;异步通信简单,双方时钟可允许一定误差。
4、同步通信只适用于点对多点,异步通信可用于点对点。
STC89C52单片机有两个引脚是专门用来做UART串行通信,1个是P3^0,一个是P3^1,它们还分别有另外的名字叫做RXD(Receive Data)和TXD(Transmit Data),由他们组成的通信接口就叫作串行接口。如图:
单片机和单片机之间通信:
图示中GND表示单片机系统电源的参考地。TXD叫串行发送引脚,RXD是串行接收引脚。
两个单片机之间要通信,首先电源基准得一样,所以要把两个单片机的GND相互连接起来。然后是单片机1的TXD引脚接到单片机2的RXD引脚上,功能是单片机1向单片机2发送信息,(这个过程分为两部分,单片机1的发送信号过程 与单片机2的接收信号过程)同理单片机2的TXD引脚接到单片机1的RXD引脚上。
这个示意图就体现了两个单片机相互收发信息的过程。
当单片机1想给单片机2发送数据时,比如发送1个0xE4这个数据,用二进制形式表示就是
0b1110 0100,在传输开始前单片机1就面临几个问题:
假设我先传输了最低位的0,那么我需要把单片机1的TXD端口置为低电平。
单片机一般是TTL或者CMOS电平,对于TTL/CMOS电平标准 :
大于2点几伏的为高电平,低于0点几伏的为低电平,之间的既不是高电平也不是低电平,之间的逻辑会比较混乱它可能会被器件判断为高电平,也可能被判断为低电平。因此电路工作尽量不要处在这个区域。处在这个区域会让你的产品时好时坏。因此开发产品的时候要查看器件的数据手册,以及电路工作过程时的电压,使之处在一个合适的电压。
1、TTL电平:
输出高电平>2.4V,输出低电平<0.4V。在室温下,一般输出高电平是3.5V,输出低电平是0.2V。最小输入高电平和低电平:输入高电平>=2.0V,输入低电平<=0.8V,噪声容限是0.4V。
2、CMOS电平:
1逻辑电平电压接近于电源电压,0逻辑电平接近于0V。而且具有很宽的噪声容限。
那么问题来了:
在不传送或者接收信息的时候,单片机1的传输端口(TXD)会处在高电平或者低电平亦或者端口的电压在此之间。单片机2的接收端口电压(RXD)又是跟随单片机1传输端口的电压。如何设定单片机1要开始传输信息了的信号,即单片机2怎么知道单片机1向他传输信息了?因此需要规范通信标准。
第1步:规定没发生通信的时候,单片机TXD和RXD端口电压保持高电平。
第2步:规范数据传输方式如下图
可以看到它定义了1个起始位,1个停止位,加上本身要传输的数据1个字节8位即共十位数据。
上述传输一个0xE4的数据事实上是传输了0 1110 0100 1,单片机1向单片机2传递1字节的数据其实是传输了10位数据,把这个10位的整体叫作一个完整的串行数据帧。
有了起始位和停止位,那单片机2 RXD 端口在检测到低电平的时候就开始准备接收数据了。同理单片机1要发送1个字节的数据,它就需要先发送1位起始位0(告诉单片机2让它准备接收),数据发送结束后再发送1位停止位1(告诉单片机2一个字节的数据传输完毕)。而且传输数据的时候是先低位后高位的顺序
第3步:需要统一设置一下传输1位数据所需要的时间,这个时间快慢用波特率( baud)表示。1波特率表示1秒传输1位数据,9600波特率表示在1秒内传输9600位数据。
那么就得出传输1位的持续时间=1/baud.,那么就可求得传递1完整数据帧的时间是10/baud,数据帧之间的时间间隔是任意的。
对于单片机1和单片机2在传输信号的时候,它们的波特率必须设置成一致才能正确的实现通信。
RS232通信接口
在早先年台式计算机上,一般会有一个9针的串行接口,这个串行接口叫RS-232接口,它和UART通信有关联,但现在的笔记本都不带这种9针串口了,所以和单片机之间通信越来越趋向使用USB虚拟串口了。
RS232使用的电平逻辑是负逻辑它不同于TTL/CMOS逻辑电平
因此计算机的9针RS232串口是不能和单片机直接连接的,需要1个电平转换芯片MAX232完成如下图:
RS232针脚说明:
1、载波检测DCD(Data Carrier detection) 2、接收数据RXD 3、发送数据TXD
4、数据终端准备好DTR(Data Terminal Ready) 5、信号地线 6、数据准备好DSR(Data Set ready)
7、请求发送RTS(Request to Send) 8、清除发送CTS(Clear to Send) 9、振铃提示RI(Ringing)
如上图在串口接线中RS232只要2脚、3脚以及5脚与相应的MAX232的引脚相连就能完成通信。
USB转串口通信
随着技术的发展,工业上还有RS-232串口通信的大量使用,但是商业技术的应用上,已经慢慢的使用USB转UART技术取代了RS232串口。
那么如何实现单片机和计算机之间的通信如图
这是本案开发板使用 USB转串口芯片CH340T实现这个功能。
单片机端口TXD用了跳线帽和USB-TX连接在一起并且是与CH340第4脚RXD端口相连,同时
单片机端口RXD用了跳线帽和USB-RX连接在一起并且是与CH340的第3脚TXD相连。
可以看到USB-RX与CH340的第三引脚TXD之间串着一个4148二极管,因为STC89C52单片机下载程序时需要冷启动。就是先点下载后上电。
所谓冷启动是指单片机从断电到通电的这么一个启动过程;而热启动是单片机始终通电。冷启动与热启动的区别在于:冷启动时单片机内部RAM中的数值是一些随机量,而热启动时单片机内部RAM的值不会被改变,与启动前相同。
上电瞬间单片机会先检测需不需要下载程序,虽然单片机的VCC是由开关来控制的,但是由于CH340T的3脚是输出引脚,如果没有此二极管,开关后级单片机在断电的情况下,CH340T的3脚和单片机的P3.0(RXD)引脚连在一起,有电流会通过这个引脚流入后级电路并且给后级的电容充电,造成后级有一定幅度的电压,这个电压值虽然只有2~3V左右,但是可能会影响正常的冷启动。加上二极管后,一方面不影响通信,另一方面可以消除这种不良影响。
通过单片机手册得知P3.0是准双向IO口
红框部分是准双向IO口, 当内部输出位为低电平的时候,经过非门,三极管基极为高电平,该三极管导通(饱和导通CE两端电压几乎相同),单片机IO口输出低电平,无论外部的按键开关按下还是弹开,IO口一直保持低电平。即不受外部信号的控制。
当内部输出为高电平的时候,经过非门,三极管基极为低电平,三极管未导通(CE两端的阻值很大,电阻R与之相比可以忽略不计)这时IO口输出高电平。当按下按键,IO口就被接地,这时IO口输出低电平。
由此可知只有当IO口输出为高电平的时候,IO口的输出才受外部电路的控制。
因此我们可知P3.0在作为RXD端口时内部输出一直是高电平的。而P3.1TXD端口作为发送端它的内部输出电平可以根据需要高低变化。
而且:对TTL电平
输出端:高电平>=2.4V,低电平<=0.4V;
接收端:高电平>=2.0V,低电平<=0.8V。
对于COMS电平
输出端:高电平=Vcc,低电平=GND(Vcc为电源电压);
接收端:高电平>=0.7Vcc,低电平<=0.2Vcc。
他们接收端的逻辑相较于输出端的逻辑电平还是有有些电压差的,这可能是考虑到信号传输过程中产生的压降干扰亦或者保护电路的存在,所以加1个二极管并不会导致它所传输后的电压处在一个混乱的逻辑区。
用IO口模拟UART串口通信
上程序:
# include<reg52.h>
sbit PIN_RXD = P3^0; //接受引脚定义
sbit PIN_TXD = P3^1; //发送引脚定义
bit RxdorTxd = 0; //指示当前状态为接受还是发送
bit RxdEnd = 0; //接受结束标志
bit TxdEnd = 0; //发送结束标志
unsigned char RxdBuf = 0; //接收缓冲区
unsigned char TxdBuf = 0; //发送缓冲器
void ConfigUART(unsigned int baud);
void StartTXD(unsigned char dat);
void StartRXD();
void main()
{
EA = 1; //打开总中断
ConfigUART(9600); //配置波特率为9600
while(1)
{
while(PIN_RXD); //等待接收引脚出现低电平,即起始位
StartRXD(); //启动接收
while(!RxdEnd); //等待接受完成
StartTXD(RxdBuf +1);//接受到的整数+1后,发送回去
while(!TxdEnd); //等待发送结束
}
}
/* 串口配置函数,baud-通信波特率 */
void ConfigUART(unsigned int baud)
{
TMOD &= 0xF0; //清零T0的控制位
TMOD |= 0x02; //配置T0为模式2
TH0 = 256 - (11059200/12)/baud; //计数T0的重载值
}
/* 启动串行接受 */
void StartRXD()
{
TL0 = 256 - ((256-TH0) >> 1);//接受启动时T0定时为半个波特率周期
ET0 = 1; //使能T0中断
TR0 = 1; //启动T0
RxdEnd = 0; //清零接受结束标志
RxdorTxd = 0; //设置当前状态为接受 1位发送
}
/* 启动串行发送,dat-待发送字节数据 */
void StartTXD(unsigned char dat)
{
TxdBuf = dat; //待发送数据保存到发送缓冲器
TL0 = TH0; //T0计算初值为重载值
ET0 = 1; //使能T0中断
TR0 = 1; //启动T0
PIN_TXD = 0; //发送起始位
TxdEnd = 0; //清零发送结束标志
RxdorTxd = 1; //设置当前状态为发送
}
/*T0中断服务函数,处理串行发送和接收 */
void interruptTimer0() interrupt 1
{
static unsigned char cnt = 0;
if(RxdorTxd)
{
cnt++;
if(cnt <= 8) //低位在先一次发送8bit数据位
{
PIN_TXD = TxdBuf & 0x01;
TxdBuf >>= 1;
}
else if(cnt == 9) //发送停止位
{
PIN_TXD = 1;
}
else //发送结束
{
cnt = 0; //复位bit计数器
TR0 = 0; //关闭T0
TxdEnd = 1; //置发送结束标志
}
}
else //串行接收处理
{
if(cnt == 0) // 处理起始位
{
if(!PIN_RXD) //起始位为0时,清零接收缓冲器,准备接受数据位
{
RxdBuf = 0;
cnt++;
}
else //起始位为1(不为0)时,中止接收
{
TR0 = 0; //关闭T0
}
}
else if(cnt <= 8) //处理8位数据位
{
RxdBuf >>= 1; //低位在先,所以将之前接收的位向右移
if(PIN_RXD) //接收脚为1时,缓冲器最高位置1
{ //而为0时不处理即仍保持位移后的0
RxdBuf |= 0x80;
}
cnt++;
}
else //停止位处理
{
cnt = 0; //复位bit计数器
TR0 = 0; //关闭T0
if(PIN_RXD) //停止位为1时,方认为数据有效
{
RxdEnd = 1; //置接收结束标志
}
}
}
}
工作逻辑导图
该程序是用定时器0模拟UART串口通信,注意这只是个模拟程序。该程序实现了计算机与单片机之间的通信。它的通信结果是把计算机传输的数据传输给单片机,单片机把数据加1后回传给计算机。使用STC-ISP自带的串口调试助手来演示这个结果。
首先确保端口是一样的上图都是COM3,波特率是9600 校验位是NO 数据位是8 停止位是1.(这里设置的是计算机的串口参数)
看下结果:可以正常通信定时器0模拟串口通信_哔哩哔哩_bilibili
简易说明该程序的工作逻辑:对于发送模块比如发送0xAA =1010 1010 从最低位开始发送
即TxdBuf = 1010 1010看下程序
if(RxdorTxd)
{
cnt++;
if(cnt <= 8) //低位在先一次发送8bit数据位
{
PIN_TXD = TxdBuf & 0x01;
TxdBuf >>= 1;
}
else if(cnt == 9) //发送停止位
{
PIN_TXD = 1;
}
else //发送结束
{
cnt = 0; //复位bit计数器
TR0 = 0; //关闭T0
TxdEnd = 1; //置发送结束标志
}
}
第一次进入中断cnt = 1
PIN_TXD = TxdBuf & 0x01 即1010 1010 0000 0001相与的结果就是把0xAA的最低位赋值给发送端口让其使能相应的电平。可知第一次进入中断TXD端口的电压是低电平,这个过程要持续到竟然第二次中断。
接着TxdBuff右移了一位即TxdBuf = 0101 0101
然后第二次中断
cnt = 2
PIN_TXD = 1这次发送端口的电平是高电平
TxdBuf = 0010 1010
接着是第3次第4次直到第8次
cnt = 8
PIN_TXD = 1设置的电平是高电平,是该传输数据的最高位1
这时候TxdBuf = 0x00
然后是第9次进入中断,第9次进入中断代表着数据位刚好发送完毕,要准备发送停止位了
因此PIN_TXD = 1发送端口直接赋值为高电平
最后第十次中断代表着停止位也发送结束了。因此cnt复位赋值为0,TR0赋值为0关闭定时器0,TXDEnd = 1,发送结束标志置1,到此一个完整的数据帧发送结束
然后是接收模块:前文我们发送了0xAA,对于本函数来说它的值是来自于接收的数据加1后的结果,因此接收到的数据是 :0xA9 =1010 1001
void StartRXD()
{
TL0 = 256 - ((256-TH0) >> 1);//接受启动时T0定时为半个波特率周期
ET0 = 1; //使能T0中断
TR0 = 1; //启动T0
RxdEnd = 0; //清零接受结束标志
RxdorTxd = 0; //设置当前状态为0接收 1为发送
}
else //串行接收处理
{
if(cnt == 0) // 处理起始位
{
if(!PIN_RXD) //起始位为0时,清零接收缓冲器,准备接受数据位
{
RxdBuf = 0;
cnt++;
}
else //起始位为1(不为0)时,中止接收
{
TR0 = 0; //关闭T0
}
}
else if(cnt <= 8) //处理8位数据位
{
RxdBuf >>= 1; //低位在先,所以将之前接收的位向右移
if(PIN_RXD) //接收脚为1时,缓冲器最高位置1
{ //而为0时不处理即仍保持位移后的0
RxdBuf |= 0x80;
}
cnt++;
}
else //停止位处理
{
cnt = 0; //复位bit计数器
TR0 = 0; //关闭T0
if(PIN_RXD) //停止位为1时,方认为数据有效
{
RxdEnd = 1; //置接收结束标志
}
}
}
可以看到发送和接收过程其实是差不多的,对于接收数据,我把第1次中断标为从第0次开始主要是为了和cnt配合便于理解。
如图可以看到它第0次进入中断经过的时间是预设中断时间的一半,这是因为在确认一个电平信号是0还是1的时候,如果采样最开始时间的电平有可能会产生误差或者干扰,因此一般都会把采样点设置在信号传递时间的中点位置,把该处的电平信号认为这个时间段传达的信号。所以对于接收模块只需在第一次接收起始位的时候,把它的中断时间设置为原先的一半,这样就能保证后续所有的信号采集点都处在中心点位置。
看下程序是怎么设置的:TL0 = 256 - ((256-TH0) >> 1);//接受启动时T0定时为半个波特率周期
256-TH0就是预设中断时间,前文是我们提到希望中断时间变为之前的一半,程序操作是右移了1位,我们举个例子 8的二进制 0000 1000,右移1为是0000 0100 = 4,可见右移1位的操作就是把数变成原先的一半。 同理左移1位的结果是0001 0000 =16 是把原先的数乘以2
开始程序过程分析
当RxdorTxd为0时进入else函数,即第0次进入中断 ,接收模块在时域上的起点就是开始位的中点位置。首先确定端口电压是否真的是低电平,如果不是就关闭中断。为什么要这么干呢?
因为这是接收模块,RXD的电压不是由单片机控制的,对于本案来说它是由计算机控制CH340T的TXD端电压控制的。单片机RXD的电压是跟随CH340TXD端的,对于接收端来说接收到的信息可能受到干扰因此需要经过再次确认。
如果是,cnt++,则cnt=1。RxdBuf = 0;接收缓冲区清0
然后是第1(其实是第二次)次进入中断cnt = 1进入else if()函数一步步的把数据传输到缓冲区。后续过程不在赘述,如果有兴趣的读者可以自行按照程序逻辑走一遍。看是否能真的把数据先低位后高位的方式存入缓冲变量。因为之前的博文,函数通过左移的方法都是先存入数据的高位。这种方法相当补充了另一种工作方式。
至此程序用定时器0模拟了UART串口通信。
通信的三种基本类型
单工通信:只允许一方向另外一方传送信息,另一方不能回传信息,比如电视遥控器、收音机广播等。
半双工通信:数据可以在双方之间传播,同一时刻只能其中一方发给另外一方,比如对讲机就是典型半双工。 上文的串口模拟程序也可以理解为半双工通信
全双工通信:发送数据的同时也能够接收数据,两者同步进行,比如我们的电话通信。
IO口模拟串口通信基本展现的串口通信的本质,但是单片机程序却需要不停的检测扫描单片机IO口收到的数据,大量占用了单片机的运行时间。因此51单片机内部有一个UART模块,能够自动接收数据,接收完了通知一下就可以了,要正确使用它,需要正确的配置特殊功能寄存器。
SCON串行控制寄存器
本案只介绍模式1,即设置SM1 = 1、SM0 = 0就是模式1,该模式就是前文模拟串口通信所使用的数据帧格式:1位起始位,8位数据位,1位停止位。
模拟串口通信是采用定时器0来表现波特率,STC89C52 UART模块波特率发送器只能由定时器T1或者定时器T2产生,而不能由定时器T0产生,这和模拟通信是完全不同的概念。
在使用定时器T2需要额外的配置寄存器默认使用定时T1。
上程序代码:
# include<reg52.h>
void ConfigUART(unsigned int baud);
void main()
{
EA = 1;
ConfigUART(9600); //配置波特率为9600
while(1);
}
/* 串口配置函数,baud-通信波特率 */
void ConfigUART(unsigned int baud)
{
SCON = 0x50; //0x50= 0101 0000 配置串口为模式1
TMOD &= 0x0F; //清零T1的控制位
TMOD |= 0x20; //0x20 = 0010 0000 配置T1的为模式2自动重载模式
TH1 = 256 - (11059200/12/32)/baud; //计算T1的重载值
TL1 = TH1; //设置初值
ET1 = 0; //禁止T1中断
ES = 1; //启动串口中断
TR1 = 1; //启动T1定时器
}
/*UART中断服务函数 */
void interruptUART() interrupt 4
{
if(RI) //接收到字节
{
RI = 0; //软件清0接收中断标志
SBUF = SBUF+1;//接收的数据+1,左边是发送SBUF,右边是接收SBUF
}
if(TI)
{
TI = 0; //软件清0发送中断标志位
}
}
看一下结果视频:自带模块串口通信_哔哩哔哩_bilibili
可以看到可以正常通信没有问题,然后对程序里面相关内容进行简单讲解:
定时器T1的重载值计算公式为:
TH1 = TL1 = 256 - 晶振值/12/2/16/波特率 那么它的
中断间隔时间 = 晶振值/12/2/16/波特率 =晶振值/12*(1/波特率)*(1/32)
晶振值/12含义是1秒内的机器周期数,(1/波特率)含义是传递1位数据所需要的时间
晶振值/12*(1/波特率)含义是传递1位数据需要多少机器周器
同理晶振值/12*(1/波特率)*(1/32)这个中断的时间=32分之1传递1位数据的时间
即把传递1位数据分成了32次时间间隔。
计算下结果是3即 中断间隔时间 = 3个机器周期这个时间是很短的
因为串口模块它的信号采样方式是把一位信号采集16次,把其中的7、8、9次的信号电平取出来,如果这三次中其中两次是高电平,就认定这个数据是1.如果两次电平是0,就认定这位为0,这样一旦受到意外的干扰读错一次 数据,依然能保证最终数据的正确性。
下图来自推荐博文的图片主要是为了方便说明:波特率的计算公式
SMOD由电源寄存器PCON控制,默认下SMOD为0,代入上式中就是之前的初值求解公式
TH1 = TL1 = 256 - 晶振值/12/2/16/波特率
当把寄存器最高置1 PCON |= 0x80;即把SMOD置1,那就可以把波特率提高1倍,如上图的公式。
这时T1的初值就变成了:TH1 = TL1 = 256 - 晶振值/12/16/波特率
这里必须要注意的一点是:如果你想要使用PCON寄存器控制波率翻倍它的初值公式依然要写成
TH1 = TL1 = 256 - 晶振值/12/2/16/波特率 只不过
现在的重载值是 TH1=256 - (11059200/12/32)/baud =256-(11059200/12/16)/(2*buad)baud*2=4800*2=9600,9600就是现在程序的波特率
如果你这里的初值重载公司写成TH1 = TL1 = 256 - 晶振值/12/16/波特率,那么它现在的波特率就依然是4800不是9600。你用串口通信软件通信波特率设置成9600它传输后的结果出错。
看视频:波特率相关_哔哩哔哩_bilibili
我们可以看到9600的波特率它的时间间隔只有3个机器周期,14400的时间间隔是2个机器周期。可见如果它的波特率再高一点的话它的时间间隔可能不满1个机器周期。因此有了另外一种工作模式即PCON最高位置1后 ConfigUART(9600);它现在的波特率是19200,但是它的时间间隔是原9600波特率的时间间隔即3个机器周期。
必须要提醒一个定时器0模拟串口通信和串口通信模块工作上还是有不少差异的,串口通信模块完成一次输入输出,它只进入两次串口通信中断。一次是RI置1响应,一次是TI置1响应,然后软件清零。不再关注传输过程只关注是否传输完成,传输完成就发个信号。
串口通信的发送接收电路在物理上有两个相同的SBUF寄存器,它们的地址也都是0x99,但是一个是用来发送缓冲,一个是用来接收缓冲。意思就是说,有两个房间,两个房间的门牌号是一样的,其中一个许进不出另一个许出不许进。这样的话就可以实现UART的全双工通信,相互之间不会产生干扰。但是在逻辑上,每次只操作SBUF,单片机会自动根据对它的执行的是“读”还是“写”操作来选择是接收SBUF还是发送SBUF。
串口通信程序的基本步骤:
1、配置串口为模式1。
2、配置定时器T1为模式2,即自动重装模式。
3、根据波特率计算TH1和TL1的初值,如果有需要可以使用PCON进行波特率加倍。
4、打开定时器控制寄存器TR1,让定时器跑起来。注意:在使用串口中断的时候定时器1中断就不能再使能了,除非你用的是定时器2的串口中断。