avri2cteensyatmega32

I2C not reading


I'm trying to interface with a BNO055 breakout board from a Teensy 2.0, but I'm having trouble reading from the BNO055. It has different registers that you can access to read data from the chip. To start, I'm just trying to read the IDs of some internal parts. No matter what I do, I seem to only get the last value that I put in TWDR. Trying to do a read doesn't seem to populate it. This is what I have in my main:

    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);
        }
    }

This is my 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));
}

Solution

  • This answer was written before the code in the question was updated in line with this answer. Not knowing this would confuse you as to how/why this answer(s|ed) the question.


    The basic problem you're having is that you're issuing I2C start conditions and I2C stop conditions in places where they don't belong. The START an STOP conditions go at the beginning and end of an entire I2C transaction and not around each individual byte.

    The data portion of a transaction is always entirely composed of data to be read or entirely composed of data to be written. So, performing a read of a register of a typical I2C device requires two I2C transactions. One involving writing the register address and the next involving reading the register's data, looking something like the following:

    Potentially the last stop of the first could/should be omitted to create a I2C "repeated start" (which you probably don't need). So as not to confuse matters, I'm just going to go with the above scheme.

    What you have, currently, looks more like this instead:

    The latter one is probably where you're getting in the most trouble and why you're seeing an unchanged TWDR; as an I2C master, you're never going to read a byte immediately following the start condition like that, since there's always supposed a byte written there containing address bits.

    Put in terms of your code, you'd want something like:

    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;
    }
    

    ... where none of send_address(), write(), and read() contain any calls to either start() or stop().

    I did test the above code, though not on a ATMega32U4; I used a different AVR that has the same TWI peripheral though and I do not have your I2C device. Moving the start() and stop() into correct places did bring things from non-functional to functional in my test rig. So, when I say "something like" the above code, I mean a lot like that. =) There may yet be other bugs, but this is the principal problem.