; ============================================================================== ; Title: Illuminated Cubes ; ; Author: Rob Jansen, Copyright (c) 2020..2020, all rights reserved. ; ; Revision: ; 2020-11-04 : Initial version. Copy of illuminated presents project. ; 2021-03-28 : V2. Extended minimum on time and lowered intensity of LED to 30% ; for small cube and 60% for lage cube to save power ; Compiler: 2.5r4 ; ; Description: Changing the color of 2 RGB LEDs which are part of 2 Cubes. ; The device is battery powered with 3 AA or 3 AAA batteries. ; 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 resetting the device again. ; The cubes color change from the color red to green and the other ; way around by fading the colors in and out. In some special occasions ; occasions both LEDs will turn blue for a short period of time ; which is just for fun and to also make use of the blue color. ; ; Sources: - ; ;=============================================================================== include 12f617 ; 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 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 --------------------------------------- ; LEDs are common anode so active low. const bit LED_ON = FALSE const bit LED_OFF = TRUE ; LED 1. alias led1_Red is pin_GP0 ; Pin 7 for 8 pin DIP. led1_Red = LED_OFF ; First put off before making output. pin_GP0_direction = output alias led1_Green is pin_GP1 ; Pin 6 for 8 pin DIP. led1_Green = LED_OFF ; First put off before making output. pin_GP1_direction = output ; LED 2. alias led2_Red is pin_GP5 ; Pin 2 for 8 pin DIP. led2_Red = LED_OFF ; First put off before making output. pin_GP5_direction = output alias led2_Green is pin_GP4 ; Pin 3 for 8 pin DIP. led2_Green = LED_OFF ; First put off before making output. pin_GP4_direction = output ; Blue combined for LED 1 and LED 2. alias led12_Blue is pin_GP2 ; Pin 5 for 8 pin DIP. led12_Blue = LED_OFF ; First put off before making output. pin_GP2_direction = output ; Make all pins digital instead of analog. enable_digital_io() ; -------------------------- 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. const word VOLTAGE_LEVEL = 210 ; This equals a battery volrage of 3.0 Volt const byte VOLTAGE_SAMPLES = 10 ; Number of samples before deciding to sleep. ; Constants to keep the LEDs on for some time, expressed in 40 ms ticks. const word NO_ON_TIME = 0 const word MIN_ON_TIME = 375 ; 15 seconds const word MAX_ON_TIME = 700 ; 30 seconds const word BLUE_ON_TIME = 500 ; Special case just for fun, blue on for 20 s. ; See timer init, gives 100 Hz. const byte PWM_PERIOD_TIME = 51 ; Constant to get a 40 milisecond timer, a value of 255 - 51 equals 10 ms. const byte TIMER_40_MS = 204 ; Timer 2 runs at 5100 Hz. ; Specify the values for the minimum and maximum brightness of the LED ; and the value to increase or decrease it. The STEP_BRIGHT also determines ; long the fade-in and fade-out takes to have to be in adjustes when the ; MAX_BRIGHT changes. Initially the value was 255 MAX_STEP / 5 STEP_BRIGHT so ; 51 each 10 ms steps so a complete fade-in takes 51 * 0,01 = 0,5 seconds. ; MAX_BRIGHT must always smaller than 255 - STEP_BRIGHT (so never 255). const byte MAX_BRIGHT_L = 153 ; Brightness large cube to 60% of the maximum. const byte MAX_BRIGHT_S = 77 ; Brightness small cube to 30% of the maximum. const byte STEP_BRIGHT = 5 ; Always keep this at 5. ; 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 ; 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 ; ------------------------- 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 word led1_red_on_time var word led1_green_on_time var word led2_red_on_time var word led2_green_on_time var word led12_blue_on_time var word new_time var byte timer_counter var byte voltage_sample_counter var byte pwm_period var byte led1_red_timer var byte led1_green_timer var byte led2_red_timer var byte led2_green_timer var byte led12_blue_timer var byte led1_red_duty_cycle var byte led1_green_duty_cycle var byte led2_red_duty_cycle var byte led2_green_duty_cycle var byte led12_blue_duty_cycle var byte power_state var bit led1_red_fade_in var bit led1_green_fade_in var bit led2_red_fade_in var bit led2_green_fade_in var bit led12_blue_fade_in var bit led1_red_fade_out var bit led1_green_fade_out var bit led2_red_fade_out var bit led2_green_fade_out var bit led12_blue_fade_out var bit adc_value_available var bit timer_tick ; ========================= 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 ; This function returns a random on time within the defined minimum and maximum. function give_random_on_time() return word is var word what_time what_time = give_random_word() % MAX_ON_TIME if what_time < MIN_ON_TIME then what_time = what_time + MIN_ON_TIME end if return what_time end function ; 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 ; 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 ; Start with all LEDs off led1_Red = LED_OFF led1_Green = LED_OFF led2_Red = LED_OFF led2_Green = LED_OFF led12_Blue = LED_OFF ; Initialze fading, we start with red for led 1 and green for led 2. led1_red_fade_in = TRUE ; Start value. led1_green_fade_in = FALSE led2_red_fade_in = FALSE led2_green_fade_in = TRUE ; Start value. led12_blue_fade_in = FALSE led1_red_fade_out = FALSE led1_green_fade_out = FALSE led2_red_fade_out = FALSE led2_green_fade_out = FALSE led12_blue_fade_out = FALSE ; Initially set the on-time for both leds to a random time. led1_red_on_time = give_random_on_time(); Random start value. led1_green_on_time = NO_ON_TIME led2_red_on_time = NO_ON_TIME led2_green_on_time = give_random_on_time(); Random start value. led12_blue_on_time = NO_ON_TIME ; Start with all LEDs off so no duty cycle. led1_red_duty_cycle = 0 led1_green_duty_cycle = 0 led2_red_duty_cycle = 0 led2_green_duty_cycle = 0 led12_blue_duty_cycle = 0 ; Clear the 40 ms timer. timer_counter = TIMER_40_MS timer_tick = FALSE ; Initialize Timer 2. T2CON_TMR2ON = FALSE ; Timer 2 off 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. T2CON_TMR2ON = TRUE ; Start Timer 2 end procedure procedure pwm_start() is T2CON_TMR2ON = TRUE end procedure procedure pwm_stop() is T2CON_TMR2ON = FALSE led1_Red = LED_OFF led1_Green = LED_OFF led2_Red = LED_OFF led2_Green = LED_OFF led12_Blue = LED_OFF 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 around 100 us. procedure pwm_cycle is pragma interrupt if PIR1_TMR2IF & PIE1_TMR2IE then PIR1_TMR2IF = FALSE ; 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 led1_red_timer = led1_red_duty_cycle led1_green_timer = led1_green_duty_cycle led2_red_timer = led2_red_duty_cycle led2_green_timer = led2_green_duty_cycle led12_blue_timer = led12_blue_duty_cycle ; Only turn LED on if it has a duty cycle to prevent flashing. if (led1_red_timer > 0) then led1_red = LED_ON end if if (led1_green_timer > 0) then led1_green = LED_ON end if if (led2_red_timer > 0) then led2_red = LED_ON end if if (led2_green_timer > 0) then led2_green = LED_ON end if if (led12_blue_timer > 0) then led12_blue = LED_ON end if else pwm_period = pwm_period - 1 end if ; If the timer of the LED is 0 then the duty cycle time has passed. if (led1_red_timer >= STEP_BRIGHT) then led1_red_timer = led1_red_timer - STEP_BRIGHT else led1_red = LED_OFF end if if (led1_green_timer >= STEP_BRIGHT) then led1_green_timer = led1_green_timer - STEP_BRIGHT else led1_green = LED_OFF end if if (led2_red_timer >= STEP_BRIGHT) then led2_red_timer = led2_red_timer - STEP_BRIGHT else led2_red = LED_OFF end if if (led2_green_timer >= STEP_BRIGHT) then led2_green_timer = led2_green_timer - STEP_BRIGHT else led2_green = LED_OFF end if if (led12_blue_timer >= STEP_BRIGHT) then led12_blue_timer = led12_blue_timer - STEP_BRIGHT else led12_blue = LED_OFF end if ; Check for 40 ms to have passed. if (timer_counter == 0) then timer_counter = TIMER_40_MS timer_tick = TRUE else timer_counter = timer_counter - 1 end if end if end procedure ; Check if the given time is an exception to show blue. If so set the correct ; time, fade-in and fade out and return TRUE. function blue_exception(word in which_time) return bit is var bit is_exception if which_time == BLUE_ON_TIME then ; Exception. Fade in blue, fade out all others. led1_red_fade_out = TRUE led1_green_fade_out = TRUE led2_red_fade_out = TRUE led2_green_fade_out = TRUE led12_blue_fade_in = TRUE ; Set the blue time but switch of the on-time for the others. led1_red_on_time = NO_ON_TIME led1_green_on_time = NO_ON_TIME led2_red_on_time = NO_ON_TIME led2_green_on_time = NO_ON_TIME led12_blue_on_time = which_time is_exception = TRUE else is_exception = FALSE end if return is_exception end function ; 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. VRCON_CMVREN = FALSE ; Disable comparator reference to save power (default). VRCON_FVREN = TRUE ; 0.6 Volt reference must be enabled 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_NDONE = TRUE end procedure ; Enable the ADC and 0.6 Volt reference again. Note this function is not used ; since we only restart from power up or reset and not from sleep. procedure adc_enable() is adc_value_available = FALSE ADCON0 = ADC_INIT_VALUE VRCON_FVREN = TRUE end procedure ; Disable the ADC and disable the 0.6 Volt reference to save power. procedure adc_disable() is ADCON0 = ADC_OFF_VALUE VRCON_FVREN = FALSE end procedure ; Disable some hardware and put the device in sleep. Power consumption is ; minimal. procedure go_to_sleep() is pwm_stop() adc_disable() INTCON_GIE = FALSE ; Interrupts cannot wake up the device. _usec_delay(200_000) ; Let all run-out. asm sleep ; Go to sleep, to be woken up by a power up or 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) if power_state == POWER_OFF then power_state = POWER_ON else power_state = POWER_OFF 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 ; Initialize the rest. adc_init() pwm_init() INTCON_PEIE = TRUE INTCON_GIE = TRUE voltage_sample_counter = 0 forever Loop ; The main loop runs at a timer tick of 40 miliseconds and takes between ; 200 us and 1.5 ms. This reference time is used for phase in and phase out ; of the LEDs, the on-time and off-time of the LEDs and for frequently ; sampling the battery voltage using the ADC. if timer_tick then timer_tick = FALSE ; Check if we need to switch off the device 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 ; Check if we need to change the colors of the LEDs. A value of 0 means ; that the LED is not active. if led1_red_on_time != NO_ON_TIME then if led1_red_on_time == (NO_ON_TIME + 1) then ; We are done with red, fade it out and start fading in green if ; there is no exception for blue. led1_red_on_time = NO_ON_TIME new_time = give_random_on_time() if !blue_exception(new_time) then led1_red_fade_out = TRUE led12_blue_fade_out = TRUE led1_green_fade_in = TRUE led1_green_on_time = new_time end if else led1_red_on_time = led1_red_on_time - 1 end if end if if led1_green_on_time != NO_ON_TIME then if led1_green_on_time == (NO_ON_TIME + 1) then ; We are done with green, fade it out and start fading in red if ; there is no exception for blue. led1_green_on_time = NO_ON_TIME new_time = give_random_on_time() if !blue_exception(new_time) then led1_green_fade_out = TRUE led12_blue_fade_out = TRUE led1_red_fade_in = TRUE led1_red_on_time = new_time end if else led1_green_on_time = led1_green_on_time - 1 end if end if if led2_red_on_time != NO_ON_TIME then if led2_red_on_time == (NO_ON_TIME + 1) then ; We are done with red, fade it out and start fading in green if ; there is no exception for blue. led2_red_on_time = NO_ON_TIME new_time = give_random_on_time() if !blue_exception(new_time) then led2_red_fade_out = TRUE led12_blue_fade_out = TRUE led2_green_fade_in = TRUE led2_green_on_time = new_time end if else led2_red_on_time = led2_red_on_time - 1 end if end if if led2_green_on_time != NO_ON_TIME then if led2_green_on_time == (NO_ON_TIME + 1) then ; We are done with green, fade it out and start fading in red if ; there is no exception for blue. led2_green_on_time = NO_ON_TIME new_time = give_random_on_time() if !blue_exception(new_time) then led2_green_fade_out = TRUE led12_blue_fade_out = TRUE led2_red_fade_in = TRUE led2_red_on_time = new_time end if else led2_green_on_time = led2_green_on_time - 1 end if end if if led12_blue_on_time != NO_ON_TIME then if led12_blue_on_time == (NO_ON_TIME + 1) then ; We are done with blue, fade it out and start fading in red om ; one and green on two. led12_blue_on_time = NO_ON_TIME led12_blue_fade_out = TRUE led1_red_fade_in = TRUE led2_green_fade_in = TRUE led1_red_on_time = give_random_on_time() led2_green_on_time = give_random_on_time() else led12_blue_on_time = led12_blue_on_time - 1 end if end if ; Handle fade-in and fade-out of all LEDs. if led1_red_fade_in then if led1_red_duty_cycle < MAX_BRIGHT_L then led1_red_duty_cycle = led1_red_duty_cycle + STEP_BRIGHT else led1_red_fade_in = FALSE end if elsif led1_red_fade_out then ; We check the minimum against step bright to get it to zero. if led1_red_duty_cycle >= STEP_BRIGHT then led1_red_duty_cycle = led1_red_duty_cycle - STEP_BRIGHT else led1_red_fade_out = FALSE end if end if if led1_green_fade_in then if led1_green_duty_cycle < MAX_BRIGHT_L then led1_green_duty_cycle = led1_green_duty_cycle + STEP_BRIGHT else led1_green_fade_in = FALSE end if elsif led1_green_fade_out then ; We check the minimum against step bright to get it to zero. if led1_green_duty_cycle >= STEP_BRIGHT then led1_green_duty_cycle = led1_green_duty_cycle - STEP_BRIGHT else led1_green_fade_out = FALSE end if end if if led2_red_fade_in then if led2_red_duty_cycle < MAX_BRIGHT_S then led2_red_duty_cycle = led2_red_duty_cycle + STEP_BRIGHT else led2_red_fade_in = FALSE end if elsif led2_red_fade_out then ; We check the minimum against step bright to get it to zero. if led2_red_duty_cycle >= STEP_BRIGHT then led2_red_duty_cycle = led2_red_duty_cycle - STEP_BRIGHT else led2_red_fade_out = FALSE end if end if if led2_green_fade_in then if led2_green_duty_cycle < MAX_BRIGHT_S then led2_green_duty_cycle = led2_green_duty_cycle + STEP_BRIGHT else led2_green_fade_in = FALSE end if elsif led2_green_fade_out then ; We check the minimum against step bright to get it to zero. if led2_green_duty_cycle >= STEP_BRIGHT then led2_green_duty_cycle = led2_green_duty_cycle - STEP_BRIGHT else led2_green_fade_out = FALSE end if end if if led12_blue_fade_in then if led12_blue_duty_cycle < MAX_BRIGHT_L then led12_blue_duty_cycle = led12_blue_duty_cycle + STEP_BRIGHT else led12_blue_fade_in = FALSE end if elsif led12_blue_fade_out then ; We check the minimum against step bright to get it to zero. if led12_blue_duty_cycle >= STEP_BRIGHT then led12_blue_duty_cycle = led12_blue_duty_cycle - STEP_BRIGHT else led12_blue_fade_out = FALSE end if end if ; We only start the ADC once in the main loop and we will check new data ; after the new timer tick which is sufficient for the ADC to do a conversion. adc_start() end if ; timer_tick end loop