-
Ming Hsieh Department of Electrical Engineering
EE 459Lx - Embedded Systems Design Laboratory
Using the I2C Interface on theATmega328P and MC908JL16
by
Allan G. Weber
1 Introduction
The I2C or I2C (Inter-Integrated Circuit ) interface is a serial
bus intended for communication between twoor more integrated
circuits on the same PC or prototyping board. Also known as a
two-wire bus, thisbus is for communication with many different
types of ICs such as EEPROMs, real-time clock, temperaturesensors,
etc. The I2C interface is not particularly fast so it is typically
used for connecting to ICs that donot require large amounts of data
to be transferred rapidly.
Some of the microcontrollers used in EE459Lx such as the Atmel
ATmega328P and the FreescaleMC908JL16 implement this interface in
hardware. The Atmel documentation calls the I2C interface theTwo
Wire Interface while the Freescale documentation refers to it as
MMIIC (Multi-Master IIC) butits the same thing.
The hardware on these microcontrollers perform many of the lower
level tasks required for transferringdata on the I2C bus. For
example, to write data to a I2C device, the program sets up the
transfer, startsit, keeps a data buffer full with the next byte to
be sent and finally terminates the transfer at the end.
Thesignaling required for sending each bit is handled by the
hardware. Reading is done in a similar manner.
Software-only implementations of the I2C protocol can also be
used to provide this interface on micro-controllers that do not
have I2C hardware.
The Agilent and Tektronix oscilloscopes in the OHE 240 lab have
special triggering capabilities that makeit possible to analyze I2C
data transfers. Individual data transfers can be captured and
viewed so that thetransfer can be examined in detail to see if the
correct information is being sent or received. This capabilityof
these scopes is an extremely useful tool for students trying to
debug any I2C aspects of their projects.
1.1 Device Addresses
In order to use the I2C interface you need to know the address
of the device on the I2C bus. The address isa seven bit number but
is often written as an eight-bit number where the upper seven bits
are the addressand the least significant bit indicates whether a
read or write is occurring. Most manufacturers datasheetsprovide
the eight bit number with the least significant bit set to zero.
Table 1 shows the 8-bit value (7-bitaddress + R/W flag) for some
common devices used in EE459 projects.
Some I2C devices allow you to specify one or more of the least
significant bits of the seven-bit addressby connecting pins on the
chip to a logical zero or one. This make it possible to have more
than one deviceof the same type on the bus or to avoid address
conflicts with other devices. The number of extra addressbits
available is indicated in Table 1.
1.2 Reading Data
Many I2C devices require reading operations to be done in two
steps. The first part of the reading operationconsists of writing
the address of the first location to be read into an internal
buffer. The write operation is
EE 459Lx, Rev. 12/24/14 1
-
IIC Device Description 8-bit Base Number of ExtraAddress Address
Bits
24LC256 32KB EEPROM 0xA0 3DS1307 Real-time clock 0xD0
nonePCF8563 Real-time clock 0xA2 noneDS1621, DS1631 Temperature
sensor 0x90 3MCP23008 8-bit port expander 0x40 3MCP23017 16-bit
port expander 0x40 3MAX7311 16-bit port expander 0x40 3
Table 1: I2C Device Addresses
then terminated and a read command is sent. This causes the
device to start sending data from the locationgiven by the data
that was previously written to it. The number of bytes that must be
written before theread can be done is dependent on the device.
2 I2C on the ATmega328P
The ATmega328P uses pins 27 and 28 for the I2C data and clock.
When I2C is not used these pins can beused as general I/O ports PC4
and PC5.
2.1 Initializing
The internal I2C hardware needs to have the baud rate (I2C clock
rate) set before any transfers can takeplace. The maximum rate that
the I2C device can handle should be described in the devices
datasheet,but most devices should be able to operate with a clock
up to 100kHz. The clock is derived from themicrocontrollers clock
by dividing it down according to this formula.
I2C Baud Rate =CPU Clock Frequency
16 + 2(TWBR) (prescalar value)The prescalar value is set by a
two bit number in the TWSR register and can be either 1, 4, 16 or
64. The
TWBR value is the eight-bit contents of the TWBR register. This
value can be calculated if we rearrange theabove formula to solve
for TWBR
TWBR =
(CPU Clock Frequency
I2C Baud Rate
) 16)/(2 prescalar value)
A suitable value for TWBR can be calculated in the program
source code using compiler preprocessorstatements. For example, if
we want a baud rate of 100kHz, we can set the prescalar to one and
then usethe following to determine the value to go in the TWBR
register.
#define FOSC 9830400 // Clock frequency = Oscillator freq.
#define BDIV (FOSC / 100000 - 16) / 2 + 1
TWSR = 0; // Set prescalar for 1
TWBR = BDIV; // Set bit rate register
The +1 at the end of the statement calculating BDIV is needed
since the integer calculations done bythe preprocessor could have
truncation errors resulting in a value of BDIV that is too low and
giving an I2Cfrequency over 100kHz. The +1 makes sure the frequency
is below 100kHz.
2.2 Writing
The following is an example of a function that writes n bytes of
data from array p to an I2C devicewith the 8-bit address
device_addr. Most of code in this routine consists of slight
variations of the same
EE 459Lx, Rev. 12/24/14 2
-
thing: load the data register with data, set the control
register to send it, wait for it to be sent, and checkthe
status.
/*
i2c_write - write bytes to an I2C device
*/
uint8_t i2c_write(uint8_t device_addr , char *p, uint16_t n)
{
uint8_t status;
To start the transmission, the control register is set to signal
a start condition on the bus.
TWCR = (1
-
*/
uint8_t i2c_read2(uint8_t device_addr , char *p, uint16_t n,
uint16_t a)
{
uint8_t status;
// To read , first write the device address and two bytes
// of the internal address
TWCR = (1
-
to be read.
// Read all but the last of n bytes from the slave device in
this loop
n--;
while (n-- > 0) {
TWCR = (1
-
3.2 Writing
The following is an example of a function that writes n bytes of
data from array p to an I2C device withthe 8-bit address
dev_addr.
The program first checks to make sure the number of bytes being
written is non-zero. The addressregister, MMADR, is loaded with the
8-bit I2C device address which is actually the 7-bit address with a
zeroadded as the least significant bit. The data register is loaded
with the first byte of the data to be written. Inthe control
register, the MMRW bit is set for a write, and finally the MMAST
bit is set to start the transmission.The byte count is decremented
since the first byte has already been sent as part of the initial
steps in theprocess.
/*
i2c_write - Write bytes to an I2C device.
*/
unsigned char i2c_write(unsigned char dev_addr , unsigned char
*p,
unsigned int n)
{
unsigned char status;
status = 0; // 0 = operation worked
if (n == 0) // check for no data
return(status );
MMADR = dev_addr; // Device address -> address reg
MMDTR = *p++; // 1st byte of data
MIMCR_MMRW = 0; // Set for transmit
MIMCR_MMAST = 1; // Start transmission
n--; // First byte already being sent
If there are more bytes to send the programs loops through the
following steps. First, wait for thetransmit buffer to be empty.
The I2C transmit hardware consists of two registers, one (MMDTR)
for holdingthe next byte to be transmitted, and a shift register
that contains the byte currently being transmited. Thecontents of
the MMDTR will be moved into the shift register for output as soon
as that register is empty andat that time the MMTXBE bit will be
set. When MMTXBE goes to a one, the MMSR_MMRXAK bit can be checked
tosee if the previously transmitted byte resulted in and ACK or NAK
response from the slave. If a NAK wasreceived, set the status value
and go send a STOP condition. Otherwise load the MMDTR with the
next byte.In summary, wait for byte k to clear out of the transmit
buffer, check the ACK/NAK status for byte k 1,move byte k + 1 into
the transmit buffer, and loop.
while (n-- > 0) {
while (!( MMSR_MMTXBE )); // Wait for TX buffer empty
if (MMSR_MMRXAK) { // Check for NAK
status = 1; // Set status for failed operation
goto nakstop; // Go set STOP condition
}
MMDTR = *p++; // Next data -> MMDTR
}
When the value of the byte count, n, reaches zero the last data
byte has been moved into MMDTR. Theprogram waits for MMTXBE to show
that this byte has moved to the output shift register and checks
theACK/NAK status of the previous byte. Due to an oddity in the
JL16 the output hardware will not generatea clock signal to receive
the acknowledge for the last byte unless there is some data in the
transmit register.The solution is to write some dummy data into
MMDTR. This will cause the proper I2C clock signal to beproduced
for the ACK to be received.
The MMAST bit is set to zero to generate a STOP condition after
the byte currenting being transmitted issent. This ends the write
before any of the dummy data gets transferred.
EE 459Lx, Rev. 12/24/14 6
-
while (!( MMSR_MMTXBE )); // Wait for TX buffer empty
if (MMSR_MMRXAK) // Check for NAK
status = 1; // Set status for failed operation
else
MMDTR = 0xff; // Dummy data to get ACK clock
nakstop:
MIMCR_MMAST = 0; // Generate STOP bit
return(status );
}
3.3 Reading
The following function reads n bytes of data from an I2C device
with the 8-bit address dev_addr. The16-bit address of where the
data is to be read from is passed in the argument a, and the
location wherethe data that is read is to be stored is in pointer
p. The first part of the program is similar to the writingfunction
described above. It loads the device address into register MMADR,
the upper part of the address datainto MMDRT, set the mode to a
write and starts the transmission.
/*
i2c_read2 - read bytes from an I2C device (two byte address)
*/
unsigned char i2c_read2(unsigned char dev_addr , unsigned char
*p,
unsigned int n, unsigned int a)
{
unsigned char status;
status = 0; // 0 = operation worked
if (n == 0) // check for no data
return(status );
MMADR = dev_addr; // Device address -> address reg.
MMDTR = a >> 8; // High byte of data address
MIMCR_MMRW = 0; // Set for transmit
MIMCR_MMAST = 1; // Initiate transfer
The code that follows is essentially the writing loop from the
write function unrolled into sending thelower part of the address
data, and then the dummy data to allow it to wait for the
acknowledge to bereceived for the second address byte. If the
device only requires a one byte data address, then that addressbyte
should be sent in the part above, and the first five lines below
can be removed.
while (!( MMSR_MMTXBE )); // Wait for TX buffer empty
if (MMSR_MMRXAK) { // Check for NAK
status = 1; // Set status for failed operation
goto nakstop; // Go set STOP condition
}
MMDTR = a & 0xff; // Low byte of data address
while (!( MMSR_MMTXBE )); // Wait for TX buffer empty
if (MMSR_MMRXAK) { // Check for NAK
status = 1; // Set status for failed operation
goto nakstop; // Go set STOP condition
}
MMDTR = 0xff; // Dummy data to get ACK clock
Now the registers are set up for reading. The device address is
loaded into MMADR, the mode is set for aread, the Repeated Start
bit is enabled, and the transfer is started. The command byte
containing the
EE 459Lx, Rev. 12/24/14 7
-
device address has to be sent so dummy data has to be put in the
data register so the JL16 can receive theACK for this byte.
MMCR_MMTXAK = 0 // MCU will ACK received data
MMADR = dev_addr; // device address -> address reg.
MIMCR_MMRW = 1; // Set for receive
MMCR_REPSEN = 1; // Enable repeated start
MIMCR_MMAST = 1; // Initiate transfer
while (!( MMSR_MMTXBE )); // Wait for TX buffer empty
if (MMSR_MMRXAK) { // Check for NAK
status = 1; // Set status for failed operation
goto nakstop; // Go set STOP condition
}
MMDTR = 0xff; // Dummy data to get ACK clock
The program now loops n 1 times waiting for the receive buffer
to be full and then transferring thereceived data to the array in
memory.
n--; // Loop n-1 times
while (n > 0) {
while (!( MMSR_MMRXBF )); // Wait for RX buffer full
*p++ = MMDRR; // Get data
n--; // Decrement total count
}
For the last byte, a flag is set telling the hardware to not
send an ACK. Once the last byte is received,the hardware is made to
generate a STOP bit and the reading operation is finished.
MMCR_MMTXAK = 1; // Don t send ACK for last byte
while (!( MMSR_MMRXBF )); // Wait for RX buffer full
*p++ = MMDRR; // Get data
nakstop:
MIMCR_MMAST = 0; // Generate STOP bit
return(status );
}
4 Viewing I2C Transfers on the Agilent 54622A Scopes
The two-channel Agilent 54622A oscilloscopes have I2C triggering
built in to them. To make use of thisfollow the steps below.
1. Turn on the scope and then turn on both input channels by
pressing the 1 and 2 buttons in thevertical section of controls
until they light up.
2. Use the large knobs above the 1 and 2 buttons to adjust the
input levels for both channels to 5Volts per division. The levels
for the channels are shown in the top left corner of the
screen.
3. Use the large knob in the horizontal section of the controls
to change the horizontal sweep speed to200sec/div. The sweep speed
is shown in the top center portion of the screen.
4. The small knob in the horizontal section changes the
horizontal position of the displayed signal. Usethis knob to move
one of the small triangles near the top of the screen over closer
to the left side ofthe screen. When an I2C signal is captured, it
will be displayed with the starting point of the signalat this
position.
5. In the trigger section of the controls (Fig. 1), press the
Edge button.
EE 459Lx, Rev. 12/24/14 8
-
Figure 1: Agilent triggering controls Figure 2: Agilent trigger
Mode menu
6. Press the softkey below the screen for channel 1, and then
use the Level knob in the triggering sectionto adjust the trigger
voltage to around 2.5 Volts. The trigger voltage level is indicated
in the top rightcorner of the screen. Press the softkey for channel
2 and set the channel 2 trigger level to around 2.5Volts.
7. In the trigger section of the controls, press the
Mode/Coupling button.
8. If the left softkey doesnt say Mode Normal, press it to bring
up the mode menu and press it againuntil Normal is selected.
9. Connect the two scope probes to scope and then connect the
probe tips to the I2C clock and data lineson your project board.
Either one can be attached to either signal but make note of which
way theyare connected.
10. In the triggering section of the scope controls, press the
More button.
11. If the label of the second softkey does not say Trigger I2C,
press this button to bring up the triggermode menu (Fig. 2) and
then continue pressing it until the check mark is by I2C.
12. Press the left softkey that is labeled Settings to bring up
the I2C Trigger Menu screen
13. The labels on two left most softkeys indicate which channel
is assigned to the I2C clock and which is forthe data (Fig. 3). If
the channel assignment is opposite to how you connected the probes,
press eitherof the two softkeys for the channel assignments. This
will bring up a menu for the channel assignmentand you can press
that key again until the setting is correct. You dont need to
change both channelsmanually since changing one causes the other to
also change.
14. Check the label on the third softkey to make sure it says
Trigger: Start. If it doesnt, press this keyto bring up the Trigger
on: menu (Fig. 4) and then press the button enough times to select
Startcondition.
At this point the scope is configured to trigger on a I2C Start
condition. Press the Single button abovethe triggering controls to
put the scope in a state where it will wait for the next Start
condition on the I2C
EE 459Lx, Rev. 12/24/14 9
-
Figure 3: Agilent channel selection Figure 4: Agilent trigger
conditions
bus and then capture the data. Do whatever is needed on your
project board to get it to generate the I2Ctransfer, and once the
data has been captured it will be displayed on the screen. The
captured data canbe expanded or shrunk horizontally using the large
knob in the horizontal section of controls. To scroll thedata left
or right, use the small knob in the horizontal section.
5 Viewing I2C Transfers on the Tektronix Scopes
The four-channel Tektronix DPO2014 and MSO2014 oscilloscopes
have I2C triggering as part of the DPO2EMBDoption. To make use of
this follow the steps below. If at any time during the setup you
want to make amenu disappear from the screen, press the Menu Off
button near the lower right corner of the screen.
1. Turn on the scope and then turn on two of four input channels
by pressing the buttons with numberson them in the vertical section
of controls until the traces appear on the screen. In this example
welluse channels 1 and 2. The wide rectangular box at the lower
left of the screen (Fig. 5) shows thechannels that have been turned
on.
2. Use the large Scale knobs below the 1 and 2 buttons to adjust
the input levels for both channelsto 5 Volts per division. The
levels for the channels are shown in the lower left part of the
screen.
3. Use the small knobs above channel buttons to vertically
position the two traces on the screen whereboth can be viewed.
4. Use the large knob in the horizontal section of the controls
to change the horizontal sweep speed to200s (time/division). The
sweep speed is shown in the box in the lower center portion of the
screen.
5. The small Position knob in the horizontal section changes the
horizontal position of the displayedsignal. Use this knob to move
one of the small T markers near the top of the screen over closer
tothe left side of the screen. When an I2C signal is captured, it
will be displayed with the starting pointof the signal at this
position.
EE 459Lx, Rev. 12/24/14 10
-
Figure 5: Tek channel settings Figure 6: Tek bus
configuration
Figure 7: Tek channel selection Figure 8: Tek trigger
conditions
6. Connect two scope probes to channels 1 and 2 of the scope and
then connect the probe tips to the I2Cclock and data lines on your
project board. Either one can be attached to either signal but make
noteof which way they are connected.
7. The Tek scopes can have two configurations stored for working
with buses. To setup a bus for I2C,press one of the B1 or B2
buttons just above the connector of channel 1. In this example well
useBus 1. Pressing the B1 button brings up the bus configuration
menu along the bottom of the screen.
8. The label for the left softkey shows the current setting for
the bus. If it doesnt say Bus B1 I2C,press the softkey below the
label to bring up a vertical menu for selecting the bus type (Fig.
6). Usingthe Multipurpose a knob in the top left part of the
controls, select the I2C setting for bus B1.
9. Press the second softkey from the left labeled Define Inputs.
This brings up the screen shown inFig. 7. The current settings for
which channel is clock and which is data is shown at the right side
ofthe screen. Use the a and b Multipurpose knobs to change these to
match how you connected theprobes to the hardware under test.
10. Press the Thresholds softkey to see the voltages threshold
settings for each channel. The thresholdsneed to be set to
something around the middle of the zero to 5 volt range. Use the
two Multipurpose
EE 459Lx, Rev. 12/24/14 11
-
controls to set each of the thresholds to 2.4 or 2.6 Volts
11. Use the Include R/W in address softkey to change that
setting to Yes. This will make thescope display the device
addresses as a eight-bit number with the least significant bit
representing theread/write flag.
12. Use the Bus Display softkey to change that setting to Hex.
This causes the bytes of data on thebus to be displayed in
hexadecimal rather than as binary numbers.
13. Press the Menu button in the Trigger section. If the Type
softkey label doesnt say Bus, pressit and use the Multipurpose a
knob to scroll down to Bus.
14. The Trigger On softkey is used to select the I2C condition
that will cause the scope to acquire data.For most purposes a Start
condition works best. If the softkey doesnt say Start, press the
softkeyto bring up the Trigger On menu (Fig. 8) and use the
Multipurpose a knob to set it for triggeringon a I2C Start
condition.
15. If the right softkey along the bottom doesnt say Mode Normal
. . . , press it and then press thesoftkey along the right side to
set the trigger mode for Normal.
16. If needed press Menu Off a few times to remove the menus
from the screen.
At this point the scope is configured to trigger on a I2C Start
condition. Press the Single button abovethe triggering controls to
put the scope in a state where it will wait for the next Start
condition on the I2Cbus and then capture the data. Do whatever is
needed on your project board to get it to generate the I2Ctransfer,
and once the data has been captured it will be displayed on the
screen. The captured data can beexpanded or shrunk horizontally
using the smaller inner knob of the Wave Inspector control in the
topmiddle portion of the front panel. To scroll the data left or
right, use the larger outer part of this control.
EE 459Lx, Rev. 12/24/14 12