AVR ATtiny87 UART接收代码重复读取字节问题排查
首先,你的核心问题是没有检查UART的新数据接收标志,导致内核循环每次调用vehicle_recv()时,都会重复读取UART数据寄存器里的同一个字节,直到新的字节到来。这就解释了为什么会出现**HH(CR)(CR)(FL)(FL)这种重复字节的情况——每个真实发送的字节被多次读取并存入了缓冲区。
具体错误点拆解
1. 未判断UART接收就绪状态
AVR的UART数据寄存器(LINDAT)在接收到新字节后,会保持该数据直到你读取它,但如果没有新字节到来,寄存器里的内容不会变化。你的代码每次进入vehicle_recv()就直接执行byte = LINDAT;,不管有没有新数据,这就导致同一个字节被循环读取多次,每一次读取都会被存入缓冲区。
比如发送*后,第一次循环读取*并进入BUFFER_RX_IN_PROG状态;第二次循环没有新字节,还是读取到*,此时因为状态是IN_PROG,就会把*再次存入buffer[1],index变成2;直到下一个字节H到来,才会开始读取H并重复同样的过程,最终缓冲区里就出现了重复的字节。
2. 终止符处理重复存储
在BUFFER_RX_IN_PROG的逻辑里,当检测到byte == 0x0a时,你连续做了两次相同的存储操作:
compass_packet.buffer[compass_packet.index] = byte; (compass_packet.index)++;
这会导致换行符0x0a被存入缓冲区两次,也是冗余错误。
修复方案
第一步:添加UART接收就绪检查
在读取LINDAT之前,必须检查UART的接收完成标志(对于ATtiny87的USART,需要检查UCSR0A寄存器的RXC0位),只有当标志位为1时,才表示有新字节可以读取:
void vehicle_recv(void) { // 先检查是否有新的UART数据到来 if (!(UCSR0A & (1 << RXC0))) { return; // 没有新数据,直接返回,不执行后续逻辑 } uint8_t n = 0; char byte; byte = LINDAT; // 现在读取的是真正的新字节 // 后续的状态机逻辑保持不变(除了修复终止符存储) if(compass_packet.state == BUFFER_RX_IDLE) { if(byte == '*' || byte == '#') { compass_packet.buffer[0] = byte; compass_packet.index = 1; compass_packet.state = BUFFER_RX_IN_PROG; } } if(compass_packet.state == BUFFER_RX_IN_PROG) { compass_packet.buffer[compass_packet.index] = byte; compass_packet.index++; if(byte == 0x0a) { // 这里不需要再重复存储byte,上面已经存过了 compass_packet.size = compass_packet.index; compass_packet.state = BUFFER_RX_DONE; } } if(compass_packet.state == BUFFER_RX_DONE) { decode_vehicle_command(&compass_packet); compass_packet.state = BUFFER_RX_IDLE; // 建议同时重置index和size,避免下一次接收的残留数据干扰 compass_packet.index = 0; compass_packet.size = 0; } }
第二步:修复终止符重复存储问题
移除if(byte == 0x0a)块里重复的存储代码,因为前面已经把byte存入缓冲区并递增了index,不需要再做一次。
额外优化:缓冲区重置
在BUFFER_RX_DONE处理完成后,重置index和size为0,确保下一次接收不会受到上一次缓冲区残留数据的影响。
为什么之前的“规避方法”能临时生效?
你提到改用RX_buffer->buffer[2]替代RX_buffer->buffer[1]能看到正确的字符,本质是跳过了重复存储的第一个冗余字节,但这只是治标不治本,解决根本问题还是要添加新数据接收的判断。
内容的提问来源于stack exchange,提问作者HumbleBumble

