' ************** Water_Pump_LCD.bas **************

'     This program runs on a PICAXE-20X2. It reads the user input water volume to pump
'	and time between pump cycles from a pot and displays the status on an 
'	HD44780 compatible 16x2 LCD display.Timing comes from from a 1Hz input derived from
'	the 60 Hz power line. Timing cycle runs contiuously, pumps water at the set time,
'	resets the time count to zero and repeats.

' ***** Constants *****

	symbol EnablePin = C.0					' LCD Enable pin connected to C.0
	symbol RegSelPin = C.1					' LCD Register Select pin connected to C.1
	symbol Setup     = C.2					' Set button used to input time and volume connected to C.2
	symbol Test      = C.3					' Test button (configured as toggle on/off) connected to C.3				
	symbol Pump      = C.4					' Signal out to the pump circuit connected to C.4
	symbol LED	     = C.5					' LED indicating 1 Hz in is received and Picaxe is counting to C.5
	symbol state     = pinC.6				' 1 Hz timing signal comes in on C.6
	symbol Pot1	     = C.7					' Potentiometer for setting input values connected to C.7	      

' ***** Variables *****
	
	symbol 	index = b1					' used as counter in For-Next loops
	symbol     intgval= b2					' used to hold each integer value sent to LCD
	symbol Time_Count = b3					' used to hold time values input on the pot 
	symbol 	  rpt = b4					' used for button repeat count
	symbol	flag  = b5					' used for branching code during initial data inputs
	symbol    timeout = b6					' N/A used during testing only			
	symbol    	    h = b7					' variable that tracks hours counted
	symbol    	    m = b8					' variable that tracks minutes counted
	symbol    	    s = b9					' variable that tracks seconds counted
	symbol    	  hrs = b10					' the time counter target hours value
	symbol    	 mins = b11					' the time counter target minutes value
	symbol    	 secs = b12					' the time counter target seconds value
	symbol	    x = b13					' variable used for toggling pump on/off when test button is pressed
	symbol	pulse = b14					' variable used to track last state of incoming 1 Hz square wave
	symbol	  MSB = b15					' used to store most significant bit of data fed to LCD
	symbol	Tsecs = w18					' variable used to hold mins + secs in secs
	symbol        Vol = w19					' used to hold desired water volume values input on the pot  
	symbol     VolMin = w20					' time (in mins) to pump programmed volume based on 750 ml/min pump rating
	symbol     VolSec = w21					' time (in secs) to pump programmed volume based on 750 ml/min pump rating
	symbol  	char =  w22					' used for character to be sent to LCD	
	symbol    ValueX  = w23					' variable used to temporarily hold characters for display
	symbol  pumptimeS = w24					' calculated pump on time in secs derived from volume input
	symbol  pumptimeM = w25					' calculated pump on time in mins derived from volume input
	symbol	    y = w26					' variable used temporarily for calculating pump on time
	
	
' ***** Directives *****

	#com 3							' specify download port
	#picaxe 20X2						' specify processor
	#no_data							' save time downloading
	#terminal off						' disable terminal window
		
' ***** Main Program *****
 	
 	dirsB = %11111111						' set all portB pins as outputs
 	dirsC = %00110011						' set portC input/outputs
 	 
' ***** Initialize the LCD *****								 

	pause 200							' pause 200 mS for LCD initialize as per HD44780 specs
	char = 56							' setup for 8-bits, 2 lines & 5X8 display
 	gosub CmdLCD						' send instruction to LCD
	char = 12							' display on, cursor off
	gosub CmdLCD						' send instruction to LCD		
 	char = 1							' clear display, go home to line 1 of LCD display	
 	gosub CmdLCD						' send instruction to LCD
	wait 1
	
		
' ***** Send intro text to line 1 of the LCD *****	
 	
 	for index = 0 to 15
 	lookup index, ("Water Pump Timer"), char		' read text characters in string
 	gosub TxtLCD						' send text in the string to LCD
 	next index
 	Wait 2
 	
 	char = 1							' clear display,go home to line 1 of LCD display	
 	gosub CmdLCD						' send instruction to LCD
 	
 	for index = 0 to 15
	lookup index, ("Programming mode"), char		' read text characters in string
	gosub TxtLCD						' send text in the string to LCD
	next index
 	Wait 2
 	gosub Volume
 	
	
' ***** Subroutines *****

Volume:								' sub to read in desired water volume to pump from pot

	char = 1							' clear display, go home to line 1 of LCD display	
 	gosub CmdLCD						' send instruction to LCD
 	for index = 0 to 15
	lookup index, ("Enter Volume ml "), char		' read text characters in string
	gosub TxtLCD						' send text in the string to LCD
	next index
	
	do
	readadc C.7, Vol						' read ADC value on pin C.7 from pot in to Vol variable
	let Vol = Vol*10 max 2500				' convert pot ADC value to allow for up to 2500 ml volume
	let VolSec = Vol*2/25			            ' calculate time (in secs) to pump programmed volume based on 750 ml/min pump rating
									' Note use 2/25 = 60/750 = 0.08. Because using 60/750 exceeds Word variable capacity
	char = 192							' go to line 2 of LCD display
	gosub CmdLCD						' send instruction to LCD
	let ValueX = Vol						' put Vol into temp variable for manipulation
	gosub IntgOnly						' go to sub to display volume set by pot on to LCD
	char = 197							' move LCD cursor right for spacing
	gosub CmdLCD						' send instruction to LCD
	for index = 0 to 1
	lookup index, ("ml"), char				' read out "ml" text to LCD
	gosub TxtLCD						' send text to LCD
	next index
	let flag = 0						' set flag to indicate program branch destination when Set button is pressed
	gosub Check_Set						' go check if Set button pressed yet
	loop								' loop & keep reading ADC value on pot until Set button is pressed
	
Interval_Hours:							' sub to read in desired pump cycle time from pot in hours
 
	char = 1							' clear display, go home to line 1 of LCD display	
 	gosub CmdLCD						' send instruction to LCD
 	for index = 0 to 15
	lookup index, ("Enter Time Hours"), char		' read text characters in string
	gosub TxtLCD						' send text in the string to LCD
	next index
	
	do
	readadc C.7, Time_Count					' read ADC value on pin C.7 from pot in to Time_Count variable
	let hrs = Time_Count/3 max 72				' convert pot ADC value into hours from 0 to 72. Assume plant goes 3 days max without water.
	char = 192							' go to line 2 of LCD display
	gosub CmdLCD						' send instruction to LCD
	let ValueX = hrs						' put hrs into temp variable for manipulation
	gosub IntgOnly						' go to sub to display hours set by pot on to LCD
	char = 197							' move LCD cursor right for spacing
	gosub CmdLCD						' send instruction to LCD
	for index = 0 to 2
	lookup index, ("hrs"), char				' read out "hrs" text to LCD
	gosub TxtLCD						' send text to LCD
	next index
	let flag = 1						' set flag to indicate program branch destination when Set button is pressed
	gosub Check_Set						' go check if Set button pressed yet
	loop								' loop & keep reading ADC value on pot until Set button is pressed

Interval_Mins:							' sub to read in desired pump cycle time from pot in minutes
	
	char = 1							' clear display, go home to line 1 of LCD display	
 	gosub CmdLCD						' send instruction to LCD
 	for index = 0 to 15
	lookup index, ("Enter Time Mins "), char		' read text characters in string
	gosub TxtLCD						' send text in the string to LCD
	next index
	
	do
	readadc C.7, Time_Count					' read value on pin C.7 from pot in to Time_Count variable
	let mins = Time_Count/4 max 60			' convert pot ADC value into mins from 0 to 60
	char = 192							' go to line 2 of LCD display
	gosub CmdLCD						' send instruction to LCD
	let ValueX = mins						' put mins into temp variable for manipulation
	gosub IntgOnly						' go to sub to display minutes set by pot on to LCD
	char = 197							' move LCD cursor right for spacing
	gosub CmdLCD						' send instruction to LCD
	for index = 0 to 3
	lookup index, ("mins"), char				' read out "mins" text to LCD
	gosub TxtLCD						' send text to LCD
	next index
	let flag = 2						' set flag to indicate program branch destination when Set button is pressed
	gosub Check_Set						' go check if Set button pressed yet
	loop								' loop & keep reading ADC value on pot until Set button is pressed

Check_Inputs:							' sub to check for zero inputs or time cycle too short for pump to dispense full volume
	
	let Tsecs = mins*60					' convert cycle time mins to secs
	if Vol = 0 then gosub Error_Volume				  ' display error if volume set to zero
	if hrs = 0 and mins = 0 then gosub Error_Time	        ' display error if time set to zero
	if hrs = 0 and VolSec > Tsecs then gosub Error_Vol_Time ' display error if time to pump exceeds cycle time set +/- ~ 1%

Display_Running:							' sub to display a message that the timer is running
	
	char = 1							' clear display, go home to line 1 of LCD display	
 	gosub CmdLCD						' send instruction to LCD
 	for index = 0 to 15
	lookup index, ("Running Timer   "), char		' read text characters in string
	gosub TxtLCD						' send text in the string to LCD
	next index
	
Running:								' sub for counting incoming 1 Hz pulses

do 	

if state = 1 and pulse = 0 then let pulse = 1 high LED gosub CountTime endif ' if pin C.6 high and previous state was low then set pulse & LED high, go count
if state = 0 and pulse = 1 then let pulse = 0 low LED endif ' if pin C.6 low and previous state was high then set pulse & LED low
gosub Check_Test							' check if Test button pressed

loop

CountTime:								' sub that increments & counts secs, mins, hrs on each new incoming 1 Hz pulse

	inc s								' increment 1 second
	if s = 60 then inc m let s = 0 endif		' if secs = 60 then increment mins and reset secs to zero
	if m = 60 then inc h let m = 0 let s = 0 endif	' if mins = 60 then increment hrs and reset mins, secs to zero
	gosub Display						' go display current count
	
	if h = hrs and m = mins then let s = 0 let m = 0 let h = 0 gosub Dispense endif ' if time count = target time set by user, rest h,m,s to 0, go pump water
	if flag = 3 then gosub TimeCheck			' if flag = 3 then pump is on, so go check new elapsed time against programmed pump on time 
	return

Display:								' sub to display counter elapsed time
		
	char = 192							' go to line 2 of LCD display
	gosub CmdLCD						' send instruction to LCD
	let ValueX = h						' put current h into a temp variable for manipulation
	gosub IntgTwoOnly						' go to sub to extract and display time digits
	
	char = 196							' move LCD cursor right for spacing
	gosub CmdLCD						' send instruction to LCD
	let ValueX = m						' put current m into a temp variable for manipulation
	gosub IntgTwoOnly						' go to sub to extract and display time digits
	
	char = 200							' move LCD cursor right for spacing
	gosub CmdLCD						' send instruction to LCD
	let ValueX = s						' put current s into a temp variable for manipulation
	gosub IntgTwoOnly						' go to sub to extract and display time digits

TimeCheck:								' sub to check if pump on time is elapsed

	if pumptimeM = m and pumptimeS = s then Low Pump let flag =0 gosub Display_Running endif ' if current time count = pump time max then turn pump off
	return							' return to counting time

Dispense:								' sub to turn on pump for required time based on the programmed volume

	gosub Display_Pumping					' go to sub that indictes pump is on and display target pump volume
	High Pump let flag = 3					' turn on pump, set the flag indicating pump is on
	let pumptimeS = Vol*2					' start to calculate the pump on time in secs based on volume
	let pumptimeS = pumptimeS/25				' complete calculation of the pump on time in secs based on volume
	let pumptimeM = pumptimeS/60				' calculate the pump on time in mins based on PumptimeS
	let y = pumptimeM*60					' calculate the number of whole minutes of pump on time in seconds
	let pumptimeS = pumptimeS - y				' calculate the number of seconds of remaining pump on time after mins are counted
	
	return

Display_Pumping:							' sub to display water is pumping and display target volume

	char = 1							' clear display, go home to line 1 of LCD display	
 	gosub CmdLCD						' send instruction to LCD
 	for index = 0 to 15
	lookup index, ("Pumping "), char			' read text characters in string
	gosub TxtLCD						' send text in the string to LCD
	next index
	char = 136							' move LCD cursor right for spacing
	gosub CmdLCD						' send instruction to LCD
	let ValueX = Vol						' put current Vol into a temp variable for manipulation
	gosub IntgOnly						' go to sub to display volume
	char = 141							' move LCD cursor right for spacing
	gosub CmdLCD						' send instruction to LCD
	char = "m"							' send an "m" to display
	gosub TxtLCD						' send text to LCD
	char = 142							' move LCD cursor right for spacing
	gosub CmdLCD						' send instruction to LCD
	char = "l"							' send an "l to display
	gosub TxtLCD						' send text to LCD
	
	return

Test_Pump:								' sub to run pump when test button is pressed

	togglebit x, 0						' toggle x between 0 and 1 when this sub is invoked
	if x = 1 then High Pump					' if x is toggled to a 1 turn pump on 
	char = 1							' clear display, go home to line 1 of LCD display	
 	gosub CmdLCD						' send instruction to LCD
 	for index = 0 to 15
	lookup index, ("Test Pumping  "), char		' read text characters in string
	gosub TxtLCD						' send text in the string to LCD
	next index
	endif

	if x = 0 then Low Pump gosub Display_Running	' if x is toggled to 0, turn off pump return to displaying time count
	endif

	return

Check_Set:								' sub to check if Set button is pressed

	button Setup, 1, 100,100, Rpt, 1, Send		' when Set button pressed, go to Send		
	return

Check_Test:								' sub to check if Test button pressed

	button Test, 1, 100,100, Rpt, 1, Test_Pump	' when Test button pressed, go to Test_Pump
	return
	
Send:									' sub to branch depending on present program flag state
	if flag = 0 then goto Interval_Hours		' branch to Interval_Hours on flag = 0		
	if flag = 1 then goto Interval_Mins			' branch to Interval_Mins  on flag = 1
	if flag = 2 then goto Check_Inputs			' branch to Check_Inputs   on flag = 2
	 	
Error_Volume:							' sub to indicate error due to volume input = 0

	char = 1							' clear display, go home to line 1 of LCD display	
 	gosub CmdLCD						' send instruction to LCD
 	for index = 0 to 15
	lookup index, ("Error Volume = 0"), char		' read text characters in string
	gosub TxtLCD						' send text in the string to LCD
	next index
	wait 2							' pause 2 sec for display
	goto Volume							' return to Volume sub for new input
	
Error_Time:								' sub to indicate error due to time input = 0
	
	char = 1							' clear display, go home to line 1 of LCD display	
 	gosub CmdLCD						' send instruction to LCD
 	for index = 0 to 15
	lookup index, ("Error Time = 0  "), char		' read text characters in string
	gosub TxtLCD						' send text in the string to LCD
	next index
	wait 2							' pause 2 sec for display
	goto Interval_Hours					' return to Interval_Hours sub for new input
	
Error_Vol_Time:							' sub to indicate error due to pump time > cycle time
	
	char = 1							' clear display, go home to line 1 of LCD display	
 	gosub CmdLCD						' send instruction to LCD
 	for index = 0 to 15
	lookup index, ("Pump Time > Mins"), char		' read text characters in string
	gosub TxtLCD						' send text in the string to LCD
	next index
	wait 2							' pause 2 sec for display
	goto Volume							' return to Volume sub for new input

CmdLCD:
	low RegSelPin						' set up LCD for a command byte
	goto LoadLCD								

TxtLCD:								' set up LCD for text byte
	high RegSelPin						

LoadLCD:								' execute loading byte into LCD
	outpinsB = char						' load byte onto outpinsB
	pulsout EnablePin,1					' load byte onto LCD
	return				

LoadIntgLCD:							' execute loading integer values onto LCD
	high RegSelPin						' set up LCD for text byte
	outpinsB = intgval					' load byte onto outpinsB
	pulsout EnablePin,1					' execute loading byte into LCD				
	return
					
IntgOnly:								' sub to extract 1000's, 100's, 10's and 1's integers from ValueX data
	for index = 3 to 0 step -1				' extract 1000's integer first then 100's then 10's then 1's
	let intgval = ValueX dig index			' use dig function to extract integers from MSB to LSB
	let MSB = ValueX dig 3					' set dig = 3 to extract most significant bit (1000's), save for later use				'
	
	if index = 3 and intgval =0 then 			' if 1000's are 0 then blank any leading "0" on display			
	let intgval =%00100000  endif 							
	
	if index = 2 and MSB = 0 and intgval =0 then	' blank the 100's level "0's" only when 1000's are also "0"
	let intgval =%00100000 endif 
	
	     if intgval = 0 then let intgval =%00110000	' Convert integers to LCD char codes
	else if intgval = 1 then let intgval =%00110001
	else if intgval = 2 then let intgval =%00110010
	else if intgval = 3 then let intgval =%00110011
	else if intgval = 4 then let intgval =%00110100
	else if intgval = 5 then let intgval =%00110101
	else if intgval = 6 then let intgval =%00110110
	else if intgval = 7 then let intgval =%00110111
	else if intgval = 8 then let intgval =%00111000
	else if intgval = 9 then let intgval =%00111001
	     
	endif
	     
	gosub LoadIntgLCD						' execute loading integer value on LCD
	next index
	return
	
IntgTwoOnly:							' extract 10's and 1's integers from ValueX data
	for index = 1 to 0 step -1				' extract 10's integer first then 1's
	let intgval = ValueX dig index			' Use dig function to extract integers from MSB to LSB
	

	     if intgval = 0 then let intgval =%00110000	' Convert integers to LCD char codes
	else if intgval = 1 then let intgval =%00110001
	else if intgval = 2 then let intgval =%00110010
	else if intgval = 3 then let intgval =%00110011
	else if intgval = 4 then let intgval =%00110100
	else if intgval = 5 then let intgval =%00110101
	else if intgval = 6 then let intgval =%00110110
	else if intgval = 7 then let intgval =%00110111
	else if intgval = 8 then let intgval =%00111000
	else if intgval = 9 then let intgval =%00111001
	     
	endif
	     
	gosub LoadIntgLCD						' execute loading integer value on LCD
	next index
	char = 194							' move LCD cursor right for spacing
	gosub CmdLCD						' send command to LCD
	char = "h"							' send an "h" to display
	gosub TxtLCD						' send text to LCD
	char = 198							' move LCD cursor right for spacing
	gosub CmdLCD						' send command to LCD
	char = "m"							' send an "m" to display
	gosub TxtLCD						' send text to LCD
	char = 202							' move LCD cursor right for spacing
	gosub CmdLCD						' send command to LCD
	char = "s"							' send an "s" to display
	gosub TxtLCD						' send text to LCD
	return
	end