The standard I2C library for the Arduino is the Wire Library. While this library is sufficient most of the time when you want to communicate with devices, there are situations when it is not applicable:
- the I2C pins SDA/SCL are in use already for other purposes,
- the code shall run on an ATtiny processor with 1 MHz on arbitrary pins,
- you are short on memory (flash and RAM), or
- you do not want to use the implicitly enabled pull-up resistors because your devices are run with 3.3 volts.
I adapted Peter Fleury's I2C software library that is written in AVR assembler, extremely light weight (just under 500 byte in flash) and very fast. Even on an ATtiny running with 1MHz, one can still operate the bus with 33 kHz, which implies that you can drive slave devices that use the SMBus protocol (which timeout if the the bus frequency is below 10 kHz).
If you want a solution running on an ARM MCU (Due, Zero, Teensy 3.x), you want to use pins on port H or above on an ATmega256, or you want to use many different I2C buses, this library is not the right solution for you. In these cases, another bit-banging I2C library written in pure C++ could perhaps help you: SlowSoftI2CMaster.
This library has the following features:
- supports only master mode
- compatible with all 8-bit AVR MCUs
- no bus arbitration (i.e., only one master allowed on bus)
- clock stretching (by slaves) supported
- timeout on clock stretching
- timeout on ACK polling for busy devices (new!)
- internal MCU pullup resistors can be used (new!)
- can make use of almost any pin (except for pins on port H and above on large ATmegas)
- very lightweight (roughly 500 bytes of flash and 0 byte of RAM, except for call stack)
- it is not interrupt-driven
- very fast (standard and fast mode on ATmega328, 33 kHz on ATtiny with 1 MHz CPU clock)
- can be easily used in multi-file projects (new)
- Optional
Wire
library compatible interface - GPL license
The simplest way to install this library is to use the library manager. Alternatively, one can download the
zip file from GitHub, uncompress, rename the directory to SoftI2CMaster
and move it into
the libraries
folder.
In order to use the library, you have to import it using the include statement:
#include <SoftI2CMaster.h>
In the program text before the include statement, some compile-time parameters have to be specified, such as which pins are used for the data (SDA) and clock (SCL) lines. These pins have to be specified in a way so that port manipulation commands can be used. Instead of specifying the number of the digital pin (0-19), the port (PORTB, PORTC, PORTD) and the port pin has to be specified. The mapping is explained here. For example, if you want to use digital pin 2 for SCL and digital pin 14 (= analog pin 0) for SDA, you have to specify port pin 2 of PORTD for SCL and port pin 0 of PORTC for SDA:
#define SCL_PIN 2
#define SCL_PORT PORTD
#define SDA_PIN 0
#define SDA_PORT PORTC
#include <SoftI2CMaster.h>
If you want to use the library in a larger project consisting of multiple cpp files, then you may wonder how the library should be imported. Since the header file contains all the source code, importing the library in more than one source file will lead to linkage errors. In order to support the usage of the library in such a context, ArminJo came up with the solution of using another compile time constant that controls of whether only the function declarations are imported. If you put the following compile time constant definition before the #include
directive
#define USE_SOFT_I2C_MASTER_H_AS_PLAIN_INCLUDE
then only the function declarations are included. In other words, in such a large project, you once include the header file without this definition and all the other times, you have to put the above definition before the #include directive.
There are a few other constants that you can define in order to
control the behavior of the library. You have to specify them before
the include
statement so that they can take effect. Note
that this is different from the usual form of using libraries! This library is
always compiled with your sketch and therefore the defines
need to be specified before the inclusion of the library!
#define I2C_HARDWARE 1
Although this is basically a bit-banging library, there is the possibility to use the hardware support for I2C, if you happen to run this library on an MCU such as the ATmega328 that implements this. If this constant is set to 1, then the hardware registers are used (and you have to use the standard SDA and SCL pins).
#define I2C_PULLUP 1
With this definition you enable the internal pullup resistors of the
MCU. Note that the internal pullups have around 50kΩ, which may be
too high. This slows down the bus speed somewhat.
Furthermore, when switching from HIGH
to
LOW
(or the other way around), the bus lines will temporarily in a high impedance
state. With low I2C frequencies, things will
probably work. However, be careful when using this option and better check with a
scope that things work out.
#define I2C_TIMEOUT ...
Since slave devices can stretch the low period of the clock
indefinitely, they can lock up the MCU. In order to avoid this, one
can define I2C_TIMEOUT
. Specify the number of
milliseconds after which the I2C functions will time out. Possible
values are 0 (no time out) to 10000 (i.e., 10 seconds). Enabling this
option slows done the bus speed somewhat.
#define I2C_MAXWAIT ...
When waiting for a busy device, one may use the function
i2c_start_wait(addr)
(see below), which sends start
commands until the device responds with an ACK
. If the
value of this constant is different from 0, then it specifies the
maximal number of start commands to be sent. Default value is 1000.
#define I2C_NOINTERRUPT 1
With this definition you disable interrupts between issuing a start
condition and terminating the transfer with a stop condition. Usually,
this is not necessary. However, if you have an SMbus device that can
timeout, one may want to use this feature. Note however, that in this
case interrupts become unconditionally disabled when calling
i2c_start(...)
und unconditionally enabled after calling i2c_stop()
.
#define I2C_CPUFREQ ...
If you are changing the CPU frequency dynamically using the clock
prescaler register CLKPR and intend to call the I2C functions with a
frequency different from F_CPU, then define this constant with the
correct frequency. For instance, if you used a prescale factor of 8,
then the following definition would be adequate: #define I2C_CPUFREQ (F_CPU/8)
#define I2C_FASTMODE 1
The standard I2C bus frequency is 100kHz. Often, however, devices permit for faster transfers up to 400kHz. If you want to allow for the higher frequency, then the above definition should be used.
#define I2C_SLOWMODE 1
In case you want to slow down the clock frequency to less than 25kHz, you can use this
definition (in this case, do not define
I2C_FASTMODE
!). This can help to make the communication
more reliable.
I have measured the maximal bus frequency under different processor
speeds. The results are displayed in the following
table. The left value is with I2C_TIMEOUT
and
I2C_PULLUP
disabled. The right value is the bus
frequency with both options enabled. Note that there is a high
clock jitter (roughly 10-15%) because the clock is implemented by delay
loops. This is not a problem for the I2C bus, though. However, the
throughput might be lower than one would expect from the numbers in
the table.
1 MHz | 2 MHz | 4 MHz | 8 MHz | 16 MHz | ||||||
---|---|---|---|---|---|---|---|---|---|---|
Slow mode (kHz) | 23 | 21 | 24 | 22 | 24 | 23 | 24 | 23 | 24 | 23 |
Standard mode (kHz) | 45 | 33 | 90 | 72 | 95 | 83 | 95 | 89 | 90 | 88 |
Fast mode (kHz) | 45 | 33 | 90 | 72 | 180 | 140 | 370 | 290 | 370 | 330 |
The following functions are provided by the library:
i2c_init()
Initialize the I2C system. Must be called once in
setup
. Will return false
if SDA or SCL is on
a low level, which means that the bus is locked. Otherwise returns
true
.
i2c_start(addr)
Initiates a transfer to the slave device with the 8-bit I2C address
addr
. Note that this library uses the 8-bit addressing
scheme different from the 7-bit scheme in the Wire library.
In addition the least significant bit of addr
must be specified as
I2C_WRITE
(=0) or I2C_READ
(=1). Returns
true
if the addressed device replies with an
ACK
. Otherwise false
is returned.
i2c_start_wait(addr)
Similar to the i2c_start
function. However, it tries
repeatedly to start the transfer until the device sends an
acknowledge. It will timeout after I2C_MAXWAIT
failed
attempts to contact the device (if this value is different from 0). By
default, this value is 1000.
i2c_rep_start(addr)
Sends a repeated start condition, i.e., it starts a new transfer
without sending first a stop condition. Same return value as
i2c_start()
.
i2c_stop()
Sends a stop condition and thereby releases the bus. No return value.
i2c_write(byte)
Sends a byte to the previously addressed device. Returns
true
if the device replies with an ACK, otherwise false
.
i2c_read(last)
Requests to receive a byte from the slave device. If last
is true
, then a NAK
is sent after receiving
the byte finishing the read transfer sequence. The function returns
the received byte.
As a small example, let us consider reading one register from an I2C device, with an address space < 256 (i.e. one byte for addressing)
// Simple sketch to read out one register of an I2C device
#define SDA_PORT PORTC
#define SDA_PIN 4 // = A4
#define SCL_PORT PORTC
#define SCL_PIN 5 // = A5
#include <SoftI2CMaster.h>
#define I2C_7BITADDR 0x68 // DS1307
#define MEMLOC 0x0A
void setup(void) {
Serial.begin(57600);
if (!i2c_init()) // Initialize everything and check for bus lockup
Serial.println("I2C init failed");
}
void loop(void){
if (!i2c_start((I2C_7BITADDR<<1)|I2C_WRITE)) { // start transfer
Serial.println("I2C device busy");
return;
}
i2c_write(MEMLOC); // send memory address
i2c_rep_start((I2C_7BITADDR<<1)|I2C_READ); // restart for reading
byte val = i2c_read(true); // read one byte and send NAK to terminate
i2c_stop(); // send stop condition
Serial.println(val);
delay(1000);
}
In the example directory, you find a much more elaborate example:
I2CShell
. This sketch can be used to interact with I2C
devices similar in the way you can use the Bus Pirate. For example,
you can type:
[ 0xAE 0 0 [ 0xAF r:5 ]
This will address the I2C device under the (8-bit) address 0xAE in write mode, set the reading register to 0, then opens the same device again in read mode and read 5 registers. A complete documentation of this program can be found in the I2CShell example folder.
Meanwhile, I have written a wrapper around SoftI2CMaster that emulates
the Wire library (master mode only). It is another C++-header file called SoftWire.h
,
which you need to include instead of SoftI2CMaster.h
. The ports and pins have to be specified as described above:
#define SDA_PORT ...
...
#include <SoftWire.h>
...
setup() {
Wire.begin()
...
}
This interface sacrifices some of the advantages of the original library, in particular its small footprint, but comes handy if you need a replacement of the original Wire library. The following section sketches the memory footprint of different I2C libraries.
There are a few constants that you can define in order to
control the behavior of the library. You have to specify them before
the include
statement so that they can take effect. Note
that this is different from the usual form of libraries! This library is
always compiled with your sketch and therefore the defines
need to be specified before the inclusion of the library!
#define I2C_RX_BUFFER_LENGTH 48
The default buffer length is 32 byte like in the standard Arduino Wire or TWI library. But if some I2C device sends more then 32 byte, you can use this definition to increase the receiver buffer size.
Finally, you can use this wrapper library in multi-file projects. By putting the following directive
#define USE_SOFTWIRE_H_AS_PLAIN_INCLUDE
before including the library, only the declaration part is included.
In order to measure the memory requirements of the different libraries, I wrote a baseline sketch, which contains all necessary I2C calls for reading and writing an EEPROM device, and compiled it against a library with empty functions. This sketch was compared to sketches that imported all the other libraries. For the Wire-like libraries, I had to rewrite the sketch, but it has the same functionality. The memory requirements differ somewhat from ATmega to ATtiny, but the overall picture is similar. The take-home message is: If you are short on memory (flash or RAM), it makes sense to use the SoftI2CMaster library.
ATmega328 | |||||||||
Library | SoftI2C- | SoftI2C- | SoftI2C- | Soft- | SlowSoft- | SlowSoft- | USI- | Tiny- | Wire |
---|---|---|---|---|---|---|---|---|---|
Master | Master | Master | Wire | I2CMaster | Wire | Wire | Wire | ||
Option | Pullup+Timeout | Hardware | |||||||
Flash | 482 | 564 | 434 | 1066 | 974 | 1556 | - | - | 1972 |
RAM | 0 | 0 | 0 | 66 | 4 | 70 | - | - | 210 |
ATtiny85 | |||||||||
Library | SoftI2C- | SoftI2C- | SoftI2C- | Soft- | SlowSoft- | SlowSoft- | USI- | Tiny- | Wire |
---|---|---|---|---|---|---|---|---|---|
Master | Master | Master | Wire | I2CMaster | Wire | Wire | Wire | ||
Option | Pullup+Timeout | Hardware | |||||||
Flash | 428 | 510 | - | 1002 | 732 | 1292 | 1108 | 1834 | - |
RAM | 0 | 0 | - | 66 | 4 | 70 | 45 | 86 | - |
One shortcoming is that one cannot use port H and above on an ATmega256. The reason is that these ports are not addressable as I/O registers.
Another shortcoming is, as mentioned, that the code runs only on AVR MCUs (because it uses assembler). If you want to use a software I2C library on the ARM platform, you could use https://github.com/felias-fogg/SlowSoftI2CMaster, which uses only C++ code. Because of this, it is much slower, but on a Genuino/Arduino Zero, the I2C bus runs with roughly 100kHz. There is also a Wire-like wrapper available for this library: https://github.com/felias-fogg/SlowSoftWire.