I2C in AVR ATmega16/ATmega32

Introduction to I2C

I2C (Inter-Integrated Circuit) is a serial bus interface connection protocol. It is also called TWI (two-wire interface) since it uses only two wires for communication, that two wires called SDA (serial data) and SCL (serial clock). AVR-based ATmega16/ATmega32 has a TWI module made up of several submodules as shown in the figure.

I2C works in two modes namely,

  • Master mode
  • Slave mode
ATmega16/32 I2C Module
ATmega16/32 I2C Module

 

SDA & SCL pins

  • These pins are used to interface the TWI based external peripherals and microcontroller.
  • The output drivers contain a slew-rate limiter. The input stages contain a spike suppression unit which removes spikes shorter than 50 ns.

Bus interface unit

  • The bus interface unit contains Start/Stop control which is responsible for the generation and detection of START, REPEATED START, and STOP conditions.
  • TWDR add/data shift register contain data to be transmitted and received.
  • ACK bit receives ack/nack in transmitter mode and it is generated through software in receiving mode.
  • The Spike suppression unit takes care of spikes whereas arbitration detection continuously monitors bus status and informs the control unit about it.

Address match unit

In slave mode, the Address match unit receives an incoming 7-bit address and compares with the address in TWAR (Two Wire Address Register) register to check whether it matches or not and upon match occur it intimate to control unit to take necessary action. It also considers the general call address if the TWGCE bit in TWAR is enabled.

Bit rate generator unit

The bit rate generator unit controls the SCL period in the master mode to generate SCL frequency. It is calculated by,

SCL frequency =  (CPU CLK frequency)/(16+2(TWBR)*4^TWPS )

Where TWPS is a value of a pre-scaler bit in TWSR.

Control unit

  • The Control unit contains TWSR (TWI status register), TWCR (TWI control register).
  • It controls the overall process of attention for necessary events, identifying events when occur, TWINT interrupts assertion, and update TWSR.
  • As long as the TWINT flag set SCL held low. TWINT set whenever TWI completes the current task.

Let see registers in the ATmega16/32 I2C module

TWBR: TWI Bit Rate Register

TWI bit rate register used in generating SCL frequency while operating in master mode

TWBR register

 

TWCR: TWI Control Register

TWI control resistor used to control events of all I2C communication.

TWCR register

Bit 7 – TWINT: TWI interrupt

  • This bit gets set whenever TWI completes its current event (like start, stop, transmit, receive, etc).
  • While I-bit in SREG and TWIE bit in TWCR is enabled then TWI interrupt vector called whenever TWI interrupt occur.
  • TWI interrupt flag must be cleared by software by writing a logical one to it. This bit is not automatically cleared by hardware.

Bit 6 – TWEA: TWI enable acknowledgment bit

  • This is TWI acknowledgment enable bit, it is set in receiver mode to generate acknowledgment and cleared in transmit mode.

Bit 5 – TWSTA: TWI START condition bit

  • The master device set this bit to generate START condition by monitoring free bus status to take control over the TWI bus.

Bit 4 – TWSTO: TWI STOP condition bit

  • The master device set this bit to generate STOP condition to leave control over the TWI bus.

Bit 3 – TWWC: TWI write collision

  • This bit gets set when writing to the TWDR register before the current transmission not complete i.e. TWINT is low.

Bit 2 – TWEN: TWI enable bit

  • This bit set to enables the TWI interface in the device and takes control over the I/O pins.

Bit 1 - Reserved

Bit 0 – TWIE: TWI interrupt enable

  • This bit is used to enable TWI to interrupt routine while the I-bit of SREG is set as long as the TWINT flag is high.

 

TWSR: TWI Status Register

TWSR register

Bit 7:Bit 3 - TWS7: TWS3: TWI status bits

  • TWI status bits shows the status of TWI control and bus

Bit 1:0 - TWPS1:TWPS0: TWI pre-scaler bits

  • TWI pre-scaler bits used in bit rate formula to calculate SCL frequency
TWPS1TWPS0ExponentPre-scaler value
0001
0114
10216
11364

 

TWDR: TWI Data Register

  • TWDR contains data to be transmitted or received.
  • It’s not writable while TWI is in process of shifting a byte.
  • The data remains stable as long as TWINT is set.
TWDR register

 

TWAR: TWI Address Register

  • TWAR register contains the address of the TWI unit in slave mode.
  • It is mostly used in the multi-master system.
TWAR register

Bit 7:1 - TWA6: TWA0: TWI address bits

  • TWI address bits contain TWI 7-bit address with which it can called by other masters in slave mode.

Bit 0 – TWGCE: TWI general call enable bit

  • TWI general call enable bit when set it enables recognition of general call over the TWI bus

 

There are four transmission modes in I2C in which the I2C device works.

  • When the device is Master it works in MT and MR transmission modes.
  • And when the device is Slave it works in ST and SR transmission modes.
SR No.Transmission modeOperation
1Master Transmitter (MT)Master device writes data to SDA.
2Master Receiver (MR)Master device read data from SDA.
3Slave Transmitter (ST)Slave device writes data to SDA.
4Slave Receiver (SR)Slave device read data from SDA.

 

Let see how to program I2C in Master and Slave mode.

Programming ATmega16 I2C in Master mode

To program AVR ATmega16 I2C in master mode first need to initialize the I2C module with SCL frequency and then need to perform read and write operation using START, REPEATED START, STOP events. Let first initialize the TWI module in ATmega16.

I2C Initialization

Here to initializeATmega16 TWI in the master mode, we need to set SCL clock frequency by setting values in the TWBR register and TWPS bits in the TWSR register.

TWBR value is defined by the above SCL frequency formula. e.g.

/* Define bit rate */
#define BITRATE(TWSR)	((F_CPU/SCL_CLK)-16)/(2*pow(4,(TWSR&((1<<TWPS0)|(1<<TWPS1)))))

Now load this value in the TWBR register

void I2C_Init()			/* I2C initialize function */
{
    TWBR = BITRATE(TWSR=0x00);	/* Get bit rate register value by formula */
}

Now let see I2C events

I2C Events

As AVR I2C is byte-oriented and interrupt based i.e. interrupts issued after every bus event completion. Events like Start, Write, Read, Stop. Also status gets updated after every bus event. So let’s see about I2C events and conditions with their function codes.

I2C Events in Master mode

START (S)

  • START condition issued by master.
  • It is generated by High to Low transition on SDA while SCL is High.
  • While the bus is a free to master device issue the START condition with the slave device address (SLA) to initiate transmission.

I2C_Start function

This function initiate the START condition

Input argument: - it has the input argument of slave device write address (SLA+W).

Return: - it returns the status of the event.

uint8_t I2C_Start(charwrite_address)/* I2C start function */
{
    uint8_t status;		/* Declare variable */
    TWCR=(1<<TWSTA)|(1<<TWEN)|(1<<TWINT); /* Enable TWI, generate START */
    while(!(TWCR&(1<<TWINT)));	/* Wait until TWI finish its current job */
    status=TWSR&0xF8;		/* Read TWI status register */
    if(status!=0x08)		/* Check weather START transmitted or not? */
    return 0;			/* Return 0 to indicate start condition fail */
    TWDR=write_address;		/* Write SLA+W in TWI data register */
    TWCR=(1<<TWEN)|(1<<TWINT);	/* Enable TWI & clear interrupt flag */
    while(!(TWCR&(1<<TWINT)));	/* Wait until TWI finish its current job */
    status=TWSR&0xF8;		/* Read TWI status register */	
    if(status==0x18)		/* Check for SLA+W transmitted &ack received */
    return 1;			/* Return 1 to indicate ack received */
    if(status==0x20)		/* Check for SLA+W transmitted &nack received */
    return 2;			/* Return 2 to indicate nack received */
    else
    return3;			/* Else return 3 to indicate SLA+W failed */
}

REPEATED START (Sr)

  • REPEATED START condition issued by the master.
  • It is generated by issuing another one START condition to initiate a new transmission.
  • REPEATED START condition with slave device address (SLA) is issued in between START and STOP condition

I2C_Repeated_Start function

This function generates REPEATED START condition for reading operation.

Input argument: - it has the input argument of slave device read address (SLA+R).

Return: - it returns the status of the event.

uint8_t I2C_Repeated_Start(charread_address) /* I2C repeated start function */
{
    uint8_t status;		/* Declare variable */
    TWCR=(1<<TWSTA)|(1<<TWEN)|(1<<TWINT);/* Enable TWI, generate start */
    while(!(TWCR&(1<<TWINT)));	/* Wait until TWI finish its current job */
    status=TWSR&0xF8;		/* Read TWI status register */
    if(status!=0x10)		/* Check for repeated start transmitted */
    return 0;			/* Return 0 for repeated start condition fail */
    TWDR=read_address;		/* Write SLA+R in TWI data register */
    TWCR=(1<<TWEN)|(1<<TWINT);	/* Enable TWI and clear interrupt flag */
    while(!(TWCR&(1<<TWINT)));	/* Wait until TWI finish its current job */
    status=TWSR&0xF8;		/* Read TWI status register */
    if(status==0x40)		/* Check for SLA+R transmitted &ack received */
    return 1;			/* Return 1 to indicate ack received */
    if(status==0x48)		/* Check for SLA+R transmitted &nack received */
    return 2;			/* Return 2 to indicate nack received */
    else
    return 3;			/* Else return 3 to indicate SLA+W failed */
}

WRITE (W)

  • WRITE data/address event is issued by the master after successful acknowledgment of START received from the slave device.

I2C_Write function

This function writes data/address on the bus

Input argument: - it has an input argument of data/address.

Return: - it returns the status of the event.

uint8_t I2C_Write(chardata)	/* I2C write function */
{
    uint8_tstatus;		/* Declare variable */
    TWDR=data;			/* Copy data in TWI data register */
    TWCR=(1<<TWEN)|(1<<TWINT);	/* Enable TWI and clear interrupt flag */
    while(!(TWCR&(1<<TWINT)));	/* Wait until TWI finish its current job */
    status=TWSR&0xF8;		/* Read TWI status register */
    if(status==0x28)		/* Check for data transmitted &ack received */
    return 0;			/* Return 0 to indicate ack received */
    if(status==0x30)		/* Check for data transmitted &nack received */
    return 1;			/* Return 1 to indicate nack received */
    else
    return 2;			/* Else return 2 for data transmission failure */
}

READ (R)

  • Read data event is issued by the master after successful REPEATED START condition.

I2C_Read_Ack function

This function read data available on the SDA line and returns its acknowledgment to the slave device about data read successful and also tells slave to transmit another data.

Input argument: - it has no input argument.

Return                 : - it returnsreceived data.

char I2C_Read_Ack()		/* I2C read ack function */
{
    TWCR=(1<<TWEN)|(1<<TWINT)|(1<<TWEA); /* Enable TWI, generation of ack */
    while(!(TWCR&(1<<TWINT)));	/* Wait until TWI finish its current job */
    returnTWDR;			/* Return received data */
}

I2C_Read_Nack function

This function read last needed data byte available on the SDA line but does not returns an acknowledgment of it. It used to indicate slave that master don’t want next data and want to stop communication.

Input argument : - it has no input argument.

Return                 : - it returns received data.

char I2C_Read_Nack()		/* I2C read nack function */
{
    TWCR=(1<<TWEN)|(1<<TWINT);	/* Enable TWI and clear interrupt flag */
    while(!(TWCR&(1<<TWINT)));	/* Wait until TWI finish its current job */
    return TWDR;		/* Return received data */
}

STOP (P)

  • STOP event issued by the master to indicate it has to stop.
  • It is generated by Low to High transition on SDA while SCL is High.

I2C_Stop function

This function initiates the STOP condition

Input argument: - it has no input argument.

Return: - it does not return any data type.

void I2C_Stop()			/* I2C stop function */
{
    TWCR=(1<<TWSTO)|(1<<TWINT)|(1<<TWEN);/* Enable TWI, generate stop */
    while(TWCR&(1<<TWSTO));	/* Wait until stop condition execution */
}

Example

Let’s take example for first Master Transmitter (MT)and Master Receiver (MR) mode. Consider ATmega16 as Master and EEPROM memory as Slave, we can write data to EEPROM in Master Transmitter (MT) mode and read the same from EEPROM in Master Receiver (MR) mode.

  • Here ATmega16 is the master device and EEPROM is a slave device

Programming steps for Write operation:

  1. Initialize I2C.
  2. Generate START condition.
  3. Send the Slave device to write address (SLA+W) and check for acknowledgment.
  4. Write memory location addresses for memory devices to which we want to write.
  5. Write data till the last byte.
  6. Generate a STOP condition.

Programming steps for reading operation:

  1. Initialize I2C.
  2. Generate START condition.
  3. Write device Write address (SLA+W) and check for acknowledgment.
  4. Write a memory location address for memory devices.
  5. Generate REPEATED START condition.
  6. Read data and return acknowledgment.
  7. Return Not acknowledgment for the last byte.
  8. Generate a STOP condition.

ATmega16/32 I2C Master Program

/*
 * ATmega16_Master_I2C.c
 * http://www.electronicwings.com
 */ 


#define F_CPU 8000000UL		/* Define CPU clock Frequency 8MHz */
#include <avr/io.h>		/* Include AVR std. library file */
#include <util/delay.h>		/* Include Delay header file */
#include <string.h>		/* Include string header file */
#include "I2C_Master_H_file.h"	/* Include I2C header file */
#include "LCD_16x2_H_file.h"	/* Include LCD header file */

#define EEPROM_Write_Addess		0xA0
#define EEPROM_Read_Addess		0xA1

int main(void)
{
    char array[10] = "test";	/* Declare array to be print */
    LCD_Init();			/* Initialize LCD */
    I2C_Init();			/* Initialize I2C */
    I2C_Start(EEPROM_Write_Addess);/* Start I2C with device write address */
    I2C_Write(0x00);		/* Write start memory address for data write */
    for (int i = 0; i<strlen(array); i++)/* Write array data */
	{
		I2C_Write(array[i]); 
	}
    I2C_Stop();			/* Stop I2C */
    _delay_ms(10);
    I2C_Start(EEPROM_Write_Addess);/* Start I2C with device write address */
    I2C_Write(0x00);		/* Write start memory address */
    I2C_Repeated_Start(EEPROM_Read_Addess);/* Repeat start I2C SLA+R */
    for (int i = 0; i<strlen(array); i++)/* Read data with acknowledgment */
	{
		LCD_Char(I2C_Read_Ack());
	}
    I2C_Read_Nack();		/* Read flush data with nack */
    I2C_Stop();			/* Stop I2C */
    return 0;
}

Programming ATmega16 I2C in Slave Mode

To program AVR basedATmega16 I2C in slave mode we need to initialize TWI module. In slave mode device can not generate START, REPEATED START or STOP condition. A slave device need to always listen to the TWI bus to be get addressed by any Master device or general call. Let initialize the TWI module in slave mode.

I2C Slave initialization function

Here to initialize ATmega16 in slave mode we need to assign a 7-bit device address in the TWAR register. After assigning the address, we need to enable TWI and acknowledgment bit in TWCR. And clear TWI interrupts the flag by writing logic 1 to it.

Input argument: - it has an input argument of the slave address.

Return: - it does not return any data type.

void I2C_Slave_Init(uint8_tslave_address)
{
    TWAR=slave_address;		/* Assign Address in TWI address register */
    TWCR=(1<<TWEN)|(1<<TWEA)|(1<<TWINT);/* Enable TWI, Enable ack generation */
}

Now let see events occurred in slave mode,

Listen to bus

  • The slave device always needs to listen to the TWI bus to check whether it gets addressed by any Master.
  • When it is addressed, TWINT bit get set. So need to monitor TWINT bit.

I2C_Slave_Listen function

Input argument : - it has no any input argument.

Return                 : - it returns event status.

int8_t I2C_Slave_Listen()
{
    while(1)
     {
	uint8_t status;			/* Declare variable */
	while(!(TWCR&(1<<TWINT)));	/* Wait to be addressed */
	status=TWSR&0xF8;		/* Read TWI status register */
	if(status==0x60||status==0x68)	/* Own SLA+W received &ack returned */
	return 0;			/* Return 0 to indicate ack returned */
	if(status==0xA8||status==0xB0)	/* Own SLA+R received &ack returned */
	return 1;			/* Return 0 to indicate ack returned */
	if(status==0x70||status==0x78)	/* General call received &ack returned */
	return 2;			/* Return 1 to indicate ack returned */
	else
	continue;			/* Else continue */
     }
}

Transmit data

  • After getting addressed by the master with the SLA+R address, then the slave must send the requested data.
  • Data to be sent need to write in the TWDR register.
  • After data write, enable TWI with acknowledgment and clear interrupt flag.
  • If acknowledgement not received frommaster,then slave device will clear TWINT flag and again listen to bus.
  • Also if REPEATED START/STOP received,then slave device will clear TWINT flag and again listen to bus.

I2C_Slave_Transmit function

Input argument: - it has an input argument of character data to be sent.

Return                 : - it returns event status.

int8_t I2C_Slave_Transmit(chardata)
{
    uint8_t status;
    TWDR=data;			/* Write data to TWDR to be transmitted */
    TWCR=(1<<TWEN)|(1<<TWINT)|(1<<TWEA);/* Enable TWI & clear interrupt flag */
    while(!(TWCR&(1<<TWINT)));	/* Wait until TWI finish its current job */
    status=TWSR&0xF8;		/* Read TWI status register */
    if(status==0xA0)		/* Check for STOP/REPEATED START received */
     {
	TWCR|=(1<<TWINT);	/* Clear interrupt flag & return -1 */
	return -1;
     }
    if(status==0xB8)		/* Check for data transmitted &ack received */
    return0;			/* If yes then return 0 */
    if(status==0xC0)		/* Check for data transmitted &nack received */
     {
	TWCR|=(1<<TWINT);	/* Clear interrupt flag & return -2 */
	return -2;
     }
    if(status==0xC8)		/* Last byte transmitted with ack received */
    return -3;			/* If yes then return -3 */
    else			/* else return -4 */
    return -4;
}

Receive data

  • After getting addressed by the master with the SLA+W address, then the slave needs to receive data sent by the master.
  • After each byte received, slaves need to return acknowledge about it to the master.
  • If REPEATED START/STOP received, then the slave device will clear the TWINT flag and again listen to the bus.

I2C_Slave_Recieve function

Input argument : - it has no input argument.

Return: - it returns received data or event status.

char I2C_Slave_Receive()
{
    uint8_tstatus;		/* Declare variable */
    TWCR=(1<<TWEN)|(1<<TWEA)|(1<<TWINT);/* Enable TWI & generation of ack */
    while(!(TWCR&(1<<TWINT)));	/* Wait until TWI finish its current job */
    status=TWSR&0xF8;		/* Read TWI status register */
    if(status==0x80||status==0x90)/* Check for data received &ack returned */
    return TWDR;		/* If yes then return received data */

/* Check for data received, nack returned & switched to not addressed slave mode */
    if(status==0x88||status==0x98)
    return TWDR;		/* If yes then return received data */
    if(status==0xA0)		/* Check wether STOP/REPEATED START */
     {
	TWCR|=(1<<TWINT);	/* Clear interrupt flag & return -1 */
	return -1;
     }
    else
    return -2;			/* Else return -2 */
}

 

Example

Let take an example, here ATmega16 will act as the Master device, and ATmega32 act as a Slave device. First Master device will send count to Slave device and then same master will read from slave device.

 

Atmega16/32 I2C Interfacing Diagram

ATmega I2C Master Slave Communication
ATmega16/32 I2C Master Slave Communication

 

Programming steps in the master device

  1. Initialize I2C.
  2. Generate START condition.
  3. Write device Write address (SLA+W) and check for acknowledgement.
  4. After acknowledgement write data to slave device.
  5. Generate REPEATED START condition with SLA+R.
  6. Receive data from slave device.

Programming steps in slave device

  1. Initialize I2C with slave device address.
  2. Listen to bus for get addressed by master.
  3. While addressed with SLA+W by master device, receive data from master device.
  4. Return acknowledgement after each byte received.
  5. Clear interrupt flag after REPEATED START/STOP received.
  6. Print received data on LCD.
  7. Again listen to bus for get addressed by master.
  8. While addressed with SLA+R by master device, transmit data to master device.
  9. Transmit data till NACK/REPEATED START/STOP receive from master.
  10. Clear interrupt flag after NACK/REPEATED START/STOP received.

ATmega16/32 I2C master program

/*
 * ATmega16_Master.c
 * http://www.electronicwings.com
 *
 */ 

#define F_CPU 8000000UL		/* Define CPU clock Frequency 8MHz */
#include <avr/io.h>		/* Include AVR std. library file */
#include <util/delay.h>		/* Include inbuilt defined Delay header file */
#include <stdio.h>		/* Include standard I/O header file */
#include <string.h>		/* Include string header file */
#include "I2C_Master_H_file.h"	/* Include I2C header file */
#include "LCD_16x2_H_file.h"	/* Include LCD header file */

#define Slave_Write_Address		0x20
#define Slave_Read_Address		0x21
#define	count				10

int main()
{
    char buffer[10];
	
    LCD_Init();			/* Initialize LCD */
    I2C_Init();			/* Initialize I2C */
	
    LCD_String_xy(1, 0, "Master Device");
	
    while (1)
     {
	LCD_String_xy(2, 0, "Sending :       ");
	I2C_Start_Wait(Slave_Write_Address);/* Start I2C with SLA+W */
	_delay_ms(5);
	for (uint8_t i = 0; i < count ; i++)
	 {
	    sprintf(buffer, "%d    ",i);
	    LCD_String_xy(2, 13, buffer);
	    I2C_Write(i);	/* Send Incrementing count */
	    _delay_ms(500);
	 }
	LCD_String_xy(2, 0, "Receiving :       ");
	I2C_Repeated_Start(Slave_Read_Address);/* Repeated Start with SLA+R */
	_delay_ms(5);
	for (uint8_t i = 0; i < count; i++)
	 {
	    if(i < count - 1)
	    sprintf(buffer, "%d    ", I2C_Read_Ack());/* Read & Ack of data */
	    else
	    sprintf(buffer, "%d    ", I2C_Read_Nack());/* Read & Nack to data */
	    LCD_String_xy(2, 13, buffer);
	    _delay_ms(500);
	 }
	I2C_Stop();		/* Stop I2C */
     }
}

ATmega16/32 I2C slave program

/*
 * ATmega32_Slave.c
 * http://www.electronicwings.com
 *
 */ 


#define F_CPU 8000000UL		/* Define CPU clock Frequency 8MHz */
#include <avr/io.h>			/* Include AVR std. library file */
#include <util/delay.h>		/* Include inbuilt defined Delay header file */
#include <stdio.h>			/* Include standard I/O header file */
#include <string.h>			/* Include string header file */
#include "LCD_16x2_H_file.h"	/* Include LCD header file */
#include "I2C_Slave_H_File.h"	/* Include I2C slave header file */

#define Slave_Address		0x20

int main(void)
{
    char buffer[10];
    int8_t count = 0;
	
    LCD_Init();
    I2C_Slave_Init(Slave_Address);
	
    LCD_String_xy(1, 0, "Slave Device");
	
    while (1)
     {
	switch(I2C_Slave_Listen())				/* Check for SLA+W or SLA+R */
	 {
	    case 0:
		{
		  LCD_String_xy(2, 0, "Receiving :       ");
		  do
		  {
		    sprintf(buffer, "%d    ", count);
		    LCD_String_xy(2, 13, buffer);
		    count = I2C_Slave_Receive();	/* Receive data byte*/
		  } while (count != -1);			/* Receive until STOP/REPEATED START */
		  count = 0;
		  break;
		}
	    case 1:
		{
		  int8_t Ack_status;
		  LCD_String_xy(2, 0, "Sending :       ");
		  do
		  {
		    Ack_status = I2C_Slave_Transmit(count);/* Send data byte */
		    sprintf(buffer, "%d    ",count);
		    LCD_String_xy(2, 13, buffer);
		    count++;
		  } while (Ack_status == 0);				/* Send until Ack is receive */
		  break;
		}
	    default:
		break;
	 }
     }
}

 

Video


Components Used

ATmega 16
ATmega 16
2
Atmega32
Atmega32
2
Breadboard
Breadboard
1
LCD16x2 Display
LCD16x2 Display
2

Downloads

ATmega16 Master I2C Project File Download
ATmega Slave I2C Project File Download
Ad