2

我正在尝试与 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));
}
4

1 回答 1

2

此答案是在根据此答案更新问题中的代码之前编写的。不知道这一点会使您对如何/为什么回答这个问题感到困惑。


您遇到的基本问题是您在不属于它们的地方发出 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]
    传送数据本身的读取事务。

可能/应该省略第一站的最后一站以创建 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()任何调用。start()stop()

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

于 2021-10-26T20:56:09.757 回答