123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335 |
- ;*************************************************************************
- ; Title : I2C (Single) Master Implementation
- ; Author: Peter Fleury <pfleury@gmx.ch>
- ; based on Atmel Appl. Note AVR300
- ; File: $Id: i2cmaster.S,v 1.13 2015/09/16 11:21:00 peter Exp $
- ; Software: AVR-GCC 4.x
- ; Target: any AVR device
- ;
- ; DESCRIPTION
- ; Basic routines for communicating with I2C slave devices. This
- ; "single" master implementation is limited to one bus master on the
- ; I2C bus.
- ;
- ; Based on the Atmel Application Note AVR300, corrected and adapted
- ; to GNU assembler and AVR-GCC C call interface
- ; Replaced the incorrect quarter period delays found in AVR300 with
- ; half period delays.
- ;
- ; USAGE
- ; These routines can be called from C, refere to file i2cmaster.h.
- ; See example test_i2cmaster.c
- ; Adapt the SCL and SDA port and pin definitions and eventually
- ; the delay routine to your target !
- ; Use 4.7k pull-up resistor on the SDA and SCL pin.
- ;
- ; NOTES
- ; The I2C routines can be called either from non-interrupt or
- ; interrupt routines, not both.
- ;
- ;*************************************************************************
- #include <avr/io.h>
- #undef SCL_PORT
- #undef SCL_DDR
- ;******----- Adapt these SCA and SCL port and pin definition to your target !!
- ;
- #define SDA 1 // SDA Port D, Pin 4
- #define SCL 2 // SCL Port D, Pin 5
- #define SDA_PORT PORTB // SDA Port D
- #define SCL_PORT PORTB // SCL Port D
- ;******----------------------------------------------------------------------
- ;-- map the IO register back into the IO address space
- #define SDA_DDR (_SFR_IO_ADDR(SDA_PORT) - 1)
- #define SCL_DDR (_SFR_IO_ADDR(SCL_PORT) - 1)
- #define SDA_OUT _SFR_IO_ADDR(SDA_PORT)
- #define SCL_OUT _SFR_IO_ADDR(SCL_PORT)
- #define SDA_IN (_SFR_IO_ADDR(SDA_PORT) - 2)
- #define SCL_IN (_SFR_IO_ADDR(SCL_PORT) - 2)
- #ifndef __tmp_reg__
- #define __tmp_reg__ 0
- #endif
- .section .text
- ;*************************************************************************
- ; delay half period
- ; For I2C in normal mode (100kHz), use T/2 > 5us
- ; For I2C in fast mode (400kHz), use T/2 > 1.25us
- ;*************************************************************************
- .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: ; 3 cycles
- #if F_CPU <= 4000000UL
- 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 ; 4 " total 20 cyles = 5.0 microsec with 4 Mhz crystal
- #elif F_CPU <= 8000000UL
- push r24 ; 2 cycle
- ldi r24, 7 ; 1 cycle
- nop ; 1 cycle
- 1: sbiw r24, 1 ; 2 cycle
- brne 1b ; 2 or 1 cycle, 4 cycles per loop
- pop r24 ; 2 ycle
- ret ; 4 cycle = total 60 cycles = 5.0 microsec with 12 Mhz crystal
- #elif F_CPU <= 12000000UL
- push r24 ; 2 cycle
- ldi r24, 12 ; 1 cycle
- nop ; 1 cycle
- 1: sbiw r24, 1 ; 2 cycle
- brne 1b ; 2 or 1 cycle, 4 cycles per loop
- pop r24 ; 2 ycle
- ret ; 4 cycle = total 60 cycles = 5.0 microsec with 12 Mhz crystal
- #elif F_CPU <= 16000000UL
- push r24 ; 2 cycle
- ldi r24, 17 ; 1 cycle
- nop ; 1 cycle
- 1: sbiw r24, 1 ; 2 cycle
- brne 1b ; 2 or 1 cycle, 4 cycles per loop
- pop r24 ; 2 ycle
- ret ; 4 cycle = total 80 cycles = 5.0 microsec with 16 Mhz crystal
- #else
- push r24 ; 2 cycle
- ldi r24, 22 ; 1 cycle
- nop ; 1 cycle
- 1: sbiw r24, 1 ; 2 cycle
- brne 1b ; 2 or 1 cycle, 4 cycles per loop
- pop r24 ; 2 ycle
- ret ; 4 cycle = total 100 cycles = 5.0 microsec with 20 Mhz crystal
- #endif
- .endfunc ;
- ;*************************************************************************
- ; Initialization of the I2C bus interface. Need to be called only once
- ;
- ; extern void i2c_init(void)
- ;*************************************************************************
- .global i2c_init
- .func i2c_init
- i2c_init:
- cbi SDA_DDR,SDA ;release SDA
- cbi SCL_DDR,SCL ;release SCL
- cbi SDA_OUT,SDA
- cbi SCL_OUT,SCL
- ret
- .endfunc
- ;*************************************************************************
- ; Issues a start condition and sends address and transfer direction.
- ; return 0 = device accessible, 1= failed to access device
- ;
- ; extern unsigned char i2c_start(unsigned char addr);
- ; addr = r24, return = r25(=0):r24
- ;*************************************************************************
- .global i2c_start
- .func i2c_start
- i2c_start:
- sbi SDA_DDR,SDA ;force SDA low
- rcall i2c_delay_T2 ;delay T/2
-
- rcall i2c_write ;write address
- ret
- .endfunc
- ;*************************************************************************
- ; Issues a repeated start condition and sends address and transfer direction.
- ; return 0 = device accessible, 1= failed to access device
- ;
- ; extern unsigned char i2c_rep_start(unsigned char addr);
- ; addr = r24, return = r25(=0):r24
- ;*************************************************************************
- .global i2c_rep_start
- .func i2c_rep_start
- i2c_rep_start:
- sbi SCL_DDR,SCL ;force SCL low
- rcall i2c_delay_T2 ;delay T/2
- cbi SDA_DDR,SDA ;release SDA
- rcall i2c_delay_T2 ;delay T/2
- cbi SCL_DDR,SCL ;release SCL
- rcall i2c_delay_T2 ;delay T/2
- sbi SDA_DDR,SDA ;force SDA low
- rcall i2c_delay_T2 ;delay T/2
-
- rcall i2c_write ;write address
- ret
- .endfunc
- ;*************************************************************************
- ; Issues a start condition and sends address and transfer direction.
- ; If device is busy, use ack polling to wait until device is ready
- ;
- ; extern void i2c_start_wait(unsigned char addr);
- ; addr = r24
- ;*************************************************************************
- .global i2c_start_wait
- .func i2c_start_wait
- i2c_start_wait:
- mov __tmp_reg__,r24
- i2c_start_wait1:
- sbi SDA_DDR,SDA ;force SDA low
- rcall i2c_delay_T2 ;delay T/2
- mov r24,__tmp_reg__
- rcall i2c_write ;write address
- tst r24 ;if device not busy -> done
- breq i2c_start_wait_done
- rcall i2c_stop ;terminate write operation
- rjmp i2c_start_wait1 ;device busy, poll ack again
- i2c_start_wait_done:
- ret
- .endfunc
- ;*************************************************************************
- ; Terminates the data transfer and releases the I2C bus
- ;
- ; extern void i2c_stop(void)
- ;*************************************************************************
- .global i2c_stop
- .func i2c_stop
- i2c_stop:
- sbi SCL_DDR,SCL ;force SCL low
- sbi SDA_DDR,SDA ;force SDA low
- rcall i2c_delay_T2 ;delay T/2
- cbi SCL_DDR,SCL ;release SCL
- rcall i2c_delay_T2 ;delay T/2
- cbi SDA_DDR,SDA ;release SDA
- rcall i2c_delay_T2 ;delay T/2
- ret
- .endfunc
- ;*************************************************************************
- ; Send one byte to I2C device
- ; return 0 = write successful, 1 = write failed
- ;
- ; extern unsigned char i2c_write( unsigned char data );
- ; data = r24, return = r25(=0):r24
- ;*************************************************************************
- .global i2c_write
- .func i2c_write
- i2c_write:
- sec ;set carry flag
- rol r24 ;shift in carry and out bit one
- rjmp i2c_write_first
- i2c_write_bit:
- lsl r24 ;if transmit register empty
- i2c_write_first:
- breq i2c_get_ack
- sbi SCL_DDR,SCL ;force SCL low
- brcc i2c_write_low
- nop
- cbi SDA_DDR,SDA ;release SDA
- rjmp i2c_write_high
- i2c_write_low:
- sbi SDA_DDR,SDA ;force SDA low
- rjmp i2c_write_high
- i2c_write_high:
- rcall i2c_delay_T2 ;delay T/2
- cbi SCL_DDR,SCL ;release SCL
- rcall i2c_delay_T2 ;delay T/2
- rjmp i2c_write_bit
-
- i2c_get_ack:
- sbi SCL_DDR,SCL ;force SCL low
- cbi SDA_DDR,SDA ;release SDA
- rcall i2c_delay_T2 ;delay T/2
- cbi SCL_DDR,SCL ;release SCL
- i2c_ack_wait:
- sbis SCL_IN,SCL ;wait SCL high (in case wait states are inserted)
- rjmp i2c_ack_wait
-
- clr r24 ;return 0
- sbic SDA_IN,SDA ;if SDA high -> return 1
- ldi r24,1
- rcall i2c_delay_T2 ;delay T/2
- clr r25
- ret
- .endfunc
- ;*************************************************************************
- ; read one byte from the I2C device, send ack or nak to device
- ; (ack=1, send ack, request more data from device
- ; ack=0, send nak, read is followed by a stop condition)
- ;
- ; extern unsigned char i2c_read(unsigned char ack);
- ; ack = r24, return = r25(=0):r24
- ; extern unsigned char i2c_readAck(void);
- ; extern unsigned char i2c_readNak(void);
- ; return = r25(=0):r24
- ;*************************************************************************
- .global i2c_readAck
- .global i2c_readNak
- .global i2c_read
- .func i2c_read
- i2c_readNak:
- clr r24
- rjmp i2c_read
- i2c_readAck:
- ldi r24,0x01
- i2c_read:
- ldi r23,0x01 ;data = 0x01
- i2c_read_bit:
- sbi SCL_DDR,SCL ;force SCL low
- cbi SDA_DDR,SDA ;release SDA (from previous ACK)
- rcall i2c_delay_T2 ;delay T/2
-
- cbi SCL_DDR,SCL ;release SCL
- rcall i2c_delay_T2 ;delay T/2
-
- i2c_read_stretch:
- sbis SCL_IN, SCL ;loop until SCL is high (allow slave to stretch SCL)
- rjmp i2c_read_stretch
-
- clc ;clear carry flag
- sbic SDA_IN,SDA ;if SDA is high
- sec ; set carry flag
-
- rol r23 ;store bit
- brcc i2c_read_bit ;while receive register not full
-
- i2c_put_ack:
- sbi SCL_DDR,SCL ;force SCL low
- cpi r24,1
- breq i2c_put_ack_low ;if (ack=0)
- cbi SDA_DDR,SDA ; release SDA
- rjmp i2c_put_ack_high
- i2c_put_ack_low: ;else
- sbi SDA_DDR,SDA ; force SDA low
- i2c_put_ack_high:
- rcall i2c_delay_T2 ;delay T/2
- cbi SCL_DDR,SCL ;release SCL
- i2c_put_ack_wait:
- sbis SCL_IN,SCL ;wait SCL high
- rjmp i2c_put_ack_wait
- rcall i2c_delay_T2 ;delay T/2
- mov r24,r23
- clr r25
- ret
- .endfunc
|