1. 概念
UART( Universal Asynchronous Receiver/Transmitter),通用异步接收器/发送器
2. 背景
两个设备间通信
3. 特点
串行、全双工、异步(这里只讨论异步,同步则需要时钟)
4. 协议层
1. 数据包
起始位(1位低电平)+主体数据(8/9位 低位在前)+校验位(1位。偶校验:主体和校验位的1加起来是偶数,奇校验相反)+停止位(1/1.5/2位高电平),双方只有数据包一致才能正常收发数据
2. 波特率
由于异步通信中没有时钟信号,所以接收双方要约定好波特率,即每秒传输的码元个数,以便对信号进行解码,常见的波特率有4800、9600、115200等。STM32中波特率的设置通过串口初始化结构体来实现
3. 起始和停止信号
数据包的首位就是起始和停止信号,起始位由一个0表示,停止位由0.5、1、1.5、2个1表示
4. 有效数据
规定了数据的长度,一般为8、9位
5. 数据校验
在有效数据后有一个可选的校验位,用于受到干扰时通信出错判断,有奇校验(odd)、偶校验(even)、0校验(space)、1校验(mark)和无校验(noparity)
5.串口初始化步骤
1.初始化GPIO的时钟
2.初始化GPIO_TX和GPIO_RX引脚的模式,TX要配置成推挽复用,RX要配置成浮空输入
3.初始化UARTx的时钟
4.配置串口的工作参数:波特率、有效字长、数据校验、停止位
5.串口中断优先级配置
6.使能串口中断,一般是接收中断
7.使能串口
8.编写逻辑代码(中断函数)
6.常用寄存器位详解
1. USART_SR(状态寄存器):
1.1 TXE 发送数据寄存器空(判断的是发送数据寄存器)
TXE=1:当发送数据寄存器(TDR)的数据转移到移位寄存器,并且TDR未进新的数据
TXE=0:TDR中有数据
1.2 TC 发送完成(判断的是移位寄存器)
TC=1:当移位寄存器为空(从TDR过来的数据已经全部移给了TX引脚),并且TXE=1(TDR未进新的数据)
TC=0:移位寄存器有数据(从TDR过来的数据未能全部移给了TX引脚),或者TXE=0(TDR进了新的数据)
1.3 RXNE 读数据寄存器非空(判断的是接收数据寄存器)
RXNE=1:RDR中有数据
RXNE=0:RDR为空
1.4 三者区别
标志位清除:
TXE--写寄存器DR清零
RXNE--读寄存器DR清零,也可软件手动清零
TC-- 读/写寄存器DR清零,也可软件手动清零
备注:(网上看到的,很形象的比喻~)
TXE是指“弹仓”空;
TC是“枪膛”空。
也就是说,你写数据到串口时,是装入弹仓,硬件会将数据移到枪膛,这时,TXE为1,TC为0,STM32硬件的TX脚正在发送数据,但你还可以装入数据到弹仓,装入后,TXE为0,TC为0.
TX发送完一个数据后,立即将数据从弹仓移入枪膛,这时,TXE为1,TC为0.
最后TX发送完数据,你又没有装入新数据,这时。TXE为1,TC为1.
TXE允许程序有更充裕的时间填写TDR寄存器,保证发送的数据流不间断。
TC可以让程序知道发送结束的确切时间,有利于程序控制外部数据流的时序。
7. 三种方式显示数据的接收和发送
1.轮询(poll)
1.HAL_UART_Receive
HAL_StatusTypeDef HAL_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
huart:初始化串口的句柄
pData:接收的buff,是一个地址
Size:要接受的大小
Timeout:超时的时间
在规定超时时间内,未接收完Size大小的数据就会返回超时状态。若能发送完,返回ok,退出函数
Timeout=0的时候,只能发送一个字节
2.HAL_UART_Transmit
在规定超时时间内,未发送完Size大小的数据就会返回超时状态。实测115200bps下,1ms能传输11帧(1+8+1=10位)
Timeout不能=0,因为要判断TC,0ms无法完成1个字节的完全发送
备注:这两个函数一般用于重定向发送接收函数
#include<stdio.h>
/* 重定向c库函数printf */
int fputc(int ch, FILE *f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xffff);
return ch;
}
/* 重定向c库函数getchar,scanf */
int fgetc(FILE *f)
{
uint8_t ch = 0;
HAL_UART_Receive(&huart1, &ch, 1, 0xffff);
return ch;
}
在keil中勾选use Micro LIB,注意如果想要打印中文,要把编码改为GB,因为stm32cubemx默认是utf8的。
2.中断(常用)
实现的功能,接收到以0x0D结尾的字符串,再通过中断发送出去。
1. 使用stm32cubemx生成初始化代码,记得勾选串口全局中断
可以在HAL_UART_MspInit发现除了引脚配置还有内核的中断使能,注意这里只是使能内核的串口中断,需要自己使能外设uart的中断
2.在MX_USART1_UART_Init函数中开启接收中断,增加这么一行
HAL_UART1_Receive_IT(&huart1, (uint8_t *)UART1_temp, 1);
huart1是初始化串口的句柄,UART1_TEMP是元素为1个的数组, 接收长度为1。
长度设置为1的原因:因为这个函数功能是接收到指定长度的数据后会进入一次中断,然后关闭接收中断, 然后调用接收完成函数HAL_UART_RxCpltCallback。想要继续接收数据的话,必须重新调用这个函数,这里我是在HAL_UART_RxCpltCallback处理完接收到的数据后调用的。
如下图:当接收到完整的一帧数据后,会开启发送中断发送数据,然后清掉接收内存
效果:
3.DMA
和中断实现的功能一样,接收到以0x0D结尾的字符串,然后再发送到串口上
1. 使用stm32cubemx生成初始化代码,Add DMA接收和发送通道
2.在MX_USART1_UART_Init函数中开启接收中断,增加这么一行
RBuff是长度为1的数组,这里因为需求是连续接收,所以模式要选成循环模式,不然接受完指定长度的数据就会关闭接收中断,当然也可以使用普通模式,然后在发送完成函数中再次调用这句话去使能接收。我这里是使用循环接收模式。
这里是接收到1个字节的长度数据后就会进入接收完成函数,然后接收到指定的数据后就发送出去,如下代码:
备注:
1.cubemx生成的dma代码的时钟初始化位置不对,需要放到初始化dma之前,不然会导致dma初始化失败
2.dma发送时候,不能立马清楚Rbuff1的内容,因为还没完全发送完成
效果: