通俗的UART讲解和源码 - hmjason
通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),通常称作UATR,是一种异步收发传输器。将数据由串行通信与并行通信间做传输转换,作为并行输入称为串行输出的芯片。UART是一种通用串行数据总线,用于异步通信。该总线双向通信,可以实现全双工传输和接收。
数据传送速率用波特率来表示,即每秒钟传送的二进制位数。例如数据传送速率为120字符/秒,而每一个字符为10位(1个起始位,7个数据位,1个校验位,1个结束位),则其传送的波特率为10×120=1200字符/秒=1200波特。
起始位:先发出一个逻辑”0”信号,表示传输字符的开始。
数据位:可以是5~8位逻辑”0”或”1”。如ASCII码(7位),扩展BCD码(8位)。小端传输
校验位:数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验)
停止位:它是一个字符数据的结束标志。可以是1位、1.5位、2位的高电平。
空闲位:处于逻辑“1”状态,表示当前线路上没有资料传送。
注:异步通信是按字符传输的,接收设备在收到起始信号之后只要在一个字符的传输时间内能和发送设备保持同步就能正确接收。下一个字符起始位的到来又使同步重新校准(依靠检测起始位来实现发送与接收方的时钟自同步的)。
1、UART通信协议
UART作为异步串口通信协议的一种,工作原理是将传输数据的每一个字符一位一位地传输。其中每一位(bit)的意义如下:
起始位:先发出一个逻辑“0”的信号,表示传输字符开始。
数据位:紧接着起始位之后。数据位的个数可以是4、5、6、7、8等,构成一个字符。通常采用ASCII码。从最低位开始传送,靠时钟定位。
奇偶校验位:数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验),以次来校验数据传送的正确性。
停止位:它是一个字符数据的结束标志。可以是1位、1.5位、2位的高电平。由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率也就越慢。
空闲位:处于逻辑“1”状态,表示当前线路上没有数据传输。
如下图所示:
2、UART工作原理
发送数据过程:空闲状态,线路处于高电平;当收到发送指令后,拉低线路的一个数据位的时间T,接着数据按低位到高位依次发送,数据发送完毕后,接着发送奇偶校验位和停止位,一帧数据发送完成。
数据接收过程:空闲状态,线路处于高电平;当检测到线路的下降沿(高电平变为低电平)时说明线路有数据传输,按照约定的波特率从低位到高位接收数据,数据接收完毕后,接着接收并比较奇偶校验位是否正确,如果正确则通知后续设备接收数据或存入缓冲。
由于UART是异步传输,没有传输同步时钟,为了保证数据的正确性,UART采用16倍数据波特率的时钟进行采样。每个数据有16个时钟采样,取中间的采样值,以保证采样不会滑码或误吗。一般UART一帧的数据位数为8,这样即使每个数据有一个时钟的误差,接收端也能正确地采样到数据。
UART的接收数据时序为:当检测到数据的下降沿时,表明线路上有数据进行传输,这是计数器CNT开始计数,当计数器为24=16+8时,采样的值为第0位数据;当计数器的值为40时,采样的值为第一位数据,依次类推,进行后面6个数据的采样。如果需要进行奇偶校验,则当计数器的值为152时,采样的值即为奇偶位;当计数器的值为168时,采样的值为“1”表示停止位,数据接收完成。
一个标准的10位异步串行通信协议(1个起始位、1个停止位和8个数据位)收发时序,如下图所示:
源码范例:
#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(){ _EMI= 1; //开总中断 ConfigUART(9600); while (1){ //配置波特率为 9600 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; //设置当前状态为接收 } /* 启动串行发送,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{ //起始位不为 0 时,中止接收 TR0 = 0; //关闭 T0 }else if (cnt <= 8){ //处理 8 位数据位 RxdBuf >>= 1; //低位在先,所以将之前接收的位向右移 //接收脚为 1 时,缓冲器最高位置 1, //而为 0 时不处理即仍保持移位后的 0 if (PIN_RXD){ RxdBuf |= 0x80; } cnt++; }else{ //停止位处理 cnt = 0; //复位 bit 计数器 TR0 = 0; //关闭 T0 if (PIN_RXD){ //停止位为 1 时,方能认为数据有效 RxdEnd = 1; //置接收结束标志 } } } }