As microcontrollers drop in price and offer more
capabilities, designers have found it more costeffective
to utilize multiple small controllers in
both single-board and multiboard systems. Such
auxiliary processors can relieve the main processor of timeconsuming
tasks such as scanning keyboards, display controllers,
and motor control. These controllers can also be configured
as a wide range of application-specific peripherals.
Recently, I was given the task of developing an interface
(software/hardware) that could easily be adapted to many
applications and be based on an industry standard commonly
found in embedded processors. After reviewing some of the
typical applications, I came up with a list of requirements to
help zero in on a hardware interface.
- Common on both 32- and 8-bit processors
- Supported by many off-the-shelf peripherals
- Peripheral interface code less than 0.5 kbyte
- Low pin count
- Data bandwidth up to 10 kbytes/s
- Low RAM usage
- Support multiple peripherals on a single bus
- Easy API to use
- No external interface drive hardware required
Because of the low pin-count requirement, a serial interface
was mandatory. Some of the more common serial interfaces
found in today’s processors include SPI, I2C, USB, and RS-232.
After weighing the various pros and cons, I settled on the I2C
because of its simplicity, flexibility, and availability on most
low-cost controllers. Low pin count and flow control also give
I2C a big advantage over SPI if higher speed isn’t required.
How I2C works
I2C is a two-wire bidirectional interface consisting of a clock
and data signals (SCL and SDA). A dozen devices or more
may be included into a single bus without additional signals.
The spec calls out three speeds of operation: 100 kbits/s, 400
kbits/s, and 3.4 Mbits/s. Most common controllers only support
the 100- and 400-kbit/s modes. The spec
allows for both a single master with multiple
slaves or a multi-master configuration.
One very important attribute of I2C is that it supports flow
control. If a slave can’t keep up between bytes, it may halt the
bus until it can catch up. This is very useful for slaves that
contain minimal I2C hardware and must support part of the
protocol in firmware. The I2C bus specification supports both
a 7- and 10-bit address protocol. I’ve found that the 7-bit
addressing is more than sufficient for most applications.
Before starting to write code, we need a good understanding
of how the I2C bus works. The I2C bus will always have at least
one master and at least one or more slaves. The master always
initiates a transfer from the master to the slave. The I2C interface
has only two signals, no matter how many peripherals are
attached to the bus.
Both signals are open-collector with pull-up resistors of
about 2.7k to VCC. The SDA signal is bidirectional and can be
driven by either the master or slave. The SCL signal is driven
by the master, but the slave may hold it low at the end of a data
byte to hold off the bus until the slave can process the data.
The master releases the SCL line after the last bit of the byte,
then checks to see if the SCL signal goes high. If it doesn’t, the
master knows that the slave is requesting the master to hold off
until the data is processed.
When data is being sent on the bus, data transitions occur
only when SCL is low. When the SCL signal is high, the data in either direction should be stable
(Fig. 1).
When the bus is idle, neither the master
nor the slaves pull down the SDA and SCL.
To initiate a transfer, the master drives the
SDA line from high to low while SCL
is high. Typically, the SDA line doesn’t
change state when SCL is high, except for
a start or stop condition. A stop condition
occurs when SCL is high and the SDA line
changes from low to high (Fig. 2).
The I2C bus transfers data in 8-bit
increments. Each time a byte is transferred,
it must be acknowledged by the device
receiving the data. All data is transferred
most significant bit (MSB) first.
At the beginning of each transfer, a
START initiates the transfer, then a 7-bit
slave address, followed by an R/W flag.
The I2C standard also supports a 10-bit
address, but this application requires only
a 7-bit address. If a slave recognizes the
address, it will pull down the SDA line
during the ACK state, then release it.
Continue on Page 2