Ok, I suspect the length of the jamming phase might be too long for DCS to handle. I made it as long as 2 interrupts, which is about 10.9ms on the TI-86. The receiver in DCS probably times out before this jamming phase is completed. I can easily change my code to simply busy-wait for (almost) exactly 9ms if this is the case.
Anyway, here's the calcnet.asm source for you to look over:
Code: ; CALCnet driver for TI-85/86
; Copyright 2011 Christopher Williams
;
; some notes about register usage:
; c is used as the data byte in the byte sender/receiver routines
; zero flag is cleared by byte sender to indicate collision
; zero flag is set by byte receiver to indicate timeout
;
; d is used as a port IN mask for the delay_and_sense routine
; e is the delay length for the delay and delay_and_sense routines
;
; b is used for loops (djnz)
;
; bc is byte count (eg, frame size)
; hl is frame start
; de is misc (checksum, etc)
LINKPORT equ 7
; output with *_OUT_MASK set to pull that line low (0=high, 1=low)
NONE_OUT_MASK equ 0b11000000
TIP_OUT_MASK equ 0b11010100
RING_OUT_MASK equ 0b11101000
BOTH_OUT_MASK equ TIP_OUT_MASK|RING_OUT_MASK
; *_IN_MASK is clear when that line is low (0=low, 1=high)
TIP_IN_MASK equ 0b00000001
RING_IN_MASK equ 0b00000010
BOTH_IN_MASK equ TIP_IN_MASK|RING_IN_MASK
CLOCK_OUT_MASK equ TIP_OUT_MASK
DATA_OUT_MASK equ RING_OUT_MASK
CLOCK_IN_MASK equ TIP_IN_MASK
DATA_IN_MASK equ RING_IN_MASK
ACK_BYTE equ 0xaa
NAK_BYTE equ 0x42 ; anything other than 0xaa
; input:
; E = number of loops (delay=E*16+5 cycles, not including CALL)
; (unconditional CALL is 17 cycles)
; output:
; E = 0
delay:
dec e ; 4
jr nz,delay ; 12/7
ret ; 10
; input:
; E = number of loops (delay=E*40+5 cycles, not including CALL)
; (unconditional CALL is 17 cycles)
; (delay may be shorter if a line is pulled low)
; D = mask for which lines to sense
; output:
; Z flag is clear if any masked line is pulled low
; (timeout: ret z, collision: ret nz)
; E = 0 after a full delay
; A is link state ANDed with the input mask (D)
wait_any:
in a,(LINKPORT) ; 11
and d ; 4
cp d ; 4
ret nz ; 5
dec e ; 4
jr nz,wait_any ; 12/7
ret ; 10
; input:
; BC = number of loops (delay=BC*50+5 cycles)
; D = mask for which lines to sense
; output:
; Z is clear if any masked line is pulled low
; A is link state ANDed with the input mask (D)
waitms_any:
in a,(LINKPORT) ; 11
and d ; 4
cp d ; 4
ret nz ; 5
dec bc ; 6
ld a,b ; 4
or c ; 4
jr nz,waitms_any ; 12/7
ret ; 10
; input:
; E = number of loops (delay=E*38+5 cycles)
; D = mask for which lines to sense (all lines must be pulled low)
; output:
; Z is clear if all masked lines are pulled low
; E = 0 after a full delay
; A is link state ANDed with the input mask (D), and possibly with high bit set
wait_all:
in a,(LINKPORT) ; 11
and d ; 4
jr z,@all ; 7
dec e ; 4
jr nz,wait_all ; 12/7
ret ; 10
@all
or 0x80 ; clear z flag
ret
; input:
; E = number of loops (delay=E*42+5 cycles)
; output:
; Z is clear if all masked lines are released, or set on timeout
wait_all_high:
in a,(LINKPORT) ; 11
and d ; 4
cp d ; 4
jr z,@all ; 7
dec e ; 4
jr nz,wait_all_high ; 12/7
ret ; 10
@all
or 0x80
ret
; delay=BC*46+5 cycles
waitms_any_high:
in a,(LINKPORT) ; 11
and d ; 4
ret nz ; 5
dec bc ; 6
ld a,b ; 4
or c ; 4
jr nz,waitms_any_high ; 12/7
ret ; 10
; input:
; C = byte to send
; output:
; zero is clear on collision
; C is complemented
; A, B, D, E are destroyed
; HL is preserved
send_byte:
ld d,BOTH_IN_MASK
; wait 52+110us and sense
ld e,24 ;(972+40)/41
call wait_any
ret nz ; collision
; pull clock and data low
ld a,BOTH_OUT_MASK
out (LINKPORT),a
; wait 52us
ld e,18
call delay
; release clock and data
ld a,NONE_OUT_MASK
out (LINKPORT),a
; wait 43us and sense if anyone is pulling either line low
ld e,5
call wait_any
ret nz ; collision
; send 8 bits
ld b,8
; complement the byte so we can output the correct levels below
; (unless my logic is backwards, in which case these 3 lines can
; be removed)
ld a,c
cpl
ld c,a
@bit
rlc c ; LSB first
; set data line to appropriate level (0=low, 1=high)
sbc a,a ; 4
and DATA_OUT_MASK ; 7
or NONE_OUT_MASK ; 7
out (LINKPORT),a
; wait 17us
; do we need to sense the clock line during this delay?
ld e,5
call delay
; pull clock low
or CLOCK_OUT_MASK
out (LINKPORT),a
; wait 35us
ld e,11
call delay
; release clock and data
ld a,NONE_OUT_MASK
out (LINKPORT),a
; wait 52us
; should we sense for collision here too?
ld e,18
call delay
djnz @bit
ret
; input:
; hl = base address
; bc = size
; output:
; zero is clear on collision
; hl = base address + size
; de = checksum
send_bytes:
push bc
ld c,(hl)
ld a,e
add a,c
ld e,a
adc a,d
sub e
ld d,a
push de
call send_byte
pop de
pop bc
ret nz ; collision
cpi
jp pe,send_bytes
xor a ; set z
ret
; setting WAIT_FULL_9ms to non-zero causes send_frame to loop for the full 9ms
; clearing it to zero causes send_frame to wait 2 interrupts (~10.9ms) instead
WAIT_FULL_9ms equ 0
Cn2_ClearRecBuf:
ld hl,inframe
jr clear_frame
Cn2_ClearSendBuf:
ld hl,outframe
; fall through
; input:
; hl = frame start
clear_frame:
ld bc,5+5+2+256
xor a
; fall through
; input:
; hl = frame start
; bc = bytes to set
; a = byte to set
; note: bc must be at least 2, otherwise this routine will treat it as 65538!
memset:
ld d,h
ld e,l
inc de
dec bc
ld (hl),a
ldir
ret
Cn2_Setup:
.if !WAIT_FULL_9ms
call reset_send_state
.endif
call Cn2_ClearRecBuf
call Cn2_ClearSendBuf
int_addr equ 0xf8f8 ;Start of interrupt code
int_vectors equ 0xfa00
; set up im2 vectors
ld hl,int_vectors
ld de,int_vectors+1
ld (hl),int_addr/256
ld bc,256
ldir
; set up int handler
ld hl,int_copy
ld de,int_addr
ld bc,int_end-int_start
ldir
; set i register
ld a,int_vectors/256
ld i,a
im 2
ret
int_copy:
.rorg int_addr
int_start:
ex af,af'
exx
ld hl,0xfc00
inc (hl)
call send_frame
; call receive_frame only if send_frame completed
;call nz,receive_frame
jp 0x66
exx
ex af,af'
reti
int_end
.rorg $$
Cn2_Setdown:
im 1
.if WAIT_FULL_9ms
ret
.else
; fall through
.endif
.if !WAIT_FULL_9ms
reset_send_state
ld hl,send_frame_start_state
; fall through
; input:
; hl = state
; output:
; none
set_send_state
ld (send_state),hl
ret
.endif
; send a frame if one is ready to send
; input:
; none
; output:
; Z is clear if this is completed
send_frame:
ld hl,0xfff0
inc (hl)
; see if frame is ready to send
ld hl,outpayloadlen+1
ld a,(hl)
and 0x80
jr z,@reset_link
push hl
ld hl,0xfff1
inc (hl)
pop hl
; clear high bit in payload length
xor (hl)
ld b,a
dec hl
ld c,(hl)
; bc = payload length
; can't send more than 256 bytes
ld hl,256
sbc hl,bc
.if WAIT_FULL_9ms
jr nc,send_frame_start_state
.else
jr c,@reset_link
@restore_send_state
ld hl,(send_state)
jp (hl)
.endif
@reset_link
ld a,NONE_OUT_MASK
out (LINKPORT),a
or a ; clear Z (NONE_OUT_MASK is non-zero)
.if WAIT_FULL_9ms
ret
.else
jr reset_send_state
.endif
send_frame_start_state
ld hl,0xfff2
inc (hl)
; see if this is a broadcast frame
ld hl,outdstid
xor a
ld b,5
@broadcastloop
or (hl)
inc hl
djnz @broadcastloop
ld (notbroadcast),a
; set the srcid to our calcid
ld hl,mycalcid
ld de,outsrcid
ld bc,5
ldir
; set counter to 3
ld b,3
ld d,BOTH_IN_MASK
@loop
; wait up to 833us or until data or clock are pulled low
ld e,124
call wait_any
jr z,@mine
; if a line was pulled low,
; decrement counter and goto @loop if counter>0
djnz @loop
; if counter == 0, return and wait for the next interrupt
or 1
ret
@mine
; pull clock low
ld a,CLOCK_OUT_MASK
out (LINKPORT),a
; wait 9ms
.if WAIT_FULL_9ms
; We could return from our interrupt at this point and come back here
; at the next interrupt (by saving our state information), but we will
; not be able to sense any activity on the data line if we do that.
ld d,DATA_IN_MASK
ld bc,1080
call waitms_any
ret nz ; collision
.else
; Here we keep the clock low for 2 complete interrupts (about 10.9ms)
; return to send_frame_5461us_1 after the next interrupt
xor a ; set Z
ld hl,@send_frame_5461us_1
jr set_send_state
@send_frame_5461us_1
; return to send_frame_5461us_2 after the next interrupt
xor a ; set Z
ld hl,@send_frame_5461us_2
jr set_send_state
@send_frame_5461us_2
call reset_send_state
.endif
; release clock
ld a,NONE_OUT_MASK
out (LINKPORT),a
; send frame header (5+5+2)
ld hl,outframe
ld bc,5+5+2
call send_bytes
ret nz ; collision
; send payload
ld bc,(outpayloadlen)
ld a,0x7f
and b
ld b,a
ld de,0 ; start with a fresh checksum
call send_bytes
; send our checksum
call send_word
ret nz ; collision
; if it's a broadcast frame, then we're done
ld a,(notbroadcast)
or a
jr z,@success
; get checksum from receiver
push hl ; save our checksum
call receive_word
pop de ; de = our checksum
jr z,@timeout ; timeout
;ld hl,(checksum)
; hl = our checksum from earlier
or a
sbc hl,de
jr nz,@nak
; send an ack byte
ld c,ACK_BYTE
call send_byte
@success
; clear high bit in payload length
ld hl,outpayloadlen+1
ld a,(hl)
and 0x7f
ld (hl),a
@timeout
or 1 ; clear z
jp reset_send_state
@nak ; checksums no matchy
; send a nak byte
ld c,NAK_BYTE
call send_byte
or 1 ; clear z
jp reset_send_state
.if 0
; input:
; hl = base address
; bc = byte count
; output:
; de = checksum
; bc = 0
; hl = base address + byte count
compute_checksum:
ld de,0
add_checksum ; call this if you want to continue a checksum
ld a,(hl)
add a,e
ld e,a
adc a,d
sub e
ld d,a
cpi
jp pe,add_checksum
ret
.endif
; receive_byte
; input: none
; output:
; c = data byte
; Z flag is set on timeout
; HL is unaffected
receive_byte:
ld d,BOTH_IN_MASK
; wait up to 355us or until clock and data are both pulled low
ld e,56
call wait_all
ret z ; timeout
; wait up to 355us or until clock and data are released
ld e,56
call wait_all_high
ret z ; timeout
;ld b,8
;ld c,0
ld bc,0x0800 ; b=8, c=0
ld d,CLOCK_IN_MASK
; read in all 8 bits
@bit
; wait up to 355us or until clock is pulled low
ld e,56
call wait_all
ret z ; timeout
; read in the link state immediately
in a,(LINKPORT)
; A contains link state
; rotate link state right 2 times to get data bit (bit 1)
rrca
rrca
rr c ; rotate that bit into C
ld e,56
call wait_all_high
ret z ; timeout
djnz @bit
; return data in C (zero flag is also clear)
ret
; 40 bytes :)
; input:
; hl = base address to put bytes in
; bc = size
; output:
; Z is set on timeout
; hl = base address + size
; DE = checksum?
receive_bytes
push de
push bc
call receive_byte
ld a,c
pop bc
pop de
ret z ; timeout
; add checksum
add a,e
ld e,a
adc a,d
sub e
ld d,a
cpi
jp pe,receive_bytes
or 1 ; clear z
ret
; input:
; de = word
; output:
; Z is clear on collision
; hl = sent word
send_word:
ex de,hl
send_word_hl
ld c,l
call send_byte
ret nz
ld c,h
jp send_byte
; input:
; none
; output:
; Z is set on timeout
; hl = word
receive_word:
call receive_byte
ret z ; timeout
push bc
call receive_byte
pop hl
ld h,c
ret ; return our timeout state
; input:
; none
; output:
; Z is set on timeout or if dstid is not broadcast id nor mycalcid
; note: this is unrolled into 3 loops:
; 1. tests both broadcast and calc id
; 2. tests for calc id
; 3. tests for broadcast id
; the first loop jumps to either loop 2 or 3 if one of the tests fails,
; and then loops 2 and 3 exit if the opposite test fails too
; this could be shortened by tracking the byte differences for each id
; (broadcast and mycalc) in 2 registers (or variables) and then failing if both
; differences become non-zero
receive_dstid:
ld hl,indstid
ld de,mycalcid
ld b,5
@dstidloop:
push bc
push de
call receive_byte
ld (hl),c
pop de
pop bc
ret z ; timeout
xor a
cp c
jr nz,@not_bcast
ld a,(de)
cp c
jr nz,@not_mycalcid
djnz @dstidloop
or 1 ; clear z
ret
@not_bcast_loop
push bc
push de
call receive_byte
ld (hl),c
pop de
pop bc
ret z
@not_bcast
ld a,(de)
cp c
inc de
inc hl
jr nz,@not_us
djnz @not_bcast_loop
or 1 ; clear Z
ret
@not_mycalcid_loop
push bc
call receive_byte
ld (hl),c
pop bc
ret z
xor a
cp c
jr nz,@not_us
@not_mycalcid
inc hl
djnz @not_mycalcid_loop
or 1 ; clear Z
ret
@not_us
xor a
ret
receive_frame:
ld a,(inpayloadlen+1)
and 0x80
ret nz ; we have a valid frame in the receive buffer already
in a,(LINKPORT)
ld d,CLOCK_IN_MASK
and d
ret nz ; clock is high - no imminent transmission
; wait up to 9ms (or a little longer) or until clock is released
ld bc,1566 ; 12ms
call waitms_any_high
ret z ; timeout
call receive_dstid
ret z ; timeout, or frame is not for us
; receive srcid and payloadlen
; ld hl,insrcid
ld bc,7
call receive_bytes
ret z ; timeout
; payload len must be between 1 and 255
ld bc,(inpayloadlen)
xor a
cp b
ret nz ; MSB must be zero
cp c
ret z ; LSB must be non-zero
; CALCnet2.2 provides no provisions for informing the sender that the
; frame is too large for us to handle, so the sender may repeatedly try
; to send the same frame without any luck
ld de,0 ; start with a fresh checksum
call receive_bytes
ret z ; timeout
; receive checksum from sender
push de ; save our checksum
call receive_word
pop de ; restore our checksum
ret z ; timeout
or a
sbc hl,de
ret nz ; bad checksum - we exit
; (should we send our checksum anyway?)
; send our checksum
call send_word
ret nz ; collision
call receive_byte
ret z ; timeout
ld a,ACK_BYTE ; is it ack?
cp c
ret nz ; not ack
ld hl,inpayloadlen+1
ld a,(hl)
or 0x80
ld (hl),a ; set MSb of payload len
ret
mycalcid
db "TI-86" ; XXX
@code_end
.segu data
.org @code_end
; misc variables
notbroadcast ds 1
.if !WAIT_FULL_9ms
send_state ds 2
.endif
outframe
outdstid ds 5
outsrcid ds 5
outpayloadlen ds 2
outpayload ds 256
inframe
indstid ds 5
insrcid ds 5
inpayloadlen ds 2
inpayload ds 256
.seg code
And here is testcn2.asm:
Code: GET_KEY equ 0x5371
K_EXIT equ 55
.org 0xd748
call Cn2_Setup
ld hl,frame
ld de,outframe
ld bc,5+5+2+1
ldir
@loop
halt
.if 1
; continuously send the frame
ld hl,outpayloadlen+1
ld a,(hl)
or 0x80
ld (hl),a
.else
; exit after the frame is sent
ld a,(outpayloadlen+1)
and 0x80
jr z,@done
.endif
call GET_KEY
cp K_EXIT
jr nz,@loop
@done
call Cn2_Setdown
ret
frame:
db 0,0,0,0,0 ; broadcast
db 0,0,0,0,0 ; our calc id (will be filled in by Cn2)
dw 1 ; payload length
db 1 ; payload
.include "calcnet.asm"