;--------------------------------------------------------------------- ; KEYSCAN 3 Code for the Rhodes Chroma keyboard ; ; Comments and jump names by D. Clarke (ac151@freenet.carleton.ca) ; Revision history (comments): ; Fall 2003, initial release ; -------------------------------------------------------------------- ; ; This code, running in the 8039, is responsible to scan the contact ; closures for the Rhodes Chroma keyboard. ; ; There are 4 main functional portions to the code. ; ; First, there is code to determine if a "new key travel" event has ; started. A new key travel event would be a transition from ; when a set of key contacts (either normally open or ; normally closed) changes from closed to open. ; If the new key travel event has started, then a snapshot is taken ; of the 'system timer', and this start time is stored in a note ; specific location in memory. (i.e., when either an attack or release ; event is started, we start to keep track of the elapsed time). ; ; Second, a check is done to see if a "release" has been completed ; (i.e., the normally open contacts were previously opened, and now ; the normally closed contacts are closed). If a release has ; occured, then the previously stored start time (for that note/contact ; number) is retrieved. That is subtracted from the current system ; time (to give a net change in system time from when the travel first ; started to when the travel stopped). Once it is guaranteed that the ; previous information has been received by the main Chroma processor, ; the keyboard scanner outputs the release velocity (time) data, and ; the note number (based on the contact number). Once the data ; is available, the 8039 causes KINT to go low, thus interrupting ; the main Chroma processor. ; ; The third functional block is very similar to the second - except this ; one checks to see if an "attack" has been completed ; (i.e., the normally closed contacts were previously opened, and now ; the normally open contacts are closed). If an attack has been completed ; then the previously stored start time (for that note/contact ; number) is retrieved. That is subtracted from the current system ; time (to give a net change in system time from when the travel first ; started to when the travel stopped). Once it is guaranteed that the ; previous information has been received by the main Chroma processor, ; the keyboard scanner outputs the attack velocity (time) data, and the ; note number (based on the contact number). Once the data is available, ; the 8039 causes KINT to go low, thus interrupting the main Chroma ; processor. ; ; The fourth functional block concerns the 'system time.' The keyboard ; scanner uses a timer built in to the 8039. When this timer expires, ; an interrupt service routine (ISR) is run. Inside this ISR a global ; variable is incremented. As the ISR is called periodically, and the ; global variable keeps track of how many times the ISR has run, this ; variable does actually represent 'time' (some fixed period related ; directly to the 8039's external clock frequency). ; ; The specific time associated with one 'tick' of this global variable will ; depend on two factors. The first is the value that is loaded into ; the timer control registers. The second is the actual clock being fed ; into the 8039 processor. ; ; In this specific case, the timer is loaded with 0xF4, and will count ; upwards to 0xFF (ISR fires when the 8-bit timer rolls over to 0x00). ; The processor is run from an 8MHz clock. ; ; As it works out, the internal timer ticks at ; (processor clock speed) / (512). ; ; We can then the calculate the ISR loop time, as below: ; ; 0xFF - 0xF4 = 12 timer ticks before ISR ; 1 timer tick = 1 / (8MHz) * 512 = 64uS ; 64uS per tick * 12 ticks = 768uS before ISR runs ; ; The main scanning loop reads a bank of contacts at a time. There are ; 8 banks (of 8 notes each) on the Chroma. That means that the main body ; has to execute 8 times before an indivual note can be scanned again. ; ; Addresses 0x10 - 0x17 contain the read status of the normally closed ; circuits for banks 0 -> 7 respectively. ; ; Addresses 0x18 - 0x1F contain the read status of the normally open ; circuits for banks 0 -> respectively. ; ; Addresses 0x20 -> 0x5F contain the 'time'/'velocity' data for the 64 ; possible notes (8 banks x 8 keys). Location 0x20 has data for note 0. ; org 0 jmp start ; Go to first line of code mov a,r7 mov a,r7 mov a,r7 mov a,r7 mov a,r7 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; 8039 Timer ISR ; Responsible to ensure that the global 'system time' is stored in R7 orl p1,#080h ; Ensure input to -KINT logic is deasserted mov r6,a ; Save "A" in temporary variable, R6 (i.e., push a) mov a,#0F4h ; A = $F4 mov t,a ; Load timer with A mov a,r6 ; Restore accumulator from temp. variable (i.e., pop a) inc r7 ; Keep track of ISR entrances retr ; Return from ISR ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Lookup table for memory addresses to store time/velocity data for ; the notes. db 020h ; Base address of Bank 0 Vel/time data (pos = note + $20) db 028h ; " 1 db 030h ; 2 db 038h ; 3 db 040h ; 4 db 048h ; 5 db 050h ; 6 db 058h ; 7 start: mov R0,#01Fh ; Top address of "Last" Normally Open (NO) status mov R1,#08 ; There are 8 keyswitch banks clr a ; A = 0 (Normally Open preload value) load_no: ; Preload Normally Open Status to 0x0 (8 banks) mov @r0,A dec r0 djnz r1 load_no mov r1,#8 cpl a ; A = $FF ("Last" closed contact indication) load_nc: ; Preload last Closed Contact Status to 0xFF (8 banks) mov @r0,a dec r0 djnz r1 load_nc inc r0 ; r0 = $10 = start address of NC switches mov r1,#018h ; r1 = start address of NO switches strt t ; Start timer en tcnti ; Enable timer interrupt ; Code above this point is only run during init. The main code loop ; is contained in "mainloop", below. ; ; During this loop, we have the following general assignments: ; r0 = NC mem loc ($10 -> $17) (When we do an external read, the top ; nibble is ignored - and the resulting ; value represents the bank address.) ; r1 = NO mem loc ($18 -> $1F) ; r2 = current NC value (the one just read) ; r3 = current NO value (the one just read) ; r4 = "closed contact", last scan (NC or NO were closed) ; r5 = 1's where NO was open but now closed ; r6 = temporary variable used in ISR ; r7 = global/current system 'timer' counter value ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; mainloop: movx a,@r0 ; A = garbage read (latch NC data from switches) movx a,@r1 ; A = curr NC Data (latch NO data from switches) mov r2,a ; r2 = curr NC Data movx a,@r1 ; A = curr NO Data (latch NO data from switches) mov r3,a ; r3 = curr NO Data orl a,r2 ; 'or' curr NC and curr NO data ; If there are any "0"'s in the 'or'd value, then there is a ; key in travel (i.e., neither connected to the NO or NC contact). ; Where there are 1's either the NC or NO contacts were closed at ; that location. xch a,@r0 ; save "contact closed" data to RAM, retrieve ; prev "contact closed" data mov r4,a ; r4 = prev contact closed data mov a,@r1 ; A = prev NO data cpl a ; take the compliment (1's where NO was not closed) anl a,r3 ; if we have a new release, will have a '1' in that loc ; at this point, any '1's in A represent where the NO contact was ; open but now is closed mov r5,a ; 1's where curr NO closed and prev NO open (new NO closed event) mov a,@r1 ; A = prev NO data anl a,r2 ; A = (prev NO) & (curr NC) ; 1's where curr NC closed AND prev NO closed ; i.e., possible new NC closure orl a,r5 ; 1's where new closure (NC or NO) cpl a ; 1's where there no new closures anl a,@r0 ; AND with 'current' contact closed value ; 1's if new closure is a NC closure cpl a ; 1's where new closure IS NOT a NC closure anl a,r4 ; 1's where we had a closure before, and now ; the current scan says that that contact is open jz check_rel ; If no new 'openings', jump ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Movement started event - either down or up (log time) ; ...else if we haven't jumped, we're currently looking at ; the start of an attack/keydown or release/keyup event. ; ; "A" has bits set (to 1) where a key has left the "at rest" ; state. ; ; We'll rotate A left to find which bits are set. ; When found, the 'time' value is stored to the memory ; location corresponding to the note in question. clr c ; clear carry mov r4,a ; r4 has "1"'s where bits have flipped (closed to open) mov a,r0 ; A = NC bank location movp a,@a ; A = base note number for bank (from lookup) mov r1,a ; r1 = memory location for note velocity mov a,r4 ; restore R4 chknxt_a: rlc a ; slide A left jnc not_carrya ; if no carry, no "1" was found - look again ; else there is the start of a 'keydown' or 'keyup' event here ; - we want to store the current 'time' mov r5,a ; Save "A" in r5 (push A) mov a,r7 ; A = current system time (number of ISR's seen) mov @r1,a ; Store 'time' (velocity) to note specific location mov a,r5 ; Restore A (pop A) clr c ; clear carry, and continue looking for bits not_carrya: inc r1 ; Increment note count jnz chknxt_a ; If we haven't flipped, jump and slide over mask mov a,r0 add a,#08 mov r1,a ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; see if we've completed a 'release' event check_rel: mov a,@r1 ; A = prev NO data anl a,r2 ; A = (curr NC) & (prev NO) jz chk_atk ; ; else have 1's where was pressed and now released clr c ; clear carry mov r4,a ; r4 has "1"'s where key was pressed but now released mov a,r0 ; A = NC bank location movp a,@a ; A = base note number for bank (from lookup) mov r1,a ; r1 = memory location for note velocity mov a,r4 ; restore R4 chknxt_r: rlc A ; slide A left jnc not_carryr ; if no carry, no "1" was found - look again mov r4,a ; save "A" in R4 (push A) mov a,r7 ; Current 'time' = number of timing loops seen cpl a ; A = -time add a,@r1 ; -current time + orig time = time delay (but -ve) cpl a ; A = +ve time delta jb7 max_relse ; If the top bit is set, assume overflow - use max value orl a,#080h ; else, set bit to indicate "release" velocity jmp waitrelclr max_relse: mov a,#0FFh ; Maximum release velocity (time) value waitrelclr: jni waitrelclr ; Don't proceed until last value read by main CPU outl p2,a ; Once it is safe, write release velocity time to port mov a,r1 ; R1 = note number add a,#0E0h ; A = A - $20 (note number relative to "0") outl p1,a ; Output note number (relative to 0), asserting -KINT orl p1,#080h ; Deassert input to -KINT circuitry mov a,R4 ; Restore from R4 clr c ; clear carry, and continue looking for bits not_carryr: inc r1 ; Increment note count jnz chknxt_r ; if we haven't flipped, jump and slide over mask mov a,r0 add a,#08 mov r1,a ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; See if we've finishing an attack (i.e., note down completed) chk_atk: mov a,@r1 ; A = prev NO data cpl a ; 1's where NOT prev NO data anl a,r3 ; 1's where curr NO & NOT last NO (i.e., new Note down) jz not_rel_done ; else if we haven't jumped, we're currently looking at the ; end of attack/key down event (i.e., NO contacts were ; open and now are closed) ; ; "A" has bits set (to 1) where key changed to down. clr c ; clear carry mov r4,a ; r4 has 1's where key was travelling but now is down mov a,r0 ; A = NC bank location movp a,@a ; A = base note number for bank mov r1,a ; r1 = memory location for note velocity mov a,r4 ; restore A (pop a) chknxt_3: rlc a ; slide A left jnc not_carry3 ; if no carry, no "1" was found - look again ; else once at this location, we've found one of the bits that are ; set - hence we want to output the current time. mov r4,a ; save A in R4 (push A) mov a,r7 ; Current 'time' = number of timing loops seen cpl a ; A = -time add a,@r1 ; -current time + orig time = time delay (but -ve) cpl A ; A = +ve time delta jb7 max_attack ; if the top bit is set, assume overflow - use max value jmp waitatkclr max_attack: mov a,#07Fh ; Maximum attack velocity (time) value waitatkclr: jni waitatkclr ; Spin wheels, waiting for RD VEL to clear INT status ; i.e., don't proceed until last value was read outl p2,a ; Once it is safe, write attack velocity to port mov a,r1 ; r1 = note number add a,#0E0h ; A = A = $20 (note number is now relative to 0) outl P1,a ; Output note number (relative to 0) and assert -KINT orl p1,#080h ; Deassert input to -KINT circuitry mov a,r4 ; Restore A from R4 clr c ; clear carry, and continue looking for bits not_carry3: inc r1 ; Increment note count jnz chknxt_3 ;if we haven't flipped, jump and slide over mask mov a,r0 add a,#08 mov r1,A ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; not_rel_done: mov a,@r1 ; orl a,R3 ; 'or' in current NO value cpl A ; 0's where current NO closed orl A,R2 ; 1's where NC closed cpl A ; 1's where NO closed ONLY mov @r1,A ; stored NO closed to RAM inc r0 ; go to next NC mem location inc r1 ; go to next NO mem location mov a,r0 ; A = index for next memory location jb3 havedone8 ; If we've done all 8 banks, jump jmp mainloop ; Else, go scan again ; we make it here when all 8 banks have been scanned havedone8: mov r0,#010h ; r0 = $10 = base address of NC mem location mov r1,a ; r1 = $18 = base address of NO mem location jmp mainloop ; Start scanning again (unconditionally) end ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;