STM32 UART读取异常:仅能完成首次迭代读取求助
UART仅能首次读取的问题分析与修复
问题根源
- 计数器未重置:读取完缓冲区数据后,
counter没有清零,后续新接收的数据会存在缓冲区的后续位置,但uart_read始终从索引0开始读旧数据;若后续新接收的数据量不足指定size,函数直接无返回值,导致调用方无法获取有效结果。 - 无缓冲区溢出保护:
counter持续累加,会超出Rx_Buff的数组长度,引发内存越界,破坏其他内存区域的数据。 - 无中断同步保护:
counter是全局变量,中断服务函数(ISR)中写、uart_read中读的操作没有同步机制,可能出现读写竞态,导致数据错乱。 - 函数返回值不完整:当
size > counter时,uart_read没有返回任何值,属于C语言未定义行为,会导致调用方得到错误的返回结果。
修复后的代码示例
// 假设已定义缓冲区常量与全局变量 #define BUFF_SIZE 256 uint8_t Rx_Buff[BUFF_SIZE]; volatile uint16_t counter = 0; // 加volatile防止编译器优化 void USART2_IRQHandler(void) { if (USART_GetITStatus(USART_MODULE, USART_IT_RXNE) != RESET) { // 新增缓冲区溢出检查,避免内存越界 if (counter < BUFF_SIZE) { Rx_Buff[counter++] = (uint8_t)USART_ReceiveData(USART_MODULE); } USART_ClearITPendingBit(USART_MODULE, USART_IT_RXNE); } } uint16_t uart_read(void *load, uint16_t size) { uint8_t *u = (uint8_t *)load; uint16_t read_len = 0; // 禁用中断,保护counter的读写操作,避免竞态 __disable_irq(); read_len = (size <= counter) ? size : counter; __enable_irq(); if (read_len > 0) { // 拷贝数据到目标缓冲区 for (uint16_t i = 0; i < read_len; i++) { u[i] = Rx_Buff[i]; } // 移动剩余未读数据到缓冲区头部,重置计数器 __disable_irq(); for (uint16_t i = read_len; i < counter; i++) { Rx_Buff[i - read_len] = Rx_Buff[i]; } counter -= read_len; __enable_irq(); } return read_len; }
关键修复点说明
- 给
counter添加volatile关键字,确保编译器不会优化对它的读写(因为它会在中断中被异步修改)。 - 中断服务函数中新增缓冲区溢出检查,防止数组越界。
uart_read中先计算实际可读取的长度,无论size是否大于当前缓存数据量,都返回有效长度,避免未定义行为。- 使用
__disable_irq()和__enable_irq()包裹对counter的读写、缓冲区数据移动操作,防止中断打断导致的数据错乱。 - 读取完成后,将缓冲区剩余未读数据移动到头部并更新
counter,确保后续新接收的数据能存入正确位置,且下次读取能获取新数据。
内容的提问来源于stack exchange,提问作者Ryan
相关产品推荐
相关产品推荐

