I²C is een multimaster seriële bus, uitgevonden door Philips. Toch is er meetstal maar 1 master op de bus en meerdere slaves. Deze slaves kunnen enkel data sturen als de master dat gevraagd heeft.
Deze bus is bedoelt voor communicatie tussen microprocessors en andere IC’s op 1 zelfde printplaat. De bedrading voor dit bussysteem is zeer eenvoudig, er zijn maar 2 draden nodig, SCL en SDA, om tot 127 IC te verbinden. Beide signaaldraden hebben een pullup nodig, want de I²C IC’s hebben allemaal een opendrain uitgang.
Bus instructies
Voordat I²C communicatie kan plaats vinden moet er een start conditie worden gegeven. Aan het einde van de communicatie moet ook een stop conditie worden gegeven.
Bus idle:
- SCL: hoog
- SDA: hoog
Start: (repeated start)
- SCL: dalende flank
- SDA: hoog
Stop:
- SCL: stijgende flank
- SDA: hoog
//! Send an I2C start condition in Master mode
void i2cSendStart(void);
//! Send an I2C stop condition in Master mode
void i2cSendStop(void);
/**
* @param void
* @return void
*/
void i2cSendStart(void){
outb(TWCR, (inb(TWCR)&TWCR_CMD_MASK) | ((1<<TWINT) | (1<<TWSTA)));
}
/**
* @param void
* @return void
*/
void i2cSendStop(void){
// leave with TWEA on for slave receiving
outb(TWCR, (inb(TWCR)&TWCR_CMD_MASK) | ((1<<TWINT) | (1<<TWEA) | (1<<TWSTO)));
}
Het register TWCR is het controle register van de hardware TWI (Two Wiire Interface oftewel I²C). De functie van de TWINT, TWSTA, ... bits kun je terug vinden in de datsheets onder het kopje TWI.
Voordat het mogelijk is om Start en Stop condities te sturen met de AVR’s hardware TWI moet deze geïnitialiseerd worden.
//! Initialize I2C (TWI) interface
void i2cInit(u16 bitrateKHz);
/**
* @param bitrateKHz I2C SCL speed
* @return void
*/
void i2cInit(u16 bitrateKHz){
// set pull-up resistors on I2C bus pins
#if defined(__AVR_ATmega16__) ||
defined(__AVR_ATmega32__)
sbi(PORTC, 0);
sbi(PORTC, 1);
cbi(PORTC, 7);
#endif
#if defined(__AVR_ATmega64__) ||
defined(__AVR_ATmega64__)
sbi(PORTD, 0);
sbi(PORTD, 1);
#endif
#if defined(__AVR_ATmega8__) ||
defined(__AVR_ATmega88__) ||
defined(__AVR_ATmega168__)
sbi(PORTC, 4);
sbi(PORTC, 5);
#endif
// clear SlaveReceive and SlaveTransmit handler to null
i2cSlaveReceive = 0;
i2cSlaveTransmit = 0;
// enable TWI (two-wire interface)
sbi(TWCR, TWEN);
// set state
I2cState = I2C_IDLE;
sbi(TWCR, TWEA);
u08 bitrate_div;
// set i2c bitrate
// SCL freq = F_CPU/(16+2*TWBR))
#ifdef TWPS0
// for processors with additional bitrate division (mega128)
// SCL freq = F_CPU/(16+2*TWBR*4^TWPS)
// set TWPS to zero
cbi(TWSR, TWPS0);
cbi(TWSR, TWPS1);
#endif
// calculate bitrate division
bitrate_div = ((F_CPU/1000l)/bitrateKHz);
if(bitrate_div >= 16) bitrate_div = (bitrate_div-16)/2;
outb(TWBR, bitrate_div);
// enable interrupts
sei();
}
De snelheid van de I²C communicatie moet worden ingesteld tijdens het initialisatie proces. De normale snelheid is 100Khz, tegenwoordig is fast I²C, 400Khz, ook veel gebruikt of zelf nog hogere snelheden.
//! send I2C data to a device on the bus (non-interrupt based)
u08 i2cSend(u08 deviceAddr, u08 length, u08* data);
/**
* @param deviceAddr target slave address
* @param length data length
* @param data pointer to data
* @return TWI bus status
*/
u08 i2cSend(u08 deviceAddr, u08 length, u08* data){
u08 retval = I2C_OK;
i2cSendStart();
i2cWaitForComplete();
i2cSendByte( deviceAddr & 0xFE );
i2cWaitForComplete();
// check if device is present and live
if( inb(TWSR) == TW_MT_SLA_ACK){
while(length) {
i2cSendByte( *data++ );
i2cWaitForComplete();
length--;
}
}
else{
// device did not ACK it's address,
// data will not be transferred
// return error
retval = I2C_ERROR_NODEV;
}
// transmit stop condition
i2cSendStop();
while( !(inb(TWCR) & BV(TWSTO)) );
return retval;
}
Bovenstaande functie is een high-level functie voor het verzenden van I²C data. Eerst wordt het start commando gestuurd, vervolgens wachten we totdat de TWI bus klaar is. Dan wordt het slave-adres verzonden. Als we data willen lezen van een slave, moet de 0e adresbit 0 zijn. Vervolgens wachten we weer, totdat het adres verzonden is.
Nu kan de data gestuurd worden, deze wordt byte per byte verzonden, na elke byte wordt er gewacht totdat de TWI bus klaar is met verzenden van de vorige byte. Ten slotte wordt het stop commando doorgestuurd.
//! receive I2C data from a device on the bus (non-interrupt based)
u08 i2cReceive(u08 deviceAddr, u08 length, u08 *data);
/**
* @param deviceAddr target slave address
* @param length data length
* @param data pointer where need to be stored at
* @return TWI bus status
*/
u08 i2cReceive(u08 deviceAddr, u08 length, u08 *data){
u08 retval = I2C_OK;
i2cSendStart();
i2cWaitForComplete();
i2cSendByte( deviceAddr | 0x01 );
i2cWaitForComplete();
// check if device is present and live
if( inb(TWSR) == TW_MR_SLA_ACK){
// accept receive data and ack it
while(length > 1){
i2cReceiveByte(TRUE);
i2cWaitForComplete();
*data++ = i2cGetReceivedByte();
// decrement length
length--;
}
// accept receive data and nack it (last-byte signal)
i2cReceiveByte(FALSE);
i2cWaitForComplete();
*data++ = i2cGetReceivedByte();
}
else{
// device did not ACK it's address,
// data will not be transferred
// return error
retval = I2C_ERROR_NODEV;
}
// transmit stop condition
i2cSendStop();
return retval;
}
Het principe voor het ontvangen van I²C is vrij gelijkend op het verzenden. Eerst het start commando verzenden, wachten, slave-adres. Bij het opvragen van slavedata is de 0e adresbit 1. Na het verzenden van het slave-adres wachten we weer.
Ook het ontvangen gebeurt byte per byte, enkel de laatste byte is speciaal om te ontvangen, omdat het een nack – not ack – moet worden gegeven. Aan het einde van de datatransitie wordt het stop commande verzonden.