;**** A P P L I C A T I O N N O T E A V R 3 0 0 ************************ ;* ;* Title : I2C (Single) Master Implementation ;* Version : 1.0 (BETA) ;* Last updated : 97.08.27 ;* Target : AT90Sxxxx (any AVR device) ;* ;* Support email : avr@atmel.com ;* ;* DESCRIPTION ;* Basic routines for communicating with I2C slave devices. This ;* "single" master implementation is limited to one bus master on the ;* I2C bus. Most applications do not need the multimaster ability ;* the I2C bus provides. A single master implementation uses, by far, ;* less resources and is less XTAL frequency dependent. ;* ;* Some features : ;* * All interrupts are free, and can be used for other activities. ;* * Supports normal and fast mode. ;* * Supports both 7-bit and 10-bit addressing. ;* * Supports the entire AVR microcontroller family. ;* ;* Main I2C functions : ;* 'i2c_start' - Issues a start condition and sends address ;* and transfer direction. ;* 'i2c_rep_start' - Issues a repeated start condition and sends ;* address and transfer direction. ;* 'i2c_do_transfer' - Sends or receives data depending on ;* direction given in address/dir byte. ;* 'i2c_stop' - Terminates the data transfer by issue a ;* stop condition. ;* ;* USAGE ;* Transfer formats is described in the AVR300 documentation. ;* (An example is shown in the 'main' code). ;* ;* NOTES ;* The I2C routines can be called either from non-interrupt or ;* interrupt routines, not both. ;* ;* STATISTICS ;* Code Size : 81 words (maximum) ;* Register Usage : 4 High, 0 Low ;* Interrupt Usage : None ;* Other Usage : Uses two I/O pins on port D ;* XTAL Range : N/A ;* ;*************************************************************************** ;*************************************************************************** ;* ;* FUNCTION ;* i2c_hp_delay ;* i2c_qp_delay ;* ;* DESCRIPTION ;* hp - half i2c clock period delay (normal: 5.0us / fast: 1.3us) ;* qp - quarter i2c clock period delay (normal: 2.5us / fast: 0.6us) ;* ;* SEE DOCUMENTATION !!! ;* ;* USAGE ;* no parameters ;* ;* RETURN ;* none ;* ;*************************************************************************** i2c_hp_delay: ldi i2cdelay,(1+(thp-7)/3) i2c_hp_delay_loop: dec i2cdelay brne i2c_hp_delay_loop ret i2c_qp_delay: ldi i2cdelay,(1+(tqp-7)/3) i2c_qp_delay_loop: dec i2cdelay brne i2c_qp_delay_loop ret ;*************************************************************************** ;* ;* FUNCTION ;* i2c_rep_start ;* ;* DESCRIPTION ;* Assert repeated start condition and sends slave address. ;* ;* USAGE ;* i2cadr - Contains the slave address and transfer direction. ;* ;* RETURN ;* Carry flag - Cleared if a slave responds to the address. ;* ;* NOTE ;* IMPORTANT! : This funtion must be directly followed by i2c_start. ;* ;*************************************************************************** i2c_rep_start: cbi PORTC,SCLP ; force SCL low sbi PORTC,SDAP ; release SDA rcall i2c_hp_delay ; half period delay sbi PORTC,SCLP ; release SCL rcall i2c_qp_delay ; quarter period delay ;*************************************************************************** ;* ;* FUNCTION ;* i2c_start ;* ;* DESCRIPTION ;* Generates start condition and sends slave address. ;* ;* USAGE ;* i2cadr - Contains the slave address and transfer direction. ;* ;* RETURN ;* Carry flag - Cleared if a slave responds to the address. ;* ;* NOTE ;* IMPORTANT! : This funtion must be directly followed by i2c_write. ;* ;*************************************************************************** i2c_start: mov i2cdata,i2cadr ; copy address to transmitt register cbi PORTC,SDAP ; force SDA low rcall i2c_qp_delay ; quarter period delay ;*************************************************************************** ;* ;* FUNCTION ;* i2c_write ;* ;* DESCRIPTION ;* Writes data (one byte) to the I2C bus. Also used for sending ;* the address. ;* ;* USAGE ;* i2cdata - Contains data to be transmitted. ;* ;* RETURN ;* Carry flag - Set if the slave respond transfer. ;* ;* NOTE ;* IMPORTANT! : This funtion must be directly followed by i2c_get_ack. ;* ;*************************************************************************** i2c_write: sec ; set carry flag rol i2cdata ; shift in carry and out bit one rjmp i2c_write_first i2c_write_bit: lsl i2cdata ; if transmit register empty i2c_write_first: breq i2c_get_ack ; goto get acknowledge cbi PORTC,SCLP ; force SCL low brcc i2c_write_low ; if bit high nop ; (equalize number of cycles) sbi PORTC,SDAP ; release SDA rjmp i2c_write_high i2c_write_low: ; else cbi PORTC,SDAP ; force SDA low rjmp i2c_write_high ; (equalize number of cycles) i2c_write_high: rcall i2c_hp_delay ; half period delay sbi PORTC,SCLP ; release SCL rcall i2c_hp_delay ; half period delay rjmp i2c_write_bit ;*************************************************************************** ;* ;* FUNCTION ;* i2c_get_ack ;* ;* DESCRIPTION ;* Get slave acknowledge response. ;* ;* USAGE ;* (used only by i2c_write in this version) ;* ;* RETURN ;* Carry flag - Cleared if a slave responds to a request. ;* ;*************************************************************************** i2c_get_ack: cbi PORTC,SCLP ; force SCL low sbi PORTC,SDAP ; release SDA rcall i2c_hp_delay ; half period delay sbi PORTC,SCLP ; release SCL i2c_get_ack_wait: ;sbis PINC,SCLP ; wait SCL high ;(In case wait states are inserted) ;rjmp i2c_get_ack_wait clc ; clear carry flag sbic PINC,SDAP_I ; if SDA is high sec ; set carry flag rcall i2c_hp_delay ; half period delay ret ;*************************************************************************** ;* ;* FUNCTION ;* i2c_do_transfer ;* ;* DESCRIPTION ;* Executes a transfer on bus. This is only a combination of i2c_read ;* and i2c_write for convenience. ;* ;* USAGE ;* i2cadr - Must have the same direction as when i2c_start was called. ;* see i2c_read and i2c_write for more information. ;* ;* RETURN ;* (depends on type of transfer, read or write) ;* ;* NOTE ;* IMPORTANT! : This funtion must be directly followed by i2c_read. ;* ;*************************************************************************** i2c_do_transfer: sbrs i2cadr,b_dir ; if dir = write rjmp i2c_write ; goto write data ;*************************************************************************** ;* ;* FUNCTION ;* i2c_read ;* ;* DESCRIPTION ;* Reads data (one byte) from the I2C bus. ;* ;* USAGE ;* Carry flag - If set no acknowledge is given to the slave ;* indicating last read operation before a STOP. ;* If cleared acknowledge is given to the slave ;* indicating more data. ;* ;* RETURN ;* i2cdata - Contains received data. ;* ;* NOTE ;* IMPORTANT! : This funtion must be directly followed by i2c_put_ack. ;* ;*************************************************************************** i2c_read: rol i2cstat ; store acknowledge ; (used by i2c_put_ack) ldi i2cdata,0x01 ; data = 0x01 i2c_read_bit: ; do cbi PORTC,SCLP ; force SCL low rcall i2c_hp_delay ; half period delay sbi PORTC,SCLP ; release SCL rcall i2c_hp_delay ; half period delay clc ; clear carry flag sbic PINC,SDAP_I ; if SDA is high sec ; set carry flag rol i2cdata ; store data bit brcc i2c_read_bit ; while receive register not full ;*************************************************************************** ;* ;* FUNCTION ;* i2c_put_ack ;* ;* DESCRIPTION ;* Put acknowledge. ;* ;* USAGE ;* (used only by i2c_read in this version) ;* ;* RETURN ;* none ;* ;*************************************************************************** i2c_put_ack: cbi PORTC,SCLP ; force SCL low ror i2cstat ; get status bit brcc i2c_put_ack_low ; if bit low goto assert low sbi PORTC,SDAP ; release SDA rjmp i2c_put_ack_high i2c_put_ack_low: ; else cbi PORTC,SDAP ; force SDA low i2c_put_ack_high: rcall i2c_hp_delay ; half period delay sbi PORTC,SCLP ; release SCL i2c_put_ack_wait: ;sbis PINC,SCLP ; wait SCL high ;rjmp i2c_put_ack_wait rcall i2c_hp_delay ; half period delay ret ;*************************************************************************** ;* ;* FUNCTION ;* i2c_stop ;* ;* DESCRIPTION ;* Assert stop condition. ;* ;* USAGE ;* No parameters. ;* ;* RETURN ;* None. ;* ;*************************************************************************** i2c_stop: cbi PORTC,SCLP ; force SCL low cbi PORTC,SDAP ; force SDA low rcall i2c_hp_delay ; half period delay sbi PORTC,SCLP ; release SCL rcall i2c_qp_delay ; quarter period delay sbi PORTC,SDAP ; release SDA rcall i2c_hp_delay ; half period delay ret ;*************************************************************************** ;* ;* FUNCTION ;* i2c_init ;* ;* DESCRIPTION ;* Initialization of the I2C bus interface. ;* ;* USAGE ;* Call this function once to initialize the I2C bus. No parameters ;* are required. ;* ;* RETURN ;* None ;* ;* NOTE ;* PORTD and DDRD pins not used by the I2C bus interface will be ;* set to Hi-Z (!). ;* ;* COMMENT ;* This function can be combined with other PORTD initializations. ;* ;*************************************************************************** i2c_init: push i2cstat ldi i2cstat,0x0b ; clear I2C status register (used ; as a temporary register) out PORTC,i2cstat ; set I2C pins to open colector ldi i2cstat,0x0e ; clear I2C status register (used out DDRC,i2cstat pop i2cstat ret ;*************************************************************************** ;* ;* FUNCTION ;* read_i2c ;* ;* DESCRIPTION ;* reduces a read to one call to reduce code size ;* ;* USAGE ;* Call this func read 1 byte off bus. ;* ;* load address into addr_12c and call funct ;* ;* RETURN ;* None ;* ;* NOTE ;* -call with address to be read ;* -will update i2cdata and data_i2c ;* ;* COMMENT ;* Read data => i2cdata = Adr(addr_i2c) **** ;* ;*************************************************************************** read_i2c: ldi i2cadr,SLAVE_ADD+i2cwr ; Set device address and write rcall i2c_start ; Send start condition and address mov i2cdata,addr_i2c ; Write word address rcall i2c_do_transfer ; Execute transfer ldi i2cadr,SLAVE_ADD+i2crd ; Set device address and read rcall i2c_rep_start ; Send repeated start condition and address sec ; Set no acknowledge (read is followed by a stop condition) rcall i2c_do_transfer ; Execute transfer (read) rcall i2c_stop ; Send stop condition - releases bus mov data_i2c,i2cdata ; load data into data_i2c to output to uart ret ;*************************************************************************** ;* ;* FUNCTION ;* write_i2c ;* ;* DESCRIPTION ;* reduces a write to one call to reduce code size ;* ;* USAGE ;* Call this func write 1 byte onto bus. ;* ;* address - addr_i2c ;* data - i2cdata ;* ;* RETURN ;* None ;* ;* NOTE ;* does not destroy the values in any registers ;* ;* COMMENT ;* Write data => Adr(addr_i2c) = i2cdata **** ;* ;*************************************************************************** write_i2c: ldi i2cadr,SLAVE_ADD+i2cwr ; Set device address and write rcall i2c_start ; Send start condition and address mov i2cdata,addr_i2c ; Write word address (0x00) rcall i2c_do_transfer ; Execute transfer mov i2cdata,data_i2c ; Set write data to 01010101b rcall i2c_do_transfer ; Execute transfer rcall i2c_stop ; Send stop condition ret ;************************************************************** ;* ;* FUNCTION: dec_init ;* ;* DESCRIPTION: ;* inits decoder, loads i2c regs by reading ;* a config 'file' out of mem and send to decoder ;* NOTE: ;* config 'file' must have address followed by data ;* -EOF is a count of bytes in config ;*************************************************************** dec_init: push r0 push temp rcall i2c_init ldi ZL,LOW(config<<1) ldi ZH,HIGH(config<<1) _dec: lpm ;read address out of mem adiw ZL,1 mov addr_i2c,r0 mov temp,r0 cpi temp,0xff ;look for ff end of file marker1 breq _end_dec_init ; end if found lpm ;read data out of mem adiw ZL,1 mov data_i2c,r0 rcall write_i2c ;write data ;**************************************************** ;rcall i2c_to_uart ;report what was sent ;crlf ;ldi sTXchar,0x0d ;rcall putchar ;ldi sTXchar,0x0a ;rcall putchar ;*************************************************** rjmp _dec _end_dec_init: pop temp pop r0 ret i2c_to_uart: push temp push temp2 ;output data to the uart and format it mov temp,addr_i2c lsr temp lsr temp lsr temp lsr temp cpi temp,0x0a brlt _add30 ldi temp2,('a'-10) rjmp _next _add30: ldi temp2,'0' _next: add temp,temp2 mov sTXchar,temp rcall putchar mov temp,addr_i2c andi temp,0x0f cpi temp,0x0a brlt _add302 ldi temp2,('a'-10) rjmp _next2 _add302:ldi temp2,'0' _next2: add temp,temp2 mov sTXchar,temp rcall putchar mov temp,data_i2c lsr temp lsr temp lsr temp lsr temp cpi temp,0x0a brlt _add303 ldi temp2,('a'-10) rjmp _next3 _add303:ldi temp2,'0' _next3: add temp,temp2 mov sTXchar,temp rcall putchar mov temp,data_i2c andi temp,0x0f cpi temp,0x0a brlt _add304 ldi temp2,('a'-10) rjmp _next4 _add304:ldi temp2,'0' _next4: add temp,temp2 mov sTXchar,temp rcall putchar ;ldi sTXchar,0x0d ;rcall _putchar ;ldi sTXchar,0x0a ;rcall _putchar pop temp2 pop temp ret ;************************************* uart_to_i2c: push temp push temp2 rcall getchar mov temp,sTXchar subi temp,0x30 cpi temp,0x0a brlt adend subi temp,0x27 adend: ;output data to the uart and format it lsl temp lsl temp lsl temp lsl temp mov temp2,temp rcall getchar mov temp,sTXchar subi temp,0x30 cpi temp,0x0a brlt adend2 subi temp,0x27 adend2: add temp2,temp mov addr_i2c,temp2 ldi sTXchar,':' rcall putchar ;get data rcall getchar mov temp,sTXchar subi temp,0x30 cpi temp,0x0a brlt adend3 subi temp,0x27 adend3: ;output data to the uart and format it lsl temp lsl temp lsl temp lsl temp mov temp2,temp rcall getchar mov temp,sTXchar subi temp,0x30 cpi temp,0x0a brlt adend4 subi temp,0x27 adend4: add temp2,temp mov data_i2c,temp2 rcall getchar cpi sTXchar,'w' breq _write rcall read_i2c rcall i2c_to_uart rjmp _endline _write: nop rcall write_i2c _endline: ;crlf ldi sTXchar,0x0d rcall putchar ldi sTXchar,0x0a rcall putchar pop temp2 pop temp ret