;**********************************************************************
;   IR Remote control for the PC using the GKOS keyboard (no mouse)   *
;                                                                     *
;   This version gkir3s.asm only adds SLEEP function to gkir3.asm     *
;   - press F key to turn power on again (= to wake up)               *
;                                                                     *
;   See later versions for better functionality.  gkir3.asm is the    *
;   first functional software for the IR transmitter including        *
;   keyboard functions only. GNU licence applies.                     *
;                                                                     *
;   Seppo Tiainen 2002, 2003, email: gkos@gkos.com                    *
;                                                                     *
;   This file is a basic code template for assembly code generation   *
;   on the PICmicro PIC16F876.                                        *
;                                                                     *
;   If interrupts are not used all code presented between the ORG     *
;   0x004 directive and the label main can be removed. In addition    *
;   the variable assignments for 'w_temp' and 'status_temp' can       *
;   be removed.                                                       *                         
;                                                                     *
;   Refer to the MPASM User's Guide for additional information on     *
;   features of the assembler (Document DS33014).                     *
;                                                                     *
;   Refer to the respective PICmicro data sheet for additional        *
;   information on the instruction set.                               *
;                                                                     *
;   Template file assembled with MPLAB V4.00 and MPASM V2.20.00       *
;                                                                     *
;**********************************************************************
;                                                                     *
;    Filename:	    gkir3s.asm -> gkir3s.hex                          *
;    Date:          June 2, 2003                                      *
;    File Version:  1.0                                               *
;                                                                     *
;    Author:   Seppo Tiainen                                          *
;    Company:  N/A                                                    *
;                                                                     * 
;                                                                     *
;**********************************************************************
;                                                                     *
;    Files required:    p16f876.inc                                   *
;                                                                     *
;**********************************************************************

	list      p=16f876            ; list directive to define processor
	#include <p16f876.inc>        ; processor specific variable definitions

	__CONFIG _CP_OFF & _WDT_OFF & _BODEN_OFF & _PWRTE_ON & _XT_OSC & _WRT_ENABLE_ON & _LVP_OFF & _CPD_OFF


;	__CONFIG _CP_OFF & _WDT_OFF & _BODEN_OFF & _PWRTE_ON & _RC_OSC & _WRT_ENABLE_ON & _LVP_OFF & _CPD_OFF


; '__CONFIG' directive is used to embed configuration data within *.asm file.
; The lables following the directive are located in the respective *.inc file.
; See respective data sheet for additional information on configuration word.

;***** VARIABLE DEFINITIONS

w_temp       EQU     0x20        ; variable used for context saving 
status_temp  EQU     0x21        ; variable used for context saving
temp0        EQU     0x22
temp1        EQU     0x23
temp2        EQU     0x24

TDcounter	EQU	0x30
maxchord	EQU	0x31
finalchord	EQU	0x32
GKOSstatus	EQU	0x33
chord		EQU	0x34
dcount1		EQU	0x35
dcount2		EQU	0x36
dcount3		EQU	0x37
TEMPX		EQU	0x38	;temp byte to send
bitcounter	EQU	0x39	;bit counter

SLEEPcnt1		EQU	0x40	;counter to goto sleep when no key activity
SLEEPcnt2		EQU	0x41	;counter to goto sleep when no key activity
SLEEPcnt3		EQU	0x42	;counter to goto sleep when no key activity


maxcounter	EQU	0x4F	;counter for max number of zero chord transmissions 
IRdevice	EQU	0x50	;Present device information of the IR packet:
				;xxxaaadg  x x x A2   A1 A1 D7(1) D6(G)
;**************
;Definitions
	#define	SLEEPdelay	d'60'	;60=>5min (nx5s)Idle Time before going to sleep
	#define	AUTOCOUNT	d'60'	;Typematic delay. Autorepeat wait time.
	#define	IRTXDATA	PORTC,0
	#define DEVICE		b'00011110'
;				  xxxaaadg            gdaaa     gdaaa-
;				b'00011110' => scope: 01111, ...011110 (=MD) gdaaa0
;	SIRC spec: 00010 = Betamax (5-bit code); 01111 = MD (5-bit code)	
;	TESTED: b'00011110' on scope: 01111; MD on scope: 11110 =>
;	b'00011110'	;Send the default IR device code in reverse order (LSB first).
;GKOS:	D0 D1 D2 D3 D4 D5    D6 D7 A0 A1 A2
;SIRC:	D0 D1 D2 D3 D4 D5    D6 A0 A1 A2 A3 A4
;GKOS:	D0-D5=chord, D6=toggle(g), D7=1(d), A0-A2=address (now: aaa=111)
;SIRC:	D0-D6=data, A0-A4=address (MD: 11110)
;	Examples:
;	dddddd gdaaa
;GKOS:	010000 01111	'a'
;SIRC:	0100000 11110 	'3'
;	ddddddd aaaaa
;****** Macros ********************************************************

Delay   macro   Time            ;Delay "Cycles" instruction cycles 
    if (Time==1) 
          nop 
          exitm 
    endif 
    if (Time==2) 
          goto $ + 1 
          exitm 
    endif 
    if (Time==3) 
          nop 
          goto $ + 1 
          exitm 
    endif 
    if (Time==4) 
          goto $ + 1 
          goto $ + 1 
          exitm 
    endif 
    if (Time==5)  
          goto $ + 1 
          goto $ + 1 
          nop 
          exitm 
    endif 
    if (Time==6) 
          goto $ + 1 
          goto $ + 1 
          goto $ + 1 
          exitm 
    endif 
    if (Time==7) 
          goto $ + 1 
          goto $ + 1 
          goto $ + 1 
          nop 
          exitm 
    endif 
    if (Time%4==0) 
          movlw (Time-4)/4 
          call Delay_Routine 
          exitm 
    endif 
    if (Time%4==1) 
          movlw (Time-5)/4 
          call Delay_Routine 
          nop 
          exitm 
    endif 
    if (Time%4==2) 
          movlw (Time-6)/4 
          call Delay_Routine 
          goto $ + 1 
          exitm 
    endif 
    if (Time%4==3) 
          movlw (Time-7)/4 
          call Delay_Routine 
          goto $ + 1 
          nop 
          exitm 
    endif 
    endm 

;**********************************************************************

	ORG     0x000       	; processor reset vector
	clrf    PCLATH          ; ensure page bits are cleared
  	goto    main            ; go to beginning of program

main

	bsf	STATUS,RP0      ; change to register bank 1
	bcf	STATUS,RP1	;           ===============
	movlw	b'11111110'	; binary value, 1 bit per port bit
	movwf	TRISC		; RC0 set to digital output IRTXDATA,
				;   all other PORTC as inputs

	bcf	OPTION_REG,7	;...for bank 1 OPTION_REG: clear bit 7 for pull-ups
				; on port B (GKOS keys)

	bcf	STATUS,RP0	; change to register bank 0
				;           ===============

	movlw		DEVICE		;Initialize IR device id
	movwf		IRdevice

	movlw		d'32'
	movwf		maxcounter	;Initialize zero-chord max send counter

	; Prepare for SLEEP operation
	bcf	INTCON, RBIF	; Clear Port B interrupt flag (just to make sure)
	bsf	INTCON, RBIE	; Enable Port B interrupts

;TEST*************
	;SLEEP
	;nop
	; Delay 1s for key release
	;movlw		d'255'		;1/4 sec DELAY
	;call		DelayWx1ms		
	;movlw		d'255'		;1/4 sec DELAY
	;call		DelayWx1ms		
	;movlw		d'255'		;1/4 sec DELAY
	;call		DelayWx1ms		
	;movlw		d'255'		;1/4 sec DELAY
	;call		DelayWx1ms		
	movlw	d'255'
	movwf	SLEEPcnt1
	movlw	SLEEPdelay	; This defines the ilde time before going to sleep
	movwf	SLEEPcnt2  ; 1=> 5s, 5=> 26s, 50=> 4:10 60=> 5 min, 100=> 8:20

;TEST end *************

;********************
GKOSloop
				;Check GKOS keyboard
	;call	ReadGKOSchord   ;return when keys released with param. finalchord	
	;btfss	GKOSstatus,0	;was there any key pressed? bit0 = 1?
	;goto	GKOSloop	;no, check again
	;goto	ChordReceived	;Yes!

;---NON-ZERO CHORDS:
ScanKeys
	;Scan keys twice before sending
	movlw	d'32'
	movwf	maxcounter	;Initialize zero-chord max send counter

	call	OneScan		;Scan Keyboard FIRST TIME

	movlw	0x00		;Is it zero (no keys down at the moment)
	xorwf	chord,W		;...
	btfsc	STATUS,Z	;...i.e. chord Equal to 0? 
	goto	ScanKeys0	;Yes. Prepare for changing Toggle bit.
				;No.

				;Chord not equal to 0 => initialize SLEEP counters
	movlw	d'255'
	movwf	SLEEPcnt1
	movlw	SLEEPdelay	; This defines the ilde time before going to sleep
	movwf	SLEEPcnt2  ; 5=> 13 sec, 100=> 4:15

	;clrf	SLEEPcnt1
	;clrf	SLEEPcnt2
	;clrf	SLEEPcnt3

	movlw	d'10'		;DELAY
	call	DelayWx1ms	

	call	OneScan		;Scan Keyboard SECOND TIME

	;movlw	0x00		;Is it zero (no keys down at the moment)
	;xorwf	chord,W		;...
	;btfsc	STATUS,Z	;...i.e. chord Equal to 0? 
	;goto	ScanKeys0	;Yes. Prepare for changing Toggle bit.
				;No.


ScanKeys_1

	btfss	GKOSstatus,1	;Typematic on? Autorepeat?
	goto	ScanKeys_11	;No.
				;Yes. Toggle D6(G)
	movlw	b'00000001'	;xxxa aadg
	xorwf	IRdevice,F	;toggle MSB

ScanKeys_11
	movf	maxchord,W	;Just send the present maxchord (of two scans) value
	movwf	finalchord
	;IR packet takes about 18 ms to send
	call	SendStart	;2200us pulse
	call	SendChord	;6 bits
	call	SendDevice	;5 bits (00010 = Betamax)

	clrf	maxchord	;clear maxchord

	goto GKOSloop


;----- ZERO CHORDS:
ScanKeys0

	movlw	AUTOCOUNT	;Initialize Autorepeat down-counter
	movwf	TDcounter	;when no keys are pressed
	bcf	GKOSstatus,1	;reset autorepeat indication

	call SLEEP_loop		; time to goto sleep? ***************

	movlw	d'0'
	movwf	maxchord

	call	OneScan		;Scan keyboard FIRST TIME

	movlw	0x00		;Is it zero (no keys down at the moment)
	xorwf	chord,W		;...
	btfsc	STATUS,Z	;...i.e. chord Equal to 0? 
	goto	ScanKeys02	;Yes. Scan second time.
				;No. Toggle D6(G) and Send next chord <> 0
	movlw	b'00000001'	;xxxa aadg
	xorwf	IRdevice,F	;toggle MSB

	goto	ScanKeys	;Continue by sending non-zero chords

ScanKeys02
	movlw	d'10'		;DELAY
	call	DelayWx1ms	

	call	OneScan		;Scan keyboard SECOND TIME

	movlw	0x00		;Is it zero (no keys down at the moment)
	xorwf	chord,W		;...
	btfsc	STATUS,Z	;...i.e. chord Equal to 0? 
	goto	ScanKeys0_1	;Yes. SEND but maximum 32 times.
				;No. Toggle D6(G) and Send next chord <> 0
	movlw	b'00000001'	;xxxa aadg
	xorwf	IRdevice,F	;toggle MSB

	goto	ScanKeys	;Continue by sending non-zero chords

ScanKeys0_1
	decfsz	maxcounter,f	;Max number of zero-chords sent?
	goto	ScanKeys_02	;No.
	incf	maxcounter,f	;Yes. Prepare for next check.
				;Do not send.
	goto	ScanKeys0
	
ScanKeys_02
	movf	chord,W		;Send the present chord value = 0 
	movwf	finalchord
	;IR packet takes about 18 ms to send
	call	SendStart	;2200us pulse
	call	SendChord	;6 bits
	call	SendDevice	;5 bits

	goto	ScanKeys0
;---
OneScan
	decfsz	TDcounter,f	;Decrement Autorepeat down counter.
	goto	OneScan_1
	incf	TDcounter,f	;Increment by one to end up here next time too.
	bsf	GKOSstatus,1	;indicate Typematic on
OneScan_1
	movf	PORTB,W		;Read GKOS Chord into param. chord
	xorlw	0xFF		;invert bits (pull-up function)
	andlw	b'00111111'	;clear RB7 & RB6 positions	
	movwf	chord		;store chord pressed at the moment
	iorwf	maxchord,F	;add any new bits (keys)
	return			;Chord in 'chord' and W



;*********************
ReadGKOSchord
	bcf	GKOSstatus,0	;chord received indicator bit0 = 0
	bcf	GKOSstatus,1	;Typematic indicator bit1 = 0
	movlw	d'0'		;clear the previous max key combination
	movwf	maxchord
	;goto	ReadGKOSchord_a1b ;This was ..._a1 before IR; ..._a1b speeds up by 10ms
	goto	ReadGKOSchord_a1
ReadGKOSchord_a
	bsf	GKOSstatus,0	;if looped back here, set bit to indicate key press
	movf	maxchord,W
	movwf	finalchord	;save the maximum chord value here too (for autorepeat)
ReadGKOSchord_a1
	movlw	d'1'		;* wait 10 ms every time before checking chord
	call	DelayWx10ms	;Delay
ReadGKOSchord_a1b
	movf	PORTB,W		;Read GKOS Chord into param. chord
	xorlw	0xFF		;invert bits (pull-up function)
	andlw	b'00111111'	;clear RB7 & RB6 positions	
	movwf	chord		;store chord pressed at the moment
	iorwf	maxchord,F	;add any new bits (keys)
	movlw	0x00		;Is it zero (no keys down at the moment)
	xorwf	chord,W		;...
	btfsc	STATUS,Z	;...i.e. chord Equal to 0? 
	goto	ReadGKOSchordX	;Yes. Exit right away. All GKOS keys are up.
				;No.
	decfsz	TDcounter,F	;Typematic Delay operating?
	goto	ReadGKOSwait	;No. Not yet. Wait and loop back
				;Yes. --TYPEMATIC REPEAT RATE!-- Use the finalchord again.
	movlw	d'1'		;* wait 10 ms and exit with maxchord as finalchord
	call	DelayWx10ms	;Typematic rate.
	incf	TDcounter,F	;set TDcounter back to 1 to get back here next time
	bsf	GKOSstatus,0	;indicate key press. Same finalchord as before.
	bsf	GKOSstatus,1	;indicate Typematic on
	movf	finalchord,W	;param. finalchord is the final result here too
	return			;Exit with Typematic repeat of character.
				;
				;
ReadGKOSwait			;Continue without Typematic repeat
	movlw	d'3'		;* wait 30 ms every time after re-checking chord
	call	DelayWx10ms	;Delay. (This was first 30 ms)
	goto	ReadGKOSchord_a	;Loop back to re-check chord value.

ReadGKOSchordX			
				;keys released and maxchord is the result
	movlw	d'25'		;* Initiate Typematic Delay Down-Counter
	movwf	TDcounter	;--TYPEMATIC DELAY--
	movf	maxchord,W
	movwf	finalchord	;param. finalchord is the final result
	return
;********************
ChordReceived
;This should be sent 3 times with an interval of 25ms but it would take about 100ms
;which is too much. Instead the packet (18ms) could be sent twice without a pause.
	call	SendStart	;2200us pulse
	call	SendChord	;6 bits
	call	SendDevice	;5 bits (00010 = Betamax)
	;(send 3 zeros here)
	;(call	SendChord)	;<=These are for possible future error detection; also, the
				;  3 spare device bits should be sent first as 0s(=not used).
		;1991: 5+3 bits, 1996: 5+3+5 bits, 1998: more 5+3+5 bit codes.
	;call	SendPause	;24ms extra delay!!!!!!How about GKOS keyb scanning????
	goto	GKOSloop	;24ms = pause if remote control key pressed continuoulsy
				;(maybe 25ms in fact...)
;*******************
SendChord
	movf	finalchord,w
	movwf	TEMPX		;TEMPX = byte to send (6 bits only, bit0 first)
	movlw	d'6'
	movwf	bitcounter	;set counter for 6 bits
SendChord_a
	btfss	TEMPX,0		;bit0=1?
	goto	SendChord_0	;No
	call	Send_1		;Yes SEND '1'
	rrf	TEMPX,f		;Next bit
	decfsz	bitcounter,f	;All 6 bits sent?
	goto	SendChord_a	;No.
	return			;yes, return
SendChord_0
	call	Send_0		;SEND '0'
	rrf	TEMPX,f		;Next bit
	decfsz	bitcounter,f	;All 6 bits sent?
	goto	SendChord_a	;No.
	return			;Yes, return
;*******************
SendDevice			;Send Device information (the IR controlled device ID)
	;movlw	DEVICE
	movf	IRdevice,W	;
	movwf	TEMPX
	movlw	d'5'
	movwf	bitcounter	;set counter for 5 bits
SendDevice_a
	btfss	TEMPX,0		;bit0=1?
	goto	SendDevice_0	;No
	call	Send_1		;Yes
	rrf	TEMPX,f		;Next bit
	decfsz	bitcounter,f	;All 5 bits sent?
	goto	SendDevice_a	;No.
	return			;yes, return
SendDevice_0
	call	Send_0
	rrf	TEMPX,f		;Next bit
	decfsz	bitcounter,f	;All 5 bits sent?
	goto	SendDevice_a	;No.
	return			;Yes, return

;===========================================
LOOP
	btfsc	PORTC,1		; test status of PORTC bit 1
	bsf     PORTC,0		; if high, set PORTC bit 0, turn on LED
	call	onesec		; call one second delay routine
	bcf     PORTC,0		; clear PORTC bit 0, turn off LED
	call	onesec		; call one second delay routine
	goto	LOOP		; loop infinitely turning LED on and off
onesec				; one second delay subroutine

	movlw	d'100'		; decimal value one hundred
	movwf	temp0		; load value d'100 into variable temp0
	movlw	d'13'	
	movwf	temp1		; load value d'13 into variable temp1
	movlw	d'250'	
	movwf	temp2		; load value d'250 into variable temp2
onesec_a
	decfsz	temp2,F		; decrement temp2 by 1, result in temp2
	goto	onesec_a	; innermost loop
	decfsz	temp1,F		; decrement temp1 by 1, result in temp1
	goto	onesec_a	; middle loop
	decfsz	temp0,F		; decrement temp0 by 1, result in temp0
	goto	onesec_a	; outer loop
	return			; return from subroutine
;*******************************************************************
DelayWx10ms
	movwf	dcount1 	; dcount1 = W
	movlw	0x64
	movwf	dcount2 
	movlw	0x20
	movwf	dcount3 
	decfsz	dcount3,F	; delay of > ((((3 * dcount3)+3)*dcount2)+3*W)
	goto 	$ - 1		;    * instruction cycle time (1us for 4MHz)
	decfsz	dcount2,F
	goto 	$ - 5		; so currently W x 10ms
	decfsz	dcount1,F
	goto 	$ - 9		

	return

;*********************
;Delays 4w+4 cycles (including call,return, and movlw) (0=256) 
Delay_Routine   addlw   -1              ;Precise delays used in I/O 
                btfss   STATUS, Z  
                goto    Delay_Routine 
                return     
;*********************
DelayWx1ms
	movwf	dcount1 	; dcount1 = W
	movlw	0x0a
	movwf	dcount2 
	movlw	0x20
	movwf	dcount3 
	decfsz	dcount3,F	; delay of > ((((3 * dcount3)+3)*dcount2)+3*W)
	goto 	$ - 1		;    * instruction cycle time (1us for 4MHz)
	decfsz	dcount2,F
	goto 	$ - 5		; so currently W x 1ms
	decfsz	dcount1,F
	goto 	$ - 9		

	return
;*******************************************************************
DelayT			;IR pulse Basic time period T = 550us
	Delay	d'200'
	Delay	d'200'
	Delay	d'150';<150
	return
DelayTplus		;IR pulse data bit check period = 825us
	Delay	d'200'
	Delay	d'200'
	Delay	d'200'
	Delay	d'200'
	Delay	d'25'
	return
;****************************************************************
SendPause		;Pause between packets = 24ms
	bcf	IRTXDATA
	movlw	d'24'
	call	DelayWx1ms
	return
;****************************************************************
SendStart		;gap T (550us), pulse 4T (2200us) 
	bcf	IRTXDATA
	call	DelayT
			;19 cycles per T -> 35.7kHz (19.5 => 36kHz)
			;One cycle: 14us high + 14us low
	movlw	d'78'	;**<78<=Oscilloscope** ;set cycle counter to 4T = 4 x 19.5 = 78
	movwf	temp0	;78 x 28us = 2184 (16us missing) 
	goto	SendPulse
;-----------
Send_1			;gap T (550us), pulse 2T (1100us)
	bcf	IRTXDATA
	call	DelayT
			;19 cycles per T -> 35.7kHz
			;One cycle: 14us high + 14us low
	movlw	d'43';**<43<=Oscilloscope**	;set cycle counter to 2T = 2 x 19 = 38
	movwf	temp0	;38 x 28us = 1064us (36us missing)
	goto	SendPulse
;----------------------
Send_0			;gap T (550us), pulse T (550us)
	bcf	IRTXDATA
	call	DelayT
			;19 cycles per T -> 35.7kHz
			;One cycle: 14us high + 14us low 
	Delay	d'10'	;the missing 18us - 8us for program lines calling
	movlw	d'22';**<22<=Oscilloscope**  ;set cycle counter to T = 1 x 19 = 19
	movwf	temp0	;19 x 28us = 532us (18us missing)
	goto	SendPulse
;-----------------------
	;This sets the Carrier Frequency! Adjusted by Oscilloscope. Do not change!
SendPulse	;Common procedure to send n cycles (n = temp0)
	bsf	IRTXDATA	;1
	Delay	0x0d; **0x0d>14us<=Oscilloscope** 13 ;1+13 = 14us
	bcf	IRTXDATA	;1
	Delay	0x0a; **0x0a>14us<=Oscilloscope** ;>14us;  ;1+11
	decfsz	temp0,f		;1+1+11
	goto	SendPulse	;1+1+1+11 = 14us
	return
;**************************************************************
SLEEP_loop

	decfsz	SLEEPcnt1,f	; 255 x 10 ms = 2.55 sec
	return	

	movlw	d'255'
	movwf	SLEEPcnt1

	decfsz	SLEEPcnt2,f	; n x 5 sec, n=60: 5 min
	return				; n=10: 25 sec, n=100: 4,25 min (4:15)

	movlw	SLEEPdelay	; This defines the ilde time before going to sleep
	movwf	SLEEPcnt2  


;	decfsz	SLEEPcnt3,f
;	return	

	movlw		d'255'		;1/4 sec DELAY
	call		DelayWx1ms		

	SLEEP
	nop

	; ROUTINES for wakeup:
	; Read port RB
	movf	PORTB,W		;Read GKOS chord into W

	; Clear flag bit RBIF
	bcf	INTCON, RBIF	; 

	; Delay 1s for key release
	movlw		d'255'		;1/4 sec DELAY
	call		DelayWx1ms		
	movlw		d'255'		;1/4 sec DELAY
	call		DelayWx1ms		
	movlw		d'255'		;1/4 sec DELAY
	call		DelayWx1ms		
	movlw		d'255'		;1/4 sec DELAY
	call		DelayWx1ms		

	return

	END                     ; directive 'end of program'

