;=============================================================================== ; Title: Dimmer ; ; Author: Rob Jansen, Copyright (c) 2016 .. 2016, all rights reserved. ; ; Revision: ; 2016-12-27 : Initial version for PIC16F1823 using Timer 2. ; 2016-12-30 : Added the mode function. ; 2016-12-30 : Adapted for PIC12F675 using Timer 1. ; 2017-01-08 : Changed operation to use only one switch. ; 2017-01-20 : Added crystal and adapted trigger pulse for Triac. ; 2017-01-28 : Added test mode. ; ; Compiler: jalv24q6 ; ; Description: Electronic Dimmer that has the following features: ; *) Dim a Lamp using zero detection for less distortion. A zero ; detection signal is connected to the external interrupt. ; *) Programmable and remembered Dim Level. ; *) Programmable and remembered Dim Mode. ; *) Operating mode and Dim Level controlled by one switch. ; ; Sources: - ; ;=============================================================================== ; Some compiler pragmas for optimizations Pragma warn all yes ; We want to know all compiler warnings Pragma opt variable_reduce yes ; Reduce variables. Note there seem to be a ; problem in the complier if this is set to no. include 12f675 ; target PICmicro ; This PIC uses the 20 MHz Crystal Pragma target clock 20_000_000 ; Oscillator frequency 20 MHz ; Configuration memory settings (fuses). These are only a selection, sufficient ; for this program. Pragma target OSC HS ; High Speed Crystal Pragma target WDT DISABLED ; No Watchdog Pragma target PWRTE ENABLED ; Power up timer enabled Pragma target BROWNOUT DISABLED ; No brownout reset Pragma target MCLR INTERNAL ; Reset internal ; Enable weak pull up just to sure that we have no floating pins. OPTION_REG_NGPPU = FALSE ; Enable Weak Pull-Up WPU = 0b_0011_0111 ; Pull-Up enable for all General Purpose pin. enable_digital_io() ; Make all pins digital I/O ; Specify the pin that control the Triac and so the Dim Level. Alias Triac Is Pin_GP1 ; Pin 6. Pin_GP1_Direction = Output ; Specify the pin that are connected to the switch. Alias Switch Is Pin_GP3 Pin_GP3_Direction = Input ; Pin 4. This is the unused reset pin. ; Specify the DIM LED. Alias LED_DIM Is Pin_GP0 ; Pin 7. Pin_GP0_Direction = Output ; The zero detection signal is connected to the external interrupt, Pin 5. ; ================== Constant and variable declarations ======================= ; Constants used for enabling and disabling the outputs. Const Bit TRIAC_ON = TRUE Const Bit TRIAC_OFF = FALSE Const Bit LED_ON = TRUE Const Bit LED_OFF = FALSE ; Constants and values used for the EEPROM. Const Byte ADDRESS_Dim_Value = 0 Const Byte ADDRESS_DIM_MODE = 1 Var Byte EEPROM_Data ; Constants and variables used for timer purposes. We also define a correction ; for the zero detection. After about 400 us that the sine wave goes through ; zero the interrupt is generated to we are 400 us too late, this needs to be ; corrected. 400 us is about 4% of the 10 ms so we need to subtract that from ; the maximum time and we also need to include the triac pulse of 24 us. So in ; total we should subtract about 450 us from the on-time to be on the save side. ; One timer tick is 1,6 us. 450 us is about 282 timer ticks and divided ; by 25 this is about 12. This will be the maximum value for the lamp. Const Word DIM_TRIAC_PULSE = 0xFFFF - 14 ; 15 clocks is 24 us. Const Word ZERO_CORRECTION = 250 ; The zero detection delay of 400 us. Const BYTE DIM_OFF = 0 ; Lamp will be continuously off. Const Byte DIM_ON = 1 ; Lamp will be continuously on. Const Byte DIM_DIM = 2 ; Lamp will be dimmed. Var Byte Dim_Mode ; Holds the current dim mode. Var Bit Dim_Mode_Changed ; If set, new setting will be stored in EEPROM. ; Constants and variables used for dimming purposes. Note that the dimming ; values are inverted which means that a high value is a low dim since the ; Dim value determines the delay time after the zero detection before we ; turn on the lamp. We use a maximum of 250 dimming steps. Const Byte MIN_DIM = 250 ; Minimum brightness = off. Const Byte MAX_DIM = 12 ; Maximum brightness = on. Const Byte DEFAULT_DIM = 125 ; Mid-way default value used at first start-up. Var Byte Duty_Cycle ; Holds the Duty Cycle for dimming. Var Byte Dim_Value ; Holds the current Dim_Value. Var Bit Dim_Value_Changed ; If set, new setting will be stored in EEPROM. ; Constants and variables used for handling the switches. Const Word KEY_DEBOUNCE_TIME = 100_000 ; 100 ms (_usec_delay). Const Word LOOP_TIME = 10_000 ; 10 ms (_usec_delay). Const Byte KEY_DOWN_TIME = 100 ; 100 * 10 ms = 1 seconds. Const Byte KEY_REPEAT_TIME = 3 ; 3 * 10 ms = 30 miliseconds per step. Var Byte Timer ; General purpose timer. Var Bit Control_Up ; Change Dim_Value up or down. Var Bit Dim_Pending ; Indicates that a DIM cycle is waiting to start. ; ========================= Functions and Procedures ========================== Procedure Handle_Interrupts Is Pragma interrupt ; This procedure handles the external interrupt and Timer 1 interrupt. It is ; important that the external interrupt is serviced first since it starts ; the start of a new Dim cycle using Timer 1. The external interrupt is ; triggered by the zero detection circuit. The overflow interrupt of Timer 1 ; will stop the DIM cycle. If INTCON_INTF Then INTCON_INTF = FALSE ; We must always stop Timer 1 and clear any pending interrupt of Timer 1. T1CON_TMR1ON = FALSE ; Stop Timer 1. Dim_Pending = TRUE ; This is the start of a new Dim Cycle. ; Preset Timer 1 for the next cycle. Note that it runs at 25 times ; the speed so we need to lower the value with a factor of 25. We add the ; zero detection correct as to prevent that the lamp switches on after ; being fully dimmed. TMR1 = 0xFFFF - (Word(Duty_Cycle) * 25) + ZERO_CORRECTION ; Stop any pending interrupt since this is a new Dim cycle. PIR1_TMR1IF = FALSE ; Check if we need to start any dim cycle. If (Duty_Cycle == MIN_DIM) Then Triac = TRIAC_OFF Elsif (Duty_Cycle == MAX_DIM) Then Triac = TRIAC_ON Else T1CON_TMR1ON = TRUE ; Start Timer 1. End If ElsIf PIR1_TMR1IF ; We only cbeck Timer 1 if there was no external interrupt. Then ; Timer 1 has overflows this can mean that we need to generate a DIM pulse ; or clear the triggerpulse for the Triac since the DIM pulse was already ; generated. If Dim_Pending Then ; This is a new trigger for a DIM cycle, activate the TRIAC. Triac = TRIAC_ON Dim_Pending = FALSE ; Reload Timer 1 with a value for a the Triac trigger pulse. TMR1 = DIM_TRIAC_PULSE Else ; Not a Dim_Pending, clear Triac trigger pulse. Triac = TRIAC_OFF ; We are done diming. T1CON_TMR1ON = FALSE ; Stop the dim timer. End If PIR1_TMR1IF = FALSE End If ; PIR1_TMR1IF End Procedure ; Handle_Interrupts Function EEPROM_Read(Byte in EE_Address) Return Byte Is ; Read one byte from the EEPROM from the given address and return it. EEADR = EE_Address ; Activate Read. EECON1_RD = TRUE Return EEDATA End Function ; EEPROM_Read Procedure EEPROM_Write(Byte in EE_Address, Byte in EE_Data) Is ; Write one byte to the EEPROM to the given address. It is important to disable ; all interrupts during write. The routine waits until the Write cycle is ; complete. EEADR = EE_Address EEDATA = EE_Data ; Writes have to be enabled. EECON1_WREN = TRUE ; Interrupt must be disabled before special write sequence is activated. INTCON_GIE = FALSE ; Do the special write sequence to write data to EEPROM. EECON2 = 0x55 EECON2 = 0xAA EECON1_WR = TRUE ; Interrupt can be enabled again. INTCON_GIE = TRUE ; It is safe to disable next writes. EECON1_WREN = FALSE ; Wait for write to complete, write bit will be cleared when write is done. While EECON1_WR Loop End Loop End Procedure ; EEPROM_Write Procedure Dimmer_Test Is ; This is a test program for the Dimmer. It dims between maximum and minimum ; brightness. This loop never ends. While TRUE Loop While (Dim_Value > MAX_DIM) Loop _usec_delay(50_000) Dim_Value = Dim_Value - 1 Duty_Cycle = Dim_Value End Loop ; Stay at this position for a while. _usec_delay(500_000) While (Dim_Value < MIN_DIM) loop _usec_delay(50_000) Dim_Value = Dim_Value + 1 Duty_Cycle = Dim_Value End Loop ; Stay at this position for a while. _usec_delay(500_000) End Loop End Procedure ; Dimmer_Test ; ========================= Main program starts here ========================== ; Switch off the output. Triac = TRIAC_OFF Duty_Cycle = 0 LED_DIM = LED_OFF ; Read stored Dim_Value value from EEPROM, if present. EEPROM_Data = EEPROM_Read(ADDRESS_Dim_Value) If (EEPROM_Data > MIN_DIM) Then Dim_Value = DEFAULT_DIM ; Out of bound value, set default. Else Dim_Value = EEPROM_Data End If ; Read stored operating mode value from EEPROM, if present. EEPROM_Data = EEPROM_Read(ADDRESS_DIM_MODE) If (EEPROM_Data > DIM_DIM) Then Dim_Mode = DIM_OFF ; Out of bound value, set default. Else Dim_Mode = EEPROM_Data End If ; Set the duty cycle depending on the DIM mode. If (Dim_Mode == DIM_OFF) Then Duty_Cycle = MIN_DIM Elsif (Dim_Mode == DIM_ON) Then Duty_Cycle = MAX_DIM Else ; Dim mode was stored. Duty_Cycle = Dim_Value LED_DIM = LED_ON End If ; Dim_Mode ; Initialize the remaining global variables. Dim_Value_Changed = FALSE Dim_Mode_Changed = FALSE Control_Up = FALSE Dim_Pending = FALSE ; We enable the external interrupt for the zero detection. This signal goes ; active low when the sine wave passing through '0'. OPTION_REG_INTEDG = FALSE ; Interrupt on falling edge. ; The duty cycle can be dimmed in 250 steps. Since we need to do this each ; bit half (100 Hz) we need a timing frequency of 250 * 100 = 25 kHz. ; We use Timer 1 for this purpose. When selecting internal clock and a ; prescale of 8 we get 20.000.000 / 4 / 8 = 625 kHz or 1,6 us so 25 times as ; high which means that the Dim increment and decrement for timer 1 should be ; done in steps of 25. T1CON_TMR1ON = FALSE ; Timer 1 off T1CON_TMR1GE = FALSE ; Disable gate enable. T1CON_T1OSCEN = FALSE ; No LP oscillator. T1CON_NT1SYNC = TRUE ; Do not synchronize external clock input. T1CON_TMR1CS = FALSE ; Use internal clock (Fosc/4). T1CON_T1CKPS = 0b11 ; Prescaler 1:8 so 125 kHz clock. TMR1 = 0 PIR1_TMR1IF = FALSE ; Clear Timer 1 interrupt flag. PIE1_TMR1IE = TRUE ; Enable Timer 1 interrupt. ; Enable all used interrupt, external and - peripheral - timers. INTCON_INTE = TRUE ; Enable external interrupt. INTCON_PEIE = TRUE ; Enable peripheral interrupt. INTCON_GIE = TRUE ; Globale interrupt enabled. ; First check if we need to activate the test mode. It can be activated by ; pressing the switch while powering up the circuit. If !Switch Then Dimmer_Test End If ; !Switch Forever Loop ; Check if the switch is pressed (active low). If !Switch Then ; Switch pressed, debounce. _usec_delay(KEY_DEBOUNCE_TIME) ; Continue if switch still pressed. If !Switch Then ; If switch is still active then either wait for key release or ; if after a certain time it is still pressed change the Dim_Value. Timer = KEY_DOWN_TIME ; DIM timer value. While !Switch & (Timer != 0) Loop _usec_delay(LOOP_TIME) Timer = Timer - 1 End Loop ; !Switch ; If the key is still down then we change the DIM mode, otherwise it was ; just a normal key press and we change the operating mode. If (Timer == 0) Then ; Timeout so still pressed, change Dim_Value until key released but only ; if we are in DIM Mode, otherwise ignore it and wait for key release. If (Dim_Mode == DIM_DIM) Then Control_Up = !Control_Up ; Toggle dimming between up and down. While !Switch Loop If Control_Up Then ; Brightness up means dim value going down. If (Dim_Value > MAX_DIM) Then Dim_Value = Dim_Value - 1 Dim_Value_Changed = TRUE End If Else ; Brightness down means dim value going up. If (Dim_Value < MIN_DIM) Then Dim_Value = Dim_Value + 1 Dim_Value_Changed = TRUE End If End If ; Control_Up ; Copy any changed Dim_Value values as new duty cycle. Duty_Cycle = Dim_Value ; Wait some time before repeating but only if key still pressed. Timer = KEY_REPEAT_TIME While !Switch & (Timer != 0) Loop _usec_delay(LOOP_TIME) Timer = Timer - 1 End Loop ; !Switch End Loop ; !Switch Else ; Not in DIM mode, wait for key release. While !Switch Loop End Loop End If ; Dim_Mode Else ; Normal key press, change mode. Case Dim_Mode Of DIM_OFF: Block ; Mode was OFF, switch to ON. Dim_Mode = DIM_ON LED_DIM = LED_OFF Duty_Cycle = MAX_DIM End Block ; Dim_Mode DIM_ON: Block ; Mode was ON, switch to Dim. Dim_Mode = DIM_DIM LED_DIM = LED_ON Duty_Cycle = Dim_Value End Block DIM_DIM: Block ; Mode was Dim, switch to Off. Dim_Mode = DIM_OFF LED_DIM = LED_OFF Duty_Cycle = MIN_DIM End Block End Case ; Dim_Mode Dim_Mode_Changed = TRUE End If ; Timer ; Check if new Dim_Value needs to be stored in EEPROM. If Dim_Value_Changed Then ; Write to EEPROM. EEPROM_Write(ADDRESS_Dim_Value,Dim_Value) Dim_Value_Changed = FALSE End If ; Dim_Value_Changed ; Check if new dim mode needs to be stored in EEPROM. If Dim_Mode_Changed Then ; Write to EEPROM. EEPROM_Write(ADDRESS_DIM_MODE,Dim_Mode) Dim_Mode_Changed = FALSE End If ; Dim_Mode_Changed End If ; !Switch End If ; !Switch End Loop