'**************************************************************** '* Name : multi_meter_clock.BAS * '* Author : [Alan Parekh] * '* : Alan's Electronic Projects Inc. * '* Notice : Copyright (c) 2009 modified Brett Oliver 2013 * '* : All Rights Reserved * '* Date : January 8, 2010 * '* Version : 1.3 * '* Notes : Modified for Picbasic Pro v 3 and syncronisation * '* : to 30 second master clock pulse inc standby and * '* : mechanical tick * '**************************************************************** #CONFIG __config _HS_OSC & _WDT_ON & _PWRTE_ON & _MCLRE_OFF & _BODEN_OFF & _LVP_OFF & _CPD_OFF & _CP_OFF #ENDCONFIG DEFINE OSC 20 ' running at 20 MHZ 'we are going to store 300 as the starting value of the meter PWM for full scale 'the storage we are using holds one byte (8 bits) so we will use 2 locations to hold the word '300 = 012C in HEX 'the upper portion of the value (01) will be stored in the first location and the lower portion (2C) will be stored in the second data @1,$01 ;store the first byte of 300 in location 1 at burn time (default max PWM time for the hour meter) data @2,$2C ;store the second byte of 300 in location 2 at burn time (default max PWM time for the hour meter) data @3,$01 ;store the first byte of 300 in location 1 at burn time (default max PWM time for the minute meter) data @4,$2C ;store the second byte of 300 in location 2 at burn time (default max PWM time for the minute meter) data @5,$01 ;store the first byte of 300 in location 1 at burn time (default max PWM time for the second meter) data @6,$2C ;store the second byte of 300 in location 2 at burn time (default max PWM time for the second meter) 'included this for the seria port use, it could be removed if the serial 'port is no longer needed include "modedefs.bas" 'serialOutput var porta.1 'serial output was porta.1 was porta.4 'Turn ON PortB pull-ups OPTION_REG.7 = 0 ;*************30second sync********************* 'OPTION_REG.4 = 0 ' increment on low to high transition RA4 ' OPTION_REG.5 = 1 ;*************end 30second sync********************* ;force port a into digital mode (defaults into analog) CMCON = 7 'set pin input and output directions trisb = %10001111 trisa = %00010100 'Brett old %00000100 was %00000110 ;pin assignments incHourTimeButton var portb.1 ;pressing this button connected here will advance the current hour incMinTimeButton var portb.2 ;pressing this button connected here will advance the current minute resetSecTimeButton var portb.3 ;pressing this button connected here will reset the seconds meterScaleSetSwitch var portb.0 ;putting the jumper in here will allow the meter max position to be set hourOutput var portb.4 ;output to hour meter minOutput var portb.5 ;output to minute meter secOutput var portb.6 ;output to second meter smoothSecondSwitch var portb.7 ;input for the smooth second option, when this is on the second dial will move without ticking heartBeatLED var porta.3 ;one second heart beat indicator mainPowerDetect var porta.2 ;used to detect if the system is running on main power or on backup power ;*************30second sync********************* syncpulse var porta.4 ;used to detect 30 second sync pulse was porta.1 syncLED var porta.0 ;used to show synchcronising Red LED ticktock var porta.5 ;pulses ticktock relay syncIndLED var porta.1 ;lights Green sync LED ;*************end 30second sync********************* ;housekeeping buttonActive con 0 ;buttons are active low true con 1 ;indicates what true means false con 0 ;indicates what false means ValueOn con 1 ;indicates what on means ValueOff con 0 ;indicates what off means hourScaleSet con 0 ;indicates that we are currently setting the hour scale minuteScaleSet con 1 ;indicates that we are currently setting the minute scale secondScaleSet con 2 ;indicates that we are currently setting the second scale powerOn con 1 ;indicates what the value is on for the mainPowerDetect pin powerOff con 0 ;indicates what the value is on for the mainPowerDetect pin ;*************30second sync********************* syncpulseOn con 1 ;indicates what the value is on for the syncpulse pin syncpulseOff con 0 ;indicates what the value is on for the syncpulse pin ;*************end 30second sync********************* ;interrupt setup DEFINE INTHAND intRoutine T1CON = %00110001 ;TMR1ON ON, 1:8 PRESCALE INTCON = %11000000 ;GIE AND INTE ON Brett old %11000000 changed to %10010000 should it read PEIE PIE1 = %00000001 ;TMR1IE ON 'set the timer to 3036 (OBDC Hex) to allow for the timer to count to 62500 TMR1L_Setpoint var byte bank0 TMR1H_Setpoint var byte bank0 TMR1L_Setpoint = $DC TMR1H_Setpoint = $0B ''''''''''''''''' ' Variables to keep track of time ''''''''''''''''' hour var byte ;keep track of the current clock hour minute var byte ;keep track of the current clock minute second var byte ;keep track of the current clock second tenthSecond var byte bank0 ;keep track of the current clock tenth second secondCounter var word ; used as a resetable second timer pwmTimer var word ;used when performing PWM timing on the meter outputs buttonPressed var bit ;used to keep track of user button presses buttonScanDelayTimer var word 'used when debouncing the button scan key presses buttonScanDelay var word 'used to hold the button scan debounce value buttonScanDelay = 10 'the button scan delay is a multiple of the PWM update time startupMode var bit 'used to indicate that the system in in startup mode startupModeTimer var byte 'used in the startup mode disableSerialOutput var bit 'used to turn off the serial output maxPWMValue var Word 'this is the largest PWM value that can be counted, this should be greater than full scale on the meters maxPWMValue = 600 'this is the PWM max count value 'These numbers repersent the portion of the maxPWM value that is needed to get full scale on the meter maxHourPWMValue var word 'the PWM value for full scale on the hours meter maxMinutePWMValue var word 'the PWM value for full scale on the minutes meter maxSecondPWMValue var word 'the PWM value for full scale on the seconds meter oneHourValue var word 'this holds the PWM value of one hour oneMinuteValue var word 'this holds the PWM value of one minute oneSecondValue var word 'this holds the PWM value of one second currentHourValue var word 'This holds the current PWM hour value currentMinuteValue var word 'This holds the current PWM hour value currentSecondValue var word 'This holds the current PWM hour value currentScaleSetMeter var byte 'this holds the value of the meter currently having the scale set currentScaleSetMeter = hourScaleSet 'default it to the hour meter wsave VAR BYTE $20 system wsave1 VAR BYTE $a0 system ' Necessary for devices with RAM in bank1 wsave2 VAR BYTE $120 system ' Necessary for devices with RAM in bank2 ;wsave3 VAR BYTE $1a0 system ' Necessary for devices with RAM in bank3 ssave VAR BYTE bank0 system psave VAR BYTE bank0 system ;*************30second sync********************* syncOff var bit ' once synced stops resync until after a second ;*************30second sync********************* ;start the actual program goto start ;the following is the interrupt routine ASM intRoutine ; Uncomment the following if the device has less than 2k of code space movwf wsave ; Save W swapf STATUS, W ; Swap STATUS to W (swap avoids changing STATUS) clrf STATUS ; Clear STATUS movwf ssave ; Save swapped STATUS movf PCLATH, W ; Move PCLATH to W movwf psave ; Save PCLATH ;set the low timer setpoint movf _TMR1L_Setpoint,W movwf TMR1L ;set the high timer setpoint movf _TMR1H_Setpoint,W movwf TMR1H incf _tenthSecond,F ;inc counter ;clear int flag bcf PIR1, 0 movf psave, W ; restore the state of everything movwf PCLATH swapf ssave, W movwf STATUS swapf wsave, F swapf wsave, W retfie endASM 'This function will keep the clock time up to date updateClock: 'Check if clock is in sync ;*************30second sync********************* if syncpulse = syncpulseOn and second < 5 and tenthSecond > 2 and syncOff =0 then ' check for sync pulse (0) on porta.4 arrives between 0.2 and 5 seconds high syncLED 'indicates sync pulse has resynchronised clock turns on Red LED to indicate clock has been syncronised in last second syncOff = 1 ' stops further sync pulse reseting clock second = 0 secondCounter = 0 tenthSecond = 0 elseif syncpulse = syncpulseOn and second < 30 and second > 25 and syncOff =0 then ' check for sync pulse (0) on porta.4 arrives between 25 and 35 seconds high syncLED 'indicates sync pulse has resynchronised clock turns on Red LED to indicate clock has been syncronised in last second syncOff = 1 ' stops further sync pulse reseting clock second = 30 secondCounter = 30 tenthSecond = 0 endif if syncpulse = syncpulseOn then high syncIndLED 'Green LED Indicates sync pulse received and clock in sync endif ;*************30second sync********************* ;check to see if we have a full second if tenthSecond >= 10 then ;since we are going to use the tenthSecond variable for two operation turn off the interrupt PIE1 = %00000000 ;disable the interrupt for the next three operations second = second + (tenthSecond / 10) ;add the correct number of seconds secondCounter = secondCounter + (tenthSecond / 10) ;add the correct number of seconds tenthSecond = tenthSecond // 10 ;keep track of the remaining tenthSecond units not removed above PIE1 = %00000001 ;re-enable the interrupt 'indicate that a second has passed on the heart beat LED toggle heartBeatLED ;*************30second sync********************* toggle ticktock 'operate or release ticktock relay low syncIndLED ' turn off Green sync ind LED after 1 sec low syncLED 'turns off RED resynchronize LED after 1 sec ;*************30second sync********************* ;check to see if the seconds have rolled over if second >= 60 then second = second - 60 minute = minute + 1 ;*************30second sync********************* syncOff = 0 'sets syncOff ready for sync on ;*************30second sync********************* ;check to see if the minutes have rolled over if minute >= 60 then minute = 0 hour = hour + 1 ;check to see if the hours have rolled over if hour >= 13 then hour = 1 endif endif endif endif return 'advances the clock by one hour incrementHours: hour = hour + 1 ;check to see if the hours have rolled over if hour >= 13 then hour = 1 endif RETURN 'advances the clock by one minute incrementMinutes: minute = minute + 1 ;check to see if the minutes have rolled over if minute >= 60 then minute = 0 hour = hour + 1 ;check to see if the hours have rolled over if hour >= 13 then hour = 1 endif endif return 'resets the clock seconds resetSeconds: second = 0 return ;just here as a jump point start: 'perform any initial startup functions that need to be done when the clock starts up gosub startup 'drop into the clock management function ClockManagement: gosub updateClock ;ensure the time stored is accurate 'check if we are operating on backup power, if we are don't power the meters ;*************30second sync********************* 'Sync clock on backup power if syncpulse = syncpulseOn and second > 25 and second < 35 and mainPowerDetect = powerOff then ' check for sync pulse (0) on porta.4 arrives between 25 and 35 seconds high syncLED 'indicates sync pulse has reset clock turns off Green LED and turn on Red LED to indicate clock has been syncronised in last second gosub turnTimeOutputsOff second = 30 secondCounter = 30 tenthSecond = 0 ;*************30second sync********************* elseif mainPowerDetect = powerOff then 'make sure we are not powering anything gosub turnTimeOutputsOff low heartBeatLED ;*************30second sync********************* low ticktock 'release ticktock relay on power fail low syncLED 'turns off sync LED unless clock synced ;*************30second sync********************* gosub outputNewLine 'serout serialoutput,N9600,[#hour, ":", #minute,":", #second] 'do nothing for a second, we can't sleep since the clock interrupt still needs to fire pause 1000 'keep in low power mode until main power has been established goto ClockManagement endif gosub refreshClock ;output the correct PWM based on the current time 'service any current button press gosub buttonScan 'check if we are in scale set mode if meterScaleSetSwitch = buttonActive then goto setMeterScales endif 'update the time output once per minute if (tenthSecond = 0 and second = 0 and minute = 0) then 'output the time if disableSerialOutput = false then gosub outputNewLine 'serout serialoutput,N9600,["Time is ", #hour, ":", #minute,":", #second] endif endif goto ClockManagement 'PWM output the current time to move the meter hands to the correct locations refreshClock: 'get the current PWM values needed for the current time currentHourValue = (hour * oneHourValue) / 10 'convert hours to a PWM value currentMinuteValue = (minute * oneMinuteValue) / 10 'convert minutes to a PWM value currentSecondValue = (second * oneSecondValue) / 10 'convert seconds to a PWM value 'add in the 10ths of a second to smooth things out if desired, otherwise the second hand will tick if smoothSecondSwitch = buttonActive then currentSecondValue = currentSecondValue + (tenthSecond * oneSecondValue) / 100 'add in the tenths of a second for a smoother display endif 'check if we are setting the meter scales or in startup mode if meterScaleSetSwitch = buttonActive or startupMode = true then 'start by setting all of the meter values to 0 so that only the one being adjusted will be shown currentHourValue = 0 currentMinuteValue = 0 currentSecondValue = 0 'METER SCALE ADJUST MODE Select Case currentScaleSetMeter case hourScaleSet currentHourValue = maxHourPWMValue case minuteScaleSet currentMinuteValue = maxMinutePWMValue case secondScaleSet currentSecondValue = maxSecondPWMValue End Select endif 'turn on all outputs gosub turnTimeOutputsOn 'loop through all possible hand (meter pointer) locations for seconds for pwmTimer = 0 to maxPWMValue 'check to see if the hour meter should still be on if pwmTimer >= currentHourValue then low hourOutput endif 'check to see if the minute meter should still be on if pwmTimer >= currentMinuteValue then low minOutput endif 'check to see if the second meter should still be on if pwmTimer >= currentSecondValue then low secOutput endif pauseus 1 'add a small delay next return 'turn on all the meter outputs turnTimeOutputsOn: high hourOutput high minOutput High secOutput return 'turn off all the meter outputs turnTimeOutputsOff: low hourOutput low minOutput low secOutput return 'check to see if any of the time adjustment buttons are being pressed buttonScan: 'default the button to not pressed buttonPressed = false 'check for the hour increment button if incHourTimeButton = buttonActive then gosub incrementHours buttonPressed = true endif 'check for the minute increment button if incMinTimeButton = buttonActive then gosub incrementMinutes buttonPressed = true endif 'check for the second reset button if resetSecTimeButton = buttonActive then gosub resetSeconds buttonPressed = true endif 'check to see if a button was actually pressed, add a small delay if it was if buttonPressed = true then for buttonScanDelayTimer = 1 to buttonScanDelay gosub refreshClock 'keep updating the clock so that the needles don't fall next 'output the new time if disableSerialOutput = false then gosub outputNewLine 'serout serialoutput,N9600,["Updated time is ", #hour, ":", #minute,":", #second,":", #tenthSecond] endif endif return 'this function will allow the scales of the meters to be set so that the max PWM count will 'allow the meter to reach exactly full scale setMeterScales: 'the three buttons will be used to adjust the three meter scales 'Hour button - this button will change which meter is being adjusted. 'Minute button - this button will decrement the PWM value for full scale 'Second button - this button will increment the PWM value for full scale 'default the button to not pressed buttonPressed = false 'check if we are swill in scale set mode or if we are complete if meterScaleSetSwitch != buttonActive then 'we are done, save value and go back to normal mode gosub writeMeterPWMValuesToFlash 'save new max values gosub calculateMeterPWMValues 'calculate new incremental values 'return to normal clock mode goto ClockManagement endif 'check for the hour increment button, in this case that means to move to the next meter to be set if incHourTimeButton = buttonActive then 'move to the next meter currentScaleSetMeter = currentScaleSetMeter +1 'there are three meters, mod by 3 just incase we fell off the end of the meter count currentScaleSetMeter = currentScaleSetMeter // 3 buttonPressed = true 'keep track of the button press endif 'check for the minute increment button, in this case we will decrement the current meter PWM value if incMinTimeButton = buttonActive then 'we need to decrement the full scale PWM value of the meter selected Select Case currentScaleSetMeter case hourScaleSet maxHourPWMValue = maxHourPWMValue -1 case minuteScaleSet maxMinutePWMValue = maxMinutePWMValue -1 case secondScaleSet maxSecondPWMValue = maxSecondPWMValue -1 End Select 'make sure we still have a valid PWM value gosub maxPWMBoundsCheck buttonPressed = true 'keep track of the button press endif 'check for the second reset button, in this case we will decrement the current meter PWM value if resetSecTimeButton = buttonActive then 'we need to decrement the full scale PWM value of the meter selected Select Case currentScaleSetMeter case hourScaleSet maxHourPWMValue = maxHourPWMValue +1 case minuteScaleSet maxMinutePWMValue = maxMinutePWMValue +1 case secondScaleSet maxSecondPWMValue = maxSecondPWMValue +1 End Select 'make sure we still have a valid PWM value gosub maxPWMBoundsCheck buttonPressed = true 'keep track of the button press endif 'check to see if a button was actually pressed, add a small delay if it was if buttonPressed = true then gosub pwmSettingOutput 'display the current settings using serial out 'allow for different button delay lengths depending if we are selecting meters or changing the meter scale if incHourTimeButton = buttonActive then 'if we are selecting the meter to be adjusted stretch the delay for buttonScanDelayTimer = 1 to (buttonScanDelay *2) gosub refreshClock 'keep updating the clock so that the needles don't fall next else 'scale increment or decrement have been pressed, add a short delay to allow quick adjustment for buttonScanDelayTimer = 1 to (buttonScanDelay /2) gosub refreshClock 'keep updating the clock so that the needles don't fall next endif else 'if no button was pressed just refresh the clock once gosub refreshClock endif gosub updateClock ;ensure the time stored is accurate 'keep looping till we are complete the adjustment goto setMeterScales 'output the pwm settings via serial pwmSettingOutput: if disableSerialOutput = false then 'serout serialoutput,N9600,["h ", #maxHourPWMValue, " m ", #maxMinutePWMValue, " s ", #maxSecondPWMValue, "mode", #currentScaleSetMeter ] gosub outputNewLine endif return 'make sure the PWM values are within the bounds, if not adjust them maxPWMBoundsCheck: 'check and adjust min bounds if maxHourPWMValue < 1 then maxHourPWMValue = 1 endif if maxmINUTEPWMValue < 1 then maxmINUTEPWMValue = 1 endif if maxsECONDPWMValue < 1 then maxsECONDPWMValue = 1 endif 'check and adjust max bounds if maxHourPWMValue > maxPWMValue then maxHourPWMValue = maxPWMValue endif if maxmINUTEPWMValue > maxPWMValue then maxmINUTEPWMValue = maxPWMValue endif if maxsECONDPWMValue > maxPWMValue then maxsECONDPWMValue = maxPWMValue endif return 'calculate the PWM values needed to display full scale calculateMeterPWMValues: 'set the PWM values 10 times larger than actual to help INT math become more accurate oneHourValue = (maxHourPWMValue * 10) / 12 oneMinuteValue = (maxMinutePWMValue * 10) / 60 oneSecondValue = (maxSecondPWMValue * 10) /60 return 'store the full scale PWM meter values to permanent memory writeMeterPWMValuesToFlash: 'write the values to permanent memory write 1, maxHourPWMValue.HIGHBYTE write 2, maxHourPWMValue.LowBYTE write 3, maxMinutePWMValue.HIGHBYTE write 4, maxMinutePWMValue.LowBYTE write 5, maxSecondPWMValue.HIGHBYTE write 6, maxSecondPWMValue.LowBYTE return 'read the full scale PWM values from permanent memory readMeterPWMValuesFromFlash: 'read the values to permanent memory read 1, maxHourPWMValue.HIGHBYTE read 2, maxHourPWMValue.LowBYTE read 3, maxMinutePWMValue.HIGHBYTE read 4, maxMinutePWMValue.LowBYTE read 5, maxSecondPWMValue.HIGHBYTE read 6, maxSecondPWMValue.LowBYTE 'perform a bounds check on the values just read in gosub maxPWMBoundsCheck return 'Advance the console to a new line outputNewLine: 'line feed and carage return ascii characters 'serout serialoutput,N9600,[10,13] return 'Startup and show the user that the meters are operational (or not) startup: startupMode = true 'indicate that we are in startup mode, this is used to perform startup meter tests 'turn the outputs off gosub turnTimeOutputsOff high heartBeatLED 'turn on the heartbeat LED during startup ;*************30second sync********************* low ticktock 'release ticktock relay during startup high syncLED 'test Green sync LED during startup high syncIndLED 'test RED syncInd LED during startup ;*************30second sync********************* 'set if the serial output should be used disableSerialOutput = true 'get the PWM value for full scale on the meters gosub readMeterPWMValuesFromFlash 'calculate scale PWM unit values gosub calculateMeterPWMValues ;default the time to 12:00 hour = 12 minute = 0 second = 0 tenthSecond = 0 secondCounter = 0 'show full scale on each of the 3 meters to test them currentScaleSetMeter = hourScaleSet for startupModeTimer = 1 to 100 gosub refreshClock next currentScaleSetMeter = minuteScaleSet for startupModeTimer = 1 to 100 gosub refreshClock next currentScaleSetMeter = secondScaleSet for startupModeTimer = 1 to 100 gosub refreshClock next 'set the current scale set back to the hour scale currentScaleSetMeter = hourScaleSet gosub outputNewLine 'serout serialoutput,N9600,["Startup complete."] gosub outputNewLine startupMode = false return