LIST P=PIC16F628A, F=INHX8M __CONFIG _CP_OFF & _WDT_OFF & _HS_OSC & _LVP_OFF & _PWRTE_ON ERRORLEVEL -302 ; disable warning "register is in bank 1" ;***************************************************************************** ; ; RC5LED program, a PWM dimmer for LED Lamps which can be controlled ; with an RC5 IR Remote Controll ; ; Program RC5LED.ASM ; Author Wim de Vree, pe1grl@amsat.org ; Revision history ; 20050715 Created from various snippets, includes code from ; San Bergmans, Oisterwijk (www.sbprojects.com) for ; the RC5 decoding although this is completely interrupt ; driven. ; 20050717 Gamma added, persistence added and much more ; 20050718 Rewritten for 8 channels on Port B ; Got rid of my own macros (single file) include ;***************************************************************************** ; Equates, I/O, vars ;***************************************************************************** #define DiagLed PORTA,0 #define IRSensor PORTA,4 ; This is where the IR decoder is conn #define HALF_TIME .900/.50 ; Number of timer counts per half bit #define PWM_CHANNELS .8 ; Number of PWM channels #define LED_TIME .5 ; Time to keep diag led on (1/20 sec) #define IRCmdRcvd Flags,0 #define LedsMuted Flags,1 #define Tick10msec Flags,2 #define DiagLedOn Flags,3 RESET_V EQU 0x0000 ; Address of RESET Vector ISR_V EQU 0x0004 ; Address of Interrupt Vector _w EQU 0x7C ; Save area for 'W' register _status EQU 0x7D ; Save area for 'STATUS' register _temp EQU 0x7F ; Temp used in int. serv. CBLOCK 0x20 ; Variable addresses start at 0x20 Phase ; Phase of PWM counter (0..255) Ticks ; Internal cntr in ISR IR_STATE ; IR receiver state machine BIT_TIMER ; Bit timer IR_SHIFT: 4 ; IR shift register IRAddress ; Remote address IRCommand ; Remote command ActChannel ; Currently selected channel Intensity: PWM_CHANNELS ; Intensity value 0..31 DutyCycle: PWM_CHANNELS ; "ON" period for channel 0 / 7 (0..255) Count ; Just a counter LedCounter ; Counter for diag led Temp Flags ENDC ;***************************************************************************** ; Program start ;***************************************************************************** ORG RESET_V ; RESET vector location RESET GOTO START ;***************************************************************************** ; This is the Periperal Interrupt routine. ;***************************************************************************** ORG ISR_V ; Interrupt vector location INTHANDLER movwf _w ; Save W, STATUS and PCLATH movf STATUS,W movwf _status bcf STATUS, RP0 ; Select bank 0 bcf PIR1, TMR2IF ; Clr Timer2 interrupt source ; TIMER TICK HAS OCCURRED, FIRST UPDATE THE 10MSEC EVENT ; COUNTER, THEN THE IR-DECODER AND FINALLY PERFORM THE PWM. ; PLEASE NOTE THAT YOU SHOULD SPEND LESS THAN 50 USEC IN ; THIS HANDLER !!!! 10 MHz = 125 cycles, 8MHz = 100 cycles ; Time spend ; Int. overhead 13 cycles ; Event timer max 5 cycles ; IR State machine max 19 cycles ; PWM (4/8 channels) 20 or 36 cycles ; TOTAL (max) 57 or 73 cycles decfsz Ticks,F goto IR_MACHINE movlw .200 ; 200*50 = 10 msec movwf Ticks ; Reload tick counter bsf Tick10msec ; raise event ;----------------------------------------------------------------------------- ; IR receiver state machine ;----------------------------------------------------------------------------- IR_MACHINE clrf PCLATH ; PCLATH to current page movf IR_STATE,W ; Jump to present state movwf PCL ;---------------------------------------STATE 0, WAIT FOR BEGIN OF START BIT-- IR_STATE_0 btfsc IRSensor ; Input still high? goto _DoPWM ; Yes! Nothing to do MOVLW HALF_TIME/2-1 ; Wait until we're in the center of the MOVWF BIT_TIMER ; start pulse MOVLW low IR_STATE_1 ; Next stop is state 1 MOVWF IR_STATE goto _DoPWM ;---------------------------STATE 1, START BIT DETECTED, CHECK IF IT IS REAL-- IR_STATE_1 DECFSZ BIT_TIMER,F ; Wait until center of start pulse goto _DoPWM ; Time's not up yet! BTFSC IRSensor ; Is the input still low? goto IR_ERROR_1 ; Nope! Exit with error MOVLW HALF_TIME ; Set interval to the center of the MOVWF BIT_TIMER ; first half of the next bit MOVLW b'00001000' ; Prepare the shift register MOVWF IR_SHIFT CLRF IR_SHIFT+1 movlw low IR_STATE_2 ; Prepare for next stop MOVWF IR_STATE goto _DoPWM ;-----------------------------------IR STATE 2, WAIT FOR FIRST HALF OF A BIT-- IR_STATE_2 DECFSZ BIT_TIMER,F ; Wait until center of first half of bit goto _DoPWM ; Keep waiting! movlw low IR_STATE_3 ; Next state is 3 if input is high btfss IRSensor movlw low IR_STATE_4 ; Input is low, next state is 4 movwf IR_STATE MOVLW HALF_TIME ; Restart bit timer MOVWF BIT_TIMER goto _DoPWM ;---------------IR STATE 3, FIRST HALF WAS HIGH NOW IT MUST BE LOW FOR A "1"-- IR_STATE_3 DECFSZ BIT_TIMER,F ; Wait until center of 2nd half of bit goto _DoPWM ; Keep waiting! BTFSC IRSensor ; Is input high now? goto _ERROR ; Nope! It's an error! BSF STATUS,C ; A '1' was received, shift it in result RLF IR_SHIFT,F RLF IR_SHIFT+1,F MOVLW HALF_TIME ; Restart bit timer MOVWF BIT_TIMER movlw low IR_STATE_2 ; In case we need some more bits skpnc ; We're done when Carry is 1 movlw low IR_STATE_5 ; Carry is 1, received entire message movwf IR_STATE goto _DoPWM _ERROR movlw low IR_ERROR_0 ; Wait until input gets high before movwf IR_STATE ; returning to state 0 goto _DoPWM ;---------------IR STATE 4, FIRST HALF WAS LOW NOW IT MUST BE HIGH FOR A "0"-- IR_STATE_4 DECFSZ BIT_TIMER,F ; Wait until center of 2nd half of bit goto _DoPWM ; Keep waiting! BTFSS IRSensor ; Is input high now? GOTO IR_ERROR_1 ; Nope! It's an error! BCF STATUS,C ; A 0 was received, shift it in result RLF IR_SHIFT,F RLF IR_SHIFT+1,F MOVLW HALF_TIME ; Restart bit timer MOVWF BIT_TIMER movlw low IR_STATE_2 ; In case we need some more bits skpnc ; We're done when Carry is 1 movlw low IR_STATE_5 ; Carry is 1, received entire message MOVWF IR_STATE goto _DoPWM ;--------------------------IR STATE 5, MESSAGE RECEIVED, START PROCESSING IT-- IR_STATE_5 movfw IR_SHIFT ; Get IR command andlw b'00111111' ; The command is only 6 bits wide btfss IR_SHIFT+1,4 ; Copy inverted extended bit to b6 of iorlw b'01000000' ; command RLF IR_SHIFT,F ; Shift the entire IR address in RLF IR_SHIFT+1,F ; 2nd byte of shift register RLF IR_SHIFT,F RLF IR_SHIFT+1,F MOVWF IR_SHIFT ; Save cleaned up hex IR command number movfw IR_SHIFT+1 andlw b'00011111' ; Address is only 5 bits wide movwf IRAddress movfw IR_SHIFT movwf IRCommand movlw low IR_STATE_6 ; Done enough for now. Let's finish it movwf IR_STATE ; in the last state bsf IRCmdRcvd goto _DoPWM ;------------------------------IR STATE 6, WAIT FOR COMMAND TO BE PICKED UP BY MAIN-- IR_STATE_6 movlw low IR_STATE_7 ; Last state btfss IRCmdRcvd ; Only if command was picked up movwf IR_STATE ; in the last state goto _DoPWM ;----------------------------------IR STATE 7, WAIT FOR INPUT TO RETURN HIGH-- IR_STATE_7 IR_ERROR_0 movlw low IR_STATE_0 ; Reset state machine only if input is btfsc IRSensor ; high movwf IR_STATE goto _DoPWM ;-----------------------------------------------------------IR ERROR STATE 1-- IR_ERROR_1 MOVLW low IR_STATE_0 ; Return to IR state 0 MOVWF IR_STATE ; Now generate the output status for the PWM channels, ; Output of the PWM is available on Port B ; PWM clock = 20 kHz ; PWM freq = 78 Hz ; PWM resolution = 8bit _DoPWM incf Phase,f ; Increment Phase clrf _temp ; All outputs OFF movf DutyCycle,W ; Channel 0 subwf Phase,W skpc bsf _temp,0 ; Output on movf DutyCycle+1,W ; Channel 1 subwf Phase,W skpc bsf _temp,1 ; Output on movf DutyCycle+2,W ; Channel 2 subwf Phase,W skpc bsf _temp,2 ; LED ON movf DutyCycle+3,W ; Channel 3 subwf Phase,W skpc bsf _temp,3 movf DutyCycle+4,W ; Channel 4 subwf Phase,W skpc bsf _temp,4 movf DutyCycle+5,W ; Channel 5 subwf Phase,W skpc bsf _temp,5 movf DutyCycle+6,W ; Channel 6 subwf Phase,W skpc bsf _temp,6 movf DutyCycle+7,W ; Channel 7 subwf Phase,W skpc bsf _temp,7 movfw _temp movwf PORTB ; Place on output lines int_done movf _status,w ; Restore W and STATUS movwf STATUS swapf _w,F swapf _w,W retfie ;***************************************************************************** ; Apply a gamma correction, code should be in PAGE 0 !!! (PCLATH should be 0) ;***************************************************************************** Gamma andlw 0x1F addwf PCL,F dt .000,.000,.001,.002,.004,.006,.009,.012 ; Gamma = 2.2 dt .015,.019,.024,.029,.035,.041,.047,.055 dt .063,.071,.080,.090,.100,.111,.122,.134 dt .147,.160,.174,.189,.204,.220,.237,.254 ;***************************************************************************** ; Initialize processor registers ;***************************************************************************** START ; POWER_ON Reset (Beginning of program) clrf STATUS ; Do initialization, Select bank 0 clrf INTCON ; Clear int-flags, Disable interrupts clrf PCLATH ; Keep in lower 2KByte clrf PORTA ; ALL PORT output should output Low. clrf PORTB movlw 0x07 movwf CMCON ; Compare module OFF bsf STATUS,RP0 movlw 0xFE ; RA0 output, others inputs movwf TRISA movlw 0x00 ; RB0-7 outputs movwf TRISB clrf PIE1 ; Disable peripheral interrupts bcf STATUS,RP0 bcf DiagLed ; Switch diag led ON movlw 0x20 ; Clear memory in BANK0 movwf FSR _memclear clrf INDF incf FSR,F movlw 0x7F subwf FSR,w bnz _memclear movlw low IR_STATE_0 ; Init IR receiver state machine movwf IR_STATE clrf Flags clrf Phase clrf ActChannel movlw ActChannel ; Load last used settings from EE movwf FSR ; FSR to first variable bsf STATUS,RP0 ; BANK1 clrf EEADR ; Data Memory Address bcf STATUS,RP0 ; BANK0 movlw PWM_CHANNELS+1 ; Active + all PWM channels call LoadEE call LoadAll ; Init Timer2 for a 20kHz (50usec) period ; this is the base for IR decoding as well as ; PWM generation. PWM freq = 78 Hz clrf T2CON ; Stop Timer2, Prescaler = 1:1, Postscaler = 1:1 clrf PIR1 ; Clear peripheral interrupts Flags bsf STATUS,RP0 movlw .124 ; Period for TMR2 (50 usec for 10 MHz) movwf PR2 bsf PIE1,TMR2IE ; Timer 2 interrupt on bcf STATUS,RP0 ; BANK0 bsf INTCON,PEIE ; Pheriperal interrupts enabled bsf INTCON,GIE ; Global interrupts enabled bsf T2CON, TMR2ON ; Timer2 starts to increment bsf DiagLed ; Diag led OFF CommandDone bcf IRCmdRcvd MainLoop btfsc IRCmdRcvd goto doIRCommand btfsc Tick10msec ; 10 msec tick, sometimes missed goto doTick ; but this is not important goto MainLoop doTick bcf Tick10msec btfss DiagLedOn goto MainLoop ; Nothing to do here... decfsz LedCounter,F goto MainLoop ; Timer not expired bcf DiagLedOn ; Led flag off bsf DiagLed ; Ledd off goto MainLoop doIRCommand bsf DiagLedOn movlw LED_TIME movwf LedCounter bcf DiagLed ; Switch diag led ON movlw 0x10 ; Volume++ xorwf IRCommand,W bz IntensityUp movlw 0x11 ; Volume-- xorwf IRCommand,W bz IntensityDown movlw 0x0D ; Mute xorwf IRCommand,W bz ToggleMute movlw 0x01 ; Channel 1 xorwf IRCommand,W bz SelectChannel movlw 0x02 ; Channel 2 xorwf IRCommand,W bz SelectChannel movlw 0x03 ; Channel 3 xorwf IRCommand,W bz SelectChannel movlw 0x04 ; Channel 4 xorwf IRCommand,W bz SelectChannel movlw 0x05 ; Channel 5 xorwf IRCommand,W bz SelectChannel movlw 0x06 ; Channel 6 xorwf IRCommand,W bz SelectChannel movlw 0x07 ; Channel 7 xorwf IRCommand,W bz SelectChannel movlw 0x08 ; Channel 8 xorwf IRCommand,W bz SelectChannel movlw 0x09 ; Channel 9 = Fully ON xorwf IRCommand,W bz IntensityFull movlw 0x00 ; Channel 0 = Fully OFF xorwf IRCommand,W bz IntensityNill movlw 0x0C ; Mute (Toglle all on/of) xorwf IRCommand,W bz Standby goto CommandDone IntensityUp call FSR2Channel movlw 0x1F xorwf INDF,W ; Max intensity reached ? bz CommandDone incf INDF,F goto NewDutyCycle IntensityDown call FSR2Channel movlw 0x00 xorwf INDF,W ; Min intensity reached ? bz CommandDone decf INDF,F goto NewDutyCycle IntensityNill call FSR2Channel clrf INDF goto NewDutyCycle IntensityFull call FSR2Channel movlw 0x1F movwf INDF goto NewDutyCycle FSR2Channel movlw Intensity movwf FSR movfw ActChannel addwf FSR,F ; FSR points to active channel return ; Standby: Save current settings to EE, then mute the PWM Standby movlw ActChannel ; Save from here into EE movwf FSR ; FSR to first variable bsf STATUS,RP0 ; BANK1 clrf EEADR ; Data Memory Address bcf STATUS,RP0 ; BANK0 movlw PWM_CHANNELS+1 ; Active + all PWM channels call SaveEE bcf LedsMuted ; No use Mute to disable the leds ToggleMute btfss LedsMuted ; Mutes ALL channels goto MuteOn MuteOff bcf LedsMuted call LoadAll goto CommandDone MuteOn bsf LedsMuted call LoadAll goto CommandDone ; Load New dutycycle for channel W into PWM module NewDutyCycle movfw ActChannel ; Load active channel in 'W' call LoadDutyCycle goto CommandDone LoadDutyCycle btfsc LedsMuted goto _muted movwf FSR movlw Intensity addwf FSR,F ; FSR to active channel intensity movfw INDF call Gamma movwf Temp movlw PWM_CHANNELS addwf FSR,F ; FSR + 8 is PWM dutycycle value movfw Temp movwf INDF ; Load Dutycycle return _muted movwf FSR movlw DutyCycle addwf FSR,F clrf INDF return LoadAll clrf Count _la movfw Count call LoadDutyCycle ; Init Dutycycle for channel N incf Count,F movlw PWM_CHANNELS xorwf Count,W bnz _la return SelectChannel movlw 1 subwf IRCommand,W movwf ActChannel goto CommandDone ;***************************************************************************** ; LoadEE loads WREG bytes starting from EE memory pointed to by EEADR ; into the location pointed to by FSR ;***************************************************************************** LoadEE: movwf Temp _loadloop bsf STATUS,RP0 ; BANK1 bsf EECON1,RD ; EE Read movf EEDATA,w ; W = EEDATA movwf INDF incf FSR,F incf EEADR,f ; To next EE Address bcf STATUS,RP0 ; BANK0 decfsz Temp,f goto _loadloop return ;***************************************************************************** ; SaveEE saves WREG bytes starting from the location pointed to by FSR ; into EE memory pointed to by EEADR ;***************************************************************************** SaveEE: movwf Temp ; Variable count bsf STATUS,RP0 ; BANK1 bsf EECON1,WREN ; Enable EE write _saveloop: bsf STATUS,RP0 ; BANK1 movf INDF,w movwf EEDATA call EEWrite incf EEADR,f incf FSR,F bsf STATUS,RP0 ; BANK1 decfsz Temp,f goto _saveloop bsf STATUS,RP0 ; BANK1 bcf EECON1,WREN ; Disable EE write bcf STATUS,RP0 ; BANK0 return ERRORLEVEL -302 EEWrite: bcf INTCON,GIE ; Disable interrupts movlw 0x55 ; Write required sequence movwf EECON2 movlw 0xAA movwf EECON2 bsf EECON1,WR bsf INTCON,GIE ; Enable interrupts again btfsc EECON1,WR ; wait for write to complete ( typ. 10 mSec) goto $-1 return ERRORLEVEL +302 ;***************************************************************************** ; EEPROM contents: Keeps last used intensities & Active channel ;***************************************************************************** org 0x2100 de .0 ; Active channel = 0 de .5,.5,.5,.5,0,0,0,0 ; Default intensities for channel 0-7 END ; End of program