2

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));
}
CaseyB
  • 24,780
  • 14
  • 77
  • 112
  • 1
    Are you 100% sure your hardware is set up correctly. Can you confirm voltages etc. with an oscilloscope? – Thomas Weller Oct 25 '21 at 18:10
  • Or a logic analyzer – Thomas Weller Oct 25 '21 at 18:25
  • 1
    When I connect to a logic analyzer both the SDA and SCL lines are constantly high. This makes me feel like I'm missing something in the init. – CaseyB Oct 27 '21 at 00:47
  • You don't really need a logic analyzer to tell that much, just an LED and a resistor. You can connect the led to light when the bus sinks the line to 0. If you max out `TWBR` and the prescaler in `TWSR` each bit will take 2 ms, which will be noticeable on an LED if you seen a 0x00. Or... additionally change `CLKPR` to divide by 128, in which case each bit will take over a quarter of a second making the start condition noticeable. A 0x00 byte would register low on SDA for a full two seconds; slow enough for even a crappy multimeter to pick up on. – timemage Oct 27 '21 at 01:44

1 Answers1

2

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:

  • [I2C-START] [WRITE(I2C-DEVICE-WRITING-ADDRESS)] [WRITE(REGISTER-ADDRESS)] [I2C-STOP]
    The write-transaction were we inform the I2C device of the register we want to read data from.

  • [I2C-START] [WRITE(I2C-DEVICE-READING-ADDESS)] [READ(REGISTER-DATA)] [I2C-STOP]
    The read-transaction that conveys the data itself.

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:

  • [I2C-START] [WRITE(I2C-DEVICE-WRITING-ADDRESS)] [I2C-STOP]
    You've an empty write transaction.
  • [I2C-START] [WRITE(REGISTER-ADDRESS)] [I2C-STOP]
    Now a transaction that tries to use a device register address as an I2C bus address
  • [I2C-START] [WRITE(I2C-DEVICE-READING-ADDRESS)] [I2C-STOP]
    You've started a read transaction but didn't read any of the data that would follow if you issued a READ.
  • [I2C-START] [READ(?)] [I2C-STOP]
    An attempt to read when you're supposed to writing either a I2C address (for either a read or write transaction).

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.

timemage
  • 536
  • 1
  • 4
  • 12
  • I've updated the code in the question to match what you described. I fin that the code now hangs in the while look in the `write` function. Am I messing something up with the logic there? – CaseyB Oct 27 '21 at 03:28
  • 1
    OOOF! I was writing to the wrong device address! Your stuff works after I figured that out! Thank you!! – CaseyB Oct 27 '21 at 04:51