_PROGRAMMING THE I2C INTERFACE_ by Mitchell Kahn [LISTING ONE] $pagelength (30) $mod186 $debug $xref NAME i2c_transmit; $include (\include\pcp_io.inc) PUBLIC i2c_xmit ;****** EQUates ****** BUS_FREE_MIN EQU 2 ; Loop counter for free bus delay. MAXIMUM_MESSAGE_LEN EQU 255 CODE_ILLEGAL_ADDR EQU 020H CODE_MSG_LEN EQU 040H ;****** STACK FRAME STRUCTURE ****** stack_frame STRUC ret_ip DW ? ret_cs DW ? buffer_offset DW ? buffer_segment DW ? count DW ? address DW ? stack_frame ENDS %*DEFINE(Drive_SCL_Low)( mov dx, P2LTCH in al, dx and al, 10111111B ; SCL is bit 6 out dx, al ) %*DEFINE(Release_SCL_High)( mov dx, P2LTCH in al, dx or al, 01000000B out dx, al ) %*DEFINE(Drive_SDA_Low)( mov dx, P2LTCH in al, dx and al, 01111111B ; SDA is bit 6 out dx, al ) %*DEFINE(Release_SDA_High)( mov dx, P2LTCH in al, dx or al, 10000000B out dx, al ) %*DEFINE(Wait_4_7_uS)( mov cx, 5 loop $ nop nop ) %*DEFINE(Wait_Half_Bit_Time)( mov cx, 3 loop $ ) %*DEFINE(Wait_SCL_Low_Time)( mov cx, 5 loop $ nop nop ) %*DEFINE(Wait_SCL_High_Time)( mov cx, 5 loop $ nop nop ) %*DEFINE(Wait_For_SCL_To_Go_Low)LOCAL wait( mov dx, P2PIN %wait: in al, dx test al, 01000000B jne %wait ) %*DEFINE(Wait_For_SCL_To_Go_High)LOCAL wait( mov dx, P2PIN %wait: in al, dx test al, 01000000B je %wait %*DEFINE(Wait_For_SDA_To_Go_High)LOCAL wait( mov dx, P2PIN %wait: in al, dx test al, 10000000B je %wait ) ) %*DEFINE(Get_SDA_Bit)( mov dx, P2PIN in al, dx and al, 0080H ) %*DEFINE(Check_For_Bus_Free)( mov dx, P2PIN in al, dx mov bl, 0C0H ; Mask for SCL and SDA. and al, bl ; If SCL and SDA are high xor al, bl ; this sequence will leave a zero in AX. ) ;***************************************************************************** ;** Revision History: 0.0 (7/90): First frozen working verion. No slave wait ;** timeout. No arbitration turn around. Inefficient register usage. ;** 0.1 (7/16/90): 8-bit registers used (improves 80C188EB. Use STRUCT for ;** stack frame clarity. Implements slave wait timeout. Saves ES. ;***************************************************************************** ;***************************************************************** ;** Procedure I2C_XMIT ** ;** Call Type: FAR ** ;** Uses : All regs. ** ;** Saves : DS and ES only. ** ;** Stack Frame: ** ;** [bp]= ip ** ;** [bp+2]= cs ** ;** [bp+4]= message offset ** ;** [bp+6]= message segment ** ;** [bp+8]= message count ** ;** [bp+10]= slave adress ** ;** Return Codes in AX register: ** ;** XX00 = Transmisiion completed without error ** ;** XX01 = Bus unavailable ** ;** XX02 = Addressed slave not responding ** ;** nn04 = Addressed slave aborted during xfer ** ;** (nn= number of bytes transferred before ** ;** transfer aborted) ** ;** XX08 = Arbitration loss (note 1) ** ;** XX10 = Bus wait timeout ** ;** XX20 = Illegal address ** ;** XX40 = Illegal message count ** ;** note 1: Arbitration loss requires that the ** ;** I2C unit switch to slave receive ** ;** mode. This is not implemented. ** ;***************************************************************** code segment public assume cs:code i2c_xmit proc far mov bp, sp push ds push es test word ptr [bp].address,01H ; Check for illegal ; address (a READ). jz addr_ok mov ax, CODE_ILLEGAL_ADDR ; Illegal addr pop es pop ds ret 8 ; Tear down stack frame addr_ok: mov cx, [bp].count ; Get message length. cmp cx, MAXIMUM_MESSAGE_LEN jle message_len_ok ; Message is 256 or less ; characters. mov ax, CODE_MSG_LEN ; Bad length return code. pop es pop ds ret 8 message_len_ok: mov si, [bp].buffer_offset ; Get message offset. mov ax, [bp].buffer_segment ; Get message segment mov ds, ax ; and put in DS. ; Test for I2C bus free condition. ; SCL and SDA must be high at least 4.7uS mov cx, BUS_FREE_MIN ; initialize free time counter. ; The following loop takes 48 clocks while cx>1 and 33 clocks ; on the last iteration. To insure that bus is free, samples ; of bus must span at least 4.7uS. At 16Mhz: 48*(62.5ns)=3uS ; The first sample is at 0us, the second at 3us, and the ; third will be at 6. Although this exceeds the 4.7us ; spec, it is better safe than sorry. bus_free_wait: %Check_For_Bus_Free jz i2c_bus_free ; At this point the bus is not available. mov ax, 01H ; 01= return code for pop es ; a busy bus. pop ds ret 8 ; return and tear down ; stack frame. i2c_bus_free: loop bus_free_wait ; bus may be free but wait ; the 4.7uS required! ; I2C bus is available, generate a START condition %Drive_SDA_Low %Wait_4_7_uS mov ax, [bp].address xchg ah, al ; ah = address next_byte: mov di, 8 ; set up bit counter next_bit: %Drive_SCL_Low %Wait_Half_Bit_Time mov bl, ah ; get current data and bl, 080H ; strip MSB mov dx, P2LTCH in al, dx and al, 7fh or al, bl ; set bit 7 to reflect ; data bit out dx, al ; xmit data bit %Wait_Half_Bit_Time %Release_SCL_High %Wait_For_SCL_To_Go_High ; At this point SCL is high so if there is another master ; attempting to gain the bus, it's data would be valid here. ; We need only check when our data is "1"... test bl, 80H ; Is data a "1"? jz won_arbitration ; If not -> don't check arbitration. mov dx, P2PIN in al, dx test al, 80H ; Is SDA high? jnz won_arbitration jmp lost_arbitration ; If SDA != 1 then we lost ; arbitration.... won_arbitration: %Wait_SCL_High_Time shl ah, 1 ; shift current byte dec di ; tick down bit counter jne next_bit ; continue bits ; a byte has been completed. Time to get an ACKNOWLEDGE. %Drive_SCL_Low %Wait_Half_Bit_Time %Release_SDA_High %Wait_Half_Bit_Time %Release_SCL_High %Wait_For_SCL_TO_Go_High ; SCL is now high. We must loop while checking SDA for 4.7us. ; With a count of 3 we have a delay of 89 clocks (5.5uS). This ; could be find tuned with NOPs when performance is critical. mov cx, 3 check_4_ack: %Get_SDA_Bit ; Is SDA a "0" jnz abort_no_ack ; if so -> abort loop check_4_ack ; if we've gotten to here, then an acknowledge was received. mov ah, byte ptr [si] inc si ; point to next byte dec word ptr [bp].count ; dec string counter js xfer_done jmp next_byte ; END OF MESSAGE: Issue a STOP condition xfer_done: mov di, 0 ; Normal completion code. jmp i2c_bus_stop abort_no_ack: cmp si, [bp].buffer_offset ; Check if this is the je slave_did_not_respond ; first byte (the address ). mov di, 4H ; Abort during xfer code. jmp i2c_bus_stop slave_did_not_respond: mov di, 02H ; i2c_bus_stop: %Drive_SCL_Low %Wait_Half_Bit_Time %Drive_SDA_Low %Wait_4_7_uS %Release_SCL_High %Wait_For_SCL_To_Go_High %Wait_4_7_uS %Release_SDA_High %Wait_For_SDA_To_Go_High mov ax, di pop es pop ds ret 8 ; Return and tear ; down stack frame. lost_arbitration: mov dx, P2LTCH in al, dx ; Release SDA and SCL or al, 0C0H out dx, al mov ax, 08H ; Lost arbitration code. pop es pop ds ret 8 i2c_xmit endp code ends end [LISTING TWO] $pagelength (30) $mod186 $debug $xref NAME i2c_receive; $include (/include/pcp_io.inc) PUBLIC i2c_recv ;****** EQUates ****** BUS_FREE_MIN EQU 1H ; Loop counter for free bus delay. MAXLEN EQU 255 ;****** STACK FRAME STRUCTURE ****** stack_frame STRUC ret_ip DW ? ret_cs DW ? buffer_offset DW ? buffer_segment DW ? count DW ? address DW ? stack_frame ENDS %*DEFINE(Drive_SCL_Low)( mov dx, P2LTCH in al, dx and al, 10111111B ; SCL is bit 6 out dx, al ) %*DEFINE(Release_SCL_High)( mov dx, P2LTCH in al, dx or al, 01000000B out dx, al ) %*DEFINE(Drive_SDA_Low)( mov dx, P2LTCH in al, dx and al, 01111111B ; SDA is bit 6 out dx, al ) %*DEFINE(Release_SDA_High)( mov dx, P2LTCH in al, dx or al, 10000000B out dx, al ) %*DEFINE(Wait_4_7_uS)( mov cx, 5 loop $ nop nop ) %*DEFINE(Wait_Half_Bit_Time)( mov cx, 3 loop $ ) %*DEFINE(Wait_SCL_Low_Time)( mov cx, 5 loop $ nop nop ) %*DEFINE(Wait_SCL_High_Time)( mov cx, 5 loop $ nop nop ) %*DEFINE(Wait_For_SCL_To_Go_Low)LOCAL wait( mov dx, P2PIN %wait: in al, dx test al, 01000000B jne %wait ) %*DEFINE(Wait_For_SCL_To_Go_High)LOCAL wait( mov dx, P2PIN %wait: in al, dx test al, 01000000B je %wait %*DEFINE(Wait_For_SDA_To_Go_High)LOCAL wait( mov dx, P2PIN %wait: in al, dx test al, 10000000B je %wait ) ) %*DEFINE(Get_SDA_Bit)( mov dx, P2PIN in al, dx and al, 0080H ) %*DEFINE(Check_For_Bus_Free)( mov dx, P2PIN in al, dx mov bl, 0C0H ; Mask for SCL and SDA. and al, bl ; If SCL and SDA are high xor al, bl ; this sequence will leave ) ; a zero in AX. code segment public assume cs:code i2c_recv proc far ; The LSB of the address for a READ always has a "1" in the LSB. ; The first step is to check for a legal address.... mov bp, sp push ds push es test word ptr [bp].address,01H ; Check for illegal ; address (an XMIT). jnz addr_ok ; The address passed was for a transmit (WRITE). This is ; illegal in this procedure.... mov ax, 20H ; Illegal addr pop es pop ds ret 8 ; Tear down stack frame addr_ok: cmp word ptr [bp].count, MAXLEN jg message_wrong_len cmp word ptr [bp].count, 1 ; check message length jge len_ok message_wrong_len: mov ax, 40H ; error code pop es pop ds ret 8 ; tear down frame len_ok: ; Test for I2C bus free condition. ; SCL and SDA must be high at least 4.7uS mov cx, BUS_FREE_MIN ; initialize free time counter. ; Following loop takes 48 clocks while cx>1 and 33 clocks on last iteration. ; To insure that bus is free, samples of bus must span at least 4.7uS. At 16Mhz ; 48*(62.5ns)= 3uS. First sample is at 0us, second at 3us, and third will be at ; 6. Although this exceeds 4.7us spec, it is better safe than sorry. bus_free_wait: %Check_For_Bus_Free jz i2c_bus_free ; At this point the bus is not available. mov ax, 01H ; 01= return code for pop es ; a busy bus. pop ds ret 8 ; return and tear down stack frame. i2c_bus_free: loop bus_free_wait ; bus may be free but wait 4.7uS required ; I2C bus is available, generate a START condition %Drive_SDA_Low %Wait_4_7_uS ; A receive begins with transmission of the ADDRESS mov di, 8 ; set up bit counter next_bit: %Drive_SCL_Low %Wait_Half_Bit_Time mov bx, [bp].address and bl, 080H ; strip MSB mov dx, P2LTCH in al, dx and al, 7fh or al, bl ; set bit 7 to reflect data bit out dx, al ; xmit data bit sal [bp].address,1 ; shift current byte %Wait_Half_Bit_Time %Release_SCL_High %Wait_For_SCL_To_Go_High ; At this point SCL is high so if there is another master ; attempting to gain the bus, it's data would be valid here. ; We need only check when our data is a "1"... test bl, 10000000B ; Is data a "1"? je won_arbitration ; If not -> don't check arbitration. mov dx, P2PIN in al, dx test al, 10000000B ; Is SDA high? jnz won_arbitration jmp lost_arbitration won_arbitration: %Wait_4_7_uS ; count off high time. dec di ; tick down bit counter jne next_bit ; continue bits ; The address has been completed. Time to get an ACKNOWLEDGE. %Drive_SCL_Low %Wait_Half_Bit_Time %Release_SDA_High %Wait_Half_Bit_Time %Release_SCL_High ; Here we are expecting to see an acknowledge from addressed slave receiver: %Wait_For_SCL_To_Go_High ; a wait state mov cx, 3 check_4_ack: mov dx, P2PIN in al, dx ; get SDA value and al, 10000000B ; is it high? jnz abort_no_ack ; if so -> abort nop nop nop ; NOPs for timing at 16Mhz loop check_4_ack ; if we've gotten to here, then an acknowledge was received. ; At this point in the code, slave receiver has acknowledged ; receipt of its address. SCL has just been driven low, SDA is floating. jmp start_recv abort_no_ack: %Drive_SCL_Low mov di, 02H ; Code for unresponsive slave. jmp i2c_bus_stop ; Now the master transmitter switches to master receiver.... start_recv: mov di, [bp].buffer_offset mov ax, [bp].buffer_segment mov es, ax next_byte_r: mov bx, 0 mov si, 8 next_bit_r: %Drive_SCL_Low %Wait_4_7_uS %Release_SCL_High %Wait_For_SCL_To_Go_High %Get_SDA_Bit shr al, 7 ; move SDA value to LSB or bl, al ; drop in lsb of bl %Wait_4_7_uS dec si ; tick down bit counter je byte_Recv_comp ; continue bits shl bl, 1 ; shift bl for next bit jmp next_bit_r ; The word has been completed. Time to send an ACKNOWLEDGE. byte_Recv_comp: mov al, bl stosb %Drive_SCL_Low %Wait_Half_Bit_Time ; Here we need to decide whether or not to transmit an acknowledge. If this is ; last byte required from slave, we do not send an ack; otherwise we do.... dec [bp].count ; decrement the message count cmp [bp].count, 0 jne send_ack %Release_SDA_High jmp do_ack send_ack: %Drive_SDA_Low do_ack: %Wait_Half_Bit_Time %Release_SCL_High %Wait_For_SCL_To_Go_High %Wait_4_7_uS %Drive_SCL_Low %Wait_Half_Bit_Time %Release_SDA_High cmp [bp].count, 0 je recv_done jmp next_byte_r recv_done: mov di, 00 i2c_bus_stop: %Wait_Half_Bit_Time %Drive_SDA_Low %Wait_4_7_uS %Release_SCL_High %Wait_For_SCL_To_Go_High %Wait_4_7_uS %Release_SDA_High %Wait_For_SDA_To_Go_High mov ax, di pop es pop ds ret 8 ; Return and tear down stack frame. lost_arbitration: mov dx, P2LTCH in al, dx ; Release SDA and SCL or al, 0C0H out dx, al pop es pop ds ret 8 i2c_recv endp code ends end [LISTING THREE] $mod186 $debug $xref $include (\include\pcp_io.inc) ; a file of EQUates for 186EB register names NAME i2c_example EXTRN i2c_recv:far, i2c_xmit:far %*DEFINE(XMIT(ADDR,COUNT,MESSAGE))( push %ADDR push %COUNT push seg %MESSAGE push offset %MESSAGE call i2c_xmit ) %*DEFINE(RECV(ADDR,COUNT,BUFFER))( push %ADDR push %COUNT push seg %BUFFER push offset %BUFFER call i2c_recv ) stack segment stack DW 20 DUP (?) t_o_s DW 0 stack ends data segment para public 'RAM' bus_msg db 00h,77h,01h,02h,04h,08h ; the LED I2C message recv_buff db 255 dup(?) data ends usr_code segment para 'RAM' assume cs:usr_code start: mov ax, data ; data segment init mov ds, ax cli assume ds:data mov ax, stack ; set up stack mov ss, ax assume ss:stack mov sp, offset t_o_s mov dx, P2DIR ; set up open-drain in ax, dx ; port pins on 186EB and ax, 3FH out dx, ax mov dx, P2CON in ax, dx and ax, 03FH out dx, ax ; The I2C address of the LED driver is 70H for a transmit. %XMIT(70H,6,bus_msg) ; send "bus" message ; The address for the clock is 0xA3 for a receive. %RECV(0A3H,15,recv_buff) ; read first 15 bytes in clock chip. usr_code ends end start Example 1: (a) 80C186 implementation of 4.7uS wait macro; (b) 80960CA implementation of 4.7uS wait macro. (a) %*DEFINE(Wait_4_7_uS)( mov cx, 5 ; 4 clocks loop $ ; 4*15+5 = 65 clocks nop ; 3 clocks nop ; 3 clocks ; total = 75 clocks ; 75 * 62.5ns = 4.69uS (close enough) ) (b) define(Wait_4_7_uS,' lda 0x17, r4 # instruction may be issued in parallel # so assume no clocks. 0b: cmpdeco 0, r4 # compare and decrement counter in r4 bne.t 0b # if !=0 branch back (predict taken # branch) # # The cmpdeco and bne.t together take 3 # clocks in parallel minimum. # # 0x17 (25 decimal) * 3 = 75 clocks # at 16MHz this is 4.69uS ')