Skip to content

Commit 0352ed2

Browse files
committed
Add core serial IO implementation
1 parent 34b165c commit 0352ed2

File tree

1 file changed

+218
-0
lines changed

1 file changed

+218
-0
lines changed

unbricked/serial-link/sio.asm

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
INCLUDE "hardware.inc"
2+
3+
; ::::::::::::::::::::::::::::::::::::::
4+
; :: ::
5+
; :: ______. ::
6+
; :: _ |````` || ::
7+
; :: _/ \__@_ |[- - ]|| ::
8+
; :: / `--<[|]= |[ m ]|| ::
9+
; :: \ .______ | ```` || ::
10+
; :: / !| `````| | + oo|| ::
11+
; :: ( ||[ ^u^]| | .. #|| ::
12+
; :: `-<[|]=|[ ]| `______// ::
13+
; :: || ```` | ::
14+
; :: || + oo| ::
15+
; :: || .. #| ::
16+
; :: !|______/ ::
17+
; :: ::
18+
; :: ::
19+
; ::::::::::::::::::::::::::::::::::::::
20+
;
21+
; This concludes the physical portion of the tutorial.
22+
; (I'm sure we can all agree that this was worth whatever it cost.)
23+
24+
25+
; Duration of timeout period in ticks. (for externally clocked device)
26+
DEF SIO_TIMEOUT_TICKS EQU 240
27+
; Duration of 'catchup' delay period in ticks. (for internally clocked device)
28+
DEF SIO_CATCHUP_TICKS EQU 1
29+
30+
DEF SIO_CONFIG_INTCLK EQU SCF_SOURCE
31+
DEF SIO_CONFIG_DEFAULT EQU 0
32+
EXPORT SIO_CONFIG_INTCLK
33+
34+
; SioStatus transfer state enum
35+
RSRESET
36+
DEF SIO_IDLE RB 1
37+
DEF SIO_XFER_START RB 1
38+
DEF SIO_XFER_STARTED RB 1
39+
DEF SIO_XFER_COMPLETED RB 1
40+
DEF SIO_XFER_FAILED RB 1
41+
EXPORT SIO_IDLE, SIO_XFER_START, SIO_XFER_STARTED, SIO_XFER_COMPLETED, SIO_XFER_FAILED
42+
43+
44+
SECTION "SioCore State", WRAM0
45+
; Sio config flags
46+
wSioConfig:: db
47+
; Sio state machine current state
48+
wSioState:: db
49+
; Source address of next value to transmit
50+
wSioTxPtr:: dw
51+
; Destination address of next received value
52+
wSioRxPtr:: dw
53+
; Number of transfers to perform (bytes to transfer)
54+
wSioCount:: db
55+
56+
; Timer state (as ticks remaining, expires at zero) for timeouts and delays.
57+
wTimer: db
58+
59+
60+
SECTION "SioCore Impl", ROM0
61+
; Initialise/reset Sio to the ready to use 'IDLE' state.
62+
; NOTE: Enables the serial interrupt.
63+
; @mut: AF, [IE]
64+
SioInit::
65+
ld a, SIO_CONFIG_DEFAULT
66+
ld [wSioConfig], a
67+
ld a, SIO_IDLE
68+
ld [wSioState], a
69+
ld a, 0
70+
ld [wTimer], a
71+
ld a, $FF ; using FFFF as 'null'/unset
72+
ld [wSioTxPtr + 0], a
73+
ld [wSioTxPtr + 1], a
74+
ld [wSioRxPtr + 0], a
75+
ld [wSioRxPtr + 1], a
76+
ld a, 0
77+
ld [wSioCount], a
78+
79+
; enable serial interrupt
80+
ldh a, [rIE]
81+
or a, IEF_SERIAL
82+
ldh [rIE], a
83+
ret
84+
85+
86+
; @mut: AF, HL
87+
SioTick::
88+
ld a, [wSioState]
89+
cp a, SIO_XFER_START
90+
jr z, process_queue
91+
cp a, SIO_XFER_STARTED
92+
jr z, .xfer_started
93+
cp a, SIO_XFER_COMPLETED
94+
jr z, process_queue
95+
; treat anything else as failed/error and do nothing
96+
ret
97+
.xfer_started:
98+
; update timeout on external clock
99+
ldh a, [rSC]
100+
and a, SCF_SOURCE
101+
ret nz
102+
ld a, [wTimer]
103+
and a, a
104+
ret z ; timer == 0, timeout disabled
105+
dec a
106+
ld [wTimer], a
107+
jr z, SioAbortTransfer
108+
ret
109+
.process_queue:
110+
; if SioCount > 0: start next transfer
111+
ld a, [wSioCount]
112+
and a, a
113+
ret z
114+
; if this device is the clock source (internal clock), do catchup delay
115+
ldh a, [rSC]
116+
bit SCB_SOURCE, a
117+
jr z, .start_next
118+
ld a, [wTimer]
119+
and a, a
120+
jr z, .start_next
121+
dec a
122+
ld [wTimer], a
123+
ret
124+
.start_next:
125+
; read the Tx pointer (points to the next value to send)
126+
ld hl, wSioTxPtr
127+
ld a, [hl+]
128+
ld h, [hl]
129+
ld l, a
130+
; set the clock source (do this first & separately from starting the transfer!)
131+
ld a, [wSioConfig]
132+
and a, SCF_SOURCE ; the sio config byte uses the same bit for the clock source
133+
ldh [rSC], a
134+
; load the value to send
135+
ld a, [hl]
136+
ldh [rSB], a
137+
; start the transfer
138+
ldh a, [rSC]
139+
or a, SCF_START
140+
ldh [rSC], a
141+
142+
; reset timeout (on externally clocked device)
143+
bit SCB_SOURCE, a
144+
jr nz, :+
145+
ld a, SIO_TIMEOUT_TICKS
146+
ld [wTimer], a
147+
:
148+
ld a, SIO_XFER_STARTED
149+
ld [wSioState], a
150+
ret
151+
152+
153+
; Abort the ongoing transfer (if any) and enter the FAILED state.
154+
; @mut: AF
155+
SioAbortTransfer::
156+
ld a, SIO_XFER_FAILED
157+
ld [wSioState], a
158+
ldh a, [rSC]
159+
res SCB_START, a
160+
ldh [rSC], a
161+
ret
162+
163+
164+
SioSerialInterruptHandler:
165+
push af
166+
push hl
167+
168+
; check that we were expecting a transfer
169+
ld a, [wSioState]
170+
cp a, SIO_XFER_STARTED
171+
ret nz
172+
173+
; store the received value
174+
ld hl, wSioRxPtr
175+
ld a, [hl+]
176+
ld h, [hl]
177+
ld l, a
178+
ldh a, [rSB]
179+
ld [hl+], a
180+
; store the updated Rx pointer
181+
ld a, l
182+
ld [wSioRxPtr + 0], a
183+
ld a, h
184+
ld [wSioRxPtr + 1], a
185+
186+
; update the Tx pointer
187+
ld hl, wSioTxPtr
188+
inc [hl] ; inc low byte
189+
jr nz, :+
190+
; inc high byte if overflow
191+
inc hl
192+
inc [hl]
193+
:
194+
195+
; update transfer count
196+
ld hl, wSioCount
197+
dec [hl]
198+
199+
; set transfer state to 'completed'
200+
ld a, SIO_XFER_COMPLETED
201+
ld [wSioState], a
202+
203+
ldh a, [rSC]
204+
and a, SCF_SOURCE
205+
jr z, :+
206+
; reset delay timer on internal clock
207+
ld a, SIO_CATCHUP_TICKS
208+
ld [wTimer], a
209+
:
210+
211+
pop hl
212+
pop af
213+
reti
214+
215+
216+
SECTION "Serial Interrupt", ROM0[$58]
217+
SerialInterrupt:
218+
jp SioSerialInterruptHandler

0 commit comments

Comments
 (0)