Vous êtes sur la page 1sur 11

Embedded projects from around the web

MCU project everyday


Home AVR Tutorials ARM Cortex Tutorials Contact Form Ads

Programming AVR I2C interface


January 8, 2012 | By admin In AVR Tutorial | I2C (also referred as IIC or TWI) is widely used interface in embedded applications. Two wire bus initially was used by Philips and become a standard among chip vendors. I2C bus consists of two lines called Serial Data Line (SDA) and Serial Clock Line (SCL). Communication is relatively fast and short distance mainly used to communicate between sensors, RTC, EEPROM, LCD. I2C protocol allows up to 128 devices connected to those two lines where each of them has unique address. Communication between devices is master and slave based. Master generates clock signal, initiates and terminates data transfer.

From electrical point of view I2C devices use open drain (open collector) pins. In order to operate correctly SDA and SCL lines require pull up resistors. Typically 4.7k resistors are used. Each communication is initiated by START signal and finished by STOP. These are always generated by master. START and STOP signals are generated by pulling SDA line low while SCL line is high. In other cases when data is transferred data line must be stable during clock high and can be changed when clock is low:

Bus is considered to be busy between START and STOP signals. So if there are more than one master each of them has to wait until bus is freed by current master with STOP signal. I2C communication packet consists of several parts: START signal; Address packet seven address bits lead by data direction bit (read or write) + acknowledge bit; Data packet eight data bits + acknowledge bit; STOP signal. Acknowledge bit is a ninth bit of every byte sent. Receiver always has to confirm successful receive with ACK by pulling SDA low or in case receiver cannot accept data it will leave SDA high (NACK) so master could stop transmitting and do other scenario if needed. I2C devices can work in four different modes: 1. 2. 3. 4. Master Transmitter initiates transfer sends data to slave device; Master Receiver initiates transfer reads data from slave device; Slave Transmitter waits for master request and then sends data; Slave Receiver waits for master transmission and accepts data.

It is worth mention that I2C interface supports multi-master transmission. It doesnt mean that all master are able to transmit data at same time. As matter of fact each master must wait for current transmission to be finished and then can initiate transfer. It may be situation when multiple masters tries to initiate transfer. In this case so called arbitration happens where each transmitter check level of bus signal and compares it with expected. If master loses arbitration it must leave bus immediately and/or switch to slave mode.

Bursting multiple bytes


I2C can send and receive multiple bytes inside single packet. This is handy for instance to write or read memory. For instance we need to read 3 bytes from EEPROM memory address 0x0F. Say that EEPROM slave address is 01111000. This is how whole reading process would look:

Note that first master has to write in order to select initial memory address. Then send start signal again in order to initiate master read mode and then after reading of all bytes is done free line by sending stop signal.

AVR I2C registers


AVR microcontroller is using TWI (Two Wire Interface) nomenclature when talking about I2C. So all

registers are named as TWI. First important register is bit rate register TWBR. It is used to scale down CPU frequency in to SCL. Additionally there are two bits (TWPS1 and TWPS2) in status register TWSR to prescale SCL frequency with values 1, 4, 16 and 64. You can find formula in datasheet that is used to calculate SCL end frequency:

As usually there is control register TWCR which has a set of bits that are used to enable TWI interrupt, TWI enable, Start, Stop. Status register TWSR holds earlier mentioned prescaller bits but its main purpose to sense I2C bus status with TWS[7:3] bits. TWDR is data register which is used to hold next byte to transmit or received byte. TWAR and TWARM register are used when AVR works as I2C slave.

Example of using I2C in AVR


As example we are going to interface old good 24C16 I2C EEPROM chip to Atmega328P.

As you can see connection is simple only SDA and SCL lines has to be connected. For different EEPROM capacities you may need to connect A0, A1 and A2 pins to GND or pull high in order to set device address. 24C16 doesnt use these pins for addressing chip. We leave them open. For demonstration I am using Arduino328P board which is used as general AVR test board.

You can use any other AVR development board to test this example. If you have Arduino board laying arround I suggest not to clear original bootloader by writing hex with some ISP adapter, but use built in bootloader to upload hex. Download http://russemotto.com/xloader/ program that communicates to bootloader so as from Arduino IDE:

Hardware is really simple lets head to software writing. For debugging purposes USART is used which was discussed in earlier tutorials. So we are gonna set it to 9600 baud and use as library usart.h. In order to have nice control of I2C interface lets split whole process in to multiple functions. First of all we need to initialize TWI (I2C): 1 2 3 4 5 v o i dT W I I n i t ( v o i d ) { / / s e tS C Lt o4 0 0 k H z T W S R=0 x 0 0 ; T W B R=0 x 0 C ;

6 7 8

/ / e n a b l eT W I T W C R=( 1 < < T W E N ) ;

so we set bit rate register to 0x0C value which sets SCL to 400kHz. We dont need any additional prescallers so set TWSR to 0. And finally we simply enable TWI by setting TWEN bit to 1. Next we take care of TWIStart and TWIStop functions that generate start and stop signals. 1 2 3 4 5 6 7 8 9 1 0 v o i dT W I S t a r t ( v o i d ) { T W C R=( 1 < < T W I N T ) | ( 1 < < T W S T A ) | ( 1 < < T W E N ) ; w h i l e( ( T W C R&( 1 < < T W I N T ) )= =0 ) ; } / / s e n ds t o ps i g n a l v o i dT W I S t o p ( v o i d ) { T W C R=( 1 < < T W I N T ) | ( 1 < < T W S T O ) | ( 1 < < T W E N ) ; }

For start we need to set TWSTA and for stop TWSTO bits along with TWINT and TWEN bits. After start signal is sent we need to wait for status (until TWINT resets to zero). Another function is TWIWrite: 1 2 3 4 5 6 v o i dT W I W r i t e ( u i n t 8 _ tu 8 d a t a ) { T W D R=u 8 d a t a ; T W C R=( 1 < < T W I N T ) | ( 1 < < T W E N ) ; w h i l e( ( T W C R&( 1 < < T W I N T ) )= =0 ) ; }

it writes data byte to TWDR register which is shifted to SDA line. It is important to wait for transmission complete within while loop. After which status can be read from status register TWSR. Reading is done in similar way. I have wrote two functions where one transmits ACK signal after byte transfer while another doesnt: 1 2 3 4 5 6 7 8 9 1 0 1 1 1 2 1 3 u i n t 8 _ tT W I R e a d A C K ( v o i d ) { T W C R=( 1 < < T W I N T ) | ( 1 < < T W E N ) | ( 1 < < T W E A ) ; w h i l e( ( T W C R&( 1 < < T W I N T ) )= =0 ) ; r e t u r nT W D R ; } / / r e a db y t ew i t hN A C K u i n t 8 _ tT W I R e a d N A C K ( v o i d ) { T W C R=( 1 < < T W I N T ) | ( 1 < < T W E N ) ; w h i l e( ( T W C R&( 1 < < T W I N T ) )= =0 ) ; r e t u r nT W D R ; }

And finally last function we gonna use is reading status: 1 2 u i n t 8 _ tT W I G e t S t a t u s ( v o i d ) {

3 4 5 6 7

u i n t 8 _ ts t a t u s ; / / m a s ks t a t u s s t a t u s=T W S R&0 x F 8 ; r e t u r ns t a t u s ;

We need to read upper five bits from TWSR register so we simply mask out three lower bits. As we will see reading status messages is essential part in detecting failures in I2C communication. After we set up TWI functions we can use them to communicate with 24C16 EEPROM chip. This chip contains 2048 bytes of EEPROM memory in order to address all bytes 11 byte addressing is used. 24Cxx chips have four high bit fixed ID which his 0b1010 lower three bits are used for addressing chip memory. This way we avoid sending two bytes for memory addressing memory. But instead we need to split 11 bits in to fit three high bits that goes to device ID 1, 2, 3 bit locations while rest byte is sent next as normal address selection.

Having this in mind we can implement EEPROM byte write function: 1 2 3 4 5 6 7 8 9 1 0 1 1 1 2 1 3 1 4 1 5 1 6 1 7 1 8 1 9 2 0 u i n t 8 _ tE E W r i t e B y t e ( u i n t 1 6 _ tu 1 6 a d d r ,u i n t 8 _ tu 8 d a t a ) { T W I S t a r t ( ) ; i f( T W I G e t S t a t u s ( )! =0 x 0 8 ) r e t u r nE R R O R ; / / s e l e c td e v i s ea n ds e n dA 2A 1A 0a d d r e s sb i t s T W I W r i t e ( ( E E D E V A D R ) | ( u i n t 8 _ t ) ( ( u 1 6 a d d r&0 x 0 7 0 0 ) > > 7 ) ) ; i f( T W I G e t S t a t u s ( )! =0 x 1 8 ) r e t u r nE R R O R ; / / s e n dt h er e s to fa d d r e s s T W I W r i t e ( ( u i n t 8 _ t ) ( u 1 6 a d d r ) ) ; i f( T W I G e t S t a t u s ( )! =0 x 2 8 ) r e t u r nE R R O R ; / / w r i t eb y t et oe e p r o m T W I W r i t e ( u 8 d a t a ) ; i f( T W I G e t S t a t u s ( )! =0 x 2 8 ) r e t u r nE R R O R ; T W I S t o p ( ) ; r e t u r nS U C C E S S ; }

As you can see after each TWI command we check status. Status codes can be found on AVR datasheet. In case of communication failure we return ERROR. Using status codes may help to track bugs in program or detect hardware failures.

Before writing byte to memory we first start I2C communication then we write device device address combined with three high memory address bits. Lowest device bit is 0 for write. Next byte we send is 8 lower memory address bits and then finally if we get ACK by checking status (018) we send data byte. Lastly we end communication by sending Stop signal. Reading requires a bit more code: 1 2 3 4 5 6 7 8 9 1 0 1 1 1 2 1 3 1 4 1 5 1 6 1 7 1 8 1 9 2 0 2 1 2 2 2 3 2 4 2 5 2 6 2 7 2 8 u i n t 8 _ tE E R e a d B y t e ( u i n t 1 6 _ tu 1 6 a d d r ,u i n t 8 _ t* u 8 d a t a ) { / / u i n t 8 _ td a t a b y t e ; T W I S t a r t ( ) ; i f( T W I G e t S t a t u s ( )! =0 x 0 8 ) r e t u r nE R R O R ; / / s e l e c td e v i s ea n ds e n dA 2A 1A 0a d d r e s sb i t s T W I W r i t e ( ( E E D E V A D R ) | ( ( u i n t 8 _ t ) ( ( u 1 6 a d d r&0 x 0 7 0 0 ) > > 7 ) ) ) ; i f( T W I G e t S t a t u s ( )! =0 x 1 8 ) r e t u r nE R R O R ; / / s e n dt h er e s to fa d d r e s s T W I W r i t e ( ( u i n t 8 _ t ) ( u 1 6 a d d r ) ) ; i f( T W I G e t S t a t u s ( )! =0 x 2 8 ) r e t u r nE R R O R ; / / s e n ds t a r t T W I S t a r t ( ) ; i f( T W I G e t S t a t u s ( )! =0 x 1 0 ) r e t u r nE R R O R ; / / s e l e c td e v i s ea n ds e n dr e a db i t T W I W r i t e ( ( E E D E V A D R ) | ( ( u i n t 8 _ t ) ( ( u 1 6 a d d r&0 x 0 7 0 0 ) > > 7 ) ) | 1 ) ; i f( T W I G e t S t a t u s ( )! =0 x 4 0 ) r e t u r nE R R O R ; * u 8 d a t a=T W I R e a d N A C K ( ) ; i f( T W I G e t S t a t u s ( )! =0 x 5 8 ) r e t u r nE R R O R ; T W I S t o p ( ) ; r e t u r nS U C C E S S ; }

Because first we need to select memory address by writing device ID and rest of memory address as write command. Then after this we repeat START signal and then we send device address with read command (last bit set to 1). If read status is OK we can store received data in to variable. For single byte we dont need to send ACK signal just STOP. Similarly EEPROM page write and read are implemented. 24C16 is divided in to 128 pages of 16 bytes . Each page start address is located in high 7 bits of address. When writing page be sure to start from first byte of page because if page address reaches its end address rols-over and writing starts from beginning of page. This way you can overwrite existing data. Im just giving my way of page write and read implementation: 1 2 3 4 5 6 7 8 u i n t 8 _ tE E W r i t e P a g e ( u i n t 8 _ tp a g e ,u i n t 8 _ t* u 8 d a t a ) { / / c a l c u l a t ep a g ea d d r e s s u i n t 8 _ tu 8 p a d d r=0 ; u i n t 8 _ ti ; u 8 p a d d r=p a g e < < 4 ; T W I S t a r t ( ) ; i f( T W I G e t S t a t u s ( )! =0 x 0 8 )

9 1 0 1 1 1 2 1 3 1 4 1 5 1 6 1 7 1 8 1 9 2 0 2 1 2 2 2 3 2 4 2 5 2 6 2 7 2 8 2 9 3 0 3 1 3 2 3 3 3 4 3 5 3 6 3 7 3 8 3 9 4 0 4 1 4 2 4 3 4 4 4 5 4 6 4 7 4 8 4 9 5 0 5 1 5 2 5 3 5 4 5 5 5 6 5 7 5 8 5 9 6 0 6 1 6 2 6 3 6 4

} u i n t 8 _ tE E R e a d P a g e ( u i n t 8 _ tp a g e ,u i n t 8 _ t* u 8 d a t a ) { / / c a l c u l a t ep a g ea d d r e s s u i n t 8 _ tu 8 p a d d r=0 ; u i n t 8 _ ti ; u 8 p a d d r=p a g e < < 4 ; T W I S t a r t ( ) ; i f( T W I G e t S t a t u s ( )! =0 x 0 8 ) r e t u r nE R R O R ; / / s e l e c tp a g es t a r ta d d r e s sa n ds e n dA 2A 1A 0b i t ss e n dw r i t ec o m m a T W I W r i t e ( ( ( E E D E V A D R ) | ( u 8 p a d d r > > 3 ) ) & ( ~ 1 ) ) ; i f( T W I G e t S t a t u s ( )! =0 x 1 8 ) r e t u r nE R R O R ; / / s e n dt h er e s to fa d d r e s s T W I W r i t e ( ( u 8 p a d d r < < 4 ) ) ; i f( T W I G e t S t a t u s ( )! =0 x 2 8 ) r e t u r nE R R O R ; / / s e n ds t a r t T W I S t a r t ( ) ; i f( T W I G e t S t a t u s ( )! =0 x 1 0 ) r e t u r nE R R O R ; / / s e l e c td e v i s ea n ds e n dr e a db i t T W I W r i t e ( ( ( E E D E V A D R ) | ( u 8 p a d d r > > 3 ) ) | 1 ) ; i f( T W I G e t S t a t u s ( )! =0 x 4 0 ) r e t u r nE R R O R ; f o r( i = 0 ;i < 1 5 ;i + + ) { * u 8 d a t a + +=T W I R e a d A C K ( ) ; i f( T W I G e t S t a t u s ( )! =0 x 5 0 ) r e t u r nE R R O R ; } * u 8 d a t a=T W I R e a d N A C K ( ) ; i f( T W I G e t S t a t u s ( )! =0 x 5 8 ) r e t u r nE R R O R ; T W I S t o p ( ) ; r e t u r nS U C C E S S ; }

r e t u r nE R R O R ; / / s e l e c tp a g es t a r ta d d r e s sa n ds e n dA 2A 1A 0b i t ss e n dw r i t ec o m m a T W I W r i t e ( ( ( E E D E V A D R ) | ( u 8 p a d d r > > 3 ) ) & ( ~ 1 ) ) ; i f( T W I G e t S t a t u s ( )! =0 x 1 8 ) r e t u r nE R R O R ; / / s e n dt h er e s to fa d d r e s s T W I W r i t e ( ( u 8 p a d d r < < 4 ) ) ; i f( T W I G e t S t a t u s ( )! =0 x 2 8 ) r e t u r nE R R O R ; / / w r i t ep a g et oe e p r o m f o r( i = 0 ;i < 1 6 ;i + + ) { T W I W r i t e ( * u 8 d a t a + + ) ; i f( T W I G e t S t a t u s ( )! =0 x 2 8 ) r e t u r nE R R O R ; } T W I S t o p ( ) ; r e t u r nS U C C E S S ;

As you can see when receiving multiple bytes ACK must e generated after each reception. Juster after final byte ACK is not needed. In main program you can see EEPROM testing routines that shows everything is working correctly. Test routine checks single byte write to custom address location and then reading. In terminal screen you can view if written and read results are same. Also a page write test is done. It writes 16 bytes of information to page 5 and then reads them to different buffer. Then write and read buffers are compared and if both are equal a success message is displayed in terminal screen. Main program: 1 2 3 4 5 6 7 8 9 1 0 1 1 1 2 1 3 1 4 1 5 1 6 1 7 1 8 1 9 2 0 2 1 2 2 2 3 2 4 2 5 2 6 2 7 2 8 2 9 3 0 3 1 3 2 3 3 3 4 3 5 3 6 3 7 3 8 3 9 4 0 4 1 4 2 4 3 4 4 4 5 4 6 4 7

# i n c l u d e< s t d i o . h > # i n c l u d e< a v r / i o . h > # i n c l u d e< a v r / p g m s p a c e . h > # i n c l u d e" u s a r t . h " # i n c l u d e" e e 2 4 c 1 6 . h " / / s e ts t r e a mp o i n t e r F I L Eu s a r t 0 _ s t r=F D E V _ S E T U P _ S T R E A M ( U S A R T 0 S e n d B y t e ,U S A R T 0 R e c e i v e B y t e , i n tm a i n ( v o i d ) { u i n t 8 _ tu 8 e b y t e ; u i n t 8 _ tu 8 e r b y t e ; u i n t 1 6 _ tu 1 6 e a d d r e s s=0 x 0 7 F 0 ; u i n t 8 _ tp a g e=5 ; u i n t 8 _ ti ; u i n t 8 _ te e r e a d p a g e [ 1 6 ] ; u i n t 8 _ te e w r i t e p a g e [ 1 6 ]={1 0 ,4 4 ,2 5 5 ,4 6 ,8 0 ,8 7 ,4 3 ,1 3 0 , 2 1 0 ,2 3 ,1 ,5 8 ,4 6 ,1 5 0 ,1 2 ,4 6} ; / / I n i t i a l i z eU S A R T 0 U S A R T 0 I n i t ( ) ; / / T W I I n i t ( ) ; / / a s s i g no u rs t r e a mt os t a n d a r dI / Os t r e a m s s t d i n = s t d o u t = & u s a r t 0 _ s t r ; p r i n t f ( " \ n W r i t eb y t e% # 0 4 xt oe e p r o ma d d r e s s% # 0 4 x " ,0 x 5 8 ,u 1 6 e a d d r e s s ) i f( E E W r i t e B y t e ( u 1 6 e a d d r e s s ,0 x 5 8 )! =E R R O R ) { p r i n t f _ P ( P S T R ( " \ n R e a db y t eF r o me e p r o m " ) ) ; i f( E E R e a d B y t e ( u 1 6 e a d d r e s s ,& u 8 e b y t e )! =E R R O R ) { p r i n t f ( " \ n * % # 0 4 x=% # 0 4 x " ,u 1 6 e a d d r e s s ,u 8 e b y t e ) ; } e l s ep r i n t f _ P ( P S T R ( " \ n S t a t u sf a i l ! " ) ) ; } e l s ep r i n t f _ P ( P S T R ( " \ n S t a t u sf a i l ! " ) ) ; p r i n t f _ P ( P S T R ( " \ n W r i t i n g1 6b y t e st op a g e5" ) ) ; i f ( E E W r i t e P a g e ( p a g e ,e e w r i t e p a g e )! =E R R O R ) { p r i n t f _ P ( P S T R ( " \ n R e a d i n g1 6b y t e sf r o mp a g e5" ) ) ; i f( E E R e a d P a g e ( p a g e ,e e r e a d p a g e )! =E R R O R ) { / / c o m p a r es e n da n dr e a db u f f e r s f o r( i = 0 ;i < 1 6 ;i + + ) { i f( e e r e a d p a g e [ i ]! =e e w r i t e p a g e [ i ] ) {

4 8 4 9 5 0 5 1 5 2 5 3 5 4 5 5 5 6 5 7 5 8 5 9 6 0 6 1 6 2 6 3 6 4 6 5 6 6 6 7 6 8 6 9 7 0 7 1 7 2 7 3 7 4 7 5 7 6 7 7 7 8 7 9 8 0 8 1

e l s ec o n t i n u e ; } i f( i = = 1 6 ) p r i n t f _ P ( P S T R ( " \ n P a g ew r i t ea n dr e a ds u c c e s s ! " ) ) ; e l s e p r i n t f _ P ( P S T R ( " \ n P a g ew r i t ea n dr e a df a i l ! " ) ) ; }e l s ep r i n t f _ P ( P S T R ( " \ n S t a t u sf a i l ! " ) ) ; } e l s ep r i n t f _ P ( P S T R ( " \ n S t a t u sf a i l ! " ) ) ;

b r e a k ;

p r i n t f _ P ( P S T R ( " \ n C o n t i n u et e s t i n gE E P R O Mf r o mt e r m i n a l ! " ) ) ; w h i l e ( 1 ) { p r i n t f ( " \ n E n t e rE E P R O Ma d d r e s st ow r i t e( M A X=% u ) :" ,E E M A X A D D s c a n f ( " % u " , & u 1 6 e a d d r e s s ) ; p r i n t f ( " E n t e rd a t at ow r i t et oE E P R O Ma ta d d r e s s% u :" ,u 1 6 e a d d s c a n f ( " % u " , & u 8 e b y t e ) ; p r i n t f _ P ( P S T R ( " \ n W r i t i n g . . . " ) ) ; E E W r i t e B y t e ( u 1 6 e a d d r e s s ,u 8 e b y t e ) ; p r i n t f _ P ( P S T R ( " \ n T e s t i n g . . . " ) ) ; i f( E E R e a d B y t e ( u 1 6 e a d d r e s s ,& u 8 e r b y t e )! = E R R O R ) { i f( u 8 e b y t e = = u 8 e r b y t e ) p r i n t f _ P ( P S T R ( " \ n S u c c e s s ! " ) ) ; e l s e p r i n t f _ P ( P S T R ( " \ n F a i l ! " ) ) ; } e l s ep r i n t f _ P ( P S T R ( " \ n S t a t u sf a i l ! " ) ) ; } / / T O D O : :P l e a s ew r i t ey o u ra p p l i c a t i o nc o d e

You can play around by sending data bytes to custom address locations from terminal screen and test various data memory locations.

AVR Studio 5 project files for download [I2CEE.zip]. Have fun!


Share this:

You may also like AVR thermometer uses thermocouple Usually we see digital temperature sensors in microcontroller projects. They are easy to use as they connect directly to MCU using one of popular ... Tiny-USB - cheap and easy USB for small projects Small microcontrollers like Attiny45 don't have USB interface. And actually they aren't meant for such connectivity. But if you find it necessary to have ... Bus pirate hacker's must have tool Actually you don't have to be a real hacker to use this tool. Anyone involved in digital electronics may find this to be real ... Adding Nokia 1100 LCD to your microcontroller project

Vous aimerez peut-être aussi