I2C Bitbanging

Back to start page

Om I2C-enhet saknas?

En mikroprocessor behöver i allmänhet inte ha flera än en I2C-enhet (TWI). Den räcker ju till att ansluta upp till hundra stycken I2C-tillbehör!
Det finns emellertid enklare (billigare) mikroprocessorer som helt saknar I2C-enhet. Då kan man behöva programmera I2C dataöverföringen bit för bit, så kallad BitBanging.

1/0 vid Open Collector logik

I2C-bussen bygger på att logiknivåerna "1" och "0" är olika starka, där "0" är den dominerande nivån. Det räcker med att någon enhets utgång längs bussen är "0" för att det värdet ska gälla.
AVR-processorernas utgångar är däremot lika starka för "1" som för "0". (Undantaget är TWI-pinnarna på de processorer som har TWI-enhet). För att använda vanliga pinnar/utgångar som I2C-pinnar behöver man därför ta till ett konstgrepp.

Man börjar med att lagra "0" för pinnen i PORT registret. Om man sedan väljer "1" (= utgång) för pinnen i DDR registret, så blir pinnen en stark "0". Om man istället väljer "0" (= ingång) i DDR registret så blir pinnen "bortkopplad" som utgång. Då gäller PullUp-motståndet på bussledningen som en svag "1".
Man styr således I2C nivåerna med DDR registret och rör inte PORT registret.

I2C utan TWI-enheten

Som exempel använder vi RTC kretsen DS1307, och vi väljer att styra den med pinnarna PB0 (SCL) och PB1 (SDA). PullUp-resistorerna sitter redan på RTC-modulens kretskort.
(Funktionerna följer tidsdiagrammet i figuren ovan. Vi har inte gjort någon tidsoptimering utan nöjt oss med en måttlig överföringshastighet, och behöver då inte heller implementera någon "clock stretching".)

Valfria pinnar

/* I2C pin connections               */
/* Redefine if other pins are used   */
#define SCL_BBpin PB0
#define SCL_DDR   DDRB
#define SCL_PORT  PORTB
#define SCL_PIN   PINB

#define SDA_BBpin PB1
#define SDA_DDR   DDRB
#define SDA_PORT  PORTB
#define SDA_PIN   PINB

I2C_Init_BB( )

SDA_PORT  &= (~( 1 << SDA_BBpin ));  // port = 0
SCL_PORT  &= (~( 1 << SCL_BBpin ));  // port = 0        
SDA_DDR   &=( ~( 1 << SDA_BBpin ));  // direction 0 = input,  pull-up 1 -> SDA high  
SCL_DDR   &= (~( 1 << SCL_BBpin ));  // direction 0 = input,  pull-up 1 -> SCL high  

I2C_Start_BB( )

SDA_DDR   &= (~( 1 << SDA_BBpin ));  // direction 0 = input,  pull-up 1 -> SDA high 
SCL_DDR   &= (~( 1 << SCL_BBpin ));  // direction 0 = input,  pull-up 1 -> SCL high  
_delay_us (10);  // slow I2C clock

SDA_DDR  |=  ( 1 << SDA_BBpin );  // direction 1 = output, port = 0 -> SDA low 
_delay_us (10);

SCL_DDR  |=  ( 1 << SCL_BBpin );  // direction 1 = output, port = 0 -> SCL low
_delay_us (10);

I2C_Stop_BB( )

SDA_DDR  |=  ( 1 << SDA_BBpin );  // direction 1 = output, port = 0 -> SDA low 
SCL_DDR  |=  ( 1 << SCL_BBpin );  // direction 1 = output, port = 0 -> SCL low     
_delay_us (10);

SCL_DDR   &= (~( 1 << SCL_BBpin ));  // direction 0 = input,  pull-up 1 -> SCL high 
_delay_us (10);

SDA_DDR   &= (~( 1 << SDA_BBpin ));  // direction 0 = input,  pull-up 1 -> SDA high 
_delay_us (10);

I2C_Write_BB( )     I2C_ReadACK_BB( )   I2C_ReadNACK_BB( )

I2C_Write_BB( ).   Efter att ha skrivit 8 bitar behöver man lämna SDA "1" så att "slaven" med "0" (ACK) kan kvittera att byten är mottagen. Man kan läsa denna bit som debuginformation.

I2C_ReadACK_BB( ).   Efter att ha läst 8 bitar, en byte, kan man ta emot ytterligare en byte i följd. Man måste då som en nionde bit skriva SDA "0" (ACK).

I2C_ReadNACK_BB( ).   Vill man bara läsa en byte, eller när det gäller den sista byten i en följd, måste man som nionde bit skriva SDA "1" (NACK).

Ställa in RTC-klockans tid

Jämför med samma programexempel för TWI-enheten.


Back to start page

 


William Sandqvist    willsandqvist@gmail.com