Protocolo I2C
Protocolo I2C
Allan G. Weber
Introduction
The I2 C or I2C (Inter-Integrated Circuit ) interface is a serial bus intended for communication between two
or more integrated circuits on the same PC or prototyping board. Also known as a two-wire bus, this
bus is for communication with many different types of ICs such as EEPROMs, real-time clock, temperature
sensors, etc. The I2 C interface is not particularly fast so it is typically used for connecting to ICs that do
not require large amounts of data to be transferred rapidly.
Some of the microcontrollers used in EE459Lx such as the Atmel ATmega328P and the Freescale
MC908JL16 implement this interface in hardware. The Atmel documentation calls the I2 C interface the
Two Wire Interface while the Freescale documentation refers to it as MMIIC (Multi-Master IIC) but
its the same thing.
The hardware on these microcontrollers perform many of the lower level tasks required for transferring
data on the I2 C bus. For example, to write data to a I2 C device, the program sets up the transfer, starts
it, keeps a data buffer full with the next byte to be sent and finally terminates the transfer at the end. The
signaling required for sending each bit is handled by the hardware. Reading is done in a similar manner.
Software-only implementations of the I2 C protocol can also be used to provide this interface on microcontrollers that do not have I2 C hardware.
The Agilent and Tektronix oscilloscopes in the OHE 240 lab have special triggering capabilities that make
it possible to analyze I2 C data transfers. Individual data transfers can be captured and viewed so that the
transfer can be examined in detail to see if the correct information is being sent or received. This capability
of these scopes is an extremely useful tool for students trying to debug any I2 C aspects of their projects.
1.1
Device Addresses
In order to use the I2 C interface you need to know the address of the device on the I2 C bus. The address is
a seven bit number but is often written as an eight-bit number where the upper seven bits are the address
and the least significant bit indicates whether a read or write is occurring. Most manufacturers datasheets
provide the eight bit number with the least significant bit set to zero. Table 1 shows the 8-bit value (7-bit
address + R/W flag) for some common devices used in EE459 projects.
Some I2 C devices allow you to specify one or more of the least significant bits of the seven-bit address
by connecting pins on the chip to a logical zero or one. This make it possible to have more than one device
of the same type on the bus or to avoid address conflicts with other devices. The number of extra address
bits available is indicated in Table 1.
1.2
Reading Data
Many I2 C devices require reading operations to be done in two steps. The first part of the reading operation
consists of writing the address of the first location to be read into an internal buffer. The write operation is
IIC Device
Description
24LC256
DS1307
PCF8563
DS1621, DS1631
MCP23008
MCP23017
MAX7311
32KB EEPROM
Real-time clock
Real-time clock
Temperature sensor
8-bit port expander
16-bit port expander
16-bit port expander
8-bit Base
Address
0xA0
0xD0
0xA2
0x90
0x40
0x40
0x40
Number of Extra
Address Bits
3
none
none
3
3
3
3
then terminated and a read command is sent. This causes the device to start sending data from the location
given by the data that was previously written to it. The number of bytes that must be written before the
read can be done is dependent on the device.
I2 C on the ATmega328P
The ATmega328P uses pins 27 and 28 for the I2 C data and clock. When I2 C is not used these pins can be
used as general I/O ports PC4 and PC5.
2.1
Initializing
The internal I2 C hardware needs to have the baud rate (I2 C clock rate) set before any transfers can take
place. The maximum rate that the I2 C 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 the
microcontrollers clock by dividing it down according to this formula.
I2 C Baud Rate =
The prescalar value is set by a two bit number in the TWSR register and can be either 1, 4, 16 or 64. The
T W BR value is the eight-bit contents of the TWBR register. This value can be calculated if we rearrange the
above formula to solve for T W BR
CPU Clock Frequency
T W BR =
16)/(2 prescalar value)
I2 C Baud Rate
A suitable value for T W BR can be calculated in the program source code using compiler preprocessor
statements. For example, if we want a baud rate of 100kHz, we can set the prescalar to one and then use
the 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;
TWBR = BDIV ;
The +1 at the end of the statement calculating BDIV is needed since the integer calculations done by
the preprocessor could have truncation errors resulting in a value of BDIV that is too low and giving an I2 C
frequency 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 I2 C device
with 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
thing: load the data register with data, set the control register to send it, wait for it to be sent, and check
the 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 << TWINT ) | (1 << TWEN ) | (1 << TWSTA ); // Send start condition
while (!( TWCR & (1 << TWINT )));
// Wait for TWINT to be set
status = TWSR & 0 xf8 ;
if ( status != 0 x08 )
// Check that START was sent OK
return ( status );
Next, the device address, with the least significant bit a zero, is sent. The address is loaded into the
data register, the transmission is started, the program loops waiting for it to finish, and then the status is
checked.
TWDR = device_addr & 0 xfe ;
TWCR = (1 << TWINT ) | (1 << TWEN );
while (!( TWCR & (1 << TWINT )));
status = TWSR & 0 xf8 ;
if ( status != 0 x18 )
return ( status );
Now the n bytes of data can be written. The loops write all n bytes in the same manner as above: load
the data register, start the transmission, wait for it to finish, and check the status.
// Write n data bytes to the slave device
while (n - - > 0) {
TWDR = * p ++;
// Put next data byte in TWDR
TWCR = (1 << TWINT ) | (1 << TWEN ); // Start transmission
while (!( TWCR & (1 << TWINT ))); // Wait for TWINT to be set
status = TWSR & 0 xf8 ;
if ( status != 0 x28 )
// Check that data was sent OK
return ( status );
}
The final step in the writing process is to send the stop condition.
TWCR =
return (0);
}
2.3
Reading
The following function reads n bytes of data from an I2 C device with the 8-bit address device_addr.
The 16-bit address of where the data is to be read from is passed in the argument a, and the location
where the data that is read is to be stored is in pointer p. The first part of the program is similar to the
writing function described above in that it sends the start condition, the device address, and two bytes of
data (the data address).
/*
i2c_read2 - read bytes from an I2C device ( two byte address )
EE 459Lx, Rev. 12/24/14
*/
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 << TWINT ) | (1 << TWEN ) | (1 << TWSTA ); // Send start condition
while (!( TWCR & (1 << TWINT )));
// Wait for TWINT to be set
status = TWSR & 0 xf8 ;
if ( status != 0 x08 )
// Check that START was sent OK
return ( status );
TWDR = device_addr & 0 xfe ;
TWCR = (1 << TWINT ) | (1 << TWEN );
while (!( TWCR & (1 << TWINT )));
status = TWSR & 0 xf8 ;
if ( status != 0 x18 )
return ( status );
TWDR = a >> 8;
TWCR = (1 << TWINT ) | (1 << TWEN );
while (!( TWCR & (1 << TWINT )));
status = TWSR & 0 xf8 ;
if ( status != 0 x28 )
return ( status );
After sending the the two-byte address, the hardware signals a second start condition followed by the
device address with the read bit set to a one.
// Put TWI into Master Receive mode by sending a repeated START condition
TWCR = (1 << TWINT ) | (1 << TWEN ) | (1
//
while (!( TWCR & (1 << TWINT )));
//
status = TWSR & 0 xf8 ;
if ( status != 0 x10 )
//
return ( status );
TWDR = device_addr | 0 x01 ;
TWCR = (1 << TWINT ) | (1 << TWEN );
while (!( TWCR & (1 << TWINT )));
status = TWSR & 0 xf8 ;
if ( status != 0 x40 )
return ( status );
<< TWSTA );
Send repeated start condition
Wait for TWINT to be set
Check that repeated START sent OK
The program can now loop reading the data from the device by waiting for the read to complete, checking
for an error and then storing the data in the array. This is done for one less byte than the number of bytes
EE 459Lx, Rev. 12/24/14
to be read.
// Read all but the last of n bytes from the slave device in this loop
n - -;
while (n - - > 0) {
TWCR = (1 << TWINT ) | (1 << TWEN ) | (1 << TWEA ); // Read byte and send ACK
while (!( TWCR & (1 << TWINT ))); // Wait for TWINT to be set
status = TWSR & 0 xf8 ;
if ( status != 0 x50 )
// Check that data received OK
return ( status );
* p ++ = TWDR ;
// Read the data
}
The last byte is read in a similar manner but the control register is set to not send an acknowledge after
receiving it. After the byte has been received and stored in the arry, the stop condition is set to terminate
the transfers.
// Read the last byte
TWCR = (1 << TWINT ) | (1 << TWEN );
while (!( TWCR & (1 << TWINT )));
status = TWSR & 0 xf8 ;
if ( status != 0 x58 )
return ( status );
* p ++ = TWDR ;
return (0);
}
I2 C on the MC908JL16
The JL16 has two pairs of pins, 13 and 14 or 8 and 9, that can be used as the I2 C interface. The selection
of which pair to use is determined by the IICSEL bit in the CONFIG2 register. If the bit is left in the default
setting of zero, the I2 C interface uses pins 13 and 14 for the data and clock. If pins 13 and 14 need to be used
for other purposes such as I/O ports PTD6 and PTD7 or for the for the SCI functions the I2 C functions can
be moved to pins 8 and 9 with the command
CONFIG2_IICSEL = 1;
3.1
// I2C on PTA2 ,3
Initializing
Before using the I2 C hardware, the program must set the baud rate (I2 C clock rate) to be used. The maximum
rate that the I2 C 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 the microcontrollers clock by dividing
it down. The divisor is determined a three bit number stored in the MIMCR register according to the formula
divisor = 2n+5
where n is a three bit number from 0 to 7. This results in divisor values from 32 to 4096. For example, if
the oscillators frequency is 9.8304MHz and we use a divisor value of 3, this will result in a I2 C baud rate of
38.4kHz.
Once the divisor is set, the I2 C interface is enabled by setting the MMEN bit in the MMCR register.
MIMCR_MMBR = 3;
MMCR_MMEN = 1;
3.2
Writing
The following is an example of a function that writes n bytes of data from array p to an I2 C device with
the 8-bit address dev_addr.
The program first checks to make sure the number of bytes being written is non-zero. The address
register, MMADR, is loaded with the 8-bit I2 C device address which is actually the 7-bit address with a zero
added as the least significant bit. The data register is loaded with the first byte of the data to be written. In
the 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 the
process.
/*
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;
if ( n == 0)
return ( status );
MMADR = dev_addr ;
MMDTR = * p ++;
MIMCR_MMRW = 0;
MIMCR_MMAST = 1;
// 0 = operation worked
// check for no data
//
//
//
//
n - -;
If there are more bytes to send the programs loops through the following steps. First, wait for the
transmit buffer to be empty. The I2 C transmit hardware consists of two registers, one (MMDTR) for holding
the next byte to be transmitted, and a shift register that contains the byte currently being transmited. The
contents of the MMDTR will be moved into the shift register for output as soon as that register is empty and
at that time the MMTXBE bit will be set. When MMTXBE goes to a one, the MMSR_MMRXAK bit can be checked to
see if the previously transmitted byte resulted in and ACK or NAK response from the slave. If a NAK was
received, 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 ));
if ( MMSR_MMRXAK ) {
status = 1;
goto nakstop ;
}
MMDTR = * p ++;
}
//
//
//
//
When the value of the byte count, n, reaches zero the last data byte has been moved into MMDTR. The
program waits for MMTXBE to show that this byte has moved to the output shift register and checks the
ACK/NAK status of the previous byte. Due to an oddity in the JL16 the output hardware will not generate
a 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 I2 C clock signal to be
produced for the ACK to be received.
The MMAST bit is set to zero to generate a STOP condition after the byte currenting being transmitted is
sent. This ends the write before any of the dummy data gets transferred.
3.3
Reading
The following function reads n bytes of data from an I2 C device with the 8-bit address dev_addr. The
16-bit address of where the data is to be read from is passed in the argument a, and the location where
the data that is read is to be stored is in pointer p. The first part of the program is similar to the writing
function described above. It loads the device address into register MMADR, the upper part of the address data
into 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;
if ( n == 0)
return ( status );
MMADR = dev_addr ;
MMDTR = a >> 8;
MIMCR_MMRW = 0;
MIMCR_MMAST = 1;
// 0 = operation worked
// check for no data
//
//
//
//
The code that follows is essentially the writing loop from the write function unrolled into sending the
lower part of the address data, and then the dummy data to allow it to wait for the acknowledge to be
received for the second address byte. If the device only requires a one byte data address, then that address
byte should be sent in the part above, and the first five lines below can be removed.
while (!( MMSR_MMTXBE ));
if ( MMSR_MMRXAK ) {
status = 1;
goto nakstop ;
}
MMDTR = a & 0 xff ;
//
//
//
//
//
//
//
//
Now the registers are set up for reading. The device address is loaded into MMADR, the mode is set for a
read, the Repeated Start bit is enabled, and the transfer is started. The command byte containing the
EE 459Lx, Rev. 12/24/14
device address has to be sent so dummy data has to be put in the data register so the JL16 can receive the
ACK for this byte.
MMCR_MMTXAK = 0
MMADR = dev_addr ;
MIMCR_MMRW = 1;
MMCR_REPSEN = 1;
MIMCR_MMAST = 1;
while (!( MMSR_MMTXBE ));
if ( MMSR_MMRXAK ) {
status = 1;
goto nakstop ;
}
MMDTR = 0 xff ;
//
//
//
//
//
//
//
//
//
The program now loops n 1 times waiting for the receive buffer to be full and then transferring the
received data to the array in memory.
n - -;
while ( n > 0) {
while (!( MMSR_MMRXBF ));
* p ++ = MMDRR ;
n - -;
}
// Loop n -1 times
// Wait for RX buffer full
// Get data
// 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;
while (!( MMSR_MMRXBF ));
* p ++ = MMDRR ;
nakstop :
MIMCR_MMAST = 0;
return ( status );
}
The two-channel Agilent 54622A oscilloscopes have I2 C triggering built in to them. To make use of this
follow the steps below.
1. Turn on the scope and then turn on both input channels by pressing the 1 and 2 buttons in the
vertical 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 5
Volts 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 to
200sec/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. Use
this knob to move one of the small triangles near the top of the screen over closer to the left side of
the screen. When an I2 C signal is captured, it will be displayed with the starting point of the signal
at this position.
5. In the trigger section of the controls (Fig. 1), press the Edge button.
6. Press the softkey below the screen for channel 1, and then use the Level knob in the triggering section
to adjust the trigger voltage to around 2.5 Volts. The trigger voltage level is indicated in the top right
corner of the screen. Press the softkey for channel 2 and set the channel 2 trigger level to around 2.5
Volts.
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 again
until Normal is selected.
9. Connect the two scope probes to scope and then connect the probe tips to the I2 C clock and data lines
on your project board. Either one can be attached to either signal but make note of which way they
are 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 I2 C, press this button to bring up the trigger
mode menu (Fig. 2) and then continue pressing it until the check mark is by I2 C.
12. Press the left softkey that is labeled Settings to bring up the I2 C Trigger Menu screen
13. The labels on two left most softkeys indicate which channel is assigned to the I2 C clock and which is for
the data (Fig. 3). If the channel assignment is opposite to how you connected the probes, press either
of the two softkeys for the channel assignments. This will bring up a menu for the channel assignment
and you can press that key again until the setting is correct. You dont need to change both channels
manually 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 key
to bring up the Trigger on: menu (Fig. 4) and then press the button enough times to select Start
condition.
At this point the scope is configured to trigger on a I2 C Start condition. Press the Single button above
the triggering controls to put the scope in a state where it will wait for the next Start condition on the I2 C
EE 459Lx, Rev. 12/24/14
bus and then capture the data. Do whatever is needed on your project board to get it to generate the I2 C
transfer, and once the data has been captured it will be displayed on the screen. The captured data can
be expanded or shrunk horizontally using the large knob in the horizontal section of controls. To scroll the
data left or right, use the small knob in the horizontal section.
The four-channel Tektronix DPO2014 and MSO2014 oscilloscopes have I2 C triggering as part of the DPO2EMBD
option. To make use of this follow the steps below. If at any time during the setup you want to make a
menu 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 numbers
on them in the vertical section of controls until the traces appear on the screen. In this example well
use channels 1 and 2. The wide rectangular box at the lower left of the screen (Fig. 5) shows the
channels that have been turned on.
2. Use the large Scale knobs below the 1 and 2 buttons to adjust the input levels for both channels
to 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 where
both can be viewed.
4. Use the large knob in the horizontal section of the controls to change the horizontal sweep speed to
200s (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 displayed
signal. Use this knob to move one of the small T markers near the top of the screen over closer to
the left side of the screen. When an I2 C signal is captured, it will be displayed with the starting point
of the signal at this position.
10
6. Connect two scope probes to channels 1 and 2 of the scope and then connect the probe tips to the I2 C
clock and data lines on your project board. Either one can be attached to either signal but make note
of which way they are connected.
7. The Tek scopes can have two configurations stored for working with buses. To setup a bus for I2 C,
press one of the B1 or B2 buttons just above the connector of channel 1. In this example well use
Bus 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). Using
a knob in the top left part of the controls, select the I2C setting for bus B1.
the Multipurpose
9. Press the second softkey from the left labeled Define Inputs. This brings up the screen shown in
Fig. 7. The current settings for which channel is clock and which is data is shown at the right side of
a and
b Multipurpose knobs to change these to match how you connected the
the screen. Use the
probes to the hardware under test.
10. Press the Thresholds softkey to see the voltages threshold settings for each channel. The thresholds
need 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
12