The I2C bus isn’t that hard to use and for most applications it’s pretty simple. However, there is a lot more depth to it than we’ve covered here.

The purpose of this final post in the series is just to give you a heads-up about further aspects of the I2C bus that you might like to explore.

Acknowledgements

For one thing, we’ve largely skirted around those acknowledgement bits – ACK and NOT ACK – that I mentioned in the section on the fundamentals of transmitting and which we used a little in the post on receiving.

The post on how you actually go about transmitting ignored the acknowledge bits sent back by the slave entirely. And that’s a bit naughty.

In effect, all we did was throw data down the bus and hoped it arrived in good shape. It’s a use of I2C that’s a bit like UDP traffic on networks. (I’d tell you a UDP joke but I couldn’t be sure you’d get it.)

All about status

I2C is capable of much more than that. Once a byte is sent or received, the TWSR status register contains information that could be critical to the proper functioning of your application. So you might want to check in there from time to time.

TWSR contains a one-byte numerical value that tells you useful things about the most recent I2C operation. But there is a snag.

The two lowest bits of TWSR are used in setting the bit rate prescaler (there wasn’t enough space in the bit-rate register TWBR) and so aren’t relevant here. It would be nice if we could just ignore them, but they do affect the overall value of TWSR. So we need to mask them out.

(Also, bit 3 of TWSR isn’t used and always returns a 0 when read. We can mask it out or ignore it. I’ll do the former.)

When reading the value of TWSR, you need to do something like:

uint8_t status = (TWSR & 0b11111000);

So when I refer to the value of TWSR, I really mean that masked value. Atmel’s data sheets make similar assumptions.

(Sidenote: A lot of the time when using ATMEGA microcontrollers, the values of the two lowest bits, TWPS0 and TWPS1 will be 0, and that’s their power-on default. But it’s safest not to rely on this.)

You can use the value of TWSR to perform error-checking at each stage of communications. You should refer to the data sheet for the specific microcontroller you’re using to see what’s available, and the codes will vary depending on whether the AVR is in transmitter (write) mode or receiver (read) mode. But here’s a very basic summary for the ATMEGA328P:

Transmitter mode 0x08 A start condition has been set 0x08 A repeated start condition has been set 0x18 SLA+W (slave address +write bit) has been sent. ACK has been received. 0x20 SLA+W (slave address +write bit) has been sent. NOT ACK has been received. 0x28 Data byte has been transmitted. ACK has been received. 0x38 Data byte has been transmitted. NOT ACK has been received. 0x38 Arbitration lost in SLA+W or data bytes. Receiver mode 0x08 A start condition has been set 0x10 A repeated start condition has been set 0x38 Arbitration lost in SLA+R (slave address plus read bit) or NOT ACK bit. 0x40 SLA+R (slave address +write bit) has been sent. ACK has been received. 0x48 SLA+R (slave address +write bit) has been sent. NOT ACK has been received. 0x50 Data byte has been received. ACK has been returned. 0x58 Data byte has been received. NOT ACK has been returned.

So you can see how these values would be handy in error checking, and how those ACK and NOT ACK conditions are useful. How you respond to these status codes is an implementation detail – in other words, that’s your problem, bud.

Slave mode

We haven’t touched at all on putting the AVR into slave mode – mainly because I haven’t tried it myself yet. But it’s easy to imagine situations in which an AVR-power device could act as a slave to, say, a Raspberry Pi. I plan just such a project soon and will report back.

Multi-master mode

I2C also offers the possibility of having more than one master on a bus. Remember that only a master device can initiate communications, so you might want several devices being able to do this within an overall system. You do have to deal with bus contention and data collisions and AVR microcontrollers afford the means to do this. But again, that’s a subject for the future.