transforming your avr microcontroller to the i2c or twi slave i_o expander project _ ermicroblog

18
Home About Copyright and Disclaimer Contact Us Blog Entry Transforming your AVR Microcontroller to the I2C or TWI Slave I/O Expander Project September 27, 2009 by rwb, under Microcontroller. The I2C bus (read as I squared C) is one of the most important embedded system serial bus interface first introduced by Philips in 1980; using just two lines called SCL (serial clock) and SDA (serial data) respectively make the I2C bus is a perfect choice to provide additional I/O capabilities to your microcontroller project without changing your microcontroller type and design in order to increase the I/O port pins. Today there are plenty choices of I2C slave I/O port expander devices available on the market such as Philips PCF8574 and Microchip MCP23008 for addressable 8-bit general I/O ports which capable of handling I2C standard bus speed of 100Khz or fast speed mode of 400Khz; the Microchip MCP23008 even can handle up to 1.7Mhz bus speed. You could read more information about using I2C interface on my previous posted blog How to use I2C-bus on the Atmel AVR Microcontroller. The usage of I2C slave devices is not merely for the I/O expander but it also use to expand your microcontroller capabilities as well as it’s functionalities such as the I2C real time clock from Maxim DS1307 ( Using Maxim DS1307 Real Time Clock with Atmel AVR Microcontroller), Microchip TC1321 10-bit digital to analog converter (DAC), Microchip MCP2331 12-bit analog to digital converter (ADC), Microchip MCP9801 high accuracy temperature sensor and Microchip 24AA128 16KB EEPROM. Although there are many types of sophisticated I2C slave devices available on the market, sometimes we Search This Site Custom Search Future Post Controlling the Motor is one of interesting topics in the embedded world especially for the robotics enthusiasts, on the next post we will learn the basic of motor electronic circuit as well as how to control it with microcontroller. Therefore don't miss it, stay tune on this blog ! ermicroblog Microcontrollers and Electronics Project Blog Transforming your AVR Microcontroller to the I2C or TWI Slave I/O E... file:///D:/Documents/SKRIPSI MAHMUDDIN - BATAN/www.ermicro... 1 of 18 10/30/2012 8:41 AM

Upload: -

Post on 07-Aug-2015

383 views

Category:

Documents


14 download

DESCRIPTION

Transforming your AVR Microcontroller to the I2C or TWI Slave I_O Expander Project _ ermicroblog

TRANSCRIPT

Home About Copyright and Disclaimer Contact Us

Blog Entry

Transforming your AVR Microcontroller to the I2C or TWI Slave I/O

Expander ProjectSeptember 27, 2009 by rwb, under Microcontroller.

The I2C bus (read as I squared C) is one of the most important embedded system serial bus interface

first introduced by Philips in 1980; using just two lines called SCL (serial clock) and SDA (serial data)

respectively make the I2C bus is a perfect choice to provide additional I/O capabilities to your

microcontroller project without changing your microcontroller type and design in order to increase the

I/O port pins.

Today there are plenty choices of I2C slave I/O port expander devices available on the market such as

Philips PCF8574 and Microchip MCP23008 for addressable 8-bit general I/O ports which capable of

handling I2C standard bus speed of 100Khz or fast speed mode of 400Khz; the Microchip MCP23008

even can handle up to 1.7Mhz bus speed. You could read more information about using I2C interface on

my previous posted blog How to use I2C-bus on the Atmel AVR Microcontroller.

The usage of I2C slave devices is not merely for the I/O expander but it also use to expand your

microcontroller capabilities as well as it’s functionalities such as the I2C real time clock from Maxim

DS1307 (Using Maxim DS1307 Real Time Clock with Atmel AVR Microcontroller), Microchip TC1321

10-bit digital to analog converter (DAC), Microchip MCP2331 12-bit analog to digital converter (ADC),

Microchip MCP9801 high accuracy temperature sensor and Microchip 24AA128 16KB EEPROM.

Although there are many types of sophisticated I2C slave devices available on the market, sometimes we

Search This Site

Custom Search

Future Post

Controlling the Motor is one ofinteresting topics in the embeddedworld especially for the roboticsenthusiasts, on the next post we willlearn the basic of motor electroniccircuit as well as how to control itwith microcontroller.

Therefore don't miss it, stay tune on

this blog !

ermicroblogMicrocontrollers and Electronics Project Blog

Transforming your AVR Microcontroller to the I2C or TWI Slave I/O E... file:///D:/Documents/SKRIPSI MAHMUDDIN - BATAN/www.ermicro...

1 of 18 10/30/2012 8:41 AM

need the I2C slave device which has the capabilities beyond that, something more powerful, more

flexible and yet easy to be configured; the answer to this requirement is to use the microcontroller it self

as the I2C slave device, that is why most of the midrange class microcontrollers have the I2C slave

peripheral feature build inside it. This is what we are going to learn on this tutorial, where the principal

we learn here could be applied to other microcontroller type as well.

The following is the list of hardware and software used in this tutorial:

AVRJazz Mega328 board from ermicro as the I2C master controller which base on the AVR

ATmega328P microcontroller (board schema).

JazzMate MCP23008 board from ermicro which base on the Microchip MCP23008 8-bit I2C I/O

expander.

AVRJazz Mega168 board from ermicro as the I2C slave device which base on the AVR ATmega168

microcontroller (board schema).

WinAVR for the GNU’s C compiler

Atmel AVR Studio 4 for the coding and debugging environment.

STK500 programmer from AVR Studio 4, using the AVRJazz Mega328 and AVRJazz Mega168

boards STK500 v2.0 bootloader facility.

The MCP23008 8-bit I/O Expander I2C slave

The Microchip MCP23008 will be a perfect model for our I2C slave device as it has the 3-bit configurable

address (000 to 111) which provides up to 7 devices that could be attached to the I2C bus which give

total of 56 ports. MCP23008 also come with 11 internal register to control its operation

Subscribe

Posts | Comments

Recommended Books

Teach Yourself Electricityand Elect...

Stan GibiliscoBest Price $4.84

or Buy New $23.07

Privacy Information

Transforming your AVR Microcontroller to the I2C or TWI Slave I/O E... file:///D:/Documents/SKRIPSI MAHMUDDIN - BATAN/www.ermicro...

2 of 18 10/30/2012 8:41 AM

As the I2C slave device, mean the MCP23008 will be the passive device that depends on the I2C master

device to initiate the communication. On this tutorial I will use the AVRJazz Mega328 learning board as

the I2C master controller; the following is the C code for the I2C master controller.

//***************************************************************************// File Name : i2cmaster.c// Version : 1.0// Description : AVR I2C Bus Master// Author : RWB// Target : AVRJazz Mega328 Board// Compiler : AVR-GCC 4.3.0; avr-libc 1.6.2 (WinAVR 20090313)// IDE : Atmel AVR Studio 4.17// Programmer : AVRJazz Mega328 STK500 v2.0 Bootloader// : AVR Visual Studio 4.17, STK500 programmer// Last Updated : 12 September 2009//***************************************************************************#include <avr/io.h>#include <util/delay.h>#include <compat/twi.h>

#define MAX_TRIES 50

#define MCP23008_ID 0x40 // MCP23008 Device Identifier#define MCP23008_ADDR 0x0E // MCP23008 Device Address#define IODIR 0x00 // MCP23008 I/O Direction Register#define GPIO 0x09 // MCP23008 General Purpose I/O Register#define OLAT 0x0A // MCP23008 Output Latch Register

#define I2C_START 0#define I2C_DATA 1#define I2C_DATA_ACK 2#define I2C_STOP 3#define ACK 1#define NACK 0

#define DATASIZE 32

/* START I2C Routine */unsigned char i2c_transmit(unsigned char type) { switch(type) { case I2C_START: // Send Start Condition TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN); break; case I2C_DATA: // Send Data with No-Acknowledge TWCR = (1 << TWINT) | (1 << TWEN); break; case I2C_DATA_ACK: // Send Data with Acknowledge TWCR = (1 << TWEA) | (1 << TWINT) | (1 << TWEN); break; case I2C_STOP: // Send Stop Condition

TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);return 0;

}

// Wait for TWINT flag set on Register TWCR while (!(TWCR & (1 << TWINT)));

// Return TWI Status Register, mask the prescaler bits (TWPS1,TWPS0) return (TWSR & 0xF8);}

char i2c_start(unsigned int dev_id, unsigned int dev_addr, unsigned char rw_type){ unsigned char n = 0; unsigned char twi_status;

Embedded CProgramming and the

Atmel...Richard H. Barnett...

Best Price $74.68or Buy New $121.27

Privacy Information

The PIC MicrocontrolleJohn Morton

Best Price $16.26or Buy New $30.23

Privacy Information

Feature Product

PICJazz 20PIN Board

Categories

Blogroll

ermicro shop

ermicroblog Amazon Store

ermicroblog on YouTube

ermicroblog video on Metacafe

Archives

Transforming your AVR Microcontroller to the I2C or TWI Slave I/O E... file:///D:/Documents/SKRIPSI MAHMUDDIN - BATAN/www.ermicro...

3 of 18 10/30/2012 8:41 AM

char r_val = -1;

i2c_retry: if (n++ >= MAX_TRIES) return r_val;

// Transmit Start Condition twi_status=i2c_transmit(I2C_START);

// Check the TWI Status if (twi_status == TW_MT_ARB_LOST) goto i2c_retry; if ((twi_status != TW_START) && (twi_status != TW_REP_START)) goto i2c_quit;

// Send slave address (SLA_W) TWDR = (dev_id & 0xF0) | (dev_addr & 0x0E) | rw_type;

// Transmit I2C Data twi_status=i2c_transmit(I2C_DATA);

// Check the TWSR status if ((twi_status == TW_MT_SLA_NACK) || (twi_status == TW_MT_ARB_LOST)) goto i2c_retry; if (twi_status != TW_MT_SLA_ACK) goto i2c_quit;

r_val=0;

i2c_quit: return r_val;}

void i2c_stop(void){ unsigned char twi_status;

// Transmit I2C Data twi_status=i2c_transmit(I2C_STOP);}

char i2c_write(char data){ unsigned char twi_status; char r_val = -1;

// Send the Data to I2C Bus TWDR = data;

// Transmit I2C Data twi_status=i2c_transmit(I2C_DATA);

// Check the TWSR status if (twi_status != TW_MT_DATA_ACK) goto i2c_quit;

r_val=0;

i2c_quit: return r_val;}

char i2c_read(char *data,char ack_type){ unsigned char twi_status; char r_val = -1;

if (ack_type) { // Read I2C Data and Send Acknowledge twi_status=i2c_transmit(I2C_DATA_ACK);

if (twi_status != TW_MR_DATA_ACK) goto i2c_quit; } else { // Read I2C Data and Send No Acknowledge twi_status=i2c_transmit(I2C_DATA);

if (twi_status != TW_MR_DATA_NACK) goto i2c_quit; }

// Get the Data *data=TWDR; r_val=0;

i2c_quit: return r_val;}

void Write_MCP23008(unsigned char reg_addr,unsigned char data){ // Start the I2C Write Transmission i2c_start(MCP23008_ID,MCP23008_ADDR,TW_WRITE);

// Sending the Register Address i2c_write(reg_addr);

Transforming your AVR Microcontroller to the I2C or TWI Slave I/O E... file:///D:/Documents/SKRIPSI MAHMUDDIN - BATAN/www.ermicro...

4 of 18 10/30/2012 8:41 AM

// Write data to MCP23008 Register i2c_write(data);

// Stop I2C Transmission i2c_stop();}

unsigned char Read_MCP23008(unsigned char reg_addr){ char data;

// Start the I2C Write Transmission i2c_start(MCP23008_ID,MCP23008_ADDR,TW_WRITE);

// Read data from MCP23008 Register Address i2c_write(reg_addr);

// Stop I2C Transmission i2c_stop();

// Re-Start the I2C Read Transmission i2c_start(MCP23008_ID,MCP23008_ADDR,TW_READ);

i2c_read(&data,NACK);

// Stop I2C Transmission i2c_stop();

return data;}

void i2c_init(void){ // Initial ATMega328P TWI/I2C Peripheral TWSR = 0x00; // Select Prescaler of 1

// SCL frequency = 11059200 / (16 + 2 * 48 * 1) = 98.743 kHz TWBR = 0x30; // 48 Decimal}

int main(void){ unsigned char ptr,data; unsigned int iDelay;

char led_pattern[DATASIZE]= {0b00000001,

0b00000011, 0b00000110, 0b00001100, 0b00011001, 0b00110011, 0b01100110, 0b11001100, 0b10011000, 0b00110000, 0b01100000, 0b11000000, 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b10000000, 0b11000000, 0b01100000, 0b00110000, 0b10011000, 0b11001100, 0b01100110, 0b00110011, 0b00011001, 0b00001100, 0b00000110, 0b00000011, 0b00000001, 0b00000000, 0b00000000, 0b00000000};

DDRD=0xFF; // Set PORTD as Output PORTD=0x00; // Set All PORTD to Low

// Set ADCSRA Register on ATMega328 ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1);

// Set ADMUX Register on ATMega328 ADMUX = 0x00; // Use Left Justified, Select Channel 0

// Initial Master I2C i2c_init();

// Initial the MCP23008 GP0 to GP7 as Output

Transforming your AVR Microcontroller to the I2C or TWI Slave I/O E... file:///D:/Documents/SKRIPSI MAHMUDDIN - BATAN/www.ermicro...

5 of 18 10/30/2012 8:41 AM

Write_MCP23008(IODIR,0b00000000); Write_MCP23008(GPIO,0b00000000); // Reset all the Output Port

// Loop Forever for (;;) { for(ptr=0;ptr < DATASIZE;ptr++) { // Start conversion by setting ADSC on ADCSRA Register ADCSRA |= (1<<ADSC);

// wait until conversion complete ADSC=0 -> Complete while (ADCSRA & (1<<ADSC));

// Get ADC the Result iDelay = ADCW;

// Write to MCP23008 GPIO Register Write_MCP23008(GPIO,led_pattern[ptr]);

// Read MCP23008 OLAT Register data=Read_MCP23008(OLAT);

PORTD=data; // Write data to the ATMega328 Port-D

_delay_ms(iDelay); // Give some delay here } }

return 0;}

/* EOF: i2cmaster.c */

Sending Data to the I2C Slave Device

In order to communicate with MCP23008 I2C slave device, the I2C master controller first has to send the

START condition to take control of the I2C bus and continue with the I2C slave device 7-bit target

address which consists of 4-bit MCP23008 I2C slave identification ID (0100) and 3-bit configurable

address (000 to 111) follow by 1-bit of the read (TW_READ, logical “1“) or write (TW_WRITE, logical

“0“) operation. After receiving its own 7-bit address then the I2C slave device will response to the I2C

master controller by sending the acknowledge (ACK) signal.

Transforming your AVR Microcontroller to the I2C or TWI Slave I/O E... file:///D:/Documents/SKRIPSI MAHMUDDIN - BATAN/www.ermicro...

6 of 18 10/30/2012 8:41 AM

Once the I2C master controller receive the I2C slave address acknowledge (ACK) signal, then it will start

to send the requested register address to the I2C slave device. The I2C master controller then will

continue to send the data to this register (GPIO) after the acknowledge (ACK) signal from I2C slave.

Next the I2C master controller will close the communication by sending the STOP signal to the I2C slave.

The write operation to the MCP23008 is implemented in the C function Write_MCP23008() which

accept two argument, register address and the data respectively:

void Write_MCP23008(unsigned char reg_addr,unsigned char data){ // Start the I2C Write Transmission i2c_start(MCP23008_ID,MCP23008_ADDR,TW_WRITE);

// Sending the Register Address i2c_write(reg_addr);

// Write data to MCP23008 Register i2c_write(data);

// Stop I2C Transmission i2c_stop();}

Reading Data from the I2C Slave Device

Reading data from the MCP23008 I2C slave device required two phase, the first one is to tell the I2C

slave device which register that we want to read; this required the write operation and then send the

stop signal to the I2C slave device. The second phase is the actual read process; the I2C master than

resend the START signal or known as restart signal again followed by I2C slave 7-bit address with read

Transforming your AVR Microcontroller to the I2C or TWI Slave I/O E... file:///D:/Documents/SKRIPSI MAHMUDDIN - BATAN/www.ermicro...

7 of 18 10/30/2012 8:41 AM

operation and it wait for acknowledge (ACK) signal to be replied by the I2C slave device. Once the I2C

slave response with acknowledge (ACK) signal then the I2C master will enter the master receive mode

and the I2C slave will continue to send the requested register data to the I2C master. Once the I2C

master receive the data it will reply the no acknowledge (NACK) signal to the I2C slave device which tell

the I2C slave that the read operation is finish and then the I2C master close the connection by sending

the STOP signal.

The read operation from the MCP23008 is implemented in the C function Read_MCP23008() which

accept the register address as its argument:

unsigned char Read_MCP23008(unsigned char reg_addr){ char data;

// Start the I2C Write Transmission i2c_start(MCP23008_ID,MCP23008_ADDR,TW_WRITE);

// Read data from MCP23008 Register Address i2c_write(reg_addr);

// Stop I2C Transmission i2c_stop();

// Re-Start the I2C Read Transmission i2c_start(MCP23008_ID,MCP23008_ADDR,TW_READ);

i2c_read(&data,NACK);

// Stop I2C Transmission i2c_stop();

return data;}

Inside the I2C Master C Program

The I2C master program start with I/O port initialization and continue with the ADC initialization; we use

the ATMega328P ADC peripheral to control the LED display delay. The delay value will be read from the

user trimport on the AVRJazz Mega328 board attached to the PC0 (ADC0) port of the ATMega328P

microcontroller. By adjusting this trimport we could adjust the LED display speed; for more information

about using the ADC peripheral you could read my previous posted blog Analog to Digital Converter AVR

C Programming

After initiate the TWI (Two Wire Interface) peripheral (Atmel’s implementation of the Philips I2C

trademark interface) by calling the i2c_init() function, then we initiate the MCP23008 general I/O

(GPIO) port for output. This could be done by assigning the 8-bit I/O direction register (IODIR) to

0×00. You could read more about using the Microchip MCP23008 as the I2C I/O expander in my

previous posted blog Build Your Microcontroller Based PID Control Line Follower Robot.

Transforming your AVR Microcontroller to the I2C or TWI Slave I/O E... file:///D:/Documents/SKRIPSI MAHMUDDIN - BATAN/www.ermicro...

8 of 18 10/30/2012 8:41 AM

// Initial the MCP23008 GP0 to GP7 as OutputWrite_MCP23008(IODIR,0b00000000);Write_MCP23008(GPIO,0b00000000); // Reset all the Output Port

Inside the infinite for loop, we simply read the ADC result from the user trimport; then we write the LED

pattern (led_pattern[]) to the MCP23008 GPIO register and then read the OLAT (Output Latch)

register which represent the last value on the GPIO register; this give us an example of read operation

from the MCP23008 I2C slave device. The 8-bit data read from OLAT register simply act as the output

feedback data and we pass this data back to the ATMega328P PORTD.

for(ptr=0;ptr < DATASIZE;ptr++) { // Start conversion by setting ADSC on ADCSRA Register ADCSRA |= (1<<ADSC);

// wait until conversion complete ADSC=0 -> Complete while (ADCSRA & (1<<ADSC));

// Get ADC the Result iDelay = ADCW;

// Write to MCP23008 GPIO Register Write_MCP23008(GPIO,led_pattern[ptr]);

// Read MCP23008 OLAT Register data=Read_MCP23008(OLAT);

PORTD=data; // Write data to the ATMega328P Port-D

_delay_ms(iDelay); // Give some delay here}

Compile and Download the I2C Master Code to the AVRJazz Mega328 board

Before compiling the code, we have to make sure the AVR Studio 4 configuration is set properly by

selecting menu project -> Configuration Option, the Configuration windows will appear as follow:

Make sure the Device selected is atmega328p and the Frequency use is 11059200 Hz.

After compiling and simulating our code we are ready to down load the code using the AVRJazz Mega328

bootloader facility. The bootloader program is activated by pressing the user switch and reset switch at

the same time; after releasing both switches, the 8 blue LED indicator will show that the bootloader

program is activate and ready to received command from Atmel AVR Studio 4 STK500 program.

Transforming your AVR Microcontroller to the I2C or TWI Slave I/O E... file:///D:/Documents/SKRIPSI MAHMUDDIN - BATAN/www.ermicro...

9 of 18 10/30/2012 8:41 AM

We choose the HEX file and press the Program Button to down load the code into the AVRJazz Mega328

board.

AVR ATMega168 microcontroller as the I2C Slave Device

On this last AVR I2C Slave tutorial we will transform the AVR ATMega168 to the I2C slave I/O device; as

mention above we will use the MCP23008 I2C I/O expander as our I2C slave model. By emulating the

MCP23008 chip using the AVR ATMega168 microcontroller we will have a good example of how to utilize

the AVR ATMega168 TWI (I2C) slave peripheral feature and at the same time it will serve as a learning

tools of how to program the microcontroller’s base I2C slave device.

Transforming your AVR Microcontroller to the I2C or TWI Slave I/O E... file:///D:/Documents/SKRIPSI MAHMUDDIN - BATAN/www.ermicro...

10 of 18 10/30/2012 8:41 AM

The following is the AVR Mega168 microcontroller C code partial emulation of the MCP23008 I2C I/O

Expander:

//***************************************************************************// File Name : i2cslave.c// Version : 1.0// Description : I2C Slave AVR Microcontroller Interface// MCP23008 Emulation GPIO = PORTD// Author : RWB// Target : AVRJazz Mega168 Learning Board// Compiler : AVR-GCC 4.3.2; avr-libc 1.6.2 (WinAVR 20090313)// IDE : Atmel AVR Studio 4.17// Programmer : AVRJazz Mega168 STK500 v2.0 Bootloader// : AVR Visual Studio 4.17, STK500 programmer// Last Updated : 12 September 2009//***************************************************************************#include <avr/io.h>#include <util/delay.h>#include <compat/twi.h>#include <avr/interrupt.h>

// MCP23008 8 Bit I/O Extention Simulation Address and Register Address#define MCP23008_ADDR 0x4E#define IODIR 0x00#define GPIO 0x09#define OLAT 0x0A

unsigned char regaddr; // Store the MCP23008 Requested Register Addressunsigned char regdata; // Store the MCP23008 Register Address Dataunsigned char olat_reg; // Simulate MCP23008 OLAT Register

// Simulated OLAT alternative return pattern#define MAX_PATTERN 7unsigned char led_pattern[MAX_PATTERN]= {0b11110000,0b01111000,0b00111110,0b00011111,0b00111110,0b01111000,0b11110000};

// Simulated OLAT Return Mode: 0-Same as GPIO, 1-Use an alternative LED pattern abovevolatile unsigned char olat_mode;

void i2c_slave_action(unsigned char rw_status){ static unsigned char iled=0;

// rw_status: 0-Read, 1-Write switch(regaddr) { case IODIR: if (rw_status) {

DDRD=~regdata; // Write to IODIR - DDRD } else {

regdata=DDRD; // Read from IODIR - DDRD }

break; case GPIO: if (rw_status) {

PORTD=regdata; // Write to GPIO - PORTD olat_reg=regdata;

} else { regdata=PIND; // Read from GPIO - PORTD

}

break; case OLAT: if (rw_status == 0) {

Transforming your AVR Microcontroller to the I2C or TWI Slave I/O E... file:///D:/Documents/SKRIPSI MAHMUDDIN - BATAN/www.ermicro...

11 of 18 10/30/2012 8:41 AM

// Read from Simulated OLAT Register if (olat_mode) { regdata=led_pattern[iled++]; if (iled >= MAX_PATTERN) iled = 0; } else { regdata=olat_reg; }

} }}

ISR(TWI_vect){ static unsigned char i2c_state; unsigned char twi_status;

// Disable Global Interrupt cli();

// Get TWI Status Register, mask the prescaler bits (TWPS1,TWPS0) twi_status=TWSR & 0xF8;

switch(twi_status) { case TW_SR_SLA_ACK: // 0x60: SLA+W received, ACK returned i2c_state=0; // Start I2C State for Register Address required

TWCR |= (1<<TWINT); // Clear TWINT Flag break;

case TW_SR_DATA_ACK: // 0x80: data received, ACK returned if (i2c_state == 0) { regaddr = TWDR; // Save data to the register address

i2c_state = 1; } else {

regdata = TWDR; // Save to the register data i2c_state = 2;

}

TWCR |= (1<<TWINT); // Clear TWINT Flag break;

case TW_SR_STOP: // 0xA0: stop or repeated start condition received while selected if (i2c_state == 2) {

i2c_slave_action(1); // Call Write I2C Action (rw_status = 1) i2c_state = 0; // Reset I2C State

}

TWCR |= (1<<TWINT); // Clear TWINT Flag break;

case TW_ST_SLA_ACK: // 0xA8: SLA+R received, ACK returned case TW_ST_DATA_ACK: // 0xB8: data transmitted, ACK received if (i2c_state == 1) {

i2c_slave_action(0); // Call Read I2C Action (rw_status = 0)

TWDR = regdata; // Store data in TWDR register i2c_state = 0; // Reset I2C State

}

TWCR |= (1<<TWINT); // Clear TWINT Flag break;

case TW_ST_DATA_NACK: // 0xC0: data transmitted, NACK received case TW_ST_LAST_DATA: // 0xC8: last data byte transmitted, ACK received case TW_BUS_ERROR: // 0x00: illegal start or stop condition default: TWCR |= (1<<TWINT); // Clear TWINT Flag i2c_state = 0; // Back to the Begining State }

// Enable Global Interrupt sei();}

int main(void){ unsigned char press_tm;

DDRB = 0xFE; // Set PORTB: PB0=Input, Others as Output PORTB = 0x00; DDRD = 0xFF; // Set PORTD to Output PORTD = 0x00; // Set All PORTD to Low

// Initial I2C Slave TWAR = MCP23008_ADDR & 0xFE; // Set I2C Address, Ignore I2C General Address 0x00 TWDR = 0x00; // Default Initial Value

// Start Slave Listening: Clear TWINT Flag, Enable ACK, Enable TWI, TWI Interrupt Enable TWCR = (1<<TWINT) | (1<<TWEA) | (1<<TWEN) | (1<<TWIE);

// Enable Global Interrupt sei();

Transforming your AVR Microcontroller to the I2C or TWI Slave I/O E... file:///D:/Documents/SKRIPSI MAHMUDDIN - BATAN/www.ermicro...

12 of 18 10/30/2012 8:41 AM

// Initial Variable Used olat_reg=0; regaddr=0; regdata=0; olat_mode=0; press_tm=0;

for(;;) { if (bit_is_clear(PINB, PB0)) { // if button is pressed _delay_us(100); // Wait for debouching if (bit_is_clear(PINB, PB0)) {

press_tm++;if (press_tm > 100) olat_mode ^= 1; // Toggle the olat_mode

} } } return 0;}

/* EOF: i2cslave.c */

AVR ATMega168 I2C Slave

The AVR ATMega168 has build in I2C (TWI) peripheral both as master or slave, most of the AVR

microcontroller families have these features and some of them (tiny families) shared the TWI pins (SCL

and SDA) with other serial peripheral pins such as SPI (Serial Peripheral Interface) which usually called

as the Universal Serial Interface (USI) (e.g. ATTiny2313, ATTiny24, ATTiny861).

As you’ve learned from the MCP23008 I2C I/O expander above, that every I2C slave device must have

the 7-bit address for its identification, the ATMega168 microcontroller implement this I2C slave address

identification in the TWAR (TWI Address) register (for more information please refer to the ATMega168

microcontroller datasheet).

By simply set the TWAR register to the MCP23008 identification address the I2C master controller will

recognize the ATMega168 microcontroller as the MCP23008 chip, remember we will use the same I2C

master program which drive the MCP23008 chip in this ATMega168 microcontroller I2C slave mode. The

following is the C code that initiates this register:

// MCP23008 8 Bit I/O Extension Simulation Address and Register Address#define MCP23008_ADDR 0x4E#define IODIR 0x00#define GPIO 0x09#define OLAT 0x0A......// Initial I2C SlaveTWAR = MCP23008_ADDR & 0xFE; // Set I2C Address, Ignore I2C General Address 0x00TWDR = 0x00; // Default Initial Value

Of course if you want to attach more ATMega168 microcontroller as the I2C slave on the same bus, you

have to differentiate the address for each of them, theoretical you could attached up to 128 I2C devices

on the same I2C bus. The 8-bit TWDR (TWI Data Register) register is responsible to receive and send

the data to or from the ATMega168 microcontroller I2C slave peripheral. Therefore by simply reading this

register or writing to this register we could receive or sending the data through the I2C bus.

The last two important register for the ATMega168 microcontroller I2C slave peripheral operation are

TWCR (TWI Control Register) and TWSR (TWI Status Register).

Transforming your AVR Microcontroller to the I2C or TWI Slave I/O E... file:///D:/Documents/SKRIPSI MAHMUDDIN - BATAN/www.ermicro...

13 of 18 10/30/2012 8:41 AM

To operate the ATMega168 microcontroller in I2C slave mode, first we have to enable the TWI peripheral

by setting the TWEN (TWI Enable) bit of the TWCR register to logical “1” and to enable the

acknowledge signal for the I2C master/slave handshake we set the TWEA (TWI Enable Acknowledge) bit

of the TWCR register to logical “1“.

For the efficient reason on this tutorial I implement the interrupt service routine for the I2C slave

operation, this could be done by setting the TWIE (TWI Enable Interrupt) bit of TWCR register to logical

“1” and enable the global interrupt bit (I) on the SREG register by calling the sei() macro definition, the

AVR-GCC implementation of the AVR “sei” assembly command. The following C code shows how to set

this TWCR register:

...// Start Slave Listening: Clear TWINT Flag, Enable ACK, Enable TWI, TWI Interrupt EnableTWCR = (1<<TWINT) | (1<<TWEA) | (1<<TWEN) | (1<<TWIE);

// Enable Global Interruptsei();

Every time the I2C master communicate with the I2C slave, the TWSR register will be updated with

valid status code and set the TWINT (TWI Interrupt) flag to logical “0“, the valid status read from the

TWSR register will be used by software implementation to decide the action required to complete the

I2C master/slave communication. The TWINT flag bit has to be reset by software before we start to the

next I2C master/slave handshake. The ISR(TWI_vect) and i2c_slave_action() are the C code that

implement the AVR ATMega168 microcontroller I2C slave peripheral.

Actually there is one more register left for the AVR ATMega168 microcontroller I2C slave peripheral

operation named TWAMR (TWI Address Mask Register) which is use to disable the corresponding bit in

the TWAR register when set to logical “1“. This could be used to change the I2C slave address

identification without changing the TWAR register content.

To make the program more interesting and at the same time demonstrating of how we could implement

more advance application to the microcontroller’s based I2C slave device; I decided to use the AVRJazz

Mega168 user switch attached to the ATMega168 PB0 to manipulate the simulated MCP23008 OLAT

register data in the i2c_slave_action() function.

...

...// Simulated OLAT alternative return pattern#define MAX_PATTERN 7unsigned char led_pattern[MAX_PATTERN]= {0b11110000,0b01111000,0b00111110,0b00011111,0b00111110,0b01111000,0b11110000};

// Simulated OLAT Return Mode: 0-Same as GPIO, 1-Use an alternative LED pattern abovevolatile unsigned char olat_mode;...... case GPIO: if (rw_status) { PORTD=regdata; // Write to GPIO - PORTD olat_reg=regdata; } else { regdata=PIND; // Read from GPIO - PORTD } break;

case OLAT: if (rw_status == 0) { // Read from Simulated OLAT Register if (olat_mode) {

regdata=led_pattern[iled++]; if (iled >= MAX_PATTERN)

iled = 0; } else {

regdata=olat_reg; } }...

As you notice from the C code above if the olat_mode variable equal to 1, then the MCP23008

Transforming your AVR Microcontroller to the I2C or TWI Slave I/O E... file:///D:/Documents/SKRIPSI MAHMUDDIN - BATAN/www.ermicro...

14 of 18 10/30/2012 8:41 AM

simulated OLAT register will return different LED pattern that is taken from the led_pattern[] variables.

The olat_mode variable is controlled by pressing the AVRJazz Mega168 board user switch read from the

ATMega168 PB0 port status inside the infinite for loop.

if (bit_is_clear(PINB, PB0)) { // if button is pressed _delay_us(100); // Wait for debouching if (bit_is_clear(PINB, PB0)) { press_tm++; if (press_tm > 100) olat_mode ^= 1; // Toggle the olat_mode }}

Compile and Download the I2C Master Code to the AVRJazz Mega168 board

Before compiling the code, we have to make sure the AVR Studio 4 configuration is set properly by

selecting menu project -> Configuration Option, the Configuration windows will appear as follow:

Make sure the Device selected is atmega168 and the Frequency use is 11059200 Hz.

After compiling and simulating our code we are ready to down load the code using the AVRJazz Mega168

bootloader facility. The bootloader program is activated by pressing the user switch and reset switch at

the same time; after releasing both switches, the 8 blue LED indicator will show that the bootloader

program is activate and ready to received command from Atmel AVR Studio 4 STK500 program.

Transforming your AVR Microcontroller to the I2C or TWI Slave I/O E... file:///D:/Documents/SKRIPSI MAHMUDDIN - BATAN/www.ermicro...

15 of 18 10/30/2012 8:41 AM

We choose the HEX file and press the Program Button to down load the code into the AVRJazz Mega168

board. Now you could enjoy the following video showing all the experiments we’ve done in this I2C slave

tutorial:

The Final Though

The usage of microcontroller’s based I2C slave device opening the enormous chances to build more

advance embedded system application, where you could attached multiple microcontroller and freeing

your master microcontroller (I2C master) to perform the main logic for your application, while the

co-microcontroller (I2C slave) could be programmed to do just a specific task such as advanced PWM

motor controller, multiple servo controller (robotics arm and leg), LCD controller (driving LCD display

through I2C), smart sensors and many more.

Last what I like the most is, you could mix and match the microcontroller brand and type such as Atmel

AVR families and Microchip PIC families, where you could take advantage of the features and strength of

both microcontrollers to support your embedded system application.

Bookmarks and Share

Transforming your AVR Microcontroller to the I2C or TWI Slave I/O E... file:///D:/Documents/SKRIPSI MAHMUDDIN - BATAN/www.ermicro...

16 of 18 10/30/2012 8:41 AM

02.02.10 #1

03.02.10 #2

07.07.11 #3

07.07.11 #4

Related Posts

Build Your Own Microcontroller Based PID Control Line Follower Robot (LFR) – Second Part

Using Maxim DS1307 Real Time Clock with Atmel AVR Microcontroller

How to use I2C-bus on the Atmel AVR Microcontroller

AVR Twinkle Twinkle Using PWM Project

AVR LCD Thermometer Using ADC and PWM Project

6 Responses to “Transforming your AVR Microcontroller to the I2C or TWI Slave I/O ExpanderProject”

Comment by mandomoose.

totally awesome. so much good stuff here and well explained

with examples and clear diagrams. Thank you for this

wonderful resource, and enjoyable music

Comment by rwb.

Thank you

Comment by jofre.

Thank you very much for sharing this , I think this is one of

the smaller codes I’ve seen for I2C.

There is only one typo on the master listing that makes the

I2C not work if the files are compiled as they are.

The bit manipulation on the I2CMaster listing to produce the

slave address:

// Send slave address (SLA_W)

TWDR = (dev_id & 0xF0) | ((dev_addr << 1) & 0x0E) |

rw_type;

ends up producing a 0x4C slave address , but the slave

address in the i2cSlave.c is defined:

#define MCP23008_ADDR 0x4E

Therefore the master will send the requests but the i2c

interrupt will never trigger. I’ve spent like 4 hours until I

found the cause , so I am putting it here to avoid same

situation to a future reader . I’ve just replaced the line in the

master listing by:

TWDR = 0x4E;

Then everything worked OK.

Thanks again.

Comment by rwb.

Transforming your AVR Microcontroller to the I2C or TWI Slave I/O E... file:///D:/Documents/SKRIPSI MAHMUDDIN - BATAN/www.ermicro...

17 of 18 10/30/2012 8:41 AM

30.04.12 #5

01.05.12 #6

Thank you, I’ve checked the original code the TWDR

statement should be:

// Send slave address (SLA_W)

TWDR = (dev_id & 0xF0) | (dev_addr & 0x0E) | rw_type;

Comment by pmbari.

hi. am trying to connect two slaves to one master using your

code. am using atmega 16. the master works well with one

slave but when i include the second slave, communication

stops after some time. what changes can i perfom on the

slave code to include a second slave. your help will be greatly

appreciated.

Comment by rwb.

Make sure you communicate with only one slave at the time.

Leave a Comment

You must be logged in to post a comment.

Copyright © 2008-2012 By ermicro. Powered by Word Press.

Transforming your AVR Microcontroller to the I2C or TWI Slave I/O E... file:///D:/Documents/SKRIPSI MAHMUDDIN - BATAN/www.ermicro...

18 of 18 10/30/2012 8:41 AM