You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Arduino CRC简易实现:跨板通信数据完整性校验方案咨询

好的,针对你在Arduino板之间发送带参数的命令、并需要验证数据完整性的需求,我来一步步给你实现基于CRC的验证方案,完全适配你的Command结构体。

1. 先扩展你的数据包结构

首先,咱们需要给原有的命令结构体加上CRC校验字段,这样接收端才能对比验证。你的Command结构体总大小是1+1+4=6字节(uint8_t是1字节,int在Arduino Uno上是4字节),用一个uint8_t类型的CRC就足够了——既节省串口带宽,计算也快。

咱们定义带CRC的结构体:

typedef struct {
  uint8_t id;
  uint8_t action;
  int param;
  uint8_t crc;  // 新增的CRC校验位
} CommandWithCRC;

2. 选择并实现CRC计算函数

对于小数据量的串口通信,CRC8是最优选择——代码简单、资源占用极低,完全能满足你的需求。这里推荐使用常用的CRC8_MAXIM算法(多项式0x31,初始值0x00),发送端和接收端必须用完全一致的算法,不然校验会失败。

下面是一个高效的CRC8计算函数(直接计算版,省内存,适合Arduino):

uint8_t computeCRC8(const uint8_t *data, size_t length) {
  uint8_t crc = 0x00;  // 初始值,和接收端保持一致
  while (length--) {
    crc ^= *data++;
    for (uint8_t i = 0; i < 8; i++) {
      if (crc & 0x80) {
        crc = (crc << 1) ^ 0x31;  // 多项式0x31,对应x^8+x^5+x^4+1
      } else {
        crc <<= 1;
      }
    }
  }
  return crc;
}

如果追求更快的速度,也可以用查表法(提前算好256个值的表,计算时直接查表),不过对于6字节的数据,直接计算的速度完全够用。

3. 发送端实现步骤

发送端的逻辑很简单:填充命令参数 → 计算CRC → 发送整个数据包:

void sendCommand(uint8_t id, uint8_t action, int param) {
  CommandWithCRC cmd;
  cmd.id = id;
  cmd.action = action;
  cmd.param = param;
  
  // 计算CRC:只计算id、action、param这三个字段,跳过crc本身
  cmd.crc = computeCRC8((uint8_t*)&cmd, sizeof(CommandWithCRC) - sizeof(uint8_t));
  
  // 通过串口发送整个结构体(按字节发送)
  Serial.write((uint8_t*)&cmd, sizeof(CommandWithCRC));
}

调用的时候直接用sendCommand(1, 0x02, 100);这种方式就行。

4. 接收端实现步骤

接收端需要先凑齐整个数据包的字节数,然后验证CRC,再执行命令:

void receiveCommand() {
  // 检查串口是否有足够的字节(整个CommandWithCRC的大小)
  if (Serial.available() >= sizeof(CommandWithCRC)) {
    CommandWithCRC receivedCmd;
    // 读取整个数据包
    Serial.readBytes((uint8_t*)&receivedCmd, sizeof(CommandWithCRC));
    
    // 计算收到的命令部分的CRC,和收到的crc对比
    uint8_t calculatedCRC = computeCRC8((uint8_t*)&receivedCmd, sizeof(CommandWithCRC) - sizeof(uint8_t));
    
    if (calculatedCRC == receivedCmd.crc) {
      // CRC验证通过,数据完好,执行命令
      Serial.println("数据验证通过,执行命令:");
      Serial.print("ID: "); Serial.println(receivedCmd.id);
      Serial.print("Action: "); Serial.println(receivedCmd.action);
      Serial.print("Param: "); Serial.println(receivedCmd.param);
      // 这里写你的命令执行逻辑
    } else {
      // CRC验证失败,丢弃数据
      Serial.println("数据损坏,丢弃!");
      // 可以选择清空串口缓存,避免残留错误数据
      while (Serial.available()) Serial.read();
    }
  }
}

然后在loop()里调用receiveCommand()就行。

5. 额外注意事项

  • 字节序统一:因为两块都是Arduino,默认都是小端序,所以结构体的字节传输不会有问题;如果以后和其他大端序设备通信,需要处理字节转换,但这里不用管。
  • 串口参数一致:发送端和接收端的波特率、数据位、停止位、奇偶校验必须完全一致,否则数据会乱码。
  • 粘包问题(可选优化):如果串口通信频繁,可能出现粘包(多个数据包连在一起)。可以给数据包加帧头(比如0xAA)和帧尾(比如0x55),接收端先找到帧头,再接收完整数据包,避免错误的起始位置。这是进阶优化,基础场景下不加也能用。
  • CRC参数统一:发送端和接收端的CRC初始值、多项式必须完全一样,不然计算出来的CRC肯定不匹配。

内容的提问来源于stack exchange,提问作者anz

火山引擎 最新活动