Skip to content

Commit 3a0e30d

Browse files
committed
Add core serial IO implementation
1 parent 34b165c commit 3a0e30d

File tree

1 file changed

+280
-0
lines changed

1 file changed

+280
-0
lines changed

unbricked/serial-link/sio.asm

Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
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+
; If internal clock source, do catchup delay
170+
ldh a, [rSC]
171+
and a, SCF_SOURCE
172+
; NOTE: preserve `A` to be used after the loop
173+
jr z, .start_xfer
174+
ld l, SIO_CATCHUP_SLEEP_DURATION
175+
.catchup_sleep_loop:
176+
nop
177+
nop
178+
dec l
179+
jr nz, .catchup_sleep_loop
180+
.start_xfer:
181+
or a, SCF_START
182+
ldh [rSC], a
183+
184+
.end:
185+
ld a, SIO_TIMEOUT_TICKS
186+
ld [wSioTimer], a
187+
pop hl
188+
pop af
189+
reti
190+
191+
192+
; @mut: AF
193+
SioTransferStart::
194+
; TODO: something if SIO_BUSY ...?
195+
196+
ld a, SIO_BUFFER_SIZE
197+
ld [wSioCount], a
198+
ld a, 0
199+
ld [wSioBufferOffset], a
200+
201+
; set the clock source (do this first & separately from starting the transfer!)
202+
ld a, [wSioConfig]
203+
and a, SCF_SOURCE ; the sio config byte uses the same bit for the clock source
204+
ldh [rSC], a
205+
; reset timeout
206+
ld a, SIO_TIMEOUT_TICKS
207+
ld [wSioTimer], a
208+
; send first byte
209+
ld a, [wSioBufferTx]
210+
ldh [rSB], a
211+
; start the transfer
212+
ldh a, [rSC]
213+
or a, SCF_START
214+
ldh [rSC], a
215+
ld a, SIO_XFER_STARTED
216+
ld [wSioState], a
217+
ret
218+
219+
220+
SECTION "SioPacket Impl", ROM0
221+
; Initialise the Tx buffer as a packet, ready for data.
222+
; Returns a pointer to the packet data section.
223+
; @return HL: packet data pointer
224+
; @mut: AF, C, HL
225+
SioPacketTxPrepare::
226+
ld hl, wSioBufferTx
227+
; packet always starts with constant ID
228+
ld a, SIO_PACKET_START
229+
ld [hl+], a
230+
; checksum = 0 for initial calculation
231+
ld a, 0
232+
ld [hl+], a
233+
; clear packet data
234+
ld a, SIO_PACKET_END
235+
ld c, SIO_PACKET_DATA_SIZE
236+
:
237+
ld [hl+], a
238+
dec c
239+
jr nz, :-
240+
ld hl, wSioBufferTx + SIO_PACKET_HEAD_SIZE
241+
ret
242+
243+
244+
; @mut: AF, C, HL
245+
SioPacketTxFinalise::
246+
ld hl, wSioBufferTx
247+
call SioPacketChecksum
248+
ld [wSioBufferTx + 1], a
249+
ret
250+
251+
252+
; @return F.Z: if check OK
253+
; @mut: AF, C, HL
254+
SioPacketRxCheck::
255+
ld hl, wSioBufferRx
256+
; expect constant
257+
ld a, [hl]
258+
cp a, SIO_PACKET_START
259+
ret nz
260+
261+
; check the sum
262+
call SioPacketChecksum
263+
and a, a
264+
ret ; F.Z already set (or not)
265+
266+
267+
; Calculate a simple 1 byte checksum of a Sio data buffer.
268+
; sum(buffer + sum(buffer + 0)) == 0
269+
; @param HL: &buffer
270+
; @return A: sum
271+
; @mut: AF, C, HL
272+
SioPacketChecksum:
273+
ld c, SIO_BUFFER_SIZE
274+
ld a, c
275+
:
276+
sub [hl]
277+
inc hl
278+
dec c
279+
jr nz, :-
280+
ret

0 commit comments

Comments
 (0)