;=============================================================================== ; Title: Digital Power Supply. ; ; Author: Rob Jansen, Copyright (c) 2020 .. 2020, all rights reserved. ; ; Revisions ; --------- ; 2020-04-19 : Initial version ; ; Compiler: jalv25r3 ; ; Description: Control software for the digital power supply. ; Control is done by push buttons for up and down. A power on/off ; and three programmable preset buttons are also available. ; ; Sources: - ; ;=============================================================================== ; Some compiler pragmas for optimizations. Pragma warn all no ; We do not want to know all compiler warnings Pragma opt variable_reduce yes ; Reduce variables include 16f1823 ; This uses the 4 MHz internal oscillator, no high speed is required. pragma target clock 4_000_000 ; Oscillator frequency 4 MHz ; Configuration memory settings (fuses). pragma target OSC INTOSC_NOCLKOUT ; Internal Clock pragma target PLLEN DISABLED ; No PLL pragma target WDT DISABLED ; No Watchdog pragma target PWRTE ENABLED ; Power up timer enabled pragma target BROWNOUT DISABLED ; No brownout reset pragma target FCMEN DISABLED ; No clock monitoring pragma target IESO DISABLED ; int/ext osc. switch pragma target LVP ENABLED ; Low voltage programming pragma target MCLR INTERNAL ; Reset internal ; Set the internal clock frequency to 4 MHz. OSCCON_IRCF = 0b1101 ; Set 4 MHz OSCCON_SCS = 0b00 ; Clock determined by FOSC (32 MHz) ; Enable weak pull up for all ports. WPUA = 0b0011_1111 WPUC = 0b0011_1111 OPTION_REG_WPUEN = FALSE ; Make all pins digital I/O instead of analog. enable_digital_io() ; ============================== Pin definitions ============================== ; This pin is externally pulled up as to shut down the power supply before it ; it is intialzed. After reset all pins are input. alias power_down_control is pin_A1 ; Pin 12 of DIP 14. ; Control pins for the digital potentiometers. Signals are active low. alias x9c_chip_select is pin_A5 ; Pin 2 of DIP 14. alias x9c_up_down is pin_A4 ; Pin 3 of DIP 14. alias x9c_increment is pin_A2 ; Pin 11 of DIP 14. ; All switches are active low. alias up_switch is pin_C4 ; Pin 6 of DIP 14. pin_C4_direction = input alias down_switch is pin_C5 ; Pin 5 of DIP 14. pin_C5_direction = input alias preset_1_switch is pin_C3 ; Pin 7 of DIP 14. pin_C3_direction = input alias preset_2_switch is pin_C2 ; Pin 8 of DIP 14. pin_C2_direction = input alias preset_3_switch is pin_C1 ; Pin 9 of DIP 14. pin_C1_direction = input alias power_switch is pin_C0 ; Pin 10 of DIP 14. pin_C0_direction = input ; ========================= Constants and Variables =========================== ; Potentiometer values. const byte MIN_WIPER_VALUE = 0 const byte MAX_WIPER_VALUE = 99 ; EEPROM address declarations. const byte EE_CURRENT_ADDRESS = 0 const byte EE_PRESET_1_ADDRESS = 1 const byte EE_PRESET_2_ADDRESS = 2 const byte EE_PRESET_3_ADDRESS = 3 ; Key and device store times. const word KEY_STORE_TIME = 200 ; 200 * 10 ms = 2 seconds. const word DEVICE_STORE_TIME = 300 ; 300 * 10 ms = 3 seconds. const byte DEBOUNCE_COUNT = 10 ; 10 * 10 ms = 100 milseconds. ; _usec_delay times. const LOOP_TIME_10_MS = 10_000 const KEY_REPEAT_TIME = 250_000 ; 0.25 seconds. ; Power supply control, active low. const bit POWER_CONTROL_ON = FALSE const bit POWER_CONTROL_OFF = TRUE ; Global variables. Only the relevant ones are initialized for power up. var word key_store_counter var word device_store_counter var byte debounce_counter var byte store_value var byte store_address var byte current_value var byte new_value var bit value_changed = FALSE var bit power_is_on ; ========================= Functions and Procedures ========================== ; Initialize the - wiper of the - potentionmeter. procedure wiper_init() is x9c_chip_select = HIGH ; Make it high before making it output. pin_A5_direction = output x9c_up_down = HIGH ; Make it high before making it output. pin_A4_direction = output x9c_increment = HIGH ; Make it high before making it output. pin_A2_direction = output end procedure ; Set the wiper to a specific value. Since we do not know where the wiper is, ; we first put it back to 0 before it is set to the given value. This also makes ; sure that the wiper is at the current position, independent of the starting ; position. procedure wiper_set'put(byte in wiper_value) is x9c_chip_select = HIGH _usec_delay(1) x9c_increment = HIGH _usec_delay(1) x9c_chip_select = LOW _usec_delay(1) x9c_up_down = LOW _usec_delay(3) for MAX_WIPER_VALUE loop x9c_increment = LOW _usec_delay(1) x9c_increment = HIGH _usec_delay(1) end loop ; Now set the wiper value. x9c_up_down = HIGH _usec_delay(3) for wiper_value loop x9c_increment = LOW _usec_delay(1) x9c_increment = HIGH _usec_delay(1) end loop end procedure ; Increment the wiper with one position without storing the value. procedure wiper_increment() is x9c_chip_select = HIGH _usec_delay(1) x9c_increment = HIGH _usec_delay(1) x9c_chip_select = LOW _usec_delay(1) x9c_up_down = HIGH _usec_delay(3) x9c_increment = LOW _usec_delay(1) x9c_chip_select = HIGH end procedure ; Decrement the wiper with one position without storing the value. procedure wiper_decrement() is x9c_chip_select = HIGH _usec_delay(1) x9c_increment = HIGH _usec_delay(1) x9c_chip_select = LOW _usec_delay(1) x9c_up_down = LOW _usec_delay(3) x9c_increment = LOW _usec_delay(1) x9c_chip_select = HIGH end procedure ; Store the last set wiper value. This procedure is not used. procedure wiper_store() is x9c_chip_select = HIGH _usec_delay(1) x9c_increment = HIGH _usec_delay(1) x9c_chip_select = LOW _usec_delay(1) ; Store value and wait store time. x9c_chip_select = HIGH _usec_delay(20_000) end procedure ; Read a byte from the EEPROM and return it. function eeprom_read(byte in ee_address) return byte is EEADRL = ee_address ; The following two bits must be cleared for EEPROM data access. EECON1_EEPGD = FALSE EECON1_CFGS = FALSE ; Activate Read. EECON1_RD = TRUE return EEDATL end function ; Write one byte to the EEPROM. It is important to disable all interrupts ; during write. The routine waits until the write cycle is complete. procedure eeprom_write(byte in ee_address, byte in ee_data) Is EEADRL = ee_address EEDATL = ee_data ; The following two bits must be cleared for EEPROM data access. EECON1_EEPGD = FALSE EECON1_CFGS = FALSE ; Writes have to be enabled. EECON1_WREN = TRUE ; Interrupt must be disabled before special write sequence is activated. -- INTCON_GIE = FALSE ; Currently no interrupts used so not needed here. ; Do the special write sequence to write data to EEPROM EECON2 = 0x55 EECON2 = 0xAA EECON1_WR = TRUE assembler ; Required for some PICs and maybe also this one. nop nop end assembler ; Interrupt can be enabled again. -- INTCON_GIE = TRUE ; Currently no interrupts used so not needed here. ; 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 ; Wait for it to end ... end loop end procedure ; Read a setting from the given eeprom address and check if the eeprom data is ; valid. If valid return the data read, otherwise return MIN_WIPER_VALUE. function get_setting_from_eeprom(byte in ee_address) return byte is var byte value value = eeprom_read(ee_address) ; Value must be in range. If not set it to MIN_WIPER_VALUE. if (value > MAX_WIPER_VALUE) then value = MIN_WIPER_VALUE end if return value end function ; Initalize the power control. Power is off after initialization. procedure power_init() is power_down_control = TRUE ; Set high before making it output. pin_A1_direction = output end procedure ; Switch the power supply on. procedure power_on() is power_is_on = TRUE power_down_control = POWER_CONTROL_ON end procedure ; Switch the power supply off. procedure power_off() is power_is_on = FALSE power_down_control = POWER_CONTROL_OFF end procedure ; ========================= Main program starts here ========================== power_init() wiper_init() ; Read the current settings from EEPROM (if any) and set the wiper accordingly. current_value = get_setting_from_eeprom(EE_CURRENT_ADDRESS) wiper_set = current_value ; Let the power stabilize. _usec_delay(100_000) ; Now we can enable the output of the power supply. power_on() forever loop ; We use a 10 ms looptime for all our timing. _usec_delay(LOOP_TIME_10_MS) ; We use a more solid detection of a key press by checking if the switch is ; actually pressed for a certain time without any debouncing in betweeen. ; The debounce counter is used for that purpose and is reset before each ; measurement. ; The power switch will toggle the power supply on or off. debounce_counter = 0 while !power_switch & (debounce_counter < DEBOUNCE_COUNT) loop _usec_delay(LOOP_TIME_10_MS) debounce_counter = debounce_counter + 1 end loop if (debounce_counter == DEBOUNCE_COUNT) then ; Toggle the power mode. if power_is_on then power_off() else power_on() end if ; Wait for power switch release to prevent a toggle repeat. while !power_switch loop end loop end if ; The up switch has a repeat functionality when pressed longer. The debounce ; time has to be taken into account in the repeat frequency. debounce_counter = 0 while !up_switch & (debounce_counter < DEBOUNCE_COUNT) loop _usec_delay(LOOP_TIME_10_MS) debounce_counter = debounce_counter + 1 end loop if (debounce_counter == DEBOUNCE_COUNT) then while !up_switch & (current_value < MAX_WIPER_VALUE) loop ; Not at maximum yet. current_value = current_value + 1 power_on() ; In case it was off. wiper_increment() ; An increment does not store it. ; Storing in EEPROM and wiper is done later to minize storage writes. store_address = EE_CURRENT_ADDRESS store_value = current_value value_changed = TRUE device_store_counter = 0 _usec_delay(KEY_REPEAT_TIME) end loop end if ; The down switch has a repeat functionality when pressed longer. The debounce ; time has to be taken into account in the repeat frequency. debounce_counter = 0 while !down_switch & (debounce_counter < DEBOUNCE_COUNT) loop _usec_delay(LOOP_TIME_10_MS) debounce_counter = debounce_counter + 1 end loop if (debounce_counter == DEBOUNCE_COUNT) then while !down_switch & (current_value > MIN_WIPER_VALUE) loop ; Not at minimum yet. current_value = current_value - 1 power_on() ; In case it was off. wiper_decrement() ; An decrement does not store it. ; Storing in EEPROM and wiper is done later to minize storage writes. store_address = EE_CURRENT_ADDRESS store_value = current_value value_changed = TRUE device_store_counter = 0 _usec_delay(KEY_REPEAT_TIME) end loop end if ; A preset switch has 2 options, either retrieve the value that was stored ; in EEPROM or use the current value and store that in EEPROM. In both cases ; the wiper needs to be set. Note that the actual storing of the value in ; EEPROM is done at the end of the main loop. ; Check if switched is pressed and filter out the debounce. debounce_counter = 0 while !preset_1_switch & (debounce_counter < DEBOUNCE_COUNT) loop _usec_delay(LOOP_TIME_10_MS) debounce_counter = debounce_counter + 1 end loop if (debounce_counter == DEBOUNCE_COUNT) then ; Pressed, now see if it stays pressed as to store the current value. key_store_counter = 0 power_on() ; In case it was off. while !preset_1_switch & (key_store_counter < KEY_STORE_TIME) loop _usec_delay(LOOP_TIME_10_MS) key_store_counter = key_store_counter + 1 end loop ; Wait for key release. while !preset_1_switch loop end loop ; Check if we need to store this value or just read a value from EEPROM. if (key_store_counter == KEY_STORE_TIME) then store_address = EE_PRESET_1_ADDRESS store_value = current_value value_changed = TRUE device_store_counter = 0 else ; If value is changed then set the new value. Switch off the output ; of the power supply while the potentiometer is being (re-)set. new_value = get_setting_from_eeprom(EE_PRESET_1_ADDRESS) if (new_value != current_value) then power_off() current_value = new_value wiper_set = current_value ; Also store this as the new current value for the next power up. store_address = EE_CURRENT_ADDRESS store_value = current_value value_changed = TRUE device_store_counter = 0 power_on() end if end if end if ; Check if switched is pressed and filter out the debounce. debounce_counter = 0 while !preset_2_switch & (debounce_counter < DEBOUNCE_COUNT) loop _usec_delay(LOOP_TIME_10_MS) debounce_counter = debounce_counter + 1 end loop if (debounce_counter == DEBOUNCE_COUNT) then ; Pressed, now see if it stays pressed as to store the current value. power_on() ; In case it was off. key_store_counter = 0 while !preset_2_switch & (key_store_counter < KEY_STORE_TIME) loop _usec_delay(LOOP_TIME_10_MS) key_store_counter = key_store_counter + 1 end loop ; Wait for key release. while !preset_2_switch loop end loop ; Check if we need to store this value or just read a value from EEPROM. if (key_store_counter == KEY_STORE_TIME) then store_address = EE_PRESET_2_ADDRESS store_value = current_value value_changed = TRUE device_store_counter = 0 else ; If value is changed then set the new value. Switch off the output ; of the power supply while the potentiometer is being (re-)set. new_value = get_setting_from_eeprom(EE_PRESET_2_ADDRESS) if (new_value != current_value) then power_off() current_value = new_value wiper_set = current_value ; Also store this as the new current value for the next power up. store_address = EE_CURRENT_ADDRESS store_value = current_value value_changed = TRUE device_store_counter = 0 power_on() end if end if end if ; Check if switched is pressed and filter out the debounce. debounce_counter = 0 while !preset_3_switch & (debounce_counter < DEBOUNCE_COUNT) loop _usec_delay(LOOP_TIME_10_MS) debounce_counter = debounce_counter + 1 end loop if (debounce_counter == DEBOUNCE_COUNT) then ; Pressed, now see if it stays pressed as to store the current value. power_on() ; In case it was off. key_store_counter = 0 while !preset_3_switch & (key_store_counter < KEY_STORE_TIME) loop _usec_delay(LOOP_TIME_10_MS) key_store_counter = key_store_counter + 1 end loop ; Wait for key release. while !preset_3_switch loop end loop ; Check if we need to store this value or just read a value from EEPROM. if (key_store_counter == KEY_STORE_TIME) then store_address = EE_PRESET_3_ADDRESS store_value = current_value value_changed = TRUE device_store_counter = 0 else ; If value is changed then set the new value. Switch off the output ; of the power supply while the potentiometer is being (re-)set. new_value = get_setting_from_eeprom(EE_PRESET_3_ADDRESS) if (new_value != current_value) then power_off() current_value = new_value wiper_set = current_value ; Also store this as the new current value for the next power up. store_address = EE_CURRENT_ADDRESS store_value = current_value value_changed = TRUE device_store_counter = 0 power_on() end if end if end if ; Check if we need to store a new current value. We do this after some time ; as to prevent writes to the EEPROM for every increment when using the up ; or down switch. This store is also used when a new preset value needs to ; be stored. if value_changed then if (device_store_counter == DEVICE_STORE_TIME) then ; Store new current value in EEPROM. eeprom_write(store_address, store_value) ; We do not store it in the wiper itself. -- wiper_store() value_changed = FALSE else device_store_counter = device_store_counter + 1 end if end if end loop