Main Content

Tiny I2C Routines for all AVR Microcontrollers

This article describes a set of minimal I2C routines that allow just about any Microchip/Atmel AVR processor to connect to I2C peripherals. To demonstrate the routines I’ve designed a port scanner that displays the I2C address of a sensor on a dot-matrix display, and a digital thermometer that reads the temperature from an I2C temperature sensor and displays it.

The main difference between these routines and the standard Arduino Wire library is that these don’t use buffers, so have much smaller memory requirements and don’t impose a limit on transmissions.

These I2C routines are designed to provide master I2C functionality for all Microchip/Atmel AVR processors. Over the years different generations of AVR chips have featured three different, incompatible peripherals to handle I2C:

Universal Serial interface (USI) peripheral
The USI provides master I2C support to ATtiny processors with a USI peripheral, namely:

- ATtiny25/45/85 and ATtiny24/44/84
- ATtiny261/461/861
- ATtiny87/167
- ATtiny2313/4313
- ATtiny1634

The routines to support the USI are based on the code described by Atmel Application Note AVR310 [1].

2-Wire Serial Interface (TWI) peripheral
This provides full master I2C support, and is featured in:

- Most of the original ATmega processors, such as the ATmega328P used in the Arduino Uno, ATmega2560 used in the Arduino Mega 2560, and the ATmega1284P.
- Two unusual ATtiny processors that provide a TWI peripheral, the ATtiny48 and 88.

Two-Wire Interface (TWI) peripheral
A new version of the TWI peripheral is featured in:

- The latest ATtiny 0-series, 1-series, and 2-series processors, such as the ATtiny414.
- The 0-series ATmega chips, such as the ATmega4809.
- The AVR DA and DB family, such as the AVR128DA48.

Universal TinyI2C routines
These universal Tiny I2C routines provide master I2C support for all three generations of AVR processors by providing three separate blocks of code. The correct block of code is selected automatically by #if defined statements depending on which Arduino core and Board setting you are using.

Although I’ve tested these routines on selected processors from each category I haven’t exhaustively tested every AVR processor. Please report any incompatibilities on GitHub.

These routines incorporate, and supersede, the two earlier versions of my minimal I2C routines:

- Minimal Tiny I2C Routines (TinyI2C) for ATtiny processors with a USI peripheral, such as the ATtiny85.
- Minimal I2C for the New AVR Microcontrollers (TinyMegaI2C) for the new 0-series and 1-series ATtiny and ATmega microcontrollers, such as the ATtiny402 or ATmega4809.

Differences from Arduino Wire
I’ve named these routines TinyI2C for two reasons: to distinguish them from the existing TinyWire libraries, such as the ones included in the Arduino and Spence Konde’s cores, and to emphasise that these routines don’t follow the Arduino Wire library naming conventions.

In addition, these routines differ from the Arduino Wire library routines in the following ways:

Low memory requirements
These routines don’t use buffers, reducing their RAM requirements to a couple of bytes. The standard Arduino Wire library uses 128-byte or 32-byte send and receive buffers. As far as I can see there’s no need for buffering as the I2C protocol incorporates handshaking, using the ACK/NACK pulses.

Unlimited transmission length
These routines don’t impose any limit on the length of transmissions. The standard Wire libraries limit the length of any transmission to the size of the buffer. This isn’t a problem with many I2C applications, such as reading the temperature from a sensor, but it is a problem with applications such as driving an I2C OLED display, which requires you to send 1024 bytes to update the whole display.

Flexible read
These routines allow you to specify in advance how many bytes you want to read from an I2C peripheral, or you can leave this open-ended and mark the last byte read. This is an advantage when you don’t know in advance how many bytes you are going to want to read.

For simplicity these routines use polling rather than interrupts, so they won’t interfere with other processes using interrupts.”

Link to article