home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Power-Programmierung
/
CD1.mdf
/
magazine
/
drdobbs
/
1992
/
06
/
i2c.asc
< prev
next >
Wrap
Text File
|
1992-05-18
|
24KB
|
676 lines
_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
')