串口(UART)一帧 = 1 个字节,硬件物理层面:起始位 + 8 位数据位 + 校验位(可选)+ 停止位,这完整一套叫 1 帧,传输 1 字节(0~255)。
TTL 串口:TX、RX、GND 三条线实现设备间通讯。
补充:TTL 电平一般 3.3V/5V;RS232 是正负差分电平,二者不能直连。
阻塞式串口通讯(串口轮询)
UART 配置:System Core 里的 SYS 选择 Serial Wire(SWD,仅调试下载,和 UART 功能本身无关);Connectivity 里打开 UART1/UART2,模式选 Asynchronous(异步)。
调用函数:
HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout); HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);第 1 个参数:串口句柄指针,例
&huart1/&huart2第 2 个参数:数据缓冲区指针,数组可以直接写数组名,因为也是地址,可以用
(uint8_t *)强制转换消除一下警告第 3 个参数:已知的收发数据(数组/字符串)长度,可以写已知的固定长度,也可以用strlen和sizeof直接读data,在本文设置为2
第 4 个参数:超时时间,单位 ms,输入
HAL_MAX_DELAY为无限等待时间。
注意事项
|是按位或,不是逻辑或;if 判断条件里逻辑或必须用||;同理区分&按位与 和&&逻辑与。- 数组名是地址常量,不可直接赋值。对数组赋值只能逐个元素赋值或者使用内存拷贝函数。
- 发送字符串时可以使用 strlen 获取有效字符长度;接收字符串时硬件收到\0,所以收到的不是 C 字符串,禁止直接使用 strlen。
串口中断(_IT,发送/接收寄存器非空中断)
UART 配置:System Core 里的 SYS 选择 Serial Wire(SWD,仅调试下载,和 UART 功能本身无关);Connectivity 里打开 UART1/UART2,模式选 Asynchronous(异步)
调用函数:函数中断版函数后缀为_IT,没有超时参数
HAL_UART_Receive_IT(&huart2, receivedata, 2); HAL_UART_Transmit_IT(&huart2, receivedata, 2);- 注意,该串口中断属于非阻塞函数,调用函数只是启动一次收发,函数立刻返回,不会等待收发完成,因此不要像之前一样在 while 循环里调用
HAL_UART_Receive_IT,否则旧接收任务还没完成就被新任务打断。 - 我们要在
stm32f1xx_it.c找到USART2_IRQHandler(void);ctrl加左键点击进入HAL_UART_IRQHandler(&huart2),找到弱定义回调:void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)。 - 然后在 main.c 或者其他 C 文件中重写这个回调函数,回调内部处理收到的数据,并且再次调用
HAL_UART_Receive_IT开启下一轮接收。 - 存储接收数据的数组记得挪到main.c文件开头改为全局变量,因为要在callback函数中调用
DMA 串口传输
DMA 通道自动搬运串口数据,传输完成后触发中断通知 CPU,大幅减少 CPU 占用。
配置:在前面串口中断配置的基础上,在Connectivity-UART2-DMA Settings,添加 TX、RX 的 DMA 通道;内存开启地址自增,UART 外设寄存器地址固定,外设地址自增关闭。
DMA 函数后缀为 DMA,也是非阻塞,也要使用callback函数:
HAL_UART_Receive_DMA(&huart2, receivedata, 2); HAL_UART_Transmit_DMA(&huart2, receivedata, 2);串口接收不定长数据(空闲中断 IDLE)
原理:UART 总线在收到数据后,一段时间没有新字节,触发 IDLE 空闲中断,配合 DMA 实现不定长接收。
不定长数据收发支持阻塞、普通中断、DMA 三种模式,不同的模式使用前面讲的三种模式的配置,只需要把代码前缀改为HAL_UARTEx_ReceiveToIdle就好了,后缀还是和之前三种模式一样,但是中断和DMA所需的回调函数不是之前那个,在之前同样的位置搜索void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size),替换我们之前的回调函数就好了。
DMA 空闲不定长接收代码(这里第三个参数要用回调函数的Size入参,是本次收到的字节数):
HAL_UARTEx_ReceiveToIdle_DMA(&huart2, receivedata, Size);DMA 默认开启半传输 HT 中断,收到一半缓冲区数据就会进入回调,容易误处理,空闲中断场景建议添加以下代码关闭半传输中断:
__HAL_DMA_DISABLE_IT(&hdma_usart2_rx, DMA_IT_HT);