-
Notifications
You must be signed in to change notification settings - Fork 1
/
uart.s
305 lines (275 loc) · 13.2 KB
/
uart.s
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
.segment "os"
.export UartInit
.export UartRxBufLen
.export UartRxBufRead
.export UartTxBufWrite
.export UartTxBufWriteBlocking
.export UartTxStr
.export UartRxInterrupt
.export UartTxInterrupt
.export UART := $DC20
.export UART_MRA = $0 ; read + write
.export UART_SRA = $1 ; read
.export UART_CSRA = $1 ; write
.export UART_CRA = $2 ; write
.export UART_RXFIFOA = $3 ; read
.export UART_TXFIFOA = $3 ; write
.export UART_IPCR = $4 ; read
.export UART_ACR = $4 ; write
.export UART_ISR = $5 ; read
.export UART_IMR = $5 ; write
.export UART_CTU = $6 ; read
.export UART_CTPU = $6 ; write
.export UART_CTL = $7 ; read
.export UART_CTPL = $7 ; write
.export UART_MRB = $8 ; read + write
.export UART_SRB = $9 ; read
.export UART_CSRB = $9 ; write
.export UART_CRB = $A ; write
.export UART_RXFIFOB = $B ; read
.export UART_TXFIFOB = $B ; write
.export UART_MISC = $C ; read + write
.export UART_IPR = $D ; read
.export UART_OPCR = $D ; write
.export UART_SOPR = $E ; write
.export UART_ROPR = $F ; write
.importzp R0, R1
.importzp ZP_INTERRUPT
.proc UartInit
JSR UartRxBufInit
JSR UartTxBufInit
JSR UartConfigure
RTS
.endproc
.proc UartConfigure
LDA #%10110000 ; Select MR0A
STA UART+UART_CRA
LDA #%10001100 ; Mode Register 0 channel A (MR0A)
; |||||||+----------> 0: baud rate extended I
; ||||||+-----------> 1: TEST2
; |||||+------------> 2: baud rate extended II
; ||||+-------------> 3: FIFO size (8, 16)
; ||++--------------> 5:4: TX Interrupt fill level
; |+----------------> 6: RxINT[2] fill level
; +-----------------> 7: RxWATCHDOG
STA UART+UART_MRA
LDA #%00010000 ; Reset to MR1A
STA UART+UART_CRA
LDA #%11010011 ; Mode Register 1 channel A (MR1A)
; ||||||++----------> 1:0: bits per char (5,6,7,*8*)
; |||||+------------> 2: parity type (even, odd)
; |||++-------------> 4:3: parity mode (with, force, *no*, multi)
; ||+---------------> 5: error mode (char, block)
; |+----------------> 6: RxINT[1]
; +-----------------> 7: RxRTS (also needs OPR[0]=1)
STA UART+UART_MRA ; auto advances to MR2A after this
LDA #%00110111 ; Mode Register 2 channel A (MR2A)
; ||||++++----------> 3:0 stop bit length (111=0x7: 1.000)
; |||+--------------> 4 CTSN Enable Tx (1: CTS on)
; ||+---------------> 5 RTSN Control Tx (1: RTS on)
; ++----------------> 6:7 Channel mode (normal, echo, lloop, rloop)
STA UART+UART_MRA ; Mode Register 2 channel A (MR2A)
LDA #%01100110 ; Clock Select Register
; ||||++++----------> 3:0: TX baud (0110 w/ MR0[2]=1: 115,200)
; ++++--------------> 7:4: RX baud
STA UART+UART_CSRA
LDA #%01100000 ; Auxiliary Control Register
; ||||++++----------> 3:0: IP3:0 change interrupt enable
; |+++--------------> 6:4: counter/timer mode/clock source (110=X1/CLK)
; +-----------------> 7: baud rate generator select
STA UART+UART_ACR
LDA #%00000001 ; Set Output Port bits Register (SOPR)
; +----------> 0: needed for MR1A[7] RxRTS=1
STA UART+UART_SOPR
LDA #%00000010 ; Interrupt mask register (IMR)
; |||||||+----------> 0: TxRDYA
; ||||||+-----------> 1: RxRDYA / FFULLA
; |||||+------------> 2: channel A change in break
; ||||+-------------> 3: counter ready
; |||+--------------> 4: TxRDYB
; ||+---------------> 5: RxRDYB
; |+----------------> 6: channel B change in break
; +-----------------> 7: IP0[3:0] change (subject to ACR[3:0])
STA UART+UART_MISC ; Maintain readable copy of IMR in MISC register
STA UART+UART_IMR
LDA #%00000101 ; Command Register A (CRA)
; | +----------> 0: enable RX
; +------------> 2: enable TX
STA UART+UART_CRA
RTS
.endproc
; UartRxBufInit initializes the in-memory buffer for UART receive data. This
; is filled from the UART RX FIFO by an interrupt.
.proc UartRxBufInit
LDA rxbuf_r ; doesn't matter where rxbuf_r points...
STA rxbuf_w ; ... as long as rxbuf_w is the same.
RTS
.endproc
; UartRxBufWrite queues the byte in A register that was previoulsy received by
; UART RX.
.proc UartRxBufWrite
LDX rxbuf_w ; load write pointer (next addr to write)
STA rxbuf,X ; store the RX byte in A into the buffer
INC rxbuf_w ; increment write pointer, with wrap-around
RTS
.endproc
; UartRxBufRead pulls a byte from the in-memory RX buffer.
; If carry bit is set, blocks polling for available data first.
; If carry bit is clear, it is assumed the caller knows there is data available
; in the buffer, in which case result will be invalid if the buffer is empty.
.proc UartRxBufRead
BCC no_poll
poll: JSR UartRxBufLen
BEQ poll
no_poll: LDX rxbuf_r ; load read pointer (first unread byte)
LDA rxbuf,X ; load RX byte from buffer into A
INC rxbuf_r ; increment read pointer, with wrap-around
RTS ; return A: RX byte
.endproc
; UartRxBufLen calculates the length of data that has been pulled from UART RX
; FIFO to in-memory RX buffer but not yet read.
.proc UartRxBufLen
LDA rxbuf_w ; load write pointer
SEC ; prepare carry bit for subtraction
SBC rxbuf_r ; subtract read pointer from write pointer
RTS ; return A: length (and associated status flags)
.endproc
; UartTxBufInit initialised the in-memory buffer for UART transmit data. This
; is flushed to the UART TX FIFO by an interrupt.
.proc UartTxBufInit
LDA txbuf_r ; doesn't matter where txbuf_r points...
STA txbuf_w ; ... as long as txbuf_w is the same.
RTS
.endproc
; UartTxBufWrite queues the byte in A register to be written to UART TX FIFO.
; UART interrupts for TxRDY are enabled.
; See also UartTxBufWriteBlocking.
.proc UartTxBufWrite
PHA
PHX
LDX txbuf_w ; load write pointer (next addr to write)
STA txbuf,X ; store the TX byte in A into the buffer
INC txbuf_w ; increment write pointer, with wrap-around
LDA UART+UART_MISC ; readable copy of IMR
ORA #1<<0 ; UART_IMR TxRDYA bit
STA UART+UART_MISC ; maintain readable copy of IMR
STA UART+UART_IMR ; Interrupt when UART TX FIFO is below fill level
PLX
PLA
RTS
.endproc
; UartTxBufWriteBlocking blocks until there is space on txbuf,
; and then calls UartTxBufWrite
.proc UartTxBufWriteBlocking
PHA
waittxbuf: JSR UartTxBufLen
CMP #$FF
BEQ waittxbuf
PLA
JMP UartTxBufWrite
.endproc
; UartTxBufRead pulls a byte from txbuf into A.
; Generally called by UartTxInterrupt during TxRDY interrupt.
; X register is not preserved.
.proc UartTxBufRead
LDX txbuf_r ; load read pointer (first unread byte)
LDA txbuf,X ; load TX byte from buffer into A
INC txbuf_r ; increment read pointer, with wrap-around
RTS ; return A: TX byte
.endproc
; UartTxBufLen calculates the length of data that has been buffered to the
; in-memory TX buffer but not yet flushed to UART TX FIFO.
.proc UartTxBufLen
LDA txbuf_w ; load write pointer
SEC ; prepare carry bit for subtraction
SBC txbuf_r ; subtract read pointer from write pointer
RTS ; resulting length returned in A register.
.endproc
; UartTxStr copies null-terminated string to txbuf.
; X,Y: string pointer
.proc UartTxStr
PHA
PHX
PHY
LDA R0 ; preserve R0,R1
PHA
LDA R1
PHA
STX R0 ; X: *string low byte
STY R1 ; Y: *string high byte
waittxbuf: JSR UartTxBufLen ; Wait for empty txbuf
BNE waittxbuf
SEI ; mask IRQ so UartTxInterrupt only fires once at the end
LDY #0
msgloop: LDA (R0),Y ; string[Y]
BEQ msgdone ; terminate on null byte
JSR UartTxBufWrite
INY
JMP msgloop
msgdone: CLI ; unmask interrupts
PLA ; restore R0,R1
STA R1
PLA
STA R0
PLY
PLX
PLA
RTS
.endproc
; UartRxInterrupt is triggered when UART RX FIFO has data (fill level reached,
; or watchdog timer elapsed). All data in UART RX FIFO is pulled into the
; larger in-memory buffer, ready for UartRxBufRead. This keeps the UART FIFO
; more empty more often, increasing throughput.
.proc UartRxInterrupt
PHA
PHX
again: LDA #1<<0 ; RxRDY: char is waiting in UART RX FIFO
BIT UART+UART_SRA
BEQ done ; if RxRDY is 0, UART RX FIFO is empty
JSR UartRxBufLen ; A <- length of data in buffer
CMP #220 ; in-memory buffer nearly full?
BCS done ; ... then don't pull a byte off the UART FIFO.
; TODO: ideally, de-assert RTS to tell the sender to stop
; transmitting, continue to pull UART FIFO into buffer, then
; re-assert RTS after the in-memory buffer is empty enough.
LDA UART+UART_RXFIFOA ; A <- FIFO
CMP #$03 ; Handle ctrl-c (TODO: ZP_UARTMODE to skip this in raw mode)
BNE writerxbuf
LDA #1<<7
TSB ZP_INTERRUPT
JMP again ; don't place the ctrl-c on rxbuf
writerxbuf: JSR UartRxBufWrite ; rxbuf <- A
JMP again
done: PLX
PLA
RTS
.endproc
.proc UartTxInterrupt
PHA
PHX
again: JSR UartTxBufLen ; A <- txbuf length
BEQ empty
waittxready: LDA UART+UART_SRA ; Is UART ready? Load UART status register...
AND #1<<2 ; SRA TxRDY: check TX FIFO is not full
BEQ waittxready ; zero means it's not ready (full), wait.
JSR UartTxBufRead ; A <- txbuf (kills X)
STA UART+UART_TXFIFOA ; UART FIFO <- A
LDA UART+UART_SRA ; Flush another byte? Load UART status register...
AND #1<<2 ; SRA TxRDY: check TX FIFO is not full
BNE again ; If TxRDY, check for another byte to flush to FIFO
JMP done ; else stop here, but leave the TxRDY interrupt enabled.
empty: LDA UART+UART_MISC ; readable copy of Interrupt Mask Register
AND #<~1<<0 ; clear UART_IMR TxRDYA bit
STA UART+UART_MISC ; Maintain readable copy of IMR in MISC register
STA UART+UART_IMR ; disable source of this interrupt, no data to TX
done: PLX
PLA
RTS
.endproc
.segment "bss"
rxbuf: .res 256 ; UART receive buffer; filled from UART RX FIFO by ISR
rxbuf_r: .res 1 ; read pointer (first addr not yet read)
rxbuf_w: .res 1 ; write pointer (next addr to be written)
txbuf: .res 256 ; UART transmit buffer; drained to UART TX FIFO by ISR
txbuf_r: .res 1 ; read pointer (first addr not yet sent to TX FIFO)
txbuf_w: .res 1 ; write pointer (next addr to be filled from TX FIFO)