; ============================================================================== ; Title: Four Candles ; ; Author: Rob Jansen, Copyright (c) 2020..2020, all rights reserved. ; ; Revision: ; 2020-12-05 : Initial version. Copy from four candles project of 2019. ; ; Compiler: 2.5r4 ; ; Description: Simulate 3 electronic Candles which are battery powered. If the ; battery voltage drops below 3.0 Volt, the device will switch off. ; Switching off means the PIC is put into sleep mode and can only ; be woken up by powering up the device or a reset. ; The reset button is used as power on-off switch. ; ; Sources: - ; ;=============================================================================== include 12f615 ; target PICmicro ; Use internal clock and internal reset. pragma target OSC INTOSC_NOCLKOUT ; Internal Clock pragma target PWRTE ENABLED ; Power up timer pragma target MCLR EXTERNAL ; Reset external, used for power on/off. pragma target WDT DISABLED ; No watchdog pragma target BROWNOUT DISABLED ; Disable to save power pragma target IOSCFS F4MHZ ; Set internal oscillator to 4 MHz pragma target clock 4_000_000 ; Oscillator frequency 4 MHz ; --------------------------------- Pins --------------------------------------- alias led_1 is pin_GP0 ; Pin 7. pin_GP0_direction = output alias led_2 is pin_GP1 ; Pin 6. pin_GP1_direction = output alias led_3 is pin_GP2 ; Pin 5. pin_GP2_direction = output alias test_pin is pin_GP4 ; Pin 3. pin_GP4_direction = output ; -------------------------- Constant declarations ----------------------------- ; ADC initialization value. Right justified, VDD 0,6 Volt reference ADC enabled. ; For the off value we remove the 0,6 Volt reference otherwise a low power ; consumption remains when in sleep mode. const byte ADC_INIT_VALUE = 0b1001_0101 const byte ADC_OFF_VALUE = 0b1000_0000 ; Values for detecting when the voltage of the battery gets too low. The higher ; the figure, the lower the supply voltage. This value can be PIC dependent so ; make sure to set it right for the PIC used in the design. const word VOLTAGE_LEVEL = 200 ; This equals a battery volrage of 3.0 Volt const byte VOLTAGE_SAMPLES = 10 ; Number of samples before deciding to sleep. ; We will only initalize the random generator with this seed if the intial ; value is 0 since that is not allowed for the random generator const word RANDOM_SEED = 0xACE1 ; Values to keep track of the power state. These are random bit patterns. const byte POWER_ON = 0b1101_0110 const byte POWER_OFF = 0b1011_1010 ; Constants to control the LED, active low. const bit LED_ON = FALSE const bit LED_OFF = TRUE ; Specify the values for the minimum and maximum brightness of the LED ; and the value to increase or decrease it. The increase and decrease values ; are different as to create a better candle effect. const byte MIN_BRIGHT = 70 const byte MAX_BRIGHT = 250 const byte UP_BRIGHT = 5 const byte DOWN_BRIGHT = 50 ; We have step incremets of 5 (UP_BRIGHT) which means that our PWM interrupt ; routine needs to run 255 / 5 = 51 times faster. const byte PWM_PERIOD_TIME = 51 ; See timer init, gives 100 Hz. ; ------------------------- Variable declarations ----------------------------- ; 16 bit shift register used to generate a random number. It generates all ; numbers between 0x0001 and 0xFFFF and has to be started with a seed. ; See http://en.wikipedia.org/wiki/Linear_feedback_shift_register var word random_shift_word ; See initialization later at power up. var word adc_capture_value var word adc_value var byte voltage_sample_counter var byte power_state var byte pwm_period var byte led_1_Timer var byte led_2_Timer var byte led_3_Timer var byte duty_cycle_1 var byte duty_cycle_2 var byte duty_cycle_3 var bit adc_value_available ; ========================= Functions and Procedures ========================== ; Generate a new random number using a Linear Feedback Shift Register (LFSR). function give_random_word return word is var word value ; Use Fibonacci LSFR x16 + x14 + x13 + x11 + 1 value = (random_shift_word ^ (random_shift_word >> 2) ^ (random_shift_word >> 3) ^ (random_shift_word >> 5)) & 0x0001 random_shift_word = (random_shift_word >> 1) | (value << 15) return random_shift_word end function ; Start timer 2 for generating the PWM signal. procedure pwm_start() is T2CON_TMR2ON = TRUE end procedure ; Switch all LEDs off and stop the timer we use for the PWM signal procedure pwm_stop() is T2CON_TMR2ON = FALSE led_1 = LED_OFF led_2 = LED_OFF led_3 = LED_OFF end procedure ; Initialization procedure for setting up the software PWM using timer 2. We ; need a frequency higher than what the eye can see (period) and due to the step ; increment of 5 (duty cycle) the timer need to run at 255 / 5 is 51 times that ; speed. If we go for a 100 Hz period then the timer should run at 5100 Hz or ; a period time of 196 us. procedure pwm_init() is pwm_stop() ; Set a random brightness for each led to start with. duty_cycle_1 = byte(give_random_word) duty_cycle_2 = byte(give_random_word) duty_cycle_2 = byte(give_random_word) ; Register PR2 holds the Timer Period using the following formula: ; Period = (PR2 + 1) * 4 * Tosc * Timer2 prescale value ; where Tosc = 1/Fosc and Fosc = 4.000.000 Hz. ; With a PR2 reload value of 191 and no prescaler ; Period = (195 + 1) * 4 * 1/4.000.000 * 1 = 196 us or 5102 HZ T2CON_TOUTPS = 0b0000 ; Postscaler is 1:1 T2CON_T2CKPS = 0b00 ; Prescaler divide by 1 PR2 = 195 ; Reload value. pwm_period = 0 ; Starts a new period PIE1_TMR2IE = TRUE ; Enable Timer 2 interrupt PIR1_TMR2IF = FALSE ; Clear Timer 2 interrupt flag. pwm_start() end procedure ; This interrupt procedure controls the PWM cycle using Timer 2. It controls ; the full operation of the LEDs. LEDs are switched on at the start of a new ; PWM period and are switched off when the duty cycle has passed. ; The routine is called every 196 us and takes at most 60 us. procedure pwm_cycle is pragma interrupt if PIR1_TMR2IF & PIE1_TMR2IE then PIR1_TMR2IF = FALSE test_pin = TRUE ; Used to measure the duration of the routine. ; First check the if we need to start a new period. if (pwm_period == 0) then ; New period. Switch LEDs on and start a new duty cycle. pwm_period = PWM_PERIOD_TIME led_1_timer = duty_cycle_1 led_2_timer = duty_cycle_2 led_3_timer = duty_cycle_3 led_1 = LED_ON led_2 = LED_ON led_3 = LED_ON else pwm_period = pwm_period - 1 end if ; If the timer of the LED is 0 then the duty cycle time has passed. if led_1_timer >= UP_BRIGHT then led_1_timer = led_1_timer - UP_BRIGHT else led_1 = LED_OFF end if if led_2_timer >= UP_BRIGHT then led_2_timer = led_2_timer - UP_BRIGHT else led_2 = LED_OFF end if if led_3_timer >= UP_BRIGHT then led_3_timer = led_3_timer - UP_BRIGHT else led_3 = LED_OFF end if test_pin = FALSE end if end procedure ; Init the ADC and the GPIO pins. We follow the steps from the datasheet. procedure adc_init() Is ; We use Fosc/8 which is OK for a 4 MHz clock. All pins are digital_io. ANSEL = 0b0001_0000 ADCON0 = ADC_INIT_VALUE adc_value_available = FALSE ; Nothing captured yet. PIR1_ADIF = FALSE PIE1_ADIE = TRUE end procedure ; Handle the ADC Interrupt if set. procedure adc_Interrupt is pragma interrupt if PIR1_ADIF & PIE1_ADIE then ; New ADC value captured, copy data if not yet read to prevent ; synchronization error with the main loop. if !adc_value_available then adc_capture_value = word(ADRESH * 255) + word(ADRESL) adc_value_available = TRUE end if PIR1_ADIF = FALSE end if end procedure ; This function indicates when a new ADC value is available and copies the ; values to the global 'ADC_Value' variable. ; a new ADC cycle is started. function new_adc_value return bit is if adc_value_available then ; New value available, copy data. adc_value = adc_capture_value adc_value_available = FALSE return TRUE else return FALSE end if end function procedure adc_start() is ADCON0_GO = TRUE end procedure procedure adc_enable() is adc_value_available = FALSE ADCON0 = ADC_INIT_VALUE end procedure procedure adc_disable() is ADCON0 = ADC_OFF_VALUE end procedure ; Disable some hardware and put the device into sleep. procedure go_to_sleep() is pwm_stop() adc_disable() power_state = POWER_OFF _usec_delay(100_000) ; Let all run-out. asm sleep ; To be woken again by a power up or a reset .... end procedure ; ========================= Main program starts here ========================== ; Power-up wait time for power up and reset button debounce. Check if ; we were reset while already powered on. If so we go to sleep. _usec_delay(100_000) pwm_stop() ; All LEDs off. if power_state == POWER_OFF then power_state = POWER_ON else go_to_sleep() ; We will never come here. Only a reset or power on gets us out of here. end if ; We do not initialze the seed for the random generator as to have a different ; random seed at every reset or power up. We do have to check if the seed for ; the random generator is not 0 since that is not allowed. if random_shift_word == 0 then random_shift_word = RANDOM_SEED end if ; Intialize the ADC and the LEDs with a random start. adc_init() pwm_init() voltage_sample_counter = 0 INTCON_PEIE = TRUE ; Enable peripheral interrupt. INTCON_GIE = TRUE ; Enable global interrupt. ; Enable weak pull up for all ports since some inputs are not connected WPU = 0b0011_1111 ; Weak pull-up enabled for all IO pins OPTION_REG_NGPPU = FALSE ; Enable Weak Pull-Up forever Loop ; Check if we need to switch off the candle because of low voltage. if new_adc_value then if (adc_value > VOLTAGE_LEVEL) then ; Battery going low, check if was not an incident. if (voltage_sample_counter == VOLTAGE_SAMPLES) then voltage_sample_counter = 0 go_to_sleep() else voltage_sample_counter = voltage_sample_counter + 1 end if else voltage_sample_counter = 0 end if end if ; This part detemines the candle effect for the 3 LEDs. The value of 0x1fff ; is chosen to get a certain candle effect. The brightness up step is smaller ; than the brightness down step and because of that we use a lower value to ; decide to increase the brightness. if (give_random_word > 0x1fff) then if (duty_cycle_1 < MAX_BRIGHT) then duty_cycle_1 = duty_cycle_1 + UP_BRIGHT end if else if (duty_cycle_1 > MIN_BRIGHT) then duty_cycle_1 = duty_cycle_1 - DOWN_BRIGHT end if end if if (give_random_word > 0x1fff) then if (duty_cycle_2 < MAX_BRIGHT) then duty_cycle_2 = duty_cycle_2 + UP_BRIGHT end if else if (duty_cycle_2 > MIN_BRIGHT) then duty_cycle_2 = duty_cycle_2 - DOWN_BRIGHT end if end if if (give_random_word > 0x1fff) then if (duty_cycle_3 < MAX_BRIGHT) then duty_cycle_3 = duty_cycle_3 + UP_BRIGHT end if else if (duty_cycle_3 > MIN_BRIGHT) then duty_cycle_3 = duty_cycle_3 - DOWN_BRIGHT end if end if ; Check the voltage by starting the ADC. We will check new data after the ; main loop time which is sufficient for the ADC to do a conversion. adc_start() ; Wait some time to get a candle effect. _usec_delay(20_000) end loop