include "em78p153s.inc" include "delay.inc" ; https://goo.gl/9JTVe9 ; ; 7 6 5 4 3 2 1 0 ; Port 5: [----|----|----|----|kpy0|kpy1|kpy3| NC ] ; Port 6: [ NC |prog|spkr|kpy2|kpx2|kpx1|open|kpx0] cycle_hz == 0x100000 ; ~4MHz internal RC clock, 4 clock cycles/instruction. x0 == 0x10 ; scratch registers x1 == 0x11 x2 == 0x12 prog_mode == 0x13 ; 0 normally, 1 when programming correct_code == 0x18 ; 12 bytes with master combination entered_code == 0x24 ; 12 bytes with current attempt code_size == 12 ; ; Startup. ; org 0x00 ; reset vector jmp setup org 0x08 ; interrupt vector clr ISR reti org 0x10 ; after vectors setup: disi mov A, @0b00001110 ; use P62 as I/O, set up prescaler for sleep contw mov A, @0b11111111 ; set all pins as input (tri-state) iow IOC5 iow IOC6 mov A, @0b11111111 ; disable pull down iow IOCB mov A, @0b00000000 ; disable open drain iow IOCC mov A, @0b11111010 ; enable pull-up on kpx0 and kpx1 iow IOCD mov A, @0b00000000 ; disable WDT, use P60 as I/O iow IOCE mov A, @0b00000010 ; enable only ICIF (I/O change interrupt flag). iow IOCF clr prog_mode ; reset programming mode flag jmp prog_setup ; initialize programming mode ; ; Main keypad scan and dispatch loop. ; scan: ; Wait for all keys to be released. ; Activate all row drivers and check the columns. mov A, @0b11110001 ; drive kpy0/1/3 low iow IOC5 clr R5 mov A, @0b10101111 ; drive prog/kpy2 low iow IOC6 clr R6 scan_idle_wait: jbs R6.0 ; skip if kpx0 high (not pressed) jmp scan_idle_wait jbs R6.2 ; skip if kpx1 high (not pressed) jmp scan_idle_wait jbs R6.3 ; skip if kpx2 high (not pressed) jmp scan_idle_wait call delay_100ms ; debounce release event (if any) scan_press_wait: ; Wait for any key to be pressed. ; Activate all row drivers and sleep until GPIO change. mov A, @0b11110001 ; drive kpy0/1/3 low iow IOC5 clr R5 mov A, @0b10101111 ; drive prog/kpy2 low iow IOC6 clr R6 jbs prog_mode.0 jmp scan_sleep ; In programming mode, make a tone until a key is pressed. mov A, @0b10001111 ; enable the speaker iow IOC6 scan_tone_loop: delay (cycle_hz / 300 / 2) - 10, x0, x1 mov A, R6 xor A, @0b00100000 mov R6, A jbs R6.0 ; skip if kpx0 high (not pressed) jmp scan_dispatch jbs R6.2 ; skip if kpx0 high (not pressed) jmp scan_dispatch jbs R6.3 ; skip if kpx0 high (not pressed) jmp scan_dispatch jmp scan_tone_loop scan_sleep: ; In non programming mode, sleep until a key is pressed. mov R6, R6 ; access port 6 to prepare for sleep clr ISR ; clear interrupt status to prepare for sleep wdtc ; clear watchdog to prepare for sleep ; slep ; wait for port 6 change (button press) - NO WORK mov A, @0b00000000 ; after waking, re-disable WDT iow IOCE jmp scan_dispatch scan_dispatch: call scan_get_key mov x0, A jbc x0.4 jmp handle_key jbc x0.5 jmp handle_enter jbc x0.6 jmp handle_prog jmp scan_press_wait scan_get_key: ; Something may have been pressed. ; Cycle through row drivers to find the specific key. mov A, @0b11111111 ; tri-state prog/kpy2 iow IOC6 mov A, @0b11110111 ; tri-state kpy1/3, drive kpy0 low iow IOC5 clr R5 delay 10, x0 ; settling time jbs R6.0 ; skip if kpx0 high (not pressed) retl @0x11 jbs R6.2 ; skip if kpx1 high (not pressed) retl @0x12 jbs R6.3 ; skip if kpx2 high (not pressed) retl @0x13 mov A, @0b11111011 ; tri-state kpy0, drive kpy1 low iow IOC5 clr R5 delay 10, x0 ; settling time jbs R6.0 ; skip if kpx0 high (not pressed) retl @0x14 jbs R6.2 ; skip if kpx1 high (not pressed) retl @0x15 jbs R6.3 ; skip if kpx2 high (not pressed) retl @0x11 ; XXX HACK FOR BMPH META mov A, @0b11111111 ; tri-state kpy1 iow IOC5 mov A, @0b11101111 ; drive kpy2 low iow IOC6 clr R6 delay 10, x0 ; settling time jbs R6.0 ; skip if kpx0 high (not pressed) retl @0x17 jbs R6.2 ; skip if kpx1 high (not pressed) retl @0x18 jbs R6.3 ; skip if kpx2 high (not pressed) retl @0x19 mov A, @0b11111111 ; tri-state kpy2 iow IOC6 mov A, @0b11111101 ; drive kpy3 low iow IOC5 clr R5 delay 10, x0 ; settling time jbs R6.0 ; skip if kpx0 high (not pressed) retl @0x1A jbs R6.2 ; skip if kpx1 high (not pressed) retl @0x1B jbs R6.3 ; skip if kpx2 high (not pressed) retl @0x20 ; return key mov A, @0b11111111 ; tri-state kpy3 iow IOC5 mov A, @0b10111111 ; drive prog low iow IOC6 clr R6 delay 10, x0 ; settling time jbs R6.3 ; skip if kpx0 high (not pressed) retl @0x40 ; prog button retl @0x00 ; no key found ; ; General code input (key code in A). ; In programming mode, adds to the correct code. ; In normal mode, adds to the entered code, and ; checks against the correct code. ; handle_key: mov x2, A ; save the key code ; Add the key to one of the arrays, based on mode. mov A, @entered_code jbc prog_mode.0 mov A, @correct_code mov RSR, A ; Shift the contents of the array to the left. mov A, @(code_size - 1) mov x0, A key_prog_loop: inc RSR ; shift every space to the left mov A, IAR dec RSR mov IAR, A inc RSR djz x0 jmp key_prog_loop ; Add the new key at the end of the array. mov A, x2 ; saved key code mov IAR, A ; write to the end ; In regular mode, check for victory. jbc prog_mode.0 jmp key_beep mov A, @code_size mov x0, A key_check_loop: mov A, x0 add A, @(correct_code - 1) mov RSR, A mov A, IAR jbc Z jmp key_check_next ; correct code 0 = wildcard mov x1, A ; remember the correct code mov A, x0 add A, @(entered_code - 1) mov RSR, A mov A, IAR sub A, x1 ; compare entered code with correct code jbs Z ; skip if zero (match) jmp key_beep ; mismatch; not done yet; beep and continue key_check_next: djz x0 jmp key_check_loop jmp key_success key_beep: ; Not done yet. Play a keypad beep. mov A, @0b11011111 ; set speaker to output iow IOC6 call play_C7_50ms ; beep call play_C7_50ms mov A, @0b11111111 ; tri-state speaker (save power) iow IOC6 jmp scan key_success: ; The right code was entered; open the door! mov A, @0b11011101 ; set speaker and open to output iow IOC6 bs R6.1 ; activate door solenoid ; https://en.wikipedia.org/wiki/Charge_(fanfare) ; G4/8 C5/8 E5/8 G5/8·S E5/16 G5/2 (120bpm = 500ms/1, 62.5ms/8) call play_G4_100ms call delay_25ms call play_C5_100ms call delay_25ms call play_E5_100ms call delay_25ms call play_G5_100ms call delay_100ms call play_E5_100ms call delay_25ms call play_G5_100ms call play_G5_100ms mov A, @0b11111101 ; tri-state speaker, leave solenoid on iow IOC6 delay cycle_hz * 5, x0, x1, x2 ; time to open door mov A, @0b11111111 ; tri-state solenoid (deactivate) iow IOC6 jmp scan ; ; Enter key. ; Terminates programming mode. ; In normal mode, just makes a sad sound. ; handle_enter: jbc prog_mode.0 ; If not programming, play wah-wah. jmp enter_complete mov A, @0b11111101 ; enable yellow light iow IOC5 bs R5.1 ; turn on yellow light mov A, @0b11011111 ; set speaker to output iow IOC6 call play_D3_100ms ; sad trombone call play_D3_100ms call play_D3_100ms call delay_100ms call play_B2_100ms call play_B2_100ms call play_B2_100ms call play_B2_100ms call play_B2_100ms call play_B2_100ms mov A, @0b11111111 ; tri-state speaker (save power) iow IOC6 call delay_100ms call delay_100ms mov A, @0b11111111 ; tri-state yellow light iow IOC5 jmp scan enter_complete: bc prog_mode.0 ; stop programming mov A, @0b11011111 ; set speaker to output iow IOC6 call play_C5_100ms ; play confirmation tones call delay_25ms call play_E5_100ms call delay_25ms call play_G5_100ms mov A, @0b11111111 ; tri-state speaker (save power) iow IOC6 jmp scan ; ; Programming button. ; Requires a 10sec hold to enter programming mode. ; handle_prog: ; If already in programming mode, do nothing. jbc prog_mode.0 jmp scan_press_wait ; Require the button held for 100ms * 100 = 10sec. mov A, @100 mov x2, A prog_hold_wait: call delay_100ms jbc R6.3 ; skip if kpx0 low (prog pressed) jmp scan ; prog released, go back to scanning djz x2 ; count 100x intervals jmp prog_hold_wait ; Sound a tone until the button is released. mov A, @0b10011111 ; enable speaker iow IOC6 prog_tone: delay (cycle_hz / 300 / 2) - 5, x0, x1 mov A, R6 xor A, @0b00100000 mov R6, A jbs R6.3 ; skip if kpx0 high (not pressed) jmp prog_tone prog_setup: ; Enter programming mode and zero the codes. bs prog_mode.0 mov A, @code_size prog_loop: add A, @(correct_code - 1) mov RSR, A clr IAR add A, @(entered_code - correct_code) mov RSR, A clr IAR add A, @(256 - entered_code) jbs Z jmp prog_loop jmp scan_press_wait ; avoid a gap in the programming tone delay_25ms: delay (cycle_hz / 40), x0, x1 ret delay_100ms: call delay_25ms call delay_25ms call delay_25ms call delay_25ms ret ; ; Musical notes (used by victory and failure). ; play_note macro hz, millis $halfwave_cycles == (cycle_hz / hz / 2) mov A, @(cycle_hz * millis / ($halfwave_cycles * 1000)) mov x2, A $note_loop: delay $halfwave_cycles - 5, x0, x1 mov A, R6 xor A, @0b00100000 mov R6, A djz x2 jmp $note_loop endm play_B2_100ms: play_note 123, 100 ret play_D3_100ms: play_note 147, 100 ret play_G4_100ms: play_note 392, 100 ret play_C5_100ms: play_note 523, 100 ret play_E5_100ms: play_note 659, 100 ret play_G5_100ms: play_note 784, 100 ret play_C7_50ms: play_note 2093, 50 ret