UART概述
UART的定义
- USART指通用同步收发器,UART指通用异步收发器
- 这些通用收发器提供了一种灵活的方式与外部设备进行单工/半双工/全双工方式的数据交互,并且可选择多种波特率,支持多种通信协议和功能模式等
UART的类别
-
STM8S单片机片内总共有3个串口资源:UART1/2/3(STM8S105则只有UART2这个串口资源,具体情况查阅对应型号的数据手册)
-
UART1/2/3之间在功能支持上存在差异,见下图:
UART配置流程
UART配置简述
-
主时钟频率配置
确定主时钟,这与后续波特率的设置相关
-
配置通信数据帧格式
设定发送/接收数据位数
设定发送/接收数据校验位以及校验方式(可选),
设定发送/接收数据停止位位数
-
计算并配置波特率
设定发送/接收数据波特率
-
使能发送/接收中断功能
此步可选,让单片机发送出数据/接收到数据时进入中断服务程序
-
使能发送/接收功能
使能之后,然后就开始向寄存器写入数据以发送/接收单字节数据,并等待发送/接收完毕
UART相关寄存器
-
控制寄存器UART_CR1
明确发送的数据帧格式,配置奇偶校验
-
本寄存器的M位(位4)配置数据帧的格式(UART数据字长度由此位决定):
配置为1,数据帧格式为1个起始位,8个数据位,n个停止位(取决于另一个寄存器UART_CR3中的STOP[1:0]位)
配置为1,数据帧格式为1个起始位,9个数据位,1个停止位;此时需要本寄存器的T8位(位6)与R8位(位7)用以储存发送与接收的第九位
-
PIEN位(位0)、PS位(位1)与PCEN位(位2)用于配置校验:
PCEN位配置为1则使能硬件奇偶校验控制(校验位的启用状态将导致数据帧格式变化,例如:M位为1且PCEN位也为1,则数据帧有1起始位+8数据位+1校验位+停止位,校验位将挤占一个数据位);
PS位用于选择校验方式,0使能偶校验,1则使能奇校验;
PIEN位配置为1则使能校验中断,当发生数据校验错误时产生中断
各个位的具体作用
-
-
控制寄存器UART_CR3
配置数据的停止位
- STM8单片机通信的停止位可以设定为1位/1.5位/2位,通过对本寄存器的STOP[1:0]位(由位5与4共同组成)配置得到:写入00则为1位,10为2位,11为1.5位
各个位的具体作用(其它位在UART3上不存在):
-
控制寄存器UART_CR2
使能发送/接收功能,配置发送/接收中断
- 配置好数据帧格式后,配置发送功能位TEN位(位3)为1以使能发送,配置接收功能位REN位(位2)为1以使能接收
- 如果需要检测数据是否被硬件传送到移位寄存器中,可以将发送数据中断使能位TIEN位(位7)置1;如果需要检测数据是否发送完毕,可以将发送完成中断使能位TCIEN位(位6)置1
- 如果需要检测是否接收到数据,可以将接收中断使能位RIEN位(位5)置1
各个位的具体功能:
-
波特率寄存器UART_BRR1/2
收发双方波特率应当匹配,配置波特率流程如下:
-
确定主时钟频率fMASTER
-
确定预设波特率:串口收发波特率=fMASTER/UART_DIV(串口分频值)
-
计算UART_DIV取值:例如欲设定波特率为9600bps,而主时钟频率为fHSI/2=8MHz,则根据公式可计算得UART_DIV=8.33.33,取833
在连续大数据量通信时这样的误差会导致缺码,可以把时钟源切换为更高精度的外部时钟源以减小误差
-
配置波特率寄存器:将计算得的数值转换为十六进制,得0341HEX,配置如下
UART_BRR1的0至7位均为UART_DIV位,定义了UART分频数的中间8位(第二、第三半位元),UART_BRR2的7至4位则是分频数的高四位,3到0位是低四位
- 特别注意,计算出的分频值不应小于16,且UART_BRR1不能被赋值为0,否则会关闭波特率时钟导致无法通信,若中间两位恰好为0则需要重新设定波特率或改变主时钟频率
-
-
数据状态寄存器UART_SR
用于反映通信过程中的一些状态变化,例如:
- 检测数据是否出错:发生奇偶校验错误时,此寄存器的PE位(位0)会被置1;
- 检测发送数据是否移入寄存器:开启发送数据中断使能(UART_CR2的TIEN位(位7)置1),此时UART_SR的TXE位(位7)为1时产生中断(表示数据已经被转入寄存器);
- 检测数据是否发送完毕:开启发送完成中断使能(UART_CR2的TCIEN位(位7)置1),此时UART_SR的TC位(位6)为1时产生中断(表示发送已经完成);
- 检测接收到数据:开启接收中断使能(UART_CR2的RIEN位(位5)置1),此时UART_SR的RXNE位(位5)或OR位(位3)为1时产生中断(表示收到数据);
各个位的具体含义:
-
数据寄存器UART_DR
内部分为数据发送寄存器TDR与数据接收寄存器RDR
上述寄存器配置完成后,即完成了对数据帧格式的约定与功能位的使能,就可以进入正常的发送/接收程序了,发送过程只需将欲发送的数据传入串口数据寄存器UART_DR即可
代码实现
-
初始化UART
//首先初始化UART2的TX与RX,以PD6连接TX,PD5连接RX为例 void UART2_IO_init() { PD_DDR=0x20; //TXD设置为OD输出 PD_CR1=0x40; //RXD设置为上拉输入 PD_CR2=0x00; //全部低速率 } //初始化函数,用于配置UART2的波特率与数据帧格式,并最后使能UART2 void UART2_init() { UART2_IO_Init(); //初始化UART的IO口 UART2_BRR2 = 0x00; //设置波特率寄存器,先设置BRR2 UART2_BRR1 = 0x0D; // 2M/9600,换算为十六进制为000D //具体配置事项见寄存器UART_BRR介绍,此处使用默认时钟2MHz,波特率为9600 UART2_CR1 = 0x00; //设置数据帧格式:8个数据位,无校验位 UART2_CR3 = 0x00; //设置 1个停止位 UART2_CR2 = 0x2C; //使能发送、接收以及接收中断 //0010 1100 三个1分别对应RIEN位接收中断 TEN位发送使能 REN位接收使能 }
-
发送程序
//让单片机通过UART向外发送数据 //原理:利用UART_SR状态寄存器判断当前发送的进程,以作出不同反应 void UART2_SendStr(unsigned char *s) //传入指向欲发送字符的指针 { while(*s != '\0') //当发送未到达终止位时 { while(UART2_SR & 0x80==0); //1000 0000,UART_SR的TXE位为1时表示数据已经被转入寄存器 //当TXE位还不是1时等待,以让数据从数据发送寄存器转移到 Transmit shift register UART2_DR = *s; //发送数据寄存器装填当前指向的一位 s++; //指针指向下一位 while((UART2_SR & 0x40)== 0); //0100 0000,UART_SR的TC位为1时表示数据发送已经完成 //当TC位还不是1的时候等待,以待数据发送完成 } }
-
接收程序
//让单片机读取自UART发送来的数据 //原理:开启接收中断后,将接收程序写在中断服务函数中 unsigned int ReceiveData=0xFF;//用一个变量存放接收到的数据 #pragma vector=23 //UART2中断向量号为21 __interrupt void UART2_Receive_Interrupt(void) { while(UART2_SR & 0x20 == 0);//等待数据接收完成 ReceiveData = UART2_DR; //保存接收而来的数据 }
-
与电脑进行串口通信的实验设计
-
在实验之前,先在电脑上打开“串口调试助手”这个软件(有很多软件都可以实现电脑与单片机的串口通信功能,自行搜索选择一个下载即可)
-
然后,使用USB线将电脑和单片机连接起来(一般的开发板上会有USB-UART接口),在串口调试助手的串口选择中选择USB连接的串口
需要注意的是,电脑未必能识别出这个串口,原因是电脑系统没有你的单片机上的USB-UART芯片的相应驱动,解决方法是看你的开发板上使用的USB-UART芯片型号(比如PL-2303),下载对应驱动安装
如果还是找不到串口,很可能是因为新的Win系统不支持这个驱动,打开电脑的设备管理器,在“通用串行总线控制器”中刚刚安装的驱动有没有带三角感叹号标识,如果有的话卸载它,去网上搜索其他能够兼容的版本
-
在软件中设置波特率、停止位、校验位、数据位等与单片机程序一致,打开串口
-
实验现象是:发送0到3不同的数时,分别控制PB0-3连接的LED灯亮灭,然后在接收窗口中应该就能看到单片机发送的数据
实验代码很简单,首先写好上文提到的初始化、发送与接收函数,再调用
unsigned char StringBuf[]="Hello,world! \r\n"; void main(void) { GPIO_init();//对LED灯初始化 UART2_init(); asm("RIM"); while(1) { UART2_SendStr(StringBuf);//发送字符串 delay_ms(3000); } } #pragma vector=23 __interrupt void UART2_Receive_Interrupt(void) { while(UART2_SR & 0x20 == 0); ReceiveData = UART2_DR; switch(ReceiveData) { case 0:PB_ODR_ODR0 = !PB_ODR_ODR0; break; case 1:PB_ODR_ODR1 = !PB_ODR_ODR1; break; case 2:PB_ODR_ODR2 = !PB_ODR_ODR2; break; case 3:PB_ODR_ODR3 = !PB_ODR_ODR3; break; } }
-