I2C 不读取

I2C not reading

我正在尝试连接来自 Teensy 2.0 的 BNO055 分线板,但我无法从 BNO055 读取数据。它有不同的寄存器,您可以访问这些寄存器以从芯片读取数据。首先,我只是想读取一些内部零件的 ID。无论我做什么,我似乎只能得到我输入 TWDR 的最后一个值。尝试阅读似乎并没有填充它。这是我的主要内容:

    void main(void) {
        CPU_PRESCALE(CPU_16MHz);

        init_sensor();

        char buffer[50];
        sprintf(buffer, "Chip ID: %X\n", read_address(0x00));

        while(1) {
            _delay_ms(20);
        }
    }

这是我的 BNO055.c:

#include "BNO055.h"

#include <avr/io.h>

// The breakout board pulls COM3_state low internally
#define DEVICE_ADDR 0x29

#define READ 0
#define WRITE 1

#define F_CPU 16000000UL
#define SCL_CLOCK 400000L

inline void start(void) {
    TWCR = (1<<TWINT) | (1<<TWSTA) | (1<<TWEN);
    while ( !(TWCR & (1<<TWINT)));
}

inline void stop(void) {
    TWCR = (1 << TWINT) | (1 << TWSTO) | (1 << TWEN);
    while(TWCR & (1 << TWSTO));
}

void send_address(uint8_t w) {
    TWDR = (DEVICE_ADDR<<1) | w ;
    TWCR = (1 << TWINT) | (1<<TWEN);
    while(!(TWCR & (1 << TWINT)));
}

uint8_t read(void) {
    TWCR = (1 << TWINT) | (1 << TWEN);
    while(!(TWCR & (1 << TWINT)));
    return TWDR;
}

void write(uint8_t data) {
    TWDR = data;
    TWCR = (1 << TWINT) | (1<<TWEN);
    while(!(TWCR & (1 << TWINT)));
}

uint8_t read_address(uint8_t addr) {
    start();
    send_address(WRITE);
    write(addr);
    stop();

    start();
    send_address(READ);
    const uint8_t value = read();
    stop();

    return value;
}

void write_address(uint8_t addr, uint8_t value) {
    start();
    send_address(WRITE);
    write(addr);
    write(value);
    stop();
}

uint8_t status(void) {
    return TWSR & 0xF8;
}

void init_sensor(void) {
    // Setup I2C
    DDRD &= ~((1 << 0) | (1 << 1));
    PORTD |= ((1 << 0) | (1 << 1));

    TWBR = ((( F_CPU / SCL_CLOCK ) - 16) / 2);
    TWSR = 0;
    TWCR = ((1 << TWEN) | (1 << TWIE) | (1 << TWEA));
}

此答案与此答案 before the code in the question was updated 并排 。不知道这一点会使您对 how/why 这个问题的答案感到困惑。


您遇到的基本问题是您在不属于它们的地方发出 I2C 启动条件和 I2C 停止条件。 START 和 STOP 条件出现在整个 I2C 事务的开始和结束处,而不是围绕每个单独的字节。

事务的数据部分总是完全由要读取的数据组成或完全由要写入的数据组成。因此,读取典型 I2C 设备的寄存器需要 两个 I2C 事务。一个涉及写入寄存器地址,下一个涉及读取寄存器的数据,如下所示:

  • [I2C-START] [WRITE(I2C-DEVICE-WRITING-ADDRESS)] [WRITE(REGISTER-ADDRESS)] [I2C-STOP]
    写入事务是我们通知 I2C 设备我们要从中读取数据的寄存器。

  • [I2C-START] [WRITE(I2C-DEVICE-READING-ADDESS)] [READ(REGISTER-DATA)] [I2C-STOP]
    传送数据本身的读取事务。

可能会省略第一个 could/should 的最后一站以创建 I2C“重复启动”(您可能不需要)。为了不混淆事情,我将采用上述方案。

你目前拥有的看起来更像这样:

  • [I2C-START] [WRITE(I2C-DEVICE-WRITING-ADDRESS)] [I2C-STOP]
    您有一个空的写入事务。
  • [I2C-START] [WRITE(REGISTER-ADDRESS)] [I2C-STOP]
    现在尝试使用设备寄存器地址作为 I2C 总线地址的事务
  • [I2C-START] [WRITE(I2C-DEVICE-READING-ADDRESS)] [I2C-STOP]
    您已经启动了一个读取事务,但没有读取任何在您发出 READ 后会跟随的数据。
  • [I2C-START] [READ(?)] [I2C-STOP]
    当您应该写入 I2C 地址(用于读取或写入事务)时尝试读取。

后一个可能是您遇到最大麻烦的地方,也是您看到未更改 TWDR 的原因;作为 I2C 主机,您永远不会像那样在开始条件之后立即读取一个字节,因为总是假定一个包含地址位的字节写入那里。

就您的代码而言,您需要这样的东西:

uint8_t read_address(uint8_t addr) {
    // The first transaction
    // that tell the I2C device
    // what register-address we want to read
    start();
    send_address(WRITE);
    write(addr);
    stop();

    // The read transaction to get the data
    // at the register address
    // given in the prior transaction.
    start();
    send_address(READ);
    const uint8_t r = read();
    stop();

    return r;
}

... 其中 send_address()write()read() 的 none 包含对 start()stop() 的任何调用。

我确实测试了上面的代码,虽然不是在 ATMega32U4 上;我使用了具有相同 TWI 外设的不同 AVR,但我没有您的 I2C 设备。 将 start()stop() 移动到正确的位置确实使我的测试装置中的东西从非功能性变为功能性。所以,当我说“类似”上面的代码时, 我的意思是 很像 。 =) 可能还有其他错误,但这是主要问题。