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

MSP430FR5972作为I2C从机向Arduino Uno传输多字节数据时的阻塞、数据丢失/重复问题求助

MSP430FR5972作为I2C从机向Arduino Uno传输多字节数据时的阻塞、数据丢失/重复问题求助

嗨,看你在MSP430作为I2C从机和Arduino主机的多字节传输上卡壳了,这确实是I2C从机中断处理里很容易踩的坑,我来帮你捋捋问题出在哪,以及怎么解决。

先拆解下你遇到的两个核心问题:


第一个尝试的问题根源:中断里的循环发送逻辑错误

你在UCTXIFG0中断里用for循环连续发送多个字节,还加了while (~(UCB0IFG & UCTXIFG))等待发送完成——这完全违背了I2C从机的工作逻辑:

  • I2C从机的发送是被动触发的:只有当主机主动请求一个字节时,UCTXIFG0标志位才会置位,触发一次中断。你在一次中断里想发多个字节,循环里的等待会永远卡着,因为主机还没发起下一个字节的请求,UCTXIFG0不会再次置位。
  • 去掉等待后直接连续写UCB0TXBUF,后面的字节会直接覆盖前面的,所以最终只有最后一个字节被重复发送两次。

第二个尝试的问题:状态变量未及时重置

你改成Arduino每次请求1字节,MSP430在中断里发txBuffer[txIndex++],但出现了数据重复、下一次传输长度为0的问题:

  • 核心原因是**txIndex没有在每次新的传输开始时重置**:当主机发起新的CMD_READ_V命令时,上一次传输的txIndex已经是3了,所以会从txBuffer[3](超出数组范围,取到的是随机值0)开始发送。
  • 另外,UCSTTIFG(START条件)的处理里只重置了接收相关的变量,没重置发送状态,导致上一次的传输残留影响到下一次。

正确的解决方案:用状态机管理从机发送

I2C从机的多字节传输必须基于单次中断发送单字节的逻辑,用状态机来跟踪当前要发送的是哪一部分数据(长度/第N个数据字节)。具体修改如下:

1. MSP430端代码修改(核心是中断逻辑和状态管理)

// 全局定义传输状态和变量
typedef enum {IDLE, SENDING_SIZE, SENDING_DATA} TxState;
TxState txState = IDLE;
uint8_t txDataCount = 0;
uint8_t txBuffer[3]; // 0:数据长度, 1:ADC高字节, 2:ADC低字节
uint8_t rxBuffer[1]; // 存储主机发送的命令
uint8_t rxIndex = 0;
uint8_t bytesReceived = 0;
#define CMD_READ_V 0x01 // 假设你的命令码是0x01

#pragma vector = USCI_B0_VECTOR
__interrupt void USCI_B0_ISR(void) {
  switch (__even_in_range(UCB0IV, USCI_I2C_UCBIT9IFG)) {
    case USCI_I2C_UCSTTIFG: // 收到START条件,重置所有传输状态
      rxIndex = 0;
      bytesReceived = 0;
      txState = IDLE;
      txDataCount = 0;
      break;
    case USCI_I2C_UCSTPIFG: // 收到STOP条件,退出低功耗模式
      bytesReceived = rxIndex;
      __low_power_mode_off_on_exit();
      break;
    case USCI_I2C_UCRXIFG0: // 收到主机的命令
      rxBuffer[rxIndex++] = UCB0RXBUF;
      if (rxIndex == 1) { // 假设命令是1字节长度
        if (rxBuffer[0] == CMD_READ_V) {
          // 这里替换成实际读取ADC的代码,先写测试值
          txBuffer[0] = 2; // 数据长度为2字节
          txBuffer[1] = 0xFA; // ADC高字节测试值
          txBuffer[2] = 0x1B; // ADC低字节测试值
          txState = SENDING_SIZE; // 切换到发送长度的状态
        }
      }
      break;
    case USCI_I2C_UCTXIFG0: // 主机请求字节,根据当前状态发送对应数据
      switch(txState) {
        case SENDING_SIZE:
          UCB0TXBUF = txBuffer[0];
          txState = SENDING_DATA; // 发送完长度后,切换到发送数据状态
          break;
        case SENDING_DATA:
          UCB0TXBUF = txBuffer[txDataCount + 1];
          txDataCount++;
          // 所有数据发送完成后,回到空闲状态
          if (txDataCount >= txBuffer[0]) {
            txState = IDLE;
            txDataCount = 0;
          }
          break;
        default: // 空闲状态下,发送默认值(或可以触发NACK,这里简化处理)
          UCB0TXBUF = 0x00;
          break;
      }
      break;
    default:
      break;
  }
}

2. Arduino端代码优化(一次性请求所有字节)

不用每次请求1字节,直接一次性请求3字节(长度+2个数据字节),这样更符合I2C的传输规范,也更高效:

#define SLAVE_ADDRESS 0x48 // 替换成你的MSP430从机地址
#define CMD_READ_V 0x01

void loop() {
  Wire.beginTransmission(SLAVE_ADDRESS);
  Wire.write(CMD_READ_V);
  Wire.endTransmission();
  
  // 一次性请求3字节:长度字节 + 2个数据字节
  int bytesReceived = Wire.requestFrom(SLAVE_ADDRESS, 3);
  if (bytesReceived == 3) {
    uint8_t rxSize = Wire.read();
    Serial.print("Data Size: ");
    Serial.println(rxSize);
    for (int i = 0; i < rxSize; i++) {
      Serial.print(Wire.read(), HEX);
      Serial.print(" ");
    }
  } else {
    Serial.println("Failed to receive all bytes");
  }
  Serial.println();
  delay(500);
}

关键注意事项

  1. 从机永远是被动响应:不能在一次中断里发送多个字节,必须靠主机多次请求触发多次中断来完成多字节传输。
  2. 中断要快进快出:绝对不能在I2C中断里做循环等待、耗时计算等操作,否则会破坏I2C的时序,导致主机超时或丢包。
  3. 每次START都要重置状态:主机发起新传输时会发送START信号,此时必须重置所有发送/接收相关的状态变量,避免上一次传输的残留影响。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.07 07:29:31