This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

[参考译文] MSP430FR2433:UCBBUSY 置位、同时通过 I2C 与两个传感器进行通信

Guru**** 2465730 points


请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

https://e2e.ti.com/support/microcontrollers/msp-low-power-microcontrollers-group/msp430/f/msp-low-power-microcontroller-forum/1385274/msp430fr2433-ucbbusy-set-while-communicating-with-two-sensors-through-i2c

器件型号:MSP430FR2433

工具与软件:

大家好!

我将通过 I2C 连接 MPU-6050加速计和 CCS811气体传感器。

使用逻辑分析仪、在 一段时间后、UCBBUSY 被置位、且一个开始位挂起。 执行两次时、它 在请求气体传感器的数据寄存器后挂起。

我想值得注意的是、SCL 线稍后会转换为高电平。

寄存器的写入/读取代码如下所示:

// Wake up the MPU-6050
slaveAddress = MPU_ADDR;
TX_Data[1] = PWR_MGMT_1;
TX_Data[0] = 0x08;      // Set 8 MHz clock; disable temperature sensor
TX_ByteCtr = 2;
i2c_wr(slaveAddress);
__delay_cycles(50000); // According to datasheet, hold ~30 ms
                       // Because of gyroscope based clock oscillator
slaveAddress = CCS811_ADDR;         // Make transition from boot to application mode
TX_Data[0] = CCS811_APP_START;
TX_ByteCtr = 1;
i2c_wr(slaveAddress);
__delay_cycles(30000);

slaveAddress = CCS811_ADDR;
TX_Data[1] = CCS811_MEAS_MODE;
TX_Data[0] = 0x10;                  // Put CCS to normal mode, no interrupt enable
TX_ByteCtr = 2;
i2c_wr(slaveAddress);
__delay_cycles(15000);

while(1)
{
    // Register pointing
    slaveAddress = MPU_ADDR;
    TX_Data[0] = ACCEL_XOUT_H;          // First address of the set
    TX_ByteCtr = 1;
    i2c_wr(slaveAddress);

    // Read six bytes of data
    slaveAddress = MPU_ADDR;
    RX_ByteCtr = 6;
    i2c_rd(slaveAddress);

    xAccel  = RX_Data[5] << 8 | RX_Data[4];
    yAccel  = RX_Data[3] << 8 | RX_Data[2];
    zAccel  = RX_Data[1] << 8 | RX_Data[0];
    //--------------------------------------------------------------------------------//
    __delay_cycles(50000);
    //--------------------------------------------------------------------------------//
    slaveAddress = CCS811_ADDR;
    TX_Data[0] = CCS811_ALG_RESULT_DATA;
    TX_ByteCtr = 1;
    i2c_wr(slaveAddress);
    // Reading operation of environment data register
    slaveAddress = CCS811_ADDR;
    RX_ByteCtr = 8;
    i2c_rd(slaveAddress);

    co2Lvl = (RX_Data[0] << 8 | RX_Data[1]);
    tvocLvl = (RX_Data[2] << 8 | RX_Data[3]);

    __delay_cycles(100000);
} //End while

寄存器 地址可从相应的器件数据表中提取。

作为参考、I2C 相关代码如下所示:

/**
 * Configuration of I2C module
 */
void i2c_conf(void)
{
    // I2C pins configuration
    P1SEL0 |= BIT2 | BIT3;

    // Disable the GPIO power-on default high-impedance mode to activate
    // previously configured port settings
    PM5CTL0 &= ~LOCKLPM5;

    // Configure USCI_B0 for I2C mode
    UCB0CTL1 |= UCSWRST;                    // Software reset enabled
    UCB0CTLW0 |= UCMODE_3 | UCMST;          // I2C mode, Master mode, SMCLK
    UCB0CTLW0 |= UCSSEL__SMCLK | UCSYNC;    // Use SMCLK as clock source, sync
    UCB0CTLW1 |= UCASTP_2;                  // Automatic stop generated by
                                            // reaching data acquisition boundary
    UCB0BR0 = 0x0008;                       // baudrate = SMCLK / 8 = ~100 kHz
    UCB0CTL1 &= ~UCSWRST;                   // Disable SW reset
}
/**
 * Write function
 */
void i2c_wr(unsigned char addr)
{
    while(UCB0CTL1 & UCTXSTP);          // Ensure stop condition sent

    UCB0CTL1 |= UCSWRST;
    UCB0I2CSA = addr;                   // Slave address
    UCB0TBCNT = TX_ByteCtr;
    UCB0CTL1 &= ~UCSWRST;
    UCB0IE |= UCTXIE | UCRXIE | UCBCNTIE;    // Enable Tx, RX and
                                             // byte count interruptions
    UCB0CTL1 |= UCTR;        // Transmitter mode
    UCB0CTL1 |= UCTXSTT;     // and send START condition

    __bis_SR_register(LPM0_bits|GIE);   // Enter LPM0 w/ interrupt
}
/**
 * Read function
 */
void i2c_rd(unsigned char addr)
{
    while(UCB0CTL1 & UCTXSTP);          // Ensure stop condition sent

    UCB0CTL1 |= UCSWRST;
    UCB0I2CSA = addr;                   // Slave address
    UCB0TBCNT = RX_ByteCtr;
    UCB0CTL1 &= ~UCSWRST;
    UCB0IE |= UCTXIE | UCRXIE | UCBCNTIE;              // Enable Tx and RX interruptions

    UCB0CTL1 &= ~UCTR;                  // Receiver mode
    UCB0CTL1 |= UCTXSTT;               // and send START condition

    __bis_SR_register(LPM0_bits|GIE);   // Enter LPM0 w/ interrupt
}
/**
 * UCB0 ISR
 */
#if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
#pragma vector = USCI_B0_VECTOR
__interrupt void USCIB0_ISR(void)
#elif defined(__GNUC__)
void __attribute__ ((interrupt(USCI_B0_VECTOR))) USCIB0_ISR (void)
#else
#error Compiler not supported!
#endif
{
  switch(__even_in_range(UCB0IV, USCI_I2C_UCBIT9IFG))
  {
    case USCI_I2C_UCRXIFG0:
            if (RX_ByteCtr--)
            {
                RX_Data[RX_ByteCtr] = UCB0RXBUF;  // Get received byte
            }
            break;
    case USCI_I2C_UCTXIFG0:
            if (TX_ByteCtr--)               // TRUE if more bytes remain
            {
                UCB0TXBUF = TX_Data[TX_ByteCtr];  // Load TX buffer
            }
        break;
    case USCI_I2C_UCBCNTIFG:
        __bic_SR_register_on_exit(CPUOFF);  // Exit LPM0
        break;
  }
}

感谢任何帮助。

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    您能告诉哪个事务发生了故障吗? (是不是总是同一个?)

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    您好!

    我又测试了两次、是的、它在同一个事务中(请求从 CCS811_ALG_RESULT_DATA -存储 CO2和 TVOC 数据的寄存器读取);它也显示相同的条件:UCBBUSY 设定和开始位挂起。

    我还检查了相关寄存器有时没有数据、因此我想知道是配置还是电路是主要原因。

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    我猜是 CCS811正在拉伸时钟-虽然您的代码可能无意中将总线挂起、但这些事故通常出现在前几次交易中。 (将时钟拉伸"很长一段时间"称为"挂起总线"、即这与总线上的现象相同)。 数据表和 AN000369均未提及时钟延展、但使用 Google 5分钟后告诉我、CCS811在某些情况下会执行此操作。

    我想知道传感器是否在扩展等待新数据的时钟;我已经在另一个传感器的某个地方看到过这种做法。 数据表(或应用说明)中没有任何内容表明、如果没有新数据、就不能读取寄存器0x02 --它们甚至包括状态寄存器的副本、所以你可以先读取然后检查--但我发现的每个示例都只能在 DATA_READY=1时读取寄存器0x02。 由于您每秒请求一次读数、因此快速实验是将循环(底部延迟)从100ms 延长到1000ms。 稍微更详尽:启用并随总线一起探测 nINT 引脚。

    您是否正在使用商业分线板? 我注意到 Adafruit 板连接 nWAKE 低,但我不知道其他人。 您不希望它在任何情况下浮动。

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    我不想对 I2C 代码进行的一个操作是允许无限等待。 每当我有类似"while (UCB0CTL1 & UCTXSTP);"的代码时、我都会添加超时。 出于调试目的、我还倾向于针对每个错误返回唯一的代码、因为这样可以更轻松地查找问题。

    如果问题是读取开始时的挂起启动条件、我会首先检查一下上一次写入(其中将寄存器编号设置为读取)是否正确完成。 此外、执行此操作的常用方法是通过重复启动的单个事务。

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    我 在检查数据寄存器(CCS811_ALG_RESULT_DATA)之前添加了验证代码(访问 CCS811_STATUS)、但错误仍然存在。

    我来看看这个。  

    nWAKE 引脚、它需要接地。 奇怪的是、我 在读取 CCS 地址时收到 NACK;恢复正常后、RST 和 INT 断开连接。  

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    我 重新编码了 ISR 以实施重复启动:

    #if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
    #pragma vector = USCI_B0_VECTOR
    __interrupt void USCIB0_ISR(void)
    #elif defined(__GNUC__)
    void __attribute__ ((interrupt(USCI_B0_VECTOR))) USCIB0_ISR (void)
    #else
    #error Compiler not supported!
    #endif
    {
      switch(__even_in_range(UCB0IV, USCI_I2C_UCBIT9IFG))
      {
        case USCI_I2C_UCRXIFG0:
                if(RX_ByteCtr)
                {
                    RX_Data[RX_ByteCtr--] = UCB0RXBUF;  // Get received byte
                    UCB0IFG |= UCRXIFG0;
                }
                else
                {
                    UCB0CTLW0 |= UCTXSTP;
                    UCB0IFG &= ~UCRXIFG0;
                    __bic_SR_register_on_exit(CPUOFF);      // Exit LPM0
                }
            break;
        case USCI_I2C_UCTXIFG0:
                    if(TX_ByteCtr)
                    {
                        TX_Data[TX_ByteCtr--] = UCB0TXBUF;  // Get received byte
                        UCB0IFG |= UCTXIFG0;
                    }
                    else
                    {
                        UCB0CTLW0 |= UCTXSTP;
                        UCB0IFG &= ~UCTXIFG0;
                        __bic_SR_register_on_exit(CPUOFF);      // Exit LPM0
                    }
            break;
      }
    }

    更糟糕的是、因为我无法进行通信、从而使 SCL 和 SDA 在 第一次 I2C 通信尝试中丢失(设置 MPU)。

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    通常、您向 TXBUF 写入一些内容来响应发送中断。

    接收中断会出现多余的奇怪现象。 冗余、因为 UCRXIFG0是通过读取 UCB0IV 和 UCB0RXBUF 来清零的、因此不需要它显式清除它。

    奇怪的是、在读取数据时有一种更简单的方法来处理停止条件。 当 ISR 开始时、I2C 硬件会等待、可能会出现以下两种情况之一。 您可以读取 RXBUF、它将开始接收下一个字节。 或者您可以设置 TXSTP、它将生成停止条件。 按照编写的内容、它会接收一个它只是忽略的额外字节。

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    在 TX 部分、我犯了一个巨大的错误、过于强调。

    这是新版本:

    #if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
    #pragma vector = USCI_B0_VECTOR
    __interrupt void USCIB0_ISR(void)
    #elif defined(__GNUC__)
    void __attribute__ ((interrupt(USCI_B0_VECTOR))) USCIB0_ISR (void)
    #else
    #error Compiler not supported!
    #endif
    {
      switch(__even_in_range(UCB0IV, USCI_I2C_UCBIT9IFG))
      {
          case USCI_I2C_UCRXIFG0:
              RX_Data[--RX_ByteCtr] = UCB0RXBUF;  // Get received byte
              if(!RX_ByteCtr)
              {
                  if(restart)
                  {
                      UCB0CTL1 |= UCTXSTT;
                      while(UCB0CTL1 & UCTXSTT);          // Ensure START sent
    
                      restart = 0;
                  }
                  else
                  {
                      UCB0CTL1 |= UCTXSTP;
                      while(UCB0CTL1 & UCTXSTP);          // Ensure STOP sent
                  }
                  __bic_SR_register_on_exit(CPUOFF);  // Exit LPM0
              }
              break;
          case USCI_I2C_UCTXIFG0:
              UCB0TXBUF = TX_Data[--TX_ByteCtr];  // Passes byte to transmit buffer
              if(!TX_ByteCtr)
              {
                  if(restart)
                  {
                      UCB0CTL1 |= UCTXSTT;
                      while(UCB0CTL1 & UCTXSTT);          // Ensure START sent
    
                      restart = 0;
                  }
                  else
                  {
                      UCB0CTL1 |= UCTXSTP;
                      while(UCB0CTL1 & UCTXSTP);          // Ensure STOP sent
                  }
                  __bic_SR_register_on_exit(CPUOFF);  // Exit LPM0
              }
              break;
      }
    }

    挂起总线问题已解决! 另一个问题是读数错误。

    看起来计数器混乱。

    在后续代码块中、应执行3次写入。

    slaveAddress = CCS811_ADDR;         // Make transition from boot to application mode
    TX_Data[0] = CCS811_APP_START;
    TX_ByteCtr = 1;
    restart = 1;
    i2c_wr(slaveAddress);
    __delay_cycles(22000);
    TX_Data[1] = CCS811_MEAS_MODE;
    TX_Data[0] = 0x10;                  // Put CCS to normal mode, no interrupt enable
    TX_ByteCtr = 2;
    i2c_wr(slaveAddress);

    但这项工作是:

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    我想我不能把这件事全部讲出来。

    首先、在执行重新启动时、必须将 I2C 端口从写入切换到读取。 (清除 UCTR)否则发送设置了写入位的器件地址。 一降再降。

    示例:

      IFG2 &= ~UCB0RXIFG;         // make sure no previous interrupt
      UCB0CTL1 &= ~UCTR;          // restart as receiver
    
      UCB0CTL1 |= UCTXSTT;        // I2C repeated start condition
      IE2 |= UCB0RXIE;                // enable receive interrupt
    

    (对于 G 系列器件、细节有所不同。)

    不知道为什么接收代码会检查 RESTART 标志。 您永远不会启动接收并切换至发送。

    发送 APP_START 的命令似乎错误。 通常必须发送寄存器地址/值对。 但查看器件数据表、这似乎是正确的。 简化视图。 您是否加载了应用程序? 您是否通过读取 STATUS 寄存器来检查它是否符合建议? 为什么设置了重新启动标志? 这将不会带来问题的结束。 (考虑您的字节计数器在下一个发送中断时将具有什么样的值。)

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    您好!

    我在 TX 中断方面遇到了很多问题、触发错误的时间(在进入低功耗模式之前)、而不是在应该触发时触发;这导致一些字节的数据 读取比预期的要多。

    我 切换回使用自动停止、现在通信似乎更加稳定。 那么、我将 保持这种方式一段时间。

    关于 app_start、您是对的、这不是必需的。 当我 首次对气体传感器接口进行编码时、我认为有必要将其启动。