Spa Controller - help needed as I'm out of ideas

Hi All,

I'm not at all close to being a great coder and I'm stuck where I have a few problems with what I thought would be an easy program to write. I want to create a Spa controller where:
-The Controller has 3 buttons, PUMP, BLOWER, LIGHT
-The pump has 2 speeds
-The blower will only turn ON if the pump is running
-The light can be turned ON or OFF at any time
-When not in use, the spa cycles ON for 15mins, then OFF for 45mins (auto cycle)
-If the PUMP button is pressed while the spa is OFF, the pump is to turn ON at low speed
-If the PUMP button is pressed while the spa is running on low speed, it will go to high speed
-If the spa is running for 1 hour, the spa turns OFF (back to its auto cycle)

I have used an interrupt for the PUMP button so it can force the spa ON at any time and also have the light switching working ok, but I seem to have issues with the blower and also the use of SYMBOLS. What is the best way to use the status of an output in an IF statement? (ie: IF an input AND an output are high, then...)

Code:
input C.0
input C.1
input C.2
output B.1
output B.2
output B.3
output B.4
symbol LIGHTBUTTON = C.0
symbol BLOWERBUTTON = C.1
symbol PUMPBUTTON = C.2
symbol LIGHTON = B.1
symbol BLOWERON = B.2
symbol PUMPLOW = B.3
symbol PUMPHIGH = B.4
main:
let W0 = time + 5					;normally +900 (15mins), changed for testing
let W1 = time + 10				;normally +2700 (45mins), changed for testing
let W2 = time + 20				;normally +3600 (60mins), changed for testing
setint %0000100,%0000100
cycleon:
setint %0000100,%0000100
disabletime
time = 0
enabletime
high PUMPLOW					;turn pump on at low speed
do
	if pinC.0 = 1 then gosub light	;turn light on if light button is pressed
	if time > W0 then				;after 15mins turn pump off
	goto cycleoff
	
end if
loop
cycleoff:
setint %0000100,%0000100
disabletime
time = 0
enabletime
low PUMPHIGH
low PUMPLOW
do
	if pinC.0 = 1 then gosub light	;turn light on if light button is pressed
	if time > W1 then				;wait 45mins with pump off before returning to pump on cycle
	goto cycleon
end if
loop
interrupt:						;do following if pump button is pressed
disabletime
time = 0
enabletime
high PUMPLOW
high PUMPHIGH
do 	if pinC.0 = 1 then gosub light	;turn light on is light button is pressed
	if pinC.1 = 1 then gosub blower	;turn blower on if blower button is pressed
	if pinC.2 = 1 then goto cycleoff
	if time > W2 then	
	goto cycleoff				;run pump on high speed for 60mins
	end if
loop
goto cycleon
blower:
;if pinC.1 = 1 and BLOWERON = 0 and PUMPHIGH = 1  (I actually wanted to have this check if 1 input was high and also 2 outputs high)
;then high BLOWER0N
;if BLOWERBUTTON = 1 and BLOWERON = 1   (I actually wanted to have this check if 1 input was high and also 1 output high)
;then low BLOWERON
if pinC.1 = 1 then
	toggle B.2
	end if
return
light:
if pinC.0 = 1 then
	toggle LIGHTON
	end if
return
Any help would be greatly appreciated.
 

techElder

Well-known member
Where to start ...

How about specifying what processor you are using?

So, first, you don't need to use an interrupt for push button detection. That just over complicates your simple program.

What you do want to do is create a program loop with all of your input testing within the loop.

Then when you detect a change in an input, go out to a subroutine to process the change and detecting any dependencies for that change to happen.

Hippy and others have suggested a Finite State Machine (FSM) programming method in projects such as yours. Search the forum for examples.
 

hippy

Ex-Staff (retired)
What is the best way to use the status of an output in an IF statement? (ie: IF an input AND an output are high, then...)
"outpins" and "outpinX.Y" can be used to determine what output pin states are.

High B.1
If pinC.3 = 1 And outpinB.1 = 1 Then ...
 

binary1248

Senior Member
I didn't see any temperature control in your code. In my spa controller (not my code) the pumps are used during heater cycles. Otherwise same as your requirements.
 
Last edited:

Pongo

Senior Member
I didn't see any temperature control in your code. In my spa controller (not my code) the pumps are used during heater cycles. Otherwise same as your requirements.
I too was surprised not to see any mention of the heater. I've repaired a spa controller and they have a lot of safety features, so don't overlook that aspect.

Are you using air/pneumatic switches for the control "buttons"?
 

hippy

Ex-Staff (retired)
Having had a chance to take a closer look at your code it isn't clear that the interrupt routine is ever exited properly ( it seems to jump to other parts of the main program ) so that may cause some problems.

It would be better to write the code in a more structured way, where there are separate parts for handling what is being done at any one time. If using an M2 chip its multi-tasking capabilities may be extremely useful here.
 
Thanks for all of the input. I will have a serious look at redoing the code. I dont have a chip for this project yet but was considering a 14M2.

There is no heater code here because the heater is a stand alone gas powered unit with its own controller and safety interlocks. Rather than mess with it I have chosen to leave it to do its own thing.

The buttons will be electrical switches.
 

BESQUEUT

Senior Member
Having had a chance to take a closer look at your code it isn't clear that the interrupt routine is ever exited properly ( it seems to jump to other parts of the main program ) so that may cause some problems.

It would be better to write the code in a more structured way, where there are separate parts for handling what is being done at any one time. If using an M2 chip its multi-tasking capabilities may be extremely useful here.
An interrupt routine must be as quick as possible. So using an infinite loop within an interrupt is a bad idea.
Also, try removing ALL GOTO statements...
 

SAborn

Senior Member
Its not the best code and lots of problems although it is workable with some understanding.
For example you use word variables W1, W2 , W3 then also use B1, B2, B3, B4, do you realize word variables are made up of 2 Byte variables and for the case of W1 = B2 + B3, W2 = B4 +B5, W3 = B6 + B7, This means every time you add to a byte variable it changes the sum value of the word variable or vise versa.

You can use W1, W2, W3, but change the byte variables to unused variables like B11, B12, B13, B14.
That is just suggesting 1 code problem to fix, there is many more.
You have a basic structure for your code just lots of dead ends to the procedures, all you really want is button presses to equal output responses and a timer function.
 

stan74

Senior Member
Don't you need end if after every if?

if pinC.0 = 1 then gosub light ;turn light on is light button is pressed
if pinC.1 = 1 then gosub blower ;turn blower on if blower button is pressed
if pinC.2 = 1 then goto cycleoff
if time > W2 then
goto cycleoff ;run pump on high speed for 60mins
end if
 

stan74

Senior Member
For speed use goto not gosub. Return takes time. If then if is faster than if and if.
If x =1 then
gosub stuff
go to main
endif
;
stuff:
return ; takes time.More in a loop
--
If x =1 then
goto stuff
endif
;
stuff:
goto main
--
if x = 1 : and if y=0 then ; 2 tests
endif
;
if x = 1 then ; only one test
if y = 0 then
endif
endif
 

stan74

Senior Member
It's a hangover from the 80's when memory and hardware was limited. I write code with subroutines and when it works make it all inline.The difference between ld a,0 and xor a. Xor a is 1 byte and 2 clock cycles, ld a,0 is 2 bytes and 3 clock cycles. It all makes a difference when you are using software to scroll the screen horizontally and move sprites.High frame rates take priority over readable code.I don't care if other people can't read my code as long as I can.
 

hippy

Ex-Staff (retired)
Let's not take the topic too far from what it should be. The first goal of any project should be to get it to work. Speed improvement and optimisations can come later if required.

For a Spa controller which works with real world durations of seconds, minutes, hours and even days, there should be little need to optimise for speed of execution. The focus can be upon creating an elegant program design which is clear and understandable and does the job desired.

The more elegant a design is the simpler it should be to understand, debug and to add to. It should also be more useful to someone wanting to see how others have done similar to what they may want to do in future.

SparkyDave admits he's not a great coder, and there's nothing wrong with that; we all started the same way. He thought this would be an easy program but has found it not to be so, and that's also something everyone else will experience at some time.

SparkyDave's lack of coding experience has probably taken him down the wrong path, up a blind alley, and he's found himself stuck. Another thing we will all have done. With a bit of guidance we can take him back and help lead him trough where he wants to go, show him the thought processes and design decisions involved, and not just help with the project but help him become a better coder.

This looks to be a project which is ideally suited to an M2 and its multi-tasking capabilities. I would start with the following framework which gets light control out of the way and we can forget about that from then onwards, can work on creating a pump handler and then a blower handler ...

Code:
#Picaxe 14M2

Symbol LIGHTBUTTON = pinC.0

Symbol LIGHTON     = B.1

; *****************
; * LIGHT CONTROL *
; *****************

Start0:
  ; Turn light off to start with
  Low LIGHTON
  Do
    ; Wait for light button to pushed
    Do : Pause 100 : Loop Until LIGHTBUTTON = 1
    ; Toggle the light
    Toggle LIGHTON
    ; Wait for the light button to be released
    Do : Pause 100 : Loop Until LIGHTBUTTON = 0
    ; Wait for next push
  Loop

; ****************
; * PUMP CONTROL *
; ****************

Start1:
  Do : Loop ; Do nothing for now

; ******************
; * BLOWER CONTROL *
; ******************

Start2:
  Do : Loop ; Do nothing for now
 

hippy

Ex-Staff (retired)
Don't you need end if after every if?

if pinC.0 = 1 then gosub light ;turn light on is light button is pressed
No; "IF <condition> THEN GOSUB <label>" is a one line IF statement in its own right -

Code:
IF <condition> THEN GOSUB <label>
If GOSUB were used in a multi-line IF then the END IF would be required -

Code:
IF <condition> THEN
  GOSUB <label>
END IF
And likewise if that multi-line IF were cajolled onto one line using colon separators -

Code:
IF <condition> THEN : GOSUB <label> : END IF
 
thanks again for all the input. Hippy you are a legend. I'm going to take in all the ideas and rewrite it. As for my way of creating the timer (15mins ON, 45mins OFF cycle plus 60min time-out when running) do you think thats a good way of doing it or is there some much better solution?
 

hippy

Ex-Staff (retired)
Having thought about it, as Texasclodhopper suggested in post #2, I do favour a Finite State Machine. That's basically, for the pump ...

Code:
PUMP_OFF:
 Set pump off
 time = 0
 Wait until button pushed or time >= 45 minutes
 If button pushed Goto PUMP_SLOW
 Else Goto PUMP_SPIN

PUMP_SPIN:
 Set pump slow/fast
 time = 0
 Wait until button pushed or time >= 15 minutes
 If button pushed Goto PUMP_SLOW
 Else Goto PUMP_OFF

PUMP_SLOW:
 Set pump slow
 time = 0
 Wait until button pushed or time >= 60 minutes
 If button pushed Goto PUMP_FAST
 Else Goto PUMP_OFF

PUMP_FAST:
 Set pump fast
 time = 0
 Wait until button pushed or time >= 60 minutes
 If button pushed Goto PUMP_OFF
 Else Goto PUMP_OFF
The nice thing about that is all the common code can be wrapped up in a Macro ...

Code:
Start1:
  ;     .--------------------------------------------- This state
  ;     |         .----------------------------------- Is the spa running, 0=No, 1=Yes
  ;     |         |   .------------------------------- Setting for PUMPLOW
  ;     |         |   |     .------------------------- Setting for PUMPHIGH
  ;     |         |   |     |     .------------------- Next state when button pushed
  ;     |         |   |     |     |          .-------- Minutes to stay in this state
  ;     |         |   |     |     |          |   .---- State to go to when time expires
  ;     |_______  |_  |__   |__   |________  |_  |________ 
  Pump( PUMP_OFF,  0, Low,  Low,  PUMP_SLOW, 45, PUMP_SPIN )
  Pump( PUMP_SPIN, 0, High, Low,  PUMP_SLOW, 15, PUMP_OFF  )
  Pump( PUMP_SLOW, 1, High, Low,  PUMP_FAST, 60, PUMP_OFF  )
  Pump( PUMP_FAST, 1, High, High, PUMP_OFF,  60, PUMP_OFF  )
The light and blower can be handled in a similar way.
 

marks

Senior Member
Spa pump controller

Hi SparkyDave,
I usually enjoy writing the type of code required for a controller.
for me the picaxe micro is a lot easier and more powerful than when using a PLC.

You have started your program much like I would have done! and perhaps it may be
just getting familiar with a few more commands.
Like outpinsB.3 = 1 I sometimes prefer instead of using high ,B.3
but many get caught not setting up dirsb %00001000 when using it.
and sometimes we can go as far as just being able to make the changes required with symbol values.

The challenge for me here to improve on switch and debouncing code I've written before
and use the simulation that many others mention for testing as normally I just load the program to chip and test.
get yourself an axe132(18m2) easy to add a cheap lcd later.

I did google a spa controller ( to see what they do ) but gave up after 2 mins , I did see they seem to be pricey $$$ lol.
So I'm relying on what you described in post #1. with some added guesses.

.On powerup (powerfailure) you will need to push the PUMPswitch to begin, otherwise will remain off
.When AUTOcycle begins it defaults to PUMPlow ,except initialy
.When in AUTOcycle if PUMPlow is high, you can operate PUMPswitch and BLOWERswitch if pump is off you will be locked out.
.When PUMPhigh is high ,PUMPlow is low this seems to be different from your interface easily changed under PumpControl.

Rich (BB code):
#picaxe 18m2
#terminal 19200
SETFREQ M16          ' OR M4 time 1 second
DISABLETIME
dirsB = %00001111             
dirsC = %10111000

                          ' in seconds for testing multiply x60 for mins
   SYMBOL STARTTIME = 45  ' 2700
   SYMBOL RUNTIME   = 15  ' 900
   SYMBOL CYCLETIME = 60  ' 3600
SYMBOL PUMPswitch   = pinC.0
SYMBOL BLOWERswitch = pinC.1
SYMBOL LIGHTswitch  = pinC.2
SYMBOL PUMPlow      = outpinB.0
SYMBOL PUMPhigh     = outpinB.1
SYMBOL BLOWERpower  = outpinB.2
SYMBOL LIGHTpower   = outpinB.3

SYMBOL PUMPspeed      = B1
SYMBOL SWITCHdebounce = B2
SYMBOL AUTOmode       = B3      SYMBOL POWERmode      = B3    SYMBOL STARTmode      = B3

Main:

  Switches:
IF SWITCHdebounce = 1 THEN
  IF PUMPswitch = 0 AND BLOWERswitch = 0 AND LIGHTswitch = 0 THEN : SWITCHdebounce = 0 : ENDIF
GOTO Switches
ENDIF 
      Pump:
      IF PUMPswitch = 0 THEN Blower
       INC PUMPspeed : IF PUMPspeed = 3 THEN : PUMPspeed = 1 : ENDIF
         SWITCHdebounce = 1
      Blower:
      IF BLOWERswitch = 0 OR PUMPspeed = 0 THEN Light
        BLOWERpower = NOT BLOWERpower
         SWITCHdebounce = 1
      Light:
      IF LIGHTswitch = 0 Then Auto
        LIGHTpower = NOT LIGHTpower
         SWITCHdebounce = 1 
Auto:
IF POWERmode = 0 and PUMPspeed = 1 THEN : ENABLETIME : STARTmode = 1 : ENDIF 'After powerup PUMPswitch press required

IF STARTmode = 1 AND TIME > STARTTIME THEN : TIME = 0 : AUTOmode  = 2 : ENDIF 'Initial runtime 45 + 15

   IF AUTOmode = 2 THEN
IF TIME > CYCLETIME THEN : TIME = 0 : ENDIF              'Total cycletime 60  
IF TIME > RUNTIME THEN : PUMPspeed = 0 : ENDIF           'Runtime 15
IF PUMPspeed = 2 AND TIME <= RUNTIME THEN PumpControl    'pumpspeed2 can be selected BUT 
IF TIME <= RUNTIME THEN : PUMPspeed = 1 : ENDIF          'Start of cycle always reverts to pumpspeed1
   ENDIF

PumpControl:
    IF PUMPspeed = 0 THEN : BLOWERpower = 0 : PUMPhigh  = 0 : PUMPlow = 0  :  ENDIF
    IF PUMPspeed = 1 THEN : PUMPhigh = 0 : PUMPlow  = 1 : ENDIF
    IF PUMPspeed = 2 THEN : PUMPlow  = 0 : PUMPhigh = 1 : ENDIF
ControlStatus:
SERTXD("PUMPlow =",#PUMPlow,"  PUMPhigh=",#PUMPhigh,"  Blower=",#BLOWERpower,"  LIGHT=",#LIGHTpower,"  TIME  ",#TIME, cr,lf)


GOTO Main:
 
Last edited:

hippy

Ex-Staff (retired)
This is my finite state machine example which takes advantage of multi-tasking to have separate tasks for the lights, pump and blower.

It all appears a bit complicated at first but ignore the macro definitions and jump to the the Start0, Start1 and Start2 sections. Those sections allows the states and the flow between them to be defined and verified. At this level we are not so much worried about the implementation as ensuring we move between states as required.

The light handling task is the simplest so it is probably worth getting to understand Start0 and the 'light' macro and how that all works before moving on to the blower and then the pump.

View attachment SpaController.txt
 
Last edited:
Top