Multi-tasking dual pump controller

Hi, I'm trying to make a (relatively) simple dual pump controller which essentially has two water sensors and two pumps each pumping water from different areas.
SS5012 dual pump controller.png
I want each pump to start independently only after its sensor has gone high (sensed water) for at least a second, and continue pumping until after the sensor has gone low again for a delayed period set by a toggle switch (1, 3, or 6 minutes). I'm using a 08M2 because I have lots of them and associated PCBs, and there are just enough pins, BUT, I do need to use the C.5 input which of course is normally used as part of the serial programming interface and therefore use the DISCONNECT command after a delay in the initialisation.

It struck me that it might be a good idea to use parallel tasks to handle the two pumps (effectively) simultaneously, but then discovered that I need to start the program with the first task; i.e. I can't do any of the initialisation tasks first.

So I made task #0 the initialisation and introduced two further tasks for the two pump controllers, only to realise that all three tasks would start simultaneously, not at all what I'd intended. Code below is an outline of what I'd intended, but is unlikely to work.

I'd be most grateful for suggestions as to a better way to achieve this more elegantly, interrupts maybe?

TIA.


Code:
START0:                    ; Task #0 - initialisation.
                        ; Task #1 - port pump control.
                        ; Task #2 - starboard pump control.
;
GOSUB demo_test
PAUSE 5000                    ; Five second delay to allow for programming
                        ; to happen at power up before the DISCONNECT command.
                        ;
DISCONNECT                    ; This allows the C.5 input to be used without
                        ; interference from the firmware looking for
                        ; programming input.
; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
START1:                    ; Task #1 - port pump control.
IF Port_Sens = 1 THEN
    GOSUB P_on ELSE
    GOSUB P_off
ENDIF
GOTO START1
; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
START2:                    ; Task #2 - starboard pump control.
;
;
IF Stbd_Sens = 1 THEN
    GOSUB S_on ELSE
    GOSUB S_off
ENDIF
GOTO START2
; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
END
 
Thinking further about this myself, I've added six second delays in tasks 1 and 2, and suspended task 0 when it's finished the initialisation. Hopefully it might work so I'll give it a go.
 

Aries

New Member
On the whole, multi-tasking is more trouble than it's worth. Better to use a single task which checks both sensors, stores when each goes high, checks when it reaches 1 second and then turns on its pump. Ditto for when the sensors go low and the pumps need to be switched off.
 

hippy

Ex-Staff (retired)
Staff member
It probably can be made to work with multi-tasking but I am inclined to agree with Aries that it is just as easy as a single task program, possibly easier.

Incomplete, untested, and not even syntax checked, but something like this should handle a single sensor and pump. Adding more is just a case of doubling up.
Code:
Symbol SENSOR1   = pinC.3
Symbol PUMP1     = C.2

Symbol LOOP_MS = 20

Symbol RUN_FOR_1_MINUTE  =  60000 / LOOP_MS ; 1 * 60 * 1000 ms / 20 =  3000
Symbol RUN_FOR_3_MINUTES = 180000 / LOOP_MS ; 3 * 60 * 1000 ms / 20 =  9000
Symbol RUN_FOR_6_MINUTES = 360000 / LOOP_MS ; 6 * 60 * 1000 ms / 20 = 18000

Symbol reserveW0    = w0
Symbol heldTime1    = w1
Symbol runPumpTime1 = w2

MainLoop:
  Do
    Gosub HandlePump1
    Pause LOOP_MS
  Loop

HandlePump1:
  ; Handle the sensor
  If SENSOR1 = 1 Then
    heldTime1 = heldTime1 + LOOP_MS Max 1000
    If heldTime1 >= 1000 Then
      runPumpTime1 = RUN_FOR_6_MINUTES
    End If
  Else
    heldTime1 = 0
  End If
  ; Handle the pump
  runPumpTime1 = runPumpTime1 Min LOOP_MS - LOOP_MS
  If runPumpTime1 > 0 Then
    High PUMP1
  Else
    Low  PUMP1
  End If
  Return
 
That's really helpful. Thanks very much Aries and Hippy, I'll try that once I've sorted out why the hardware is doing strange things.
 
One month on, I've managed to get my PC working again since it destroyed itself after an operating system update, and have sorted out hardware issues with the PicAxe control board, which works nicely now.
I've taken your nice code Hippy and adapted it to my hardware, I believe without changing the essential functionality, but it doesn't work quite as expected. It does basically work as it's supposed to but where I'm expecting run-on delays of 1, 3, or 6 minutes, I'm actually getting delays of about 7, 20 or 40 seconds, roughly a factor of the intended delay /9.
The maths and symbol values all looks good to me but there's obviously something I'm missing somewhere? Help please?
Code here:
Code:
; 
SS5012  pump controller Hippys code.bas
; Last saved 13:26 23/06/2023
; 159/2048 bytes used.
;
;
#picaxe 08M2
;
;
OUTPUT C.0
OUTPUT C.1
OUTPUT C.2
;
; C.0 (Ser Out) is a spare output selected via the Prog/Out jumper select header.
;
; C.1 is the output to an IRF44 MOSFET current from the port bilge pump.
;
; C.2 is the output to an IRF44 MOSFET current from the starboard bilge pump.
;
INPUT  C.3
INPUT  C.4
INPUT  C.5
;
; C.3 Input from port water level sensor. A high indicates that water is sensed.
;
; C.4 Input from starboard water level sensor. A high indicates that water is sensed.
;
; C.5 (Ser In) is the input from a 3-position switch giving V+, Gnd & 50%V+.
; A track cut on the PCB allows this to be selected via new jumper select header
; instead of the Ser In for programming.
;
; The switch allows the timer to be set to three different values of delay before
; pump operation. For initial testing these will be about 1, 3 & 10 minutes.
;
SYMBOL Spare_Out          = C.0     ; Use a logical name for the o/p pin.
SYMBOL Port_Pump         = C.1     ; Use a logical name for the o/p pin.
SYMBOL Stbd_Pump          = C.2     ; Use a logical name for the o/p pin.
;
SYMBOL Stbd_Sens          = pinC.3     ; Use a logical name for the i/p pin.
SYMBOL Switch_in         = C.4     ; Use a logical name for the i/p pin.
SYMBOL Port_Sens          = pinC.5     ; Use a logical name for the i/p pin.
;
SYMBOL reserveW0        = w0        ;
SYMBOL Switch_state    = b3        ; An analogue value for the switch position
                        ; normal values will be 0, 127 or 255.
SYMBOL Time_to_run         = w2      ; A word to set the timer max value as set
                        ; by the three-way switch.
SYMBOL holdTimeP        = w3        ; The hold time before allowing the pump to run.
SYMBOL holdTimeS        = w4        ; The hold time before allowing the pump to run.
SYMBOL runPumpTimeP     = w5        ; The over-run pump time after the sensor has gone low.
SYMBOL runPumpTimeS     = w6        ; The over-run pump time after the sensor has gone low.
;
SYMBOL loop_ms = 20            ; Loop time in milliseconds.
                        ; This sets the maximum delay before discovering
                        ; if the second sensor has triggered when one pump
                        ; is already running.
; Original values for the final thing.
SYMBOL run_for_1_minute  =  60000 / loop_ms ; 1 * 60 * 1000 ms / 20 =  3000
SYMBOL run_for_3_minutes = 180000 / loop_ms ; 3 * 60 * 1000 ms / 20 =  9000
SYMBOL run_for_6_minutes = 360000 / loop_ms ; 6 * 60 * 1000 ms / 20 = 18000
#REM
;
; The following values are divided by 30 to give the delay in seconds x2 for testing.
SYMBOL run_for_1_minute  =  2000 / loop_ms ; 1 * 60 * 1000 ms / 20 =  3000
SYMBOL run_for_3_minutes = 6000 / loop_ms ; 3 * 60 * 1000 ms / 20 =  9000
SYMBOL run_for_6_minutes = 12000 / loop_ms ; 6 * 60 * 1000 ms / 20 = 18000
#ENDREM
;
PAUSE 5000
;
DISCONNECT
;
MainLOOP:
  DO
    GOSUB check_switch
    GOSUB HandleStbd_Pump
    GOSUB HandlePort_Pump
    PAUSE loop_ms
  LOOP
; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
HandleStbd_Pump:
; Handle the sensor
IF Stbd_Sens = 1 THEN
    holdTimeS = holdTimeS + loop_ms MAX 1000
    IF holdTimeS >= 1000 THEN
    runPumpTimeS = Time_to_run
END IF
ELSE
    holdTimeS = 0
END IF
; Handle the pump
runPumpTimeS = runPumpTimeS MIN loop_ms - loop_ms
IF runPumpTimeS > 0 THEN
    HIGH Stbd_Pump
ELSE
    LOW  Stbd_Pump
END IF
RETURN
; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
;
HandlePort_Pump:
; Handle the sensor
IF Port_Sens = 1 THEN
    holdTimeP = holdTimeP + loop_ms MAX 1000
    IF holdTimeP >= 1000 THEN
    runPumpTimeP = Time_to_run
END IF
ELSE
    holdTimeP = 0
END IF
; Handle the pump
runPumpTimeP = runPumpTimeP MIN loop_ms - loop_ms
IF runPumpTimeP > 0 THEN
    HIGH Port_Pump
ELSE
    LOW  Port_Pump
END IF
RETURN
; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
;
check_switch:
    READADC Switch_in , Switch_state      ; Read the state of the 3-way switch.
                            ; Possible values are 0, 128 & 255.
    IF Switch_state > 8 THEN LET Switch_state = Switch_state - 8 : ENDIF
    LET Switch_state = Switch_state / 64
    IF Switch_state = 0 THEN : Time_to_run = run_for_3_minutes : ENDIF
    ;
    IF Switch_state = 1 THEN : Time_to_run = run_for_1_minute : ENDIF
    ;
    IF Switch_state = 3 THEN : Time_to_run = run_for_6_minutes : ENDIF
    ;
RETURN
; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

Attachments

hippy

Ex-Staff (retired)
Staff member
Code:
; Handle the pump
runPumpTimeS = runPumpTimeS MIN loop_ms - loop_ms
That might be a bug. The 'runPumpTimeS' is set to a number of 'loop_ms' loops to execute wheras my code is treating it as a time. I suspect this may fix things -
Code:
runPumpTimeS = runPumpTimeS MIN 1 - 1
And similar for 'runPumpTimeP'.

Calling the variable a 'time' is with hindsight a mistake, would probably have been better as 'runPumpLoops' or something like that.
 
Many thanks Hippy, I've edited the code as suggested but now instead of a 60 second delay, it's about twice as long at 130 seconds, and the 3 minute delay is approximately 6½ minutes. I haven't tried the 10 minute delay as I didn't want to wait for nearly 20 minutes.

So something's still not quite right. Obviously, I could just alter the "run_for_1_minute" etc values, but it would be better I feel to understand why it's still wrong?

Code:
HandleStbd_Pump:
; Handle the sensor
IF Stbd_Sens = 1 THEN
    holdTimeS = holdTimeS + loop_ms MAX 1000
    IF holdTimeS >= 1000 THEN
    runPumpLoops_S = Time_to_run
END IF
ELSE
    holdTimeS = 0
END IF
; Handle the pump
runPumpLoops_S = runPumpLoops_S MIN 1 - 1
IF runPumpLoops_S > 0 THEN
    HIGH Stbd_Pump
ELSE
    LOW  Stbd_Pump
END IF
RETURN
 

hippy

Ex-Staff (retired)
Staff member
Taking twice as long as it should would suggest to me there's more code execution overhead than expected so the loop time isn't the 20 ms I was hoping it to be. I expected some uncompensated for overhead but not that much,

If you change 'Pause loop_ms' to 'Pause 10" that would probably bring it closer to expected.
 
Hi Hippy, you're absolutely right that it's the code execution time that's confusing the issue, but decreasing the loop_ms time makes it worse as the execution time is a bigger percentage of the total time. I've increased loop_ms to 100ms and found that instead of 60 seconds I was getting 72 seconds and pro rata for the other times. So decreasing the set values to 50,000 (50 secs), 150,000 (150 secs) & 300,000 (300 secs) gives me the desired 1, 3 and 6 minute delays I was after.

So problem solved. It's much more elegant than messing about with interrupts or multi-tasking and does exactly what I wanted. Thanks very much.
 

hippy

Ex-Staff (retired)
Staff member
but decreasing the loop_ms time makes it worse
That probably would be the case. Sorry if I wasn't clear I was suggesting changing just "Pause loop_ms" not the actual 'loop_ms' value.

'loop_ms' is the time the loop takes to execute and there should really be a second 'pause_time_ms' value which makes the loop last 'loop_ms' ...
Code:
Symbol loop_ms          = 20
Symbol overhead_time_ms = ?
Symbol pause_time_ms    = loop_ms - overhead_time_ms

Do
  ...
  Pause pause_time_ms
Loop
The challenge is determining 'overhead_time_ms'. That can be determined by toggling a pin in the loop and looking at it on a scope, logic analyser, etc, or calculated by measuring the period of something which should actually take some other time; that was my 'it's twice as long so halve the pause time', overhead = 10ms, pause = 10 ms, loop = 20ms = 'loop_ms'.

But if overhead is greater than 'loop_ms', there's no choice but increase 'loop_ms', or run at a faster speed which reduces the time taken to execute the overhead.
 
Top