知识
串口是一种通讯协议,存在于 设备-设备 之间。在介绍串口协议之前,我们先来看看通信网络中的分层。如果参考OSI模型, 网络OSI七层模型及各层作用 那么它属于数据链路层。
串行数据通信的方向性结构有三种,即单工、半双工和全双工。
串口通信的概念非常简单,串口按位(bit)发送和接收字节。尽管比按字节(byte)的并行通信慢,但是串口可以在使用一根线发送数据的同时用另一根线接收数据。它很简单并且能够实现远距离通信。比如IEEE488定义并行通行状态时,规定设备线总常不得超过20米,并且任意两个设备间的长度不得超过2米;而对于串口而言,长度可达1200米。
凭借着其改善的信号完整性和传播速度,串行通信总线正在变得越来越普遍,甚至在短程距离的应用中,其优越性已经开始超越并行总线不需要串行化元件(serializer),并解决了诸如时钟偏移(Clock skew)、互联密度等缺点。PCI到PCI Express的升级就一个例子。
USART(Universal Synchronous/Asynchronous Receiver/Transmitter, 通用同步/异步串行接收/发送器)是一个全双工通用同步/异步串行收发模块,该接口是一个高度灵活的串行通信设备。
uart和usart的区别
UART:universal asynchronous receiver and transmitter通用异步收/发器
USART:universal synchronous asynchronous receiver and transmitter通用同步/异步收/发器
从名字上可以看出,USART在UART基础上增加了同步功能。
- 当我们使用USART在异步通信的时候,它与UART没有什么区别
- 在同步通信时,USART能够提供主动时钟。如STM32的USART可以提供时钟支持ISO7816的智能卡接口。
串口的有关参数
典型地,串口用于ASCII码字符的传输。通信使用3根线完成:地线(GND),发送(Tx),接收(Rx)。由于串口通信是异步的,端口能够在一根线上发送数据同时在另一根线上接收数据。其他线用于握手,但是不是必须的。串口通信最重要的参数是波特率、数据位、停止位和奇偶校验。对于两个进行通行的端口,这些参数必须匹配:
波特率 (Baud rate)
同步通讯需要时钟信号来进行同步;而异步通信由于没有时钟信号,因此两个通讯设备之间需要约定好波特率,即每个码元的长度,以便对信号进行解码。常见的波特率为4800、9600、115200等。
这是一个衡量通信速度的参数。它表示每秒钟传送的bit的个数。例如300波特表示每秒钟发送300个bit。当我们提到时钟周期时,我们就是指波特率例如如果协议需要4800波特率,那么时钟是4800Hz。这意味着串口通信在数据线上的采样率为4800Hz。通常电话线的波特率为14400,28800和36600。波特率可以远远大于这些值,但是波特率和距离成反比。高波特率常常用于放置的很近的仪器间的通信,典型的例子就是GPIB设备的通信。
数据位 (Data bit)
这是衡量通信中实际数据位的参数。当计算机发送一个信息包,实际的数据不会是8位的,标准的值是5、7和8位。如何设置取决于你想传送的信息。比如,标准的ASCII码是0~127(7位)。扩展的ASCII码是0~255(8位)。如果数据使用简单的文本(标准 ASCII码),那么每个数据包使用7位数据。每个包是指一个字节,包括开始/停止位,数据位和奇偶校验位。由于实际数据位取决于通信协议的选取,术语“包”指任何通信的情况。
停止位 (Stop bit)
用于表示单个包的最后一位。典型的值为1,1.5和2位。由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也越慢。
奇偶校验位 (Parity bit)
在串口通信中一种简单的检错方式。有四种检错方式:偶、奇、高和低。当然没有校验位也是可以的。对于偶和奇校验的情况,串口会设置校验位(数据位后面的一位),用一个值确保传输的数据有偶个或者奇个逻辑高位。例如,如果数据是011,那么对于偶校验,校验位为0,保证逻辑高的位数是偶数个。如果是奇校验,校验位位1,这样就有3个逻辑高位。高位和低位不真正的检查数据,简单置位逻辑高或者逻辑低校验。这样使得接收设备能够知道一个位的状态,有机会判断是否有噪声干扰了通信或者是否传输和接收数据是否不同步。
CubeMX 配置 USART (以 USART2 为例)
关于串口的时候有3种模式。
HOST-OS : Windows-10
STM32 Cube :v5.6
MCU : STM32F429
LIB : stm32cube_fw_f4_v1250
轮询模式
CPU不断查询IO设备,如设备有请求则加以处理。例如CPU不断查询串口是否传输完成,如传输超过则返回超时错误。轮询方式会占用CPU处理时间,效率较低。
CubeMx 配置
1)在Pinout & Configuration中,选择一个 UART/USART
2)Mode
: Asynchronous
(异步); Hardware Flow Control
(硬件流控) 选择 Disable
3)Configuration
- Parameter Settings
中 (任意设置都可以,但通讯双方要匹配)
- Baud Rate : 波特率,一般使用
115200
- Word Length : 字长 8
- Parity: 校验
- Stop Bits : 停止位
4)填写有关的项目属性
5)右上角,GENERATE CODE
添加代码
实现:简单地发送有关的数据。
/* USER CODE BEGIN 1 */
uint8_t aTxBuffer[] = "Hello CubeMx
";
int len = strlen(aTxBuffer);
/* USER CODE END 1 */
/* USER CODE BEGIN 2 */
HAL_UART_Transmit(&huart2, aTxBuffer, len, 0xFFFF); // 表示通过串口发送len个字符。ch为字符的存储地址,0xFFFF为超时时间。
/* USER CODE END 2 */
编译,运行;在串口助手中应该可以看到打印的消息。
中断模式
当I/O操作完成时,输入输出设备控制器通过中断请求线向处理器发出中断信号,处理器收到中断信号之后,转到中断处理程序,对数据传送工作进行相应的处理。
配置
1)在Pinout & Configuration页中的Connectivity
,选择一个 UART/USART
Mode
:Asynchronous
(异步);Hardware Flow Control
(硬件流控) 选择Disable
Configuration
-Parameter Settings
中 (任意设置都可以,但通讯双方要匹配)- Baud Rate : 波特率,一般使用
115200
- Word Length : 字长 8
- Parity: 校验
- Stop Bits : 停止位
- Baud Rate : 波特率,一般使用
Configuration
-NVIC Settings
中 : 勾选Enabled
(开启中断)
2)在Pinout & Configuration中,System Core
,选择NVIC
Configuration
-Parameter Settings
中 ,确认UsartX global interrupt
的Enable
是勾选的Configuration
-Code generation
中,确认UsartX global interrupt
的Select for init sequence ordering
是勾选的。
3)填写有关的项目属性
4)右上角,GENERATE CODE
添加代码
实现:接收数据并重新发回(echo)。
例子只是作为演示,在实际工程中不要这么用。
/* USER CODE BEGIN PV */
// 为了方便,这里使用到了全局变量
uint8_t aTxBuffer[] = "Hello CubeMx
";
uint8_t aRxBuffer[20];/* Buffer used for recv */
/* USER CODE END PV */
/* USER CODE BEGIN 2 */
HAL_UART_Transmit_IT(&huart2, (uint8_t *)aTxBuffer, sizeof(aTxBuffer) - 1);
HAL_UART_Receive_IT(&huart2, (uint8_t *)aRxBuffer, 1);
/* USER CODE END 2 */
在main.c文件后面重写HAL_UART_RxCpltCallback
中断接收完成回调函数;每次收完数据以后,
/* USER CODE BEGIN 4 */
/**
* @brief Rx Transfer completed callbacks
* @param huart: uart handle
* @retval None
*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
/* Prevent unused argument(s) compilation warning */
UNUSED(huart);
/* NOTE : This function should not be modified, when the callback is needed,
the HAL_UART_RxCpltCallback can be implemented in the user file
*/
if(huart == &huart2)
{
HAL_UART_Transmit(huart, (uint8_t *)aRxBuffer, 10,0xFFFF);
HAL_UART_Receive_IT(huart, (uint8_t *)aRxBuffer, 1);
}
}
/* USER CODE END 4 */
注意事项
有关资料:关于HAL UART 发送接收死锁问题、慎用HAL_UART_RxCpltCallback中调用HAL_UART_Receive_IT
HAL_UART_RxCpltCallback
中使用HAL_UART_Transmit
的问题分析:
由于HAL_UART_RxCpltCallback()
函数是在中断里被调用的;
此时,如果在HAL_UART_RxCpltCallback
使用了HAL_UART_Receive_IT()
,最好不用HAL_UART_Transmit()
因为发送过程会锁定串口,这时来了读取中断,其中的下一次HAL_UART_Receive_IT()
会因为获得不了设备而失败,因此中断的链条就打断了。
HAL_UART_RxCpltCallback
里面也不能用HAL_UART_Receive_IT
,因为会把ErrorCode
覆盖掉,HAL_UART_Receive_IT
只是开启中断函数, 可以在对应的IRQHandler
(例如USART2_IRQHandler
)最后执行。
解决方案:收发尽量一致(要么都是中断,要么都是阻塞式)
- 要么:选择
HAL_UART_Receive_IT
放在主程序的while(1)
循环中 - 要么:在
HAL_UART_RxCpltCallback
中使用HAL_UART_Transmit
之前关闭中断,调用完成以后再次打开,将此后再使用HAL_UART_Receive_IT
接收中断。 - 要么:
HAL_UART_RxCpltCallback
中只使用HAL_UART_Receive_IT
重新开启中断,不使用HAL_UART_Transmit
。如果一定要发,维护一条任务队列,在中断回调函数中添加需要发送的数据,让其在正常的调度环境中发送。
DMA模式
DMA(直接内存存取技术),直接传送。即在内存与IO设备间传送一个数据块的过程中,不需要CPU的任何中间干涉,只需要CPU在过程开始时向设备发出“传送块数据”的命令,然后通过中断来得知过程是否结束和下次操作是否准备就绪。
我们留在下一讲进行讲解。
附录:使用printf串口打印
以 USART2 为例,在USART的初始化文件中添加如下代码:
printf
的函数putc
不要用中断,要用直接发送的HAL_UART_Transmit
。否则中断无法发送。
/* USER CODE BEGIN 0 */
#include "stdio.h"
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
/* 重定向printf*/
PUTCHAR_PROTOTYPE
{
HAL_UART_Transmit(&huart2, (uint8_t*)&ch,1,HAL_MAX_DELAY);
return ch;
}
/* 重定向scanf */
int fgetc(FILE *f)
{
uint8_t ch = 0;
HAL_UART_Receive(&huart2, &ch, 1, 0xffff);
return ch;
}
/* USER CODE END 0 */
附录:Hal 库 串口 收发 有关函数
轮询模式
轮询模式 | 功能 |
---|---|
HAL_UART_Transmit | 串口轮询模式发送,使用超时管理机制。 |
HAL_UART_Receive | 串口轮询模式接收,使用超时管理机制。 |
中断模式
中断模式 | 功能 |
---|---|
HAL_UART_Transmit_IT | 串口中断模式发送 |
HAL_UART_Receive_IT | 串口中断模式接收(当接受满指定数量的字符打开中断) |
DMA模式
DMA模式 | |
---|---|
HAL_UART_Transmit_DMA | 串口DMA模式发送 |
HAL_UART_Receive_DMA | 串口DMA模式接收 |
中断回调函数
轮询模式没有中断回调函数,中断模式以及DMA模式的传输具备此功能。
串口相关的中断回调函数 | |
---|---|
HAL_UART_TxHalfCpltCallback | 一半数据(half transfer)发送完成后,中断处理函数调用此函数。 |
HAL_UART_RxHalfCpltCallback | 一半数据(half transfer)接收完成后,中断处理函数调用此函数。 |
HAL_UART_TxCpltCallback | 发送完成后,中断处理函数调用此函数。 |
HAL_UART_RxCpltCallback | 接收完成后,中断处理函数调用此函数。 |
HAL_UART_ErrorCallback | 传输过程中出现错误时,中断处理函数调用此函数。 |
UART串口接收数据异常导致卡死
有一个项目要用到串口通讯,异常数据会使串口直接卡死,而且不会恢复,只能重新上电才能恢复。
仿真查询,会一直进中断死在这边:
void USART3_IRQHandler(void)
{
/* USER CODE BEGIN USART3_IRQn 0 */
/* USER CODE END USART3_IRQn 0 */
HAL_UART_IRQHandler(&huart3);
/* USER CODE BEGIN USART3_IRQn 1 */
/* USER CODE END USART3_IRQn 1 */
}
网上查询了有很多类似的情况,错误由ORE导致,这个错误置起来后一直清不掉
原因找到了,但是不知道怎么解决,但是网上也找不到解决方法,只能自己解决了
1、先查手册,查看错误标志有哪些,要怎么清掉,不会上传图片所以手册截图就不发了
2、怎么调用HAL库文件清错误标志
#define __HAL_UART_GET_FLAG(__HANDLE__, __FLAG__) (((__HANDLE__)->Instance->SR & (__FLAG__)) == (__FLAG__)) // 获取错误标志
#define __HAL_UART_CLEAR_FLAG(__HANDLE__, __FLAG__) ((__HANDLE__)->Instance->SR = ~(__FLAG__)) //清标志
3、建一个错误回调函数,下面这个我试过可以用,
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
uint32_t isrflags = READ_REG(huart->Instance->SR);//手册上有讲,清错误都要先读SR
if((__HAL_UART_GET_FLAG(huart, UART_FLAG_PE))!=RESET)
{
READ_REG(huart->Instance->DR);//PE清标志,第二步读DR
__HAL_UART_CLEAR_FLAG(huart, UART_FLAG_PE);//清标志
}
if((__HAL_UART_GET_FLAG(huart, UART_FLAG_FE))!=RESET)
{
READ_REG(huart->Instance->DR);//FE清标志,第二步读DR
__HAL_UART_CLEAR_FLAG(huart, UART_FLAG_FE);
}
if((__HAL_UART_GET_FLAG(huart, UART_FLAG_NE))!=RESET)
{
READ_REG(huart->Instance->DR);//NE清标志,第二步读DR
__HAL_UART_CLEAR_FLAG(huart, UART_FLAG_NE);
}
if((__HAL_UART_GET_FLAG(huart, UART_FLAG_ORE))!=RESET)
{
READ_REG(huart->Instance->CR1);//ORE清标志,第二步读CR
__HAL_UART_CLEAR_FLAG(huart, UART_FLAG_ORE);
}
}