USART介绍
USART通讯是一种广泛使用的在硬件上使用的异步通讯协议。
USART通信可以自定义通讯协议。
USART是串口通信,用于2台设备之间的直接通信,其接口接线方式:
USART通讯过程
数据不传输
当不传输数据时,UART数据传输线通常保持高电平。
起始位
开始数据传输,发送端的会将传输线从高电平拉到低电平并保持1个时钟周期,当接收端检测到高到低电压跃迁时,便开始以波特率对应的频率读取数据帧中的位。
数据帧
数据帧包含所传输的实际数据。如果使用奇偶校验位,数据帧长度可以是5位到8位。
在不使用奇偶校验位的情况下,数据帧长度可以是9位。
在大多数情况下,数据以最低有效位优先方式发送。
奇偶校验
由于数据传输可以受到各种不确定因素导致出错,就需要使用奇偶校验来一定程度上确保数据的稳定性。
接收端读取数据帧后,将计数值为1的位,检查总数是偶数还是奇数。如果奇偶校验位为0(偶数奇偶校验),则数据帧中的1或逻辑高位总计应为偶数。如果奇偶校验位为1(奇数奇偶校验),则数据帧中的1或逻辑高位总计应为奇数。
当奇偶校验位与数据匹配时,UART会认为传输未出错。但是,如果奇偶校验位为0,而总和为奇数,或者奇偶校验位为1,而总和为偶数,则UART认为数据帧中的位已改变。
停止位
发送端将数据传输线从低电压驱动到高电压并保持1到2位时间。
HAL库使用USART通讯
在HAL库中,已经封装了对USART通讯的发送和接收函数,这里提供介绍。
数据类型
HAL_StatusTypeDef
枚举类型,表示USART串口状态
复制代码c1234567typedef enum
{
HAL_OK = 0x00U, // 成功
HAL_ERROR = 0x01U, // 错误
HAL_BUSY = 0x02U, // 繁忙
HAL_TIMEOUT = 0x03U // 超时
} HAL_StatusTypeDef;
函数介绍
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
此函数用于开启某USART串口通信中断,来接收消息。
参数(UART_HandleTypeDef *huart)USART串口编号
参数(uint8_t *pData)接收数据缓冲区
参数(uint16_t Size)缓冲区大小
返回值:USART串口状态枚举类型(HAL_StatusTypeDef)
示例:
复制代码1234567uint8_t __Usart1_Buffer;
uint8_t __Usart2_Buffer;
// 开启USART1串口通信
HAL_UART_Receive_IT(&huart1, &__Usart1_Buffer, 1);
// 开启USART2串口通信
HAL_UART_Receive_IT(&huart2, &__Usart2_Buffer, 1);
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
此函数是一个中断函数,当发送端有消息发送时,接收端就受到消息时候,在接收端就会触发此中断(此函数需要自己定义),消息将会储存在缓冲区。
参数(UART_HandleTypeDef *huart)USART串口编号,常用于区分不同的Usart串口的消息
返回值:无
示例:
复制代码c1234567891011void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1)
{
// USART1消息
}
else if (huart->Instance == USART2)
{
// USART2消息
}
}
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size, uint32_t Timeout)
此函数用于发送数据。
参数(UART_HandleTypeDef *huart)USART串口编号
参数(uint8_t *pData)发送的数据
参数(uint16_t Size)发送数据的大小
参数(uint32_t Timeout)超时时间,如果发送数据时,检测到串口繁忙,就会等待。
返回值:USART串口状态枚举类型(HAL_StatusTypeDef)
示例:
复制代码123uint8_t message[] = "hello world";
// 通过USART1发送数据
HAL_UART_Transmit(&huart1, (uint8_t *)message, sizeof(message), 100);
设置私有协议示例
USART传输的数据是字节流,所以不带有协议,这里可以自己设计一个私有协议(一般情况)
设置协议头(帧头)
协议头的长度不限,一般长度为1或2个字节。
例如0x55 0x32
设置数据传输格式
方法一
可以设置数据结束标志位来判断消息是否结束
复制代码text帧头标志位 数据 结束标识符(0x00) 示例:0x55 0x32 0xFA 0x44 0x00
这里中间的0xFA 0x44是接收数据,用于处理
方法二
可以设置数据长度来判断消息是否接受完成
复制代码text帧头标志位 长度 数据 示例:0x55 0x32 0x02 0xFA 0x44
这里中间的0xFA 0x44是接收数据,0x02是表示有2个数据
单纯接收字符串示例
UART回调函数
在数据结尾必须带上\r\n
复制代码c12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758uint8_t USART1_RxBuffer[1]; //recv buffer
uint16_t USART1_RX_STA = 0; //recv state
uint8_t USART1_RX_BUF[200]; //recv content
uint8_t USART2_RxBuffer[1]; //recv buffer
uint16_t USART2_RX_STA = 0; //recv state
uint8_t USART2_RX_BUF[200]; //recv content
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
//usart1 -> PA9 TX | PA10 RX
if(huart->Instance==USART1)
{
if((USART1_RX_STA&0x8000)==0)
{
if(USART1_RX_STA&0x4000)
{
if(USART1_RxBuffer[0]!=0x0a)USART1_RX_STA=0;
else USART1_RX_STA|=0x8000;
}
else
{
if(USART1_RxBuffer[0]==0x0d)USART1_RX_STA|=0x4000;
else
{
USART1_RX_BUF[USART1_RX_STA&0X3FFF]=USART1_RxBuffer[0] ;
USART1_RX_STA++;
if(USART1_RX_STA>(200-1))USART1_RX_STA=0;
}
}
}
HAL_UART_Receive_IT(&huart1, (uint8_t *)USART1_RxBuffer, 1);
}
//usart2 -> PA2 TX | PA3 RX
if(huart->Instance==USART2)
{
if((USART2_RX_STA&0x8000)==0)
{
if(USART2_RX_STA&0x4000)
{
if(USART2_RxBuffer[0]!=0x0a)USART2_RX_STA=0;
else USART2_RX_STA|=0x8000;
}
else
{
if(USART2_RxBuffer[0]==0x0d)USART2_RX_STA|=0x4000;
else
{
USART2_RX_BUF[USART1_RX_STA&0X3FFF]=USART2_RxBuffer[0] ;
USART2_RX_STA++;
if(USART2_RX_STA>(200-1))USART2_RX_STA=0;
}
}
}
HAL_UART_Receive_IT(&huart2, (uint8_t *)USART2_RxBuffer, 1);
}
}
UART接收完成检测
复制代码c123456789101112// 当字符串接收完成时,执行此函数,可以判断并回调
void Usart_CheckRecvFinish(void (*callback)(uint16_t, uint8_t), uint8_t isBlock, uint16_t *USART_RX_STA, uint8_t id)
{
do
{
if (*USART_RX_STA & 0x8000)
{
callback(*USART_RX_STA & 0x3fff, id);
*USART_RX_STA = 0;
}
} while (isBlock);
}
电赛E题代码
2023年OpenMV + STM32解决方案(国一)
2024年省一源码