-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.asm
642 lines (530 loc) · 18.6 KB
/
main.asm
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
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
;ATtiny214/ATtiny414/ATtiny814
;1 VCC
;2 PA4 out RESET_OUT (0=/RESET=open, 1=/RESET=low)
;3 PA5 out LOCK0_OUT to 4066 (0=off, 1=on)
;4 PA6 out LOCK1_OUT to 4066 (0=off, 1=on)
;5 PA7 out LOCK2_OUT to 4066 (0=off, 1=on)
;6 PB3 out LOCK3_OUT to 4066 and LED (0=off, 1=on)
;7 PB2 in /LOCK2_KEY (0=down, 1=up)
;8 PB1 in /LOCK1_KEY (0=down, 1=up)
;9 PB0 in /LOCK0_KEY (0=down, 1=up)
;10 UPDI/PA0 in /LOCK3_KEY (0=down, 1=up)
;11 PA1 out /LOCK0_LED (0=on, 1=off)
;12 PA2 out /LOCK1_LED (0=on, 1=off)
;13 PA3 out /LOCK2_LED (0=on, 1=off)
;14 GND
;Definitions file will be included first by the Makefile.
.area code (abs)
.list (me)
.32bit
;RAM
current_keys = SRAM_START+0 ;Current state of keys
previous_keys = SRAM_START+1 ;State of keys last time around the main loop
lock0_down_ticks = SRAM_START+2 ;Number of ticks LOCK0 has been held down
;Constants
TICK_MS = 20 ;Milliseconds in one tick
RESET_MS = 50 ;Milliseconds to hold /RESET low to reset
LOCK0_DOWN_MS = 1500 ;Milliseconds of LOCK0 held down to cause reset
;Constants for bit positions used with GPIO functions
LOCK3 = 3
LOCK2 = 2
LOCK1 = 1
LOCK0 = 0
.org PROGMEM_START/2 ;/2 because PROGMEM_START constant is byte-addressed
;but ASAVR treats program space as word-addressed.
;Vectors
rjmp reset ;00 RESET
rjmp isr_fatal ;01 CRCSCAN_NMI
rjmp isr_fatal ;02 BOD_VLM
rjmp isr_pin_change ;03 PORTA_PORT
rjmp isr_pin_change ;04 PORTB_PORT
rjmp isr_fatal ;05 Undefined
rjmp isr_fatal ;06 RTC_CNT
rjmp isr_fatal ;07 RTC_PIT
rjmp isr_fatal ;08 TCA0_LUNF / TCA0_OVF
rjmp isr_fatal ;09 TCA0_HUNF
rjmp isr_fatal ;0a TCA0_LCMP0 / TCA0_CMP0
rjmp isr_fatal ;0b TCA0_LCMP1 / TCA0_CMP1
rjmp isr_fatal ;0c TCA0_CMP2 / TCA0_LCMP2
rjmp isr_fatal ;0d TCB0_INT
rjmp isr_fatal ;0e TCD0_OVF
rjmp isr_fatal ;0f TCD0_TRIG
rjmp isr_fatal ;10 AC0_AC
rjmp isr_fatal ;11 ACD0_RESRDY
rjmp isr_fatal ;12 ACD0_WCOMP
rjmp isr_fatal ;13 TWI0_TWIS
rjmp isr_fatal ;14 TWI0_TWIM
rjmp isr_fatal ;15 SPI0_INT
rjmp isr_fatal ;16 USART0_RXC
rjmp isr_fatal ;17 USART0_DRE
rjmp isr_fatal ;18 USART0_TXC
rjmp isr_fatal ;19 NVMCTRL_EE
;Code starts at first location after vectors
.assume . - ((PROGMEM_START/2) + INT_VECTORS_SIZE)
;Unexpected interrupt
;
isr_fatal:
jmp fatal
;Pin change interrupt
;
;The pin change interrupts are enabled in order to wake
;from sleep but are not used for anything else. When a
;pin change interrupt occurs, its flags must be cleared.
;
isr_pin_change:
push r16
ldi r16, 0xff ;Clear every flag
sts PORTA_INTFLAGS, r16 ; on PORTA
sts PORTB_INTFLAGS, r16 ; on PORTB
pop r16
reti
;Reset
;
;Perform all startup activities, then start running the main loop.
;
reset:
;Set main clock to 16 MHz to get through init quickly
;in case the computer checks for a key at boot.
ldi r16, CPU_CCP_IOREG_gc
clr r17 ;No prescaler = 16 MHz
out CPU_CCP, r16 ;Unlock Protected I/O Registers
sts CLKCTRL_MCLKCTRLB, r17 ;Disable main clock prescaler
;Initialize stack pointer
ldi r16, <INTERNAL_SRAM_END
out CPU_SPL, r16
ldi r16, >INTERNAL_SRAM_END
out CPU_SPH, r16
;Initialize GPIO and restore 4066 contacts to last saved state
rcall gpio_init
rcall eeprom_read_contacts
rcall gpio_write_contacts
;Now that the 4066 is set up, drop down to 1 MHz. The clock
;will run at 1 MHz from now on to save a little power.
ldi r16, CPU_CCP_IOREG_gc
ldi r17, 0x03<<1 | CLKCTRL_PEN_bm ;Prescaler for 1 MHz
out CPU_CCP, r16 ;Unlock Protected I/O Registers
sts CLKCTRL_MCLKCTRLB, r17 ;Set main clock prescaler
;Initialize variables to defaults
clr r16
sts current_keys, r16
sts previous_keys, r16
sts lock0_down_ticks, r16
;Initialize remaining peripherals and enable interrupts
rcall sleep_init
sei
;Fall through
main_loop:
rcall read_debounced_keys ;Read keys (delays 1 tick to debounce)
sts current_keys, r16
rcall task_keys ;Check keys and update 4066 contacts
rcall task_reset ;Reset computer if LOCK0 is held down
rcall task_eeprom ;Store 4066 contacts in EEPROM
rcall task_leds ;Update LEDs from 4066 contacts
rcall task_sleep ;Go to sleep until a key changes
lds r16, current_keys
sts previous_keys, r16 ;Save keys for next time around
rjmp main_loop ;Loop forever
;TASKS ======================================================================
;
;The main loop calls all of these tasks each time around. There is a
;delay of 1 tick between each iteration of the main loop, which these
;tasks can use for timing.
;
;Check each key and toggle its 4066 contact if it was just pushed down.
;
task_keys:
ldi r18, 1<<LOCK3 ;First key to check (highest bit position)
1$: lds r16, current_keys
and r16, r18 ;Leave only key of interest from current
breq 2$ ;Branch if key is not down
lds r17, previous_keys
and r17, r18 ;Leave only key of interest from previous
eor r17, r16 ;Compare with current state of key
breq 2$ ;Branch if key has not changed
;Key has changed and is down
rcall gpio_read_contacts
eor r16, r18 ;Toggle the 4066 contact
rcall gpio_write_contacts
2$: lsr r18 ;Rotate right to next key
brne 1$ ;Loop until all keys are checked
ret
;Check for reset request and reset the computer if needed
;If LOCK0 is held down long enough, reset the computer, restore the
;previous LOCK0 state, and block until LOCK0 is released.
;
task_reset:
lds r16, current_keys
lds r17, lock0_down_ticks
sbrs r16, LOCK0 ;Skip next if LOCK0 is down
clr r17
cpi r17, #0xff ;Cap tick counter (do not wrap to 0)
breq 1$
inc r17
1$: sts lock0_down_ticks, r17
cpi r17, LOCK0_DOWN_MS/TICK_MS ;Held down long enough to reset?
brlo 3$
;LOCK0 held down long enough; it's time to reset the computer
;Reset count for next time
clr r16
sts lock0_down_ticks, r16
;Restore LOCK0 to its state before being pressed down
rcall gpio_read_contacts
ldi r17, 1<<LOCK0
eor r16, r17
rcall gpio_write_contacts
rcall gpio_write_leds
;Pulse /RESET low
rcall gpio_reset_on
ldi r16, RESET_MS
rcall wait_n_ms
rcall gpio_reset_off
;Wait for LOCK0 to be released (prevents multiple resets)
2$: rcall read_debounced_keys
sts current_keys, r16
wdr
sbrc r16, LOCK0
rjmp 2$
3$: ret
;Update 4066 contact state in EEPROM if needed
;
task_eeprom:
lds r16, current_keys
or r16, r16
brne 1$ ;Do nothing if any key is down
rcall eeprom_read_contacts
mov r17, r16
rcall gpio_read_contacts
cp r16, r17
breq 1$ ;Do nothing if no change
;Contacts do not match the EEPROM, time to write to the EEPROM
mov r17, r16 ;Save contacts in R17
clr r16 ;Turn off LEDs so that if power is lost,
rcall gpio_write_leds ; they take no residual power from EEPROM
mov r16, r17 ;Recall contacts
rcall eeprom_write_contacts
1$: ret
;Update the LEDs from the 4066 contacts
;
;This task should be called before the sleep task to ensure
;the LEDs reflect the 4066 contacts because other tasks may
;temporarily change the LEDs.
;
task_leds:
rcall gpio_read_contacts
rjmp gpio_write_leds
;Go to sleep until a key changes
;
;This task should always be the last one called. It puts
;the MCU to sleep until a key is pressed in order to save
;power. If this task is removed from the main loop, all
;other tasks will continue to work the same.
;
task_sleep:
lds r16, current_keys
or r16, r16
brne 1$ ;Do nothing if any key is down
sleep ;Returns when MCU wakes back up
1$: ret
;UTILITIES ==================================================================
;Read the keys with gpio_read_keys and debounce for one tick
;
read_debounced_keys:
push r18
push r17
1$: ldi r18, TICK_MS ;Debounce time
2$: rcall wait_1_ms
rcall gpio_read_keys ;Returns keys in R16
cp r16, r17 ;Same as last keys?
mov r17, r16
brne 1$ ; No: start all over
dec r18
brne 2$ ;Loop for debounce time
pop r17
pop r18
ret
;Busy wait for N milliseconds in R16
;
wait_n_ms:
rcall wait_1_ms
dec r16
brne wait_n_ms
ret
;Busy wait for 1 millisecond
;Assumes 1 MHz clock
;
wait_1_ms:
push r16
push r17
ldi r16, 0x02
1$: ldi r17, 0xa6
2$: dec r17
brne 2$
dec r16
brne 1$
pop r17
pop r16
ret
;GPIO =======================================================================
;
;The GPIO routines abstract the I/O pins such that the bit
;positions in the constants LOCK0-LOCK3 are used for all the
;routines (the key inputs, LED outputs, and 4066 contacts).
;
;The bit values use positive logic:
;
; - A bit of "0" means key up / LED off / 4066 contact open
; - A bit of "1" means key down / LED on / 4066 contact closed
;
;Read all the key switch inputs and return them in R16
;0=key up, 1=key down
;
gpio_read_keys:
push r17
clr r16
lds r17, PORTA_IN
sbrs r17, 0 ;UPDI/PA0 clear
ori r16, 1<<LOCK3 ; sets LOCK3 bit
lds r17, PORTB_IN
sbrs r17, 2 ;PB2 clear
ori r16, 1<<LOCK2 ; sets LOCK2 bit
sbrs r17, 1 ;PB1 clear
ori r16, 1<<LOCK1 ; sets LOCK1 bit
sbrs r17, 0 ;PB0 clear
ori r16, 1<<LOCK0 ; sets LOCK0 bit
pop r17
ret
;Write all the 4066 contacts from the value in R16
;0=contact open, 1=contact closed
;
gpio_write_contacts:
push r17
lds r17, PORTA_OUT
andi r17, 0xff ^ (1<<7 | 1<<6 | 1<<5)
sbrc r16, LOCK0
ori r17, 1<<5 ;LOCK0 bit set sets PA5
sbrc r16, LOCK1
ori r17, 1<<6 ;LOCK1 bit set sets PA6
sbrc r16, LOCK2
ori r17, 1<<7 ;LOCK2 bit set sets PA7
sts PORTA_OUT, r17
lds r17, PORTB_OUT
andi r17, 0xff ^ (1<<3)
sbrc r16, LOCK3
ori r17, 1<<3 ;LOCK3 bit set sets PB3
sts PORTB_OUT, r17
pop r17
ret
;Read the state of all the 4066 contacts into R16
;0=contact open, 1=contact closed
;
gpio_read_contacts:
push r17
clr r16
lds r17, PORTA_OUT
sbrc r17, 5
ori r16, 1<<LOCK0 ;PA5 set sets LOCK0 bit
sbrc r17, 6
ori r16, 1<<LOCK1 ;PA6 set sets LOCK1 bit
sbrc r17, 7
ori r16, 1<<LOCK2 ;PA7 set sets LOCK2 bit
lds r17, PORTB_OUT
sbrc r17, 3
ori r16, 1<<LOCK3 ;PB3 set sets LOCK3 bit
pop r17
ret
;Write all the LED outputs from the value in R16
;0=LED off, 1=LED on
;
gpio_write_leds:
push r17
lds r17, PORTA_OUT
andi r17, 0xff ^ (1<<3 | 1<<2 | 1<<1)
sbrs r16, LOCK0
ori r17, 1<<1 ;LOCK0 bit clear sets PA1
sbrs r16, LOCK1
ori r17, 1<<2 ;LOCK1 bit clear sets PA2
sbrs r16, LOCK2
ori r17, 1<<3 ;LOCK2 bit clear sets PA3
sts PORTA_OUT, r17
;Note: The LED for LOCK3 is different. It's ignored
;here because it's on whenever its 4066 contact is on.
pop r17
ret
;Read the state of all the LED outputs into R16
;0=LED off, 1=LED on
;
gpio_read_leds:
push r17
clr r16
lds r17, PORTA_OUT
sbrs r17, 1
ori r16, 1<<LOCK0 ;PA1 clear sets LOCK0 bit
sbrs r17, 2
ori r16, 1<<LOCK1 ;PA2 clear sets LOCK1 bit
sbrs r17, 3
ori r16, 1<<LOCK2 ;PA3 clear sets LOCK2 bit
;Note: The LED for LOCK3 is different. It's on whenever
;its 4066 contact is on, so the 4066 contact is tested here.
lds r17, PORTB_OUT
sbrc r17, 3
ori r16, 1<<LOCK3 ;PB3 set sets LOCK3 bit
pop r17
ret
;Pull the /RESET pin to GND, resetting the computer
;
gpio_reset_on:
ldi r16, 1<<4 ;PA4
sts PORTA_OUTSET, r16 ;set PA4=1 which pulls /RESET low
ret
;Open the /RESET pin, allowing the computer to run
;
gpio_reset_off:
ldi r16, 1<<4 ;PA4
sts PORTA_OUTCLR, r16 ;set PA4=0 which makes /RESET open
ret
;Set initial GPIO directions and states
;
; - Key pins as inputs
; - LED pins as outputs; LEDs not lit
; - 4066 pins as outputs; contacts open
; - RESET_OUT pin as output; /RESET=open
;
gpio_init:
;Key Inputs
ldi r16, 1<<2 | 1<<1 | 1<<0 ;PB2, PB1, PB0
sts PORTB_DIRCLR, r16 ;Set pins as input
;Enable pull-ups and pin-change interrupts
ldi r16, PORT_PULLUPEN_bm | PORT_ISC_BOTHEDGES_gc
sts PORTB_PIN2CTRL, r16 ; on PB2
sts PORTB_PIN1CTRL, r16 ; on PB1
sts PORTB_PIN0CTRL, r16 ; on PB0
;UPDI/PA0 Key Input
ldi r16, 1<<0 ;UPDI/PA0
sts PORTA_DIRCLR, r16 ;Set UPDI pin also as a GPIO input
ldi r16, PORT_ISC_BOTHEDGES_gc
sts PORTA_PIN0CTRL, r16 ;Enable pin change interrupt on PA0
;Note: The UPDI/PA0 pin is configured for UPDI in the fuses. In UPDI
;mode, the pin can still be used as an input. The MCU can still be
;programmed with UPDI as long as the key is not pressed down. No pull-up
;is configured because UPDI mode has its own special pull-up.
;4066 Outputs
ldi r16, 1<<7 | 1<<6 | 1<<5 ;PA7, PA6, PA5
sts PORTA_OUTCLR, r16 ;Set 4066s initially off (0=off)
sts PORTA_DIRSET, r16 ;Set pins as outputs
ldi r16, 1<<3 ;PB3
sts PORTB_OUTCLR, r16 ;Set 4066s initially off (0=off)
sts PORTB_DIRSET, r16 ;Set pins as outputs
;LED Outputs
ldi r16, 1<<3 | 1<<2 | 1<<1 ;PA3, PA2, PA1
sts PORTA_OUTSET, r16 ;Sets LEDs initially off (1=off)
sts PORTA_DIRSET, r16 ;Set pins as outputs
;RESET_OUT Output
ldi r16, 1<<4 ;PA4
sts PORTA_OUTCLR, r16 ;Set RESET_OUT initially off (0=off)
sts PORTA_DIRSET, r16 ;Set PA4 as output
ret
;SLEEP ======================================================================
sleep_init:
ldi r16, SLPCTRL_SEN_bm | SLPCTRL_SMODE_PDOWN_gc
sts SLPCTRL_CTRLA, r16
ret
;EEPROM =====================================================================
;
;The EEPROM is used to store the state of the 4066 contacts, which is only
;1 byte. However, there are 64 bytes available in the EEPROM on the
;ATtiny214 and 128 bytes on the ATTiny414/814. Since each EEPROM location
;can only be erased about 100K times, the entire area is used to store the
;1 byte using this wear-leveling algorithm:
;
; - To write the 4066 contacts byte, the lowest location in the EEPROM that
; is erased (0xFF) will receive the byte using the write-only (no erase)
; command. If no location contains 0xFF, the entire EEPROM will be erased
; back to 0xFF and the first location will receive the byte.
;
; - To read the 4066 contacts byte from the EEPROM, the byte in the highest
; location that does not contain 0xFF will be returned.
;
;The above algorithm will hopefully allow the 4066 contacts to change
;64 * 100K (6.4 million) times on the ATtiny214 or 128 * 100K (12.8 million)
;times on the ATtiny414/814 before the EEPROM wears out.
;
;Read 4066 contact state from the EEPROM into R16
;Destroys Y
;
;Find the highest location in the EEPROM that is
;not erased (0xFF) and return its value. If the
;entire EEPROM is empty, return 0 (all contacts off).
;
eeprom_read_contacts:
ldi YL, <(EEPROM_END+1)
ldi YH, >(EEPROM_END+1)
1$: ld r16, -Y ;Load byte currently in EEPROM
cpi r16, #0xff ;Erased (0xFF)?
brne 2$ ; No: branch to return this byte
cpi YL, #<EEPROM_START
brne 1$
cpi YH, #>EEPROM_START
brne 1$
clr r16 ;EEPROM is empty; return 0 (all off)
2$: ret
;Store R16 as the 4066 contact state in the EEPROM
;Destroys R16, R17, Y
;
;Find the lowest location in the EEPROM that is erased (0xFF) and
;write the value there. If there is no unerased location, erase
;the entire EEPROM and write the value in the first location.
;
eeprom_write_contacts:
rcall eeprom_wait_ready
ldi YL, <EEPROM_START
ldi YH, >EEPROM_START
1$: ld r17, Y+ ;Load byte currently in EEPROM
cpi r17, #0xff ;Erased (0xFF)?
breq 2$ ; Yes: branch to write byte here
cpi YL, <(EEPROM_END+1)
brne 1$
cpi YH, >(EEPROM_END+1)
brne 1$
;No unerased byte; erase EEPROM and reset pointer
push r16 ;Save 4066 contacts
ldi r16, NVMCTRL_CMD_EEERASE_gc ;Erase EEPROM command
rcall eeprom_send_cmd
pop r16 ;Recall 4066 contacts
ldi YL, <(EEPROM_START+1)
ldi YH, <(EEPROM_START+1)
2$: st -Y, r16 ;Store 4066 contacts in buffer
ldi r16, NVMCTRL_CMD_PAGEWRITE_gc ;Write-only command
;Fall through
;Send the NVMCTRL command in R16
;Destroys R17
;
eeprom_send_cmd:
ldi r17, CPU_CCP_SPM_gc
out CPU_CCP, r17 ;Unlock NVMCTRL_CTRLA
sts NVMCTRL_CTRLA, r16 ;Perform EEPROM command in R16
;Fall through
;Block until the EEPROM is ready
;Destroys R17
;
eeprom_wait_ready:
lds r17, NVMCTRL_STATUS
sbrc r17, NVMCTRL_EEBUSY_bp ;Skip next if EEPROM is ready
rjmp eeprom_wait_ready
ret
;END OF CODE ================================================================
;Fill all unused program words with a nop sled that ends with
;a software reset in case the program counter somehow gets here.
.nval filler_start,.
.rept ((PROGMEM_END/2) - filler_start - fatal_size)
nop
.endm
;Fatal error causes software reset
fatal:
cli ;Disable interrupts
ldi r16, CPU_CCP_IOREG_gc
ldi r17, RSTCTRL_SWRE_bm
out CPU_CCP, r16 ;Unlock Protected I/O Registers
sts RSTCTRL_SWRR, r17 ;Software Reset
fatal_size = . - 1 - fatal
;Entire flash memory should be filled
.assume . - (PROGMEM_SIZE/2)