1 ;; LED Matrix driver |
|
2 ;; vim: set ft=avr: |
|
3 |
|
4 .dseg |
|
5 ;; I/O addresses |
|
6 ; Control port |
|
7 .set MATRIX_DDR = DDRB |
|
8 .set MATRIX_PORT = PORTB |
|
9 |
|
10 ; Pin for matrix driver Output Enable |
|
11 .set MATRIX_OE = PORTB1 ; Output Enable, active low, externally pulled high |
|
12 |
|
13 ;; Matrix properties |
|
14 ; Matrix width in columns |
|
15 .set MATRIX_COLS = 8 ; physical columns |
|
16 |
|
17 ; Framebuffer width in columns |
|
18 .set MATRIX_BUF_COLS = 16 ; framebuffer columns |
|
19 |
|
20 ;; SPI addresses |
|
21 ; row drivers (8 bits) |
|
22 .set MATRIX_SPI_ROW = 0 ; row mask source |
|
23 |
|
24 ; column sinks (8 bits) |
|
25 .set MATRIX_SPI_COL = 1 ; column scan sink |
|
26 |
|
27 ;; Matrix state |
|
28 ; Matrix framebuffer |
|
29 ; this holds the columns data as a 1 byte bitmask of row data per column (8 bits -> 8 rows) |
|
30 matrix_colbuf: .byte MATRIX_BUF_COLS ; framebuffer (row data by column) |
|
31 |
|
32 ; Column scan bit |
|
33 ; in the matrix refresh loop, we push out each column's row data in turn |
|
34 ; this bit tracks the currently refreshing column |
|
35 matrix_colbit: .byte 1 ; column scan bit |
|
36 |
|
37 ; Matrix viewport offset |
|
38 ; the visible matrix data is taken directly from the framebuffer, but it can be taken at an arbitrary offset |
|
39 ; this determines the starting offset for the visible viewport's left edge from the start of the framebuffer in columns |
|
40 matrix_colshift: .byte 1 ; viewport left column offset |
|
41 |
|
42 ;; Text |
|
43 ; Maximum length of message |
|
44 .set TEXT_MAXLENGTH = 64 |
|
45 |
|
46 ; Scrolling speed (kiloticks per frame) |
|
47 .set TEXT_SPEED = 1 |
|
48 |
|
49 text_buffer: .byte TEXT_MAXLENGTH ; display message buffer |
|
50 text_offset: .byte 1 ; current offset in text |
|
51 |
|
52 .cseg |
|
53 |
|
54 ;; Normalize the outputs, enable the matrix, and set up buffers |
|
55 Matrix_Init: |
|
56 ; Setup ENable port |
|
57 sbi MATRIX_PORT, MATRIX_OE ; high -> disabled |
|
58 sbi MATRIX_DDR, MATRIX_OE ; out |
|
59 |
|
60 ; blank hardware |
|
61 ldi r16, 0 |
|
62 |
|
63 sts spi_outbuf + 0, r16 ; column sinks |
|
64 sts spi_outbuf + 1, r16 ; row drivers |
|
65 |
|
66 ; write out |
|
67 rcall SPI_SendRecv |
|
68 |
|
69 ; enable |
|
70 cbi MATRIX_PORT, MATRIX_OE ; low -> enabled |
|
71 |
|
72 ; init buffers |
|
73 ldi r16, 0b1 |
|
74 sts matrix_colbit, r16 |
|
75 |
|
76 ldi r16, 0 |
|
77 sts matrix_colshift, r16 |
|
78 |
|
79 ldi r16, 0 |
|
80 ldi r17, MATRIX_BUF_COLS |
|
81 ldi YL, low(matrix_colbuf) |
|
82 ldi YH, high(matrix_colbuf) |
|
83 |
|
84 m_init_mzero: |
|
85 st Y+, r16 |
|
86 |
|
87 ; loop until zero |
|
88 dec r17 |
|
89 brne m_init_mzero |
|
90 |
|
91 ; Use Timer0, 32k cycles -> 500Hz scan rate |
|
92 ldi r16, 32 |
|
93 rcall Timer0_Start |
|
94 |
|
95 ; done |
|
96 ret |
|
97 |
|
98 ;; Scan the matrix's next column from the viewport |
|
99 ;; Interrupt-driven |
|
100 Matrix_ScanCol: |
|
101 ; Save registers |
|
102 push r16 |
|
103 push r17 |
|
104 |
|
105 ; Column bit |
|
106 ; load |
|
107 lds r16, matrix_colbit |
|
108 |
|
109 ; start packet |
|
110 cbi SPI_PORT, SPI_SS |
|
111 |
|
112 ; output single column-enable bit |
|
113 out SPDR, r16 |
|
114 |
|
115 ; Compute col index |
|
116 ldi r17, 0 |
|
117 |
|
118 m_sc_colidx: |
|
119 ; shift |
|
120 lsr r16 |
|
121 |
|
122 ; done if we shifted the bit out |
|
123 brcs m_sc_row |
|
124 |
|
125 ; count shifts |
|
126 inc r17 |
|
127 rjmp m_sc_colidx |
|
128 |
|
129 m_sc_row: |
|
130 ; Column shift |
|
131 ; load |
|
132 lds r16, matrix_colshift |
|
133 |
|
134 ; add to col index |
|
135 add r17, r16 |
|
136 |
|
137 ; Row mask |
|
138 ; base |
|
139 ldi XL, low(matrix_colbuf) |
|
140 ldi XH, high(matrix_colbuf) |
|
141 |
|
142 ; offset |
|
143 ldi r16, 0 |
|
144 |
|
145 add XL, r17 |
|
146 adc XH, r16 |
|
147 |
|
148 ; load |
|
149 ld r16, X |
|
150 |
|
151 ; output full row-enable bitmask |
|
152 rcall SPI_Wait |
|
153 out SPDR, r16 |
|
154 |
|
155 ; Update col bit |
|
156 lds r16, matrix_colbit |
|
157 |
|
158 ; shift left |
|
159 lsl r16 |
|
160 brcc m_sc_colout |
|
161 |
|
162 ; overflow, take bit from C |
|
163 rol r16 |
|
164 |
|
165 m_sc_colout: |
|
166 ; store |
|
167 sts matrix_colbit, r16 |
|
168 |
|
169 ; End of packet |
|
170 rcall SPI_Wait |
|
171 sbi SPI_PORT, SPI_SS |
|
172 |
|
173 ; Done |
|
174 pop r17 |
|
175 pop r16 |
|
176 |
|
177 ret |
|
178 |
|
179 ;; Scan the matrix once in one go |
|
180 ;; XXX: doesn't support colshift |
|
181 Matrix_ScanFull: |
|
182 ; Row index |
|
183 ldi ZL, low(matrix_colbuf) |
|
184 ldi ZH, high(matrix_colbuf) |
|
185 |
|
186 ; Column bit |
|
187 ldi r25, 0 |
|
188 sec ; set C |
|
189 |
|
190 m_pulse_col: |
|
191 ; rotate bit left from C |
|
192 rol r25 |
|
193 |
|
194 ; overflow |
|
195 brcs m_pulse_end |
|
196 |
|
197 ; store in output buffer |
|
198 sts spi_outbuf + 1, r25 |
|
199 |
|
200 ; Row mask |
|
201 ld r16, Z+ |
|
202 |
|
203 sts spi_outbuf + 0, r16 |
|
204 |
|
205 ; Display |
|
206 rcall SPI_SendRecv |
|
207 |
|
208 ; Next column |
|
209 rjmp m_pulse_col |
|
210 |
|
211 m_pulse_end: |
|
212 ; Done |
|
213 ret |
|
214 |
|
215 ;; Reset the viewport to the start (left edge) of the framebuffer |
|
216 Matrix_ShiftZero: |
|
217 ; Constant offset |
|
218 ldi r16, 0 |
|
219 |
|
220 ; Set |
|
221 rjmp Matrix_ShiftSet |
|
222 |
|
223 ;; Shift the viewport one column to the left in the framebuffer, looping around to the end of the framebuffer |
|
224 ; This moves the visible output one column to the right |
|
225 Matrix_ShiftLeft: |
|
226 ; Decrement-loop current value |
|
227 ; current value |
|
228 lds r16, matrix_colshift |
|
229 |
|
230 ; shift window left |
|
231 dec r16 |
|
232 |
|
233 ; test for underflow (MSB/N set) -> don't skip reset |
|
234 brpl Matrix_ShiftSet |
|
235 |
|
236 ; reset window to right edge |
|
237 ldi r16, MATRIX_BUF_COLS - MATRIX_COLS |
|
238 |
|
239 ; Set |
|
240 rjmp Matrix_ShiftSet |
|
241 |
|
242 ;; Shift the viewport one column to the right in the framebuffer, looping around to the start of the FB |
|
243 ; This moves the visible output one column to the left |
|
244 Matrix_ShiftRight: |
|
245 ; Increment-loop current value |
|
246 ; current value |
|
247 lds r16, matrix_colshift |
|
248 |
|
249 ; shift window right |
|
250 inc r16 |
|
251 |
|
252 ; test for overflow -> don't skip reset |
|
253 cpi r16, MATRIX_BUF_COLS - MATRIX_COLS |
|
254 brlt Matrix_ShiftSet |
|
255 |
|
256 ; reset window to left edge |
|
257 ldi r16, 0 |
|
258 |
|
259 ; Set |
|
260 rjmp Matrix_ShiftSet |
|
261 |
|
262 ;; Set the matrix viewport offset |
|
263 ;; Input: r16 |
|
264 Matrix_ShiftSet: |
|
265 ; store new value |
|
266 sts matrix_colshift, r16 |
|
267 |
|
268 ; done |
|
269 ret |
|
270 |
|
271 ;; Rewinds the currently visible viewport to the beginning of the framebuffer |
|
272 ; This copies the currently visible viewport data to the beginning of the framebuffer and resets the offset |
|
273 Matrix_ShiftRewind: |
|
274 ; current view offset |
|
275 ldi XL, low(matrix_colbuf) |
|
276 ldi XH, high(matrix_colbuf) |
|
277 |
|
278 ; offset |
|
279 lds r16, matrix_colshift |
|
280 |
|
281 ; add |
|
282 ldi r17, 0 |
|
283 add XL, r16 |
|
284 adc XH, r17 |
|
285 |
|
286 ; start of framebuffer |
|
287 ldi YL, low(matrix_colbuf + 0) |
|
288 ldi YH, high(matrix_colbuf + 0) |
|
289 |
|
290 ; viewport width |
|
291 ldi r17, MATRIX_COLS |
|
292 |
|
293 matrix_shiftrew_loop: |
|
294 ; copy |
|
295 ld r16, X+ |
|
296 st Y+, r16 |
|
297 |
|
298 ; count |
|
299 dec r17 |
|
300 brne matrix_shiftrew_loop |
|
301 |
|
302 ; done, reset offset |
|
303 rjmp MAtrix_ShiftZero |
|
304 |
|
305 |
|
306 ;; Load a NUL-terminated ASCII string from PGM into the text buffer |
|
307 ; Input: Z - Address of NUL-terminated ASCII string in PGM |
|
308 Text_LoadString: |
|
309 ; Setup |
|
310 ; storage buffer |
|
311 ldi YL, low(text_buffer) |
|
312 ldi YH, high(text_buffer) |
|
313 |
|
314 ; max. length |
|
315 ldi r18, TEXT_MAXLENGTH |
|
316 |
|
317 text_loads_loop: |
|
318 ; Test max length |
|
319 ; count and check for overflow |
|
320 dec r18 |
|
321 brne text_loads_char |
|
322 |
|
323 ; Load char |
|
324 ; force NUL |
|
325 ldi r16, 0x00 |
|
326 |
|
327 text_loads_char: |
|
328 ; load next char |
|
329 lpm r16, Z+ |
|
330 |
|
331 text_loads_store: |
|
332 ; Store and test NUL |
|
333 ; store it |
|
334 st Y+, r16 |
|
335 |
|
336 ; test for NUL |
|
337 tst r16 |
|
338 brne text_loads_loop |
|
339 |
|
340 ; Update scroll offset |
|
341 ; reset offset |
|
342 ldi r17, 0 |
|
343 sts text_offset, r17 |
|
344 |
|
345 ; done |
|
346 ret |
|
347 |
|
348 ;; Shows the loaded string of ASCII text on the display, scrolling it horizontally |
|
349 ; Uses font.inc for rendering |
|
350 ; XXX: uses blocking timer sleeps and doesn't return until done |
|
351 Text_ShowString: |
|
352 ; Load initial char |
|
353 ldi XL, low(text_buffer + 0) |
|
354 ldi XH, high(text_buffer + 0) |
|
355 |
|
356 ; load char |
|
357 ld r16, X+ |
|
358 push XL |
|
359 push XH |
|
360 |
|
361 ; one column spacing |
|
362 ldi YL, low(matrix_colbuf + 1) |
|
363 ldi YH, high(matrix_colbuf + 1) |
|
364 |
|
365 ; render to framebuffer |
|
366 rcall Font_Render |
|
367 |
|
368 ; reset viewport |
|
369 rcall Matrix_ShiftZero |
|
370 |
|
371 ; Load next char |
|
372 text_shows_next: |
|
373 ; next char |
|
374 pop XH |
|
375 pop XL |
|
376 ld r16, X+ |
|
377 push XL |
|
378 push XH |
|
379 |
|
380 ; test NUL |
|
381 tst r16 |
|
382 breq text_shows_end |
|
383 |
|
384 ; offscreen |
|
385 ldi YL, low(matrix_colbuf + 1 + 6 + 1) |
|
386 ldi YH, high(matrix_colbuf + 1 + 6 + 1) |
|
387 |
|
388 ; render |
|
389 rcall Font_Render |
|
390 |
|
391 ; Animate to next char |
|
392 ldi r20, 7 |
|
393 |
|
394 text_shows_animloop: |
|
395 ; sleep |
|
396 ldi XH, high(TEXT_SPEED * 1024) |
|
397 ldi XL, low(TEXT_SPEED * 1024) |
|
398 |
|
399 rcall Timer_Sleep |
|
400 |
|
401 ; shift |
|
402 rcall Matrix_ShiftRight |
|
403 |
|
404 ; count |
|
405 dec r20 |
|
406 brne text_shows_animloop |
|
407 |
|
408 ; Rewind to next char |
|
409 rcall Matrix_ShiftRewind |
|
410 |
|
411 sbi PIND, PIND7 |
|
412 |
|
413 ; load next char and animate it in |
|
414 rjmp text_shows_next |
|
415 |
|
416 text_shows_end: |
|
417 ; Done |
|
418 pop XH |
|
419 pop XL |
|
420 |
|
421 |
|
422 ; Clear second frame |
|
423 ldi YL, low(matrix_colbuf + 16) |
|
424 ldi YH, high(matrix_colbuf + 16) |
|
425 ldi r16, MATRIX_COLS |
|
426 ldi r17, 0 |
|
427 |
|
428 text_shows_end2: |
|
429 st -Y, r17 |
|
430 |
|
431 dec r16 |
|
432 brne text_shows_end2 |
|
433 |
|
434 ; Blink |
|
435 text_shows_end3: |
|
436 ; on |
|
437 ldi r16, 0 |
|
438 rcall Matrix_ShiftSet |
|
439 |
|
440 ; sleep |
|
441 ldi XH, high(TEXT_SPEED * 6 * 1024) |
|
442 ldi XL, low(TEXT_SPEED * 6 * 1024) |
|
443 |
|
444 rcall Timer_Sleep |
|
445 |
|
446 ; off |
|
447 ldi r16, 8 |
|
448 rcall Matrix_ShiftSet |
|
449 |
|
450 ; sleep |
|
451 ldi XH, high(TEXT_SPEED * 6 * 1024) |
|
452 ldi XL, low(TEXT_SPEED * 6 * 1024) |
|
453 |
|
454 rcall Timer_Sleep |
|
455 |
|
456 ; loop |
|
457 rjmp text_shows_end3 |
|
458 |
|
459 ; XXX: end |
|
460 ret |
|
461 |
|