STM32串口数据收发
一、数据收发模式
根据数据处理时机和CPU资源占用的不同,串口数据收发主要分为轮询和中断两种基本模式。在此之上,利用空闲中断可以实现不定长数据的接收。
二、 定长数据的收发
定长数据收发指在通信前,数据的长度(字节数)是已知且固定的。
1. 轮询模式
工作原理:CPU持续、主动地检测串口状态寄存器中的标志位(如TXE发送缓冲区空、RXNE接收缓冲区非空),根据状态来决定下一步操作。
优点:实现简单,代码流程直观,延迟稳定可预测。
缺点:在数据收发完成前,CPU被长期占用,无法执行其他任务,效率低下。
发送函数:
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
huart: 串口实例指针(如 &huart1)
pData: 待发送数据缓冲区的首地址
Size: 待发送数据的字节数(定长)
Timeout: 超时时间(单位:毫秒)。若在指定时间内未完成发送,函数将返回超时错误。
返回值:函数执行状态(HAL_OK, HAL_ERROR, HAL_BUSY, HAL_TIMEOUT)
接收函数:
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
参数与发送函数类似。
pData:用于存储接收数据的缓冲区首地址。
Size:期望接收数据的字节数。函数会一直等待,直到收够指定数量的数据或超时。
2. 中断模式
工作原理:CPU启动数据传输后便立即返回,继续执行主程序。当数据传输完成(如发送完成、接收完成)时,串口硬件会触发一个中断信号,CPU随即暂停当前任务,转而执行对应的中断服务程序和回调函数来处理后续事宜。
优点:不阻塞主程序,CPU利用率高,实时响应性好。
缺点:编程模型稍复杂,需要处理中断和回调。
发送函数:
HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
参数与轮询发送类似,但没有超时参数。函数启动发送后立即返回。
发送完成回调函数:
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);
当指定数量的数据全部发送完毕后,HAL库会在中断服务程序中自动调用此函数。
用户可以在此函数内编写发送完成后的操作,如点亮LED、启动下一次发送等。
接收函数:
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
启动中断接收。函数会配置好接收缓冲区和长度,然后等待串口接收中断。
接收完成回调函数:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);
当收够了指定数量(Size)的数据后,HAL库会自动调用此函数。
用户可以在此函数内处理刚刚接收到的完整一帧数据。
三、 不定长数据的接收
在实际应用中,数据长度往往是变化的。利用空闲中断是实现串口不定长数据接收的常用且高效的方法。
1. 空闲中断原理
空闲状态:在一帧数据传输结束后,通信线路(RX)会维持高电平,这个状态被称为“空闲状态”。
中断触发:当硬件检测到总线空闲状态持续时间大于一个字节的传输时间时,便会将空闲中断标志位(IDLE) 置1。
条件:如果此时空闲中断使能位(IDLEIE) 已被软件开启,就会触发串口空闲中断。
2. 实现流程
开启接收中断与空闲中断:在程序初始化时,不仅开启普通接收中断,还要使能空闲中断。
数据接收:每个字节到达时,都会触发接收中断,将数据存入用户定义的缓存数组中。
检测空闲:当一帧数据全部发送完毕,总线进入空闲状态,触发空闲中断。
处理数据:在空闲中断的服务程序或回调函数中,可以确定当前缓存数组中存储的数据就是一帧完整的不定长数据,随后将其取出处理(如打印、解析等)。
重启接收:处理完数据后,重新启动中断接收,等待下一帧数据。
四、 HAL库相关函数
通常通过 __HAL_UART_ENABLE_IT(huart, UART_IT_IDLE)使能空闲中断。
空闲中断触发后,会在中断服务程序中调用 HAL_UART_IRQHandler,用户可以在自己重写的 HAL_UARTEx_RxEventCallback回调函数中处理这帧不定长数据。
五、相关代码及分析
1. 输出重定向
使得在嵌入式系统中可以使用printf等标准输出函数,但实际输出会通过UART串口发送,而不是默认的控制台。
调用流程:printf("Hello") → 调用fputc逐字符输出 → HAL_UART_Transmit → UART1硬件发送
// 告诉编译器不要使用半主机模式(semihosting)
// 半主机模式是通过调试器进行输入输出的机制,在独立运行的嵌入式系统中需要禁用
#pragma import (__use_no_semihosting)
// 定义一个简化的FILE结构体
// 在标准库中FILE结构体很复杂,这里只需要满足链接器的最小要求
struct __FILE
{
int handle; // 文件句柄,在实际嵌入式应用中通常不会使用
};
// 声明标准输出文件流
// 这是C标准库中预定义的标准输出文件指针,链接器会使用这个变量
FILE __stdout;
// 重定义系统退出函数
// 在嵌入式系统中通常不需要程序退出,这个空函数防止链接器报错
void _sys_exit(int x)
{
x=x; // 空操作,只是为了避免"未使用参数"的编译器警告
}
// 重定向字符输出函数
// 这是最关键的函数,将标准输出的字符重定向到UART串口
int fputc(int ch, FILE *f)
{
uint8_t temp[1] = {ch}; // 将要输出的字符存入临时数组
HAL_UART_Transmit(&huart1, temp, 1, 2); // 通过UART1发送字符
return ch; // 返回字符,符合fputc函数的原型要求
}
2. UART初始化函数
// UART1初始化函数
void MX_USART1_UART_Init(void)
{
// 设置UART实例为USART1
huart1.Instance = USART1;
// 配置波特率为115200bps
huart1.Init.BaudRate = 115200;
// 设置数据字长为8位
huart1.Init.WordLength = UART_WORDLENGTH_8B;
// 设置停止位为1位
huart1.Init.StopBits = UART_STOPBITS_1;
// 设置无奇偶校验
huart1.Init.Parity = UART_PARITY_NONE;
// 设置工作模式为发送和接收模式
huart1.Init.Mode = UART_MODE_TX_RX;
// 设置硬件流控制为无(不使用RTS/CTS)
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
// 设置过采样率为16倍
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
// 调用HAL库的UART初始化函数,如果初始化失败则执行错误处理
if (HAL_UART_Init(&huart1) != HAL_OK)
{
// 初始化失败,调用错误处理函数
Error_Handler();
}
}
3. UART接收完成回调函数
UART接收完成回调函数属于STM32 HAL库的中断处理机制
UART接收数据 → 达到指定长度 → 产生中断 → HAL库处理 → 调用HAL_UART_RxCpltCallback → 执行用户代码
// UART接收完成中断回调函数
// 当UART接收到指定数量的数据后,会自动调用这个函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
// 检查触发回调的是否是USART1
if(huart->Instance==USART1)
{
// 重新启动UART接收,准备接收6个字节的数据到rxBuffer
HAL_UART_Receive(&huart1,(uint8_t *)&rxBuffer,6,1000);
// 通过UART1发送tx_data中的数据,发送长度为字符串长度
HAL_UART_Transmit(&huart1,(uint8_t *)&tx_data,strlen(tx_data),1000);
}
}
4. UART接收中止完成回调函数
触发条件:当UART接收被主动中止时调用
常见场景:调用HAL_UART_AbortReceive()函数,接收超时,通信错误
功能:将接收到的数据回显发送
用途:常用于调试或实现echo功能
UART接收中止完成回调函数
void HAL_UART_AbortReceiveCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance==USART1)// 检查是否USART1的接收被中止
{
HAL_UART_Transmit(&huart1,(uint8_t *)&rxBuffer,num,1000);// 将接收到的所有数据通过UART1发送回去
HAL_UART_Receive_IT(&huart1,(uint8_t *)&rxBuffer,1);// 重新启动UART1的中断接收
num=0;// 重置数据计数器为0
}
}
更多推荐
所有评论(0)