You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 08:54:01