Any Port, Any Pin: A TWI Master for Attiny, Atmega

Any Port, Any Pin: A TWI Master for Attiny, Atmega

I2C is a powerful way to communicate with sensors. TWI, I2C, SMBus, 2-Wire . . . it’s an invaluable way to reach sensors in microcontroller projects.  While there are many examples of how to use I2C and other 2-wire protocols on Atmel chip’s designated pins, how do you develop an I2C master on non-standard pins?

Peter Fleury has written a great I2C Master Library here.  The library can be downloaded here and can be adapted for both the Attiny and Atmega chips.

We had to adapt this library for use in a project where we wanted to use the Attiny and Atmega.  Below we’ll walk you through, step by step, on what we did to make this library work for the Attiny and the Atmega.

There are two steps to adapting this to your project:  change the pins and port definitions and match the delay clock speed.

Changing the Pins:

The Pinout for ATtiny25/45/85

We’ll use the Attiny88 as an example.  The pinout for normal use is in the picture to the right.  SDA and SCL are normally defined by the standard TWI libraries as PB2 and PB0.  For our project, we wanted to move the SCL and SDA lines them to pins PB3 and PB4.

The pins are selected in the i2Cmaster.S file that comes from the original code.  Open this file in a text editor or AVR Studio and you’ll find the following:

#define SDA                        4            // SDA Port D, Pin 4
#define SCL                         5            // SCL Port D, Pin 5
#define SDA_PORT          PORTD           //SDA Port D
#define SCL_PORT          PORTD           // SCL Port D

Now, simply redefine the pins to where you need them.  Again, in our example, we’re moving the I2C master over to PB3 and PB4.

#define SDA                                  4            //
#define SCL                                   3            //
#define SDA_PORT                    PORTB           //
#define SCL_PORT                     PORTB           //
As you can see above, we’ve redefined the ports and the pins.

Changing the Delay Routine:

The second step is changing the delay routine.  Because I2C is a 2-wire protocol, it does not need an accurate clock (precise, but not accurate).  However, some sensors require a both precise and accurate (for example many SMBus sensors require clocks speeds of less than or equal to 100kHz).

Fleury’s files are written in AVR Assembler, which is not an incredibly user-friendly or easy-to-use language.  We dug around and found some info on delays in AVR here, at least enough to help us modify the delay.

Let’s start with the original code below.  It assumes a 4 Mhz crystal.  :

;*************************************************************************
;delay half period
;For I2C in normal mode (100kHz), use T/2 > 5us
; For I2C in fast mode (400kHz),   use T/2 > 1.3us
;*************************************************************************
.stabs “”,100,0,0,i2c_delay_T2
.stabs “i2cmaster.S”,100,0,0,i2c_delay_T2
.func i2c_delay_T2 ; delay 5.0 microsec with 4 Mhz crystal
i2c_delay_T2:        ; 4 cycles
rjmp 1f      ;2   “
1: rjmp 2f      ; 2   “
2: rjmp 3f      ; 2   “
3: rjmp 4f      ; 2   “
4: rjmp 5f      ; 2   “
5: rjmp 6f      ; 2   “
6: nop          ; 1   ”
ret          ; 3   ”
.endfunc     ; total 20 cyles = 5.0 microsec with 4 Mhz crystal

Modifying this section of the program is all about getting the counts correct.  In our example, we’re using an external clock of 16 Mhz, 4 times as fast as the original program was written for.  From the assembler page, we can see that the “nop” function delays 1 count or cycle.  To get 100 kHz, Fleury has originally written for 20 counts to give a 5.0 microsecond delay.  For our example, we’ll simply put in 80 counts to delay 4 times as long.  We can do that with “nop”s, and we’ll paste it below.

;*************************************************************************

;delay half period
;For I2C in normal mode (100kHz), use T/2 > 5us
; For I2C in fast mode (400kHz),   use T/2 > 1.3us
;*************************************************************************
.stabs “”,100,0,0,i2c_delay_T2
.stabs “i2cmaster.S”,100,0,0,i2c_delay_T2
.func i2c_delay_T2 ; delay 5.0 microsec with 4 Mhz crystal

i2c_delay_T2:    ; 4 cycles
; a nop delays 1 count.
; you have to align this up to the clock speed of the processor.
; To get 5.0 microseconds with a 16 Mhz crystal, you have to
; have 80 count delays.  Without this, it won’t work.
nop ; deleays 1 count
nop; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count (5)
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count (10)
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count (15)
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count (20)
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count (25)
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count (30)
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count (35)
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count (40)
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count (45)
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count (50)
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count (55)
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count (60)
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count (65)
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count
nop ; deleays 1 count (70)
nop ; 1
nop ; 1   ”
nop ; 1   ”
ret          ; 3   ”
.endfunc     ; total of 80 cycles = 5.0 microsec with 16 MHZ crystal ;

Get the modified i2Cmaster.S file here.

Any questions?