Skip to content

Commit 431baff

Browse files
committed
Add core serial IO implementation
1 parent 34b165c commit 431baff

File tree

1 file changed

+283
-0
lines changed

1 file changed

+283
-0
lines changed

unbricked/serial-link/sio.asm

+283
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
; ::::::::::::::::::::::::::::::::::::::
2+
; :: ::
3+
; :: ______. ::
4+
; :: _ |````` || ::
5+
; :: _/ \__@_ |[- - ]|| ::
6+
; :: / `--<[|]= |[ m ]|| ::
7+
; :: \ .______ | ```` || ::
8+
; :: / !| `````| | + oo|| ::
9+
; :: ( ||[ ^u^]| | .. #|| ::
10+
; :: `-<[|]=|[ ]| `______// ::
11+
; :: || ```` | ::
12+
; :: || + oo| ::
13+
; :: || .. #| ::
14+
; :: !|______/ ::
15+
; :: ::
16+
; :: ::
17+
; ::::::::::::::::::::::::::::::::::::::
18+
19+
INCLUDE "hardware.inc"
20+
21+
; Duration of timeout period in ticks. (for externally clocked device)
22+
DEF SIO_TIMEOUT_TICKS EQU 60
23+
24+
; Catchup delay duration
25+
DEF SIO_CATCHUP_SLEEP_DURATION EQU 100
26+
27+
DEF SIO_CONFIG_INTCLK EQU SCF_SOURCE
28+
DEF SIO_CONFIG_RESERVED EQU $02
29+
DEF SIO_CONFIG_DEFAULT EQU $00
30+
EXPORT SIO_CONFIG_INTCLK
31+
32+
; SioStatus transfer state enum
33+
RSRESET
34+
DEF SIO_IDLE RB 1
35+
DEF SIO_FAILED RB 1
36+
DEF SIO_DONE RB 1
37+
DEF SIO_BUSY RB 0
38+
DEF SIO_XFER_STARTED RB 1
39+
EXPORT SIO_IDLE, SIO_FAILED, SIO_DONE
40+
EXPORT SIO_BUSY, SIO_XFER_STARTED
41+
42+
DEF SIO_BUFFER_SIZE EQU 32
43+
44+
45+
; PACKET
46+
47+
DEF SIO_PACKET_HEAD_SIZE EQU 2
48+
DEF SIO_PACKET_DATA_SIZE EQU SIO_BUFFER_SIZE - SIO_PACKET_HEAD_SIZE
49+
50+
DEF SIO_PACKET_START EQU $70
51+
DEF SIO_PACKET_END EQU $7F
52+
53+
54+
SECTION "SioBufferRx", WRAM0, ALIGN[8]
55+
wSioBufferRx:: ds SIO_BUFFER_SIZE
56+
57+
58+
SECTION "SioBufferTx", WRAM0, ALIGN[8]
59+
wSioBufferTx:: ds SIO_BUFFER_SIZE
60+
61+
62+
SECTION "SioCore State", WRAM0
63+
; Sio config flags
64+
wSioConfig:: db
65+
; Sio state machine current state
66+
wSioState:: db
67+
; Number of transfers to perform (bytes to transfer)
68+
wSioCount:: db
69+
wSioBufferOffset:: db
70+
; Timer state (as ticks remaining, expires at zero) for timeouts and delays.
71+
; HACK: this is only "public" (::) for access by debug display code.
72+
wSioTimer:: db
73+
74+
75+
SECTION "Sio Serial Interrupt", ROM0[$58]
76+
SerialInterrupt:
77+
jp SioInterruptHandler
78+
79+
80+
SECTION "SioCore Impl", ROM0
81+
; Initialise/reset Sio to the ready to use 'IDLE' state.
82+
; NOTE: Enables the serial interrupt.
83+
; @mut: AF, [IE]
84+
SioInit::
85+
ld a, SIO_CONFIG_DEFAULT
86+
ld [wSioConfig], a
87+
ld a, SIO_IDLE
88+
ld [wSioState], a
89+
ld a, 0
90+
ld [wSioTimer], a
91+
ld a, 0
92+
ld [wSioCount], a
93+
ld [wSioBufferOffset], a
94+
95+
; enable serial interrupt
96+
ldh a, [rIE]
97+
or a, IEF_SERIAL
98+
ldh [rIE], a
99+
ret
100+
101+
102+
; @mut: AF, HL
103+
SioTick::
104+
ld a, [wSioState]
105+
cp a, SIO_XFER_STARTED
106+
jr z, .xfer_started
107+
; anything else: do nothing
108+
ret
109+
.xfer_started:
110+
ld a, [wSioCount]
111+
and a, a
112+
jr nz, :+
113+
ld a, SIO_DONE
114+
ld [wSioState], a
115+
ret
116+
:
117+
; update timeout on external clock
118+
ldh a, [rSC]
119+
and a, SCF_SOURCE
120+
ret nz
121+
ld a, [wSioTimer]
122+
and a, a
123+
ret z ; timer == 0, timeout disabled
124+
dec a
125+
ld [wSioTimer], a
126+
jr z, SioAbort
127+
ret
128+
129+
130+
; Abort the ongoing transfer (if any) and enter the FAILED state.
131+
; @mut: AF
132+
SioAbort::
133+
ld a, SIO_FAILED
134+
ld [wSioState], a
135+
ldh a, [rSC]
136+
res SCB_START, a
137+
ldh [rSC], a
138+
ret
139+
140+
141+
SioInterruptHandler:
142+
push af
143+
push hl
144+
145+
; check that we were expecting a transfer (to end)
146+
ld hl, wSioCount
147+
ld a, [hl]
148+
and a
149+
jr z, .end
150+
dec [hl]
151+
; Get buffer pointer offset (low byte)
152+
ld a, [wSioBufferOffset]
153+
ld l, a
154+
; Get received value
155+
ld h, HIGH(wSioBufferRx)
156+
ldh a, [rSB]
157+
; NOTE: incrementing L here
158+
ld [hl+], a
159+
; Store updated buffer offset
160+
ld a, l
161+
ld [wSioBufferOffset], a
162+
; If completing the last transfer, don't start another one
163+
; NOTE: We are checking the zero flag as set by `dec [hl]` up above!
164+
jr z, .end
165+
; Next value to send
166+
ld h, HIGH(wSioBufferTx)
167+
ld a, [hl]
168+
ldh [rSB], a
169+
call SioPortStart
170+
171+
.end:
172+
ld a, SIO_TIMEOUT_TICKS
173+
ld [wSioTimer], a
174+
pop hl
175+
pop af
176+
reti
177+
178+
179+
; @mut: AF
180+
SioTransferStart::
181+
; TODO: something if SIO_BUSY ...?
182+
183+
ld a, SIO_BUFFER_SIZE
184+
ld [wSioCount], a
185+
ld a, 0
186+
ld [wSioBufferOffset], a
187+
188+
; set the clock source (do this first & separately from starting the transfer!)
189+
ld a, [wSioConfig]
190+
and a, SCF_SOURCE ; the sio config byte uses the same bit for the clock source
191+
ldh [rSC], a
192+
; reset timeout
193+
ld a, SIO_TIMEOUT_TICKS
194+
ld [wSioTimer], a
195+
; send first byte
196+
ld a, [wSioBufferTx]
197+
ldh [rSB], a
198+
call SioPortStart
199+
ld a, SIO_XFER_STARTED
200+
ld [wSioState], a
201+
ret
202+
203+
204+
; @mut: AF, L
205+
SioPortStart:
206+
; If internal clock source, do catchup delay
207+
ldh a, [rSC]
208+
and a, SCF_SOURCE
209+
; NOTE: preserve `A` to be used after the loop
210+
jr z, .start_xfer
211+
ld l, SIO_CATCHUP_SLEEP_DURATION
212+
.catchup_sleep_loop:
213+
nop
214+
nop
215+
dec l
216+
jr nz, .catchup_sleep_loop
217+
.start_xfer:
218+
or a, SCF_START
219+
ldh [rSC], a
220+
ret
221+
222+
223+
SECTION "SioPacket Impl", ROM0
224+
; Initialise the Tx buffer as a packet, ready for data.
225+
; Returns a pointer to the packet data section.
226+
; @return HL: packet data pointer
227+
; @mut: AF, C, HL
228+
SioPacketTxPrepare::
229+
ld hl, wSioBufferTx
230+
; packet always starts with constant ID
231+
ld a, SIO_PACKET_START
232+
ld [hl+], a
233+
; checksum = 0 for initial calculation
234+
ld a, 0
235+
ld [hl+], a
236+
; clear packet data
237+
ld a, SIO_PACKET_END
238+
ld c, SIO_PACKET_DATA_SIZE
239+
:
240+
ld [hl+], a
241+
dec c
242+
jr nz, :-
243+
ld hl, wSioBufferTx + SIO_PACKET_HEAD_SIZE
244+
ret
245+
246+
247+
; @mut: AF, C, HL
248+
SioPacketTxFinalise::
249+
ld hl, wSioBufferTx
250+
call SioPacketChecksum
251+
ld [wSioBufferTx + 1], a
252+
ret
253+
254+
255+
; @return F.Z: if check OK
256+
; @mut: AF, C, HL
257+
SioPacketRxCheck::
258+
ld hl, wSioBufferRx
259+
; expect constant
260+
ld a, [hl]
261+
cp a, SIO_PACKET_START
262+
ret nz
263+
264+
; check the sum
265+
call SioPacketChecksum
266+
and a, a
267+
ret ; F.Z already set (or not)
268+
269+
270+
; Calculate a simple 1 byte checksum of a Sio data buffer.
271+
; sum(buffer + sum(buffer + 0)) == 0
272+
; @param HL: &buffer
273+
; @return A: sum
274+
; @mut: AF, C, HL
275+
SioPacketChecksum:
276+
ld c, SIO_BUFFER_SIZE
277+
ld a, c
278+
:
279+
sub [hl]
280+
inc hl
281+
dec c
282+
jr nz, :-
283+
ret

0 commit comments

Comments
 (0)