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.
工具与软件:
大家好!
我将通过 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、您是对的、这不是必需的。 当我 首次对气体传感器接口进行编码时、我认为有必要将其启动。