;:ts=8 ; Internal memory allocation ; 00-07: Register bank 0 -- used for non-interrupt code ; 08-0F: Register bank 1 -- used for interrupt code ; 10-17: Register bank 2 \ ; 18-1F: Register bank 3 > misc. variables go here ; 20-2F: Bit addressable space / ; 30-7F: Generic memory -- used for stack .equ ireg0, 8 .org 0x10 .flag quietflag,0x20.0 .flag badflag,0x20.1 .flag xoffflag,0x20.2 .flag freq38,0x20.3 .flag bailoutflag,0x20.4 .flag attnflag,P1.2 .org 0x28 tabpos: .skip 1 bufptr: .skip 1 keycode: .skip 1 pausecount: .skip 1 ; External memory allocation .equ bufsize,127 .equ bufpage,0x82 .org 0x100*bufpage inbuf: .skip bufsize+1 .org 0x9f00 gloop: .skip 3 gotchar: .skip 3 ljmp gotcarrier puts: .skip 3 printhex2: .skip 3 .equ gkscode,25 ; These are the first two bytes of data sent by ; the NEC remote control, common to all codes. .equ idbyte1,0xdf .equ idbyte2,0x20 .org 0x9000 ;---------------------------------------------------------------------- ; Enter here when we were sitting at the main character wait loop with ; nothing better to do, and detected an IR carrier pulse. ; ; We stay in the remote control decoder code until a character comes in ; on the serial port and it is not a "request key status" code. At that ; point we return to the label "gotchar" with the unknown code in A, ; and signal a "key up" event if a key is down. ; On initial entry, stash R1. On exit, must restore this. gotcarrier: push 1 ; OK, so we got a carrier. Time it to see if it's a ; preamble. Preambles are long, over 8000 instruction ; cycles. gotcarrier2: mov R0,#0 ; 30 * 256 = 7680, close enough. So we must complete ; 256 iterations of the following loop, which takes ; 30 instruction cycles. gcloop: jb P1.0,gotpause ; 2 ..2 jnb SCON.0,gc_nochar ; 2 ..4 ; A character came in on the serial port. mov a,SBUF ; 1 ..5 clr SCON.0 ; 1 ..6 ; If it's not the "get key status" code, bail out. cjne a,#gkscode,bailout ; 2 ..8 ; Reply with the current key status and clear the ; attention line. The serial transmitter had better ; be ready or we go for a panic bailout. jnb SCON.1,bailout ; 2 ..10 mov SBUF,keycode ; 2 ..12 clr SCON.1 ; 1 ..13 setb attnflag ; 1 ..14 ; We were able to comply with the host's request without ; any emergencies and we are still counting clock cycles. mov R1,#4 ; 1 ..15 sjmp gc_nop ; 2 ..17 gc_nochar: mov R1,#10 ; 1 ..5 gc_nop: nop ; 1 ..6 gcloop1: djnz R1,gcloop1 ; 22 ..28 djnz R0,gcloop ; 2 ..30 ljmp gotpreamble ; Enter here if, anywhere in the remote control code, ; we get a character on the serial port that is not ; the "request key status" character. If a key is down, ; we signal that it was released. bailout: push ACC lcall sigkeyup pop ACC pop 1 ljmp gotchar ; Subroutine to signal a "key up" event if necessary. sigkeyup: mov a,keycode jz sku_notdown mov keycode,#0 clr attnflag sku_notdown: ret ; Enter here if the IR code pulse we just saw wasn't a ; preamble. ; Time what's left of the pause. If greater than ; the delay between "key still down" codes, signal a ; "key up" event. gotpause: lcall timepause jnb bailoutflag,gp_notbail gp_bailout: ljmp bailout ; Whenever we jump to the following, A nonzero means an ; unacceptably long pause time which means key up. gp_notbail: jz gp_notkeyup lcall sigkeyup pop 1 ljmp gloop ; Be sure to signal a "key up" event if we come ; through here too many times without anything making ; sense. This prevents IR noise from causing a bogus ; "key still down" indication. gp_notkeyup: mov a,keycode jz gp_nokeydown djnz pausecount,gp_nokeydown lcall sigkeyup pop 1 ljmp gloop gp_nokeydown: ljmp gotcarrier2 ; Enter here when a pulse long enough to be a preamble ; was received. First, wait for the end of the ; preamble. If that takes too long, signal key up. ; This prevents a "stuck on" IR transmitter from causing a ; bogus key down indication. gotpreamble: lcall waitpause jb bailoutflag,gp_bailout cjne R0,#0,gpa_ok lcall sigkeyup ljmp gotcarrier2 ; Then time the pause following. gpa_ok: lcall timepause jb bailoutflag,gp_bailout ; If it's a really long pause, signal key up and get out. jnz gp_notbail ; No pause that is part of a code has the high byte of its ; time set, so if this is the case, treat it as a garbage ; pause. mov a,R2 jnz gp_notkeyup ; Pauses between 0x48 and 0x80 ticks directly following ; a preamble mean "key still down". Pauses shorter than ; 0x48 are garbage. mov a,R1 clr C subb a,#0x48 jc gp_notkeyup subb a,#0x38 jnc newkeydown ; It's a "key still down" code. Reload the garbage pause ; counter. mov pausecount,#10 ljmp gotcarrier2 ; Finally, continue here if we saw a valid preamble, then ; a pause between 0x80 and 0x100 cycles long. We are ; currently in the pulse that separates pauses. From ; here on, we either signal a "key down" event or signal ; "key up" and get out to "gloop". newkeydown: lcall decodebyte jb bailoutflag,nkd_bailout jnz badcode cjne R3,#idbyte1,badcode lcall decodebyte jb bailoutflag,nkd_bailout cjne R3,#idbyte2,badcode jnz badcode lcall decodebyte jb bailoutflag,nkd_bailout jnz badcode mov R5,3 lcall decodebyte jb bailoutflag,nkd_bailout jnz badcode ; The last two bytes of the code are in R5/R3. ; Look them up in the code table. mov dptr,#codetable mov R0,#1 lookuploop: movx a,@dptr jz badcode inc dptr cjne a,5,d_nomatch movx a,@dptr clr C subb a,R3 jz d_gotmatch d_nomatch: inc dptr inc R0 sjmp lookuploop d_gotmatch: mov keycode,R0 mov pausecount,#10 clr attnflag ljmp gotcarrier2 ; Numeric buttons 1-9 give 1-9, button 0 gives 10 codetable: .word 0x7788,0xb748,0x37c8,0xd728,0x57a8 .word 0x9768,0x17e8,0xe718,0x6798,0xf708 ; Power: 11, volume up: 12, volume down: 13, Mute: 14, ; channel up: 15, channel down: 16 .word 0xef10,0xbf40,0x3fc0,0xaf50,0xff00,0x7f80 .byte 0 badcode: lcall sigkeyup pop 1 ljmp gloop nkd_bailout: ljmp bailout ;---------------------------------------------------------------------- ; Subroutine to decode a byte of IR remote control ; data. Returns decoded byte in R3, A set nonzero if ; anything went wrong and R3 is invalid. decodebyte: mov R3,#0 mov R4,#8 decodeloop: lcall waitpause jb bailoutflag,db_ret mov a,R0 jz db_timeout lcall timepause jb bailoutflag,db_ret jnz db_timeout mov a,R2 jnz db_timeout mov a,R1 clr C subb a,#0x28 mov a,R3 rlc a mov R3,a mov a,R1 clr C subb a,#0x50 jnc db_timeout djnz R4,decodeloop clr a db_ret: ret db_timeout: mov a,#1 ret ;---------------------------------------------------------------------- ; Subroutine to wait out the end of a pulse. Returns ; R0 set to zero if a timeout occurs. Replies to key ; status requests. The length of the timeout is not ; critical, it's just there to prevent failing on account ; of a "stuck on" IR source. waitpause: clr bailoutflag mov R0,#255 wploop: jnb SCON.0,wp_nochar mov a,SBUF clr SCON.0 cjne a,#gkscode,wp_bailout jnb SCON.1,wp_bailout mov SBUF,keycode clr SCON.1 setb attnflag wp_nochar: jb P1.0,wp_ret mov R1,#20 wp_delay: djnz R1,wp_delay djnz R0,wploop wp_ret: ret wp_bailout: setb bailoutflag ret ; And here's the all-important subroutine to time the ; length of pauses. Works on a 23-instruction cycle to ; be compatible with pause times sampled at 40.07KHz. ; Start the count at 1 to account (roughly) for all the ; time spent computing since the pause started. Total ; accuracy isn't necessary here. ; Stop timing and return A nonzero if the pause is longer ; than the pause between "key still down" codes. timepause: clr bailoutflag mov R1,#1 mov R2,#0 tploop: jb SCON.0,tp_gotchar ; 2 ..2 nop ; 1 ..3 nop ; 1 ..4 nop ; 1 ..5 nop ; 1 ..6 nop ; 1 ..7 nop ; 1 ..8 nop ; 1 ..9 nop ; 1 ..10 sjmp tp_common ; 2 ..12 tp_gotchar: mov a,SBUF ; 1 ..3 clr SCON.0 ; 1 ..4 cjne a,#gkscode,wp_bailout ; 2 ..6 jnb SCON.1,wp_bailout ; 2 ..8 mov SBUF,keycode ; 2 ..10 clr SCON.1 ; 1 ..11 setb attnflag ; 1 ..12 tp_common: mov a,R1 ; 1 ..13 add a,#1 ; 1 ..14 mov R1,a ; 1 ..15 mov a,R2 ; 1 ..16 addc a,#0 ; 1 ..17 mov R2,a ; 1 ..18 nop ; 1 ..19 cjne a,#0x12,loopbot ; 2 ..21 ret loopbot: jb P1.0,tploop ; 2 ..23 clr a ret