; ============================================================================== ; Title: Intelligent Tail Light ; ; Author: Rob Jansen, Copyright (c) 2019..2019, all rights reserved. ; ; Revision: ; 2019-03-31 : Initial version. ; ; Compiler: 2.5r2 ; ; Description: Intelligent Tail Light for a Bike. Switches on and off when a ; button is pressed. When powered on it stays on when motion is ; detected via a motion switch. Switches off automatically when no ; motion is detected for 10 minutes but also when the battery ; supply voltage drops below 2.1 ; ; Sources: ; ; ------------------------------ Pragmas --------------------------------------- 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 Internal ; Reset internal pragma target WDT Disabled ; No watchdog pragma target BROWNOUT Disabled ; No brown-out pragma target IOSCFS F4MHZ ; Set internal oscillator to 4 MHz pragma target clock 4_000_000 ; oscillator frequency 4 MHz ; --------------------------------- Pins --------------------------------------- alias motion_switch is pin_GP5 ; Pin 2. pin_GP5_direction = input alias motion_switch_activation is pin_GP4 ; Pin 3. pin_GP4_direction = output alias power_switch is pin_GP2 ; Pin 5 pin_GP2_direction = input alias test_pin is pin_GP1 pin_GP1_direction = output ; Pin 6. Set as output as not to float. alias led is pin_GP0 ; Pin 7. pin_GP0_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. const word VOLTAGE_LEVEL = 280 ; This equals a battery volrage of 2.1 Volt const byte VOLTAGE_SAMPLES = 10 ; Number of samples before deciding to sleep. ; Timeout of motion is set to 10 minutes, main loop uses a 100 us looptime. const dword NO_MOTION = 6_000_000 const byte ADC_LOOP_TIME = 200 ; Loop time for ADC measurement is 20 ms. ; LED constants const bit LED_ON = FALSE const bit LED_OFF = TRUE ; Motion switch constants. We enable and disable the motion switch as to prevent ; that a current flows through the 10k resistor when the switch connects to ; ground which is a waste of power. const bit MOTION_SWITCH_ENABLE = FALSE const bit MOTION_SWITCH_DISABLE = TRUE ; ------------------------- Variable declarations ----------------------------- var word adc_capture_value var word adc_value var dword motion_counter var byte adc_loop_timer var byte voltage_sample_counter var bit adc_value_available var bit motion_value ; ------------------------- Functions and Procedures--------------------------- ; 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. ; Also disable the comparator reference voltages to save power. VRCON_CMVREN = FALSE VRCON_FVREN = FALSE 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. 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 ; Set the external interrupt for detection of the power switched being pressed. ; The external interrupt wakes up the tail light when in sleep mode. procedure power_init() is INTCON_INTF = FALSE INTCON_INTE = TRUE end procedure ; This interrupt procedure will just wake up the controller from sleep due to ; the power switch being pressed. procedure power_activated() is pragma interrupt if INTCON_INTF then INTCON_INTF = FALSE end if end procedure ; Wakeup the device after waking up from sleep. procedure wakeup() is led = LED_ON ; Wait for the power switch to be relased. while !power_switch loop end loop _usec_delay(50_000) ; Some debounce time. adc_enable() motion_counter = 0 motion_switch_activation = MOTION_SWITCH_ENABLE end procedure ; Disable some hardware and put the device in sleep. procedure go_to_sleep() is led = LED_OFF adc_disable() ; Disable the motion switch. This also saves power. motion_switch_activation = MOTION_SWITCH_DISABLE _usec_delay(100_000) ; Let all run-out. asm sleep ; If we get here we have woken up again, initialize device. wakeup() end procedure ; ========================= Main program starts here ========================== _usec_delay(200_000) ; Power-up wait time. enable_digital_io() led = LED_OFF adc_init() adc_loop_timer = 0 power_init() motion_counter = 0 motion_value = FALSE motion_switch_activation = MOTION_SWITCH_ENABLE ; Enable all used interrupt, external and peripheral. INTCON_PEIE = TRUE ; Enable peripheral interrupt. INTCON_GIE = TRUE ; Globale interrupt enabled. ; We start with a sleep. go_to_sleep() forever loop test_pin = TRUE ; Set main loop time base to 100 us. Correction is needed since the ; loop itself takes time too. _usec_delay(50) ; Check if the power switch is pressed. If so we switch the tail light off. ; It has to be released first before a new press is accepted. if !power_switch then _usec_delay(50_000) ; Debounce if !power_switch then ; Still pressed, wait for release. while !power_switch loop end loop _usec_delay(50_000) ; Released, debounce. go_to_sleep() end if end if ; We check the ADC value once and a while. if (adc_loop_timer == ADC_LOOP_TIME) then adc_loop_timer = 0 ; Check if we need to switch off the tail light 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 ; We only start the ADC once in the main loop and we will check new data ; after the main loop time which is sufficient for the ADC to do a conversion. adc_start() else adc_loop_timer = adc_loop_timer + 1 end if ; Check if we need to switch off the device because the bike stopped moving ; for a while. First check for any motion. if (motion_switch != motion_value) then ; There was motion. motion_value = motion_switch motion_counter = 0 else if (motion_counter == NO_MOTION) then go_to_sleep() else motion_counter = motion_counter + 1 end if end if test_pin = FALSE end loop