“No man is an island, entire of itself…,” John Donne
Likewise in embedded systems, a simple function rarely exists in
isolation. A simple pushbutton and LEDs controller is useful, but if
that controller cannot report when a user has pressed a button or share
the status its LEDs indicate, the controller’s worth is lessened.
The key to increased value is communication and increasingly the
Inter-IC (I2C) protocol, a
2-wire master/slave communications bus standard, is the communication
bus of choice for embedded systems of all sizes.
I2C enabled devices are available as off-the-shelf, drop-in
functions, but the true power of the bus is revealed when a custom
device is created using a microcontroller. Customization enhances the
fit; communication multiplies the value of the solution.
Before you proceed to incorporate I2C communications in your system,
you face two strategic questions:
1) How to use I2C
communications in your system, and
2) How to verify your system
is working properly?
In Part 1 I will answer
the first question, concentrating on the design rules, things to
consider for the I2C master, and guideline to a successful slave
implementation. In Part 2, I
will concentrate on test and verification, and you will see that design
and test go hand in hand in any successful embedded project. Together
we will describe a strategy for designing an embedded system (or
subsystem) that uses I2C communication to increase its value,
flexibility and reliability.
Introduction to I2C
I2C is a low- to medium-data-rate master/slave communication bus.
Philips, the creator of I2C, describes the bus as a simple
bi-directional 2-wire bus for efficient inter-IC control [1]. The bus consists of just two
wires or circuit traces, one for clock and the other for data, with a
pull-up resistor on each wire of the bus. The physical layer is a
simple handshaking protocol that relies upon open collector outputs on
the bus devices and the device driving or releasing the bus lines.
The master supplies the clock; it initiates and terminates
transactions and the intended slave (based upon the address provided by
the master) acknowledges the master by driving or releasing the bus.
The slave cannot terminate the transaction but can indicate a desire to
by a “NAK” or not-acknowledge. Addressing opens the lines of
communication between the master and its intended slave device and the
master keeps the connection open until it wishes to terminate the
connection (when the master is finished with the slave).
The simple hardware design and relatively low data rates allow any
engineer to take advantage of I2C as a communication solution. You can
learn more about the protocol by reading through the Philips
specification and various application notes [2] (almost every I2C slave device
also describes the specification with sufficient detail to gain a
working understanding of it [3]).
Nearly every microcontroller vendor has taken care of the physical
implementation of the bus, so you do not need to understand all the
details in order to harness the power of I2C (just as you do not need
to understand RS232 communications to be able to use devices with
HyperTerm).
Off-the-shelf components like serial EEPROM, remote temperature
sensors and I/O port expanders using I2C are available as slave
devices. Since most microcontroller vendors offer I2C master and slave
capabilities on most of their devices, there is nothing to prevent an
embedded designer from taking advantage of I2C.
Creating a custom I2C slave device is the point of this article, and
it is easier than you think, as long as you keep to a plan and follow
some simple rules.
Simple Rules Lead to Success
Creating a custom I2C slave requires firmware: the application-general
protocol (how data is interpreted from the physical layer/hardware
registers), the application-specific interfaces (what data is
exchanged, and functional implementation (what a specific piece of data
means, what action does it cause). I have four rules to a successful
I2C firmware implementation:
1) Make the master generic;
let the slaves specialize.
2) Use a standardized
register-based protocol.
3) Use well-defined
(unchanging) interfaces.
4) Follow an established set
of design practices.
These rules are not hard to understand, nor are they hard to follow.
Some are just reminders of what many embedded designers would do
anyway. Without rules many small subsystems develop organically, and
before you know it you're getting ready to ship something that you wish
you could re-design (which the schedule seldom permits). Starting your
design with these rules in mind will reduce your regret over the
results.
Rule
#1: Make the master generic
and let the slaves specialize.
The master in an I2C design is often the master of the entire system,
and as such has many things on its mind. Designing specialized slaves,
that provide specific functionality shields the complex master in your
system from the winds of change as the solution solidifies. Adding
features to the slaves (as they inevitably creep in) will have little
impact on the systems as a whole, when the master’s role is a generic
one.
Rule
#2: Use a standardized
register-based protocol.
When you read the datasheet for an off-the-shelf I2C slave[4], you will
find a description of a set of registers, what data they contain or
what actions writing data to them causes. This set of registers looks
to a master like a memory map.
The details of the registers may differ from slave to slave, but all
follow a standard pattern: write the first data byte to set the
internal register-pointer, and then write the second data byte to
transfer the data to the register pointed-to by the register-pointer
(the first data byte).
When a master wants to read from the slave, it first writes the
register-pointer to the slave, then starts a read transaction and reads
out the data pointed-to by the register-pointer. Many devices allow
multi-byte reads and writes, but it is in this way that they differ
from device-to-device.
If you use this same register-based protocol in your design, you
benefit by having everyone intuitively understand the protocol. This
makes a team more productive solving real problems (rather than
debugging a new protocol). The standardization yields greater dividends
because it enables a design (both the master and slave) to be more
easily reused.
Rule
#3: Use well-defined
(unchanging) interfaces.
This is a tenet of any project, but it bears reiteration. Define the
interfaces and honor them as contracts. In rule #1 I stated the master
would be generic, the slave would carry the burden of the task. To be
able to pull this off, the interface between the master and slave must
be set early and set in concrete. Use simple, easy to understand
interfaces. Keep the interfaces to the essentials, let the slave boil
the data down or expound a command. The interface need not reflect the
slave implementation details, in fact it is best not to.
Rule
#4: Follow an established set
of design practices.
This rule is less well defined, but the main point is that designs
should be supported with established, accepted design practices. The
practices will vary from team to team, but the point of this rule is
that you want the team’s concentration to focus on the value-added
function to be accomplished, and not worry about all the methods.
For an I2C slave there are two practices of particular usefulness:
first, maintain separate foreground and background threads or tasks;
second, partition the data interface into read-only and read/write
blocks, and protect against writing to read-only registers.
Considerations for the I2C Master
It is the master in the system that often has the least flexibility. So
many other requirements and concerns find a home in the master, that
there cannot be many rules or guidelines imposed upon the master by a
slave device. And yet, that is precisely what happens when
off-the-shelf I2C slave devices are chosen.
Most require the master not only to configure the slave, but also
often to repeatedly poll and command it. A custom I2C slave
implementation using a microcontroller can shift the balance of the
work into the slave, make life easier for the master, and therefore
make it easier to add higher-value functionality to the system.
There are a few things to keep in mind with the master for a more
successful implementation: plan for tolerance to a slave’s timing, use
high-level commands rather than micromanaging the slave, and implement
interfaces early then resist changing them.
While the master is in control of the timing at the physical layer,
often a slave has many things to do at the functional layer. When a
slave is designed to act upon a specific command, it may not be
instantaneous, and therefore the master should not expect immediacy. A
tolerant timing profile, where the master does not make snap decisions
about a slave’s response, is the best approach.
Remember masters: be tolerant, and do not make snap decisions (if
instantaneous action is required, we’ll see in the next section where
the slave can help out).
Rule #3 above
says to define interfaces and then follow them, but what these
interfaces should be or should look like isn’t addressed. For the
master’s benefit, create an interface of high-level commands; limit the
interactions of the master. With a custom slave, the master need not
even configure the slave (this can be programmed into the start-up
section of the firmware), and it only needs to be contacted when an
unusual event occurs (i.e. fault condition).
Lastly, since the main implementation task for the master is the
interfaces, the earlier these interfaces are implemented and tested,
the sooner the master can move on to its other demands. If the
interfaces are defined, followed and consist of high level commands,
the implementation burden on the master is light, and the actual
requirements of each command can be explored in the context of the
greater system.
Remember, the master will drive functional requirements for the
slaves, so allow as much time as possible for the interfaces and the
intents of the high-level commands to be hardened in the context of the
system.
Considerations for the I2C Slave
Implementation
When a custom slave is the goal, implementation of the slave is likely
to be the largest task, and the fundamental reason of pursuing a custom
slave device is to get a custom fit.
As discussed earlier, this custom fit can entail offloading the
master of lower-level functions, or it can be as simple as multiplying
the number of I/O pins for a host processor (without the burdens that
off-the-shelf devices can impose). A custom device can serve as a
buffer between several off-the-shelf I2C slave devices, again reducing
the burden on the master or providing address arbitration for slaves
that have none.
For whatever reason (there are so many) that you pursue a custom I2C
slave, keep in mind the following guidelines to increase the value of
your results: separate the firmware that handles the communications
protocol from everything else, maintain coherent data at all times, and
provide an “emergency signal” to the master to limit the master’s need
to poll the slave.
The “perceived” main task of the slave is not communicating or
managing the communications protocol. The truth is, if not handled
correctly, or if designed incorrectly, the main design and debug task
will be the communications.
The first step on the path to successfully managing communications
was to adopt a standard register-based protocol, rule #2
above. The second step is to use a tested, debugged firmware
implementation of that protocol. It may be possible to find this
already implemented for your microcontroller of choice by searching
through their application notes or user forums.
(Cypress Semiconductor offers an
implementation of this protocol called EZ I2C for all of their PSoC
microcontroller devices, built into the development tools (see
the Sidebar “Creating
a Custom I2C Device in 15 Minutes.”).
However you accomplish this, the key is to resist reinventing for
each project the protocol or communication management firmware.
Make the protocol managment a
separate task
If this is your first time and you want to keep the I2C protocol
firmware separate from everything else, put the protocol management
into a separate task from the rest of the system. For a microcontroller
this means attaching it to the I2C interrupt.
Since interrupts need to be handled quickly with minimal system
disruption, the interrupt handler will not do any data manipulation or
execute any higher-level functions. The interrupt handler will simply
take the data off the bus and transfer it to a register or retrieve
data from a register and provide it for transmission on the bus.
When the protocol management is handled in a separate task
(interrupt) form the main application, the registers look like a
dual-port RAM. On one side, the main application is acts upon and
updates the data in the registers. On the other side the I2C protocol
task transfers data received from the master into the registers and
from the registers to the bus for the master. Implementing the protocol
management as described above will allow it to be transferred from
project to project.
Once the protocol is taken care of, the next concern is the actual
data in the registers. The protocol will be asynchronously transferring
data in and out of the registers. It is the application’s job to keep
the data coherent, so that the data the master sees accurately reflects
what is going on.
This can be ensured by making all data 8-bits wide, since I2C
transfers are byte-by-byte. But many data elements will not fit into
8-bits and retain the desired resolution. Also, coherency is not just
about keeping multiple bytes of a data element in-sync; it means that
at no point will a data element contain a anything but its true value
(registers are not used for temporary values, such as setting a
register to zero and then setting individual status bits true).
Keep all data transfers atomic; if a multi-byte data element is to
be updated, disable interrupts around the transfer. When calculations
are needed before a register can be determined, use a separate
temporary variable or scratchpad and then transfer the final data.
Finally, slaves should never require a master to intervene unless
absolutely necessary. The master is the one to decide when it is
absolutely necessary, so in your slave design you need to provide
interrupt points, but also provide the master the ability to decide not
to be interrupted. Provide a mask bit for each status that can cause an
interrupt.
A slave should be designed to handle everything that comes up. The
emergency interrupt is there to inform the master what has happened,
but not to wait for the master to take action. Designing your
interrupts this way will further reduce the burden on the master while
allowing for a wide range of unusual situations to be handled.
Conclusion
I2C provides a relatively painless approach to creating custom
functions that stand-alone and partition a system into a high-level
master and lower-level functioning slaves. When Philips created the bus
they envisioned system designers building entire devices like computers
and televisions by dropping ready-made slave devices onto the 2-wire
bus.
While this did not happen, the I2C bus has become an embedded system
standard. I2C enabled devices are available as off-the-shelf, drop-in
functions, but not to the degree and in the variety required to
build-up a complex device.
Microcontroller-implemented custom slave devices can fill this gap
and for a company they can provide a portfolio of plug-and-play
functions that address the repetitive portion of their designs. Follow
the rules and you can successfully harness the I2C bus in your system.
In Part 2 I will address
the test and verification process for I2C in your system. We will
revisit and build upon many of the design considerations and show how
they support and improve the test and verification of an I2C-enabled
system.
Jon Pearson is
the product manager for PSoC development tools at Cypress Semiconductor, located in
Lynnwood, WA. He can be reached at jpx@cypress.com.
References:
[1] The I2C-Bus Specification,
Version 2.1. January 2000
[2] Philips has a page
describing the bus and providing links to the official specification
and other resources that can be found at http://www/semiconductors.philips.com/products/interfeace_control/i2c/facts
[3] Cypress Semiconductor
offers I2C port expanders and has an application note describing them,
AN2305 – Using Cypress I2C port Expanders with Flash Storage, which can
be found at http://www.cypress.com by searching on “AN2304”
[4] Cypress CY8C9520 datasheet
(found at http://www.cypress.com
); Philips PCF8574 datasheet (found at http://www.semiconductors.philips.com
)