Kind of multitasking within one cog - thanks for the idea
Erlend
Posts: 612
In an earlier thread I asked for help how to run a pulse-driven pump and a servo simultaneously within one cog. I think I had my mind so tuned to the fantastic cog-way the Prop offers that I blocked any thoughts of doing it the old way is still possible. @Ariba came up with the idea to set up a timeline with defined points at which an action needed to be taken - like setting th ouptut to the servo high or low, which must be done every 20mSec - whereas at all other times something else could be done, like setting the pump pulse output high or low. Below is what I believe is a nice implementation of this scheme; performing the full PID control of pump and gas heater - which is part of the espresso process:
I just wanted to share it, in case there are others out there who need this idea.
Erlend
{{
HeatWaterControl, Controls impulse pump and gas burner to feed hot water at pressure, ver.1,
Erlend Fjosna 2014
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
Runs in separate cog, is called (start) to run automatic hot water control, continously reading control parameters from global varables (temp, pressure, volume,
duration, opmode) to ignite, control, and monitor a gas burner, and run and modulate an impulse pump, based on setpoints passed in the call. Also measures for flame-out,
and initiates a shutdown in case of loss of flame or failure to ignite
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
}}
{
Aknowledgements: I got the idea of CNT-multitaksing from 'Ariba' at the parallax forum
...
=========================================================================================================================================================================================
This code is based on a scheme where CNT is continually checked to see when it is time to read, set, or reset values - in order to maintain two pulse trains and one frequency counter.
CNT*mSec: |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| read CNT in a REPEAT loop, and act at each 'milestone' as shown
Servo50Hz: : 20mS : : : : : : : :
_ _ _ _ _ __ _ _ _
ServoPulse: | |________________| |_________________| |_________________| |_________________| |_________________| |___ variable pulse width 1000..2000us, but pulse every 20mS
__________ __________ __________ __________ ___________
VariFreq: | |_______| |________| |_______________| |________________| variable space witdh 200ms..16ms, but every pulse duration 10mS
FreqCount: Now_____________________________________________ Now______________________________________________ Now____ read counter 2/Sec to get frequency, store it, and reset counter
RunAlgorithm Now________________________________________________________________________________________________Now_ run control algorithm and update control values every sec
=======================================================================================================================================================================
}
CON
_clkmode = xtal1 + pll16x
_xinfreq = 5_000_000 ' use 5MHz crystal
clk_freq = (_clkmode >> 6) * _xinfreq ' system freq as a constant
mSec = clk_freq / 1_000 ' ticks in 1ms
uSec = clk_freq / 1_000_000 ' ticks in 1us
CiMinFlame = 300 'Hz
VAR
LONG HWCstack[256]
LONG iTempWater
LONG iPressWater
LONG iFlameRate
LONG iLevelWater
LONG iOpmode
LONG iServoRate
LONG iServoPuls
LONG iServoPulsStart
LONG iServoPulsEnd
LONG iPumpRate
LONG iPumpPuls
LONG iPumpPulsStart
LONG iPumpPulsEnd
LONG iFreqCountIval
LONG iFreqCountNow
LONG iAlgorithmIval
LONG iAlgoritmNow
LONG iPumpStrokes
LONG iFreqUV
LONG LptrTempWater
LONG LptrPressWater
LONG LptrFlameRate
LONG LptrLevelWater
LONG LptrHeatServo
LONG LptrOpmode
LONG LPINpump
LONG LPINignt
LONG LPINservo
LONG LPINfreqCount
LONG LiSPtemp
LONG LiSPpress
LONG LiSPtime
LONG LiSPvol
LONG iHeat
LONG iHeatNominal
LONG iHeatP
LONG iHeatI
LONG iHeatD
LONG iGainP
LONG iGainI
LONG iGainD
LONG iTempWaterOld
LONG iFreqPump
BYTE cog
OBJ
PUB Start(PINpump, PINignt, PINservo, PINfreqCount{NEW}, ptrTempWater, ptrPressWater, ptrFlameRate, ptrLevelWater, ptrHeatServo, ptrOpmode, iSPtemp, iSPpress, iSPtime, iSPvol): Success
Stop
Success:= (cog:= COGNEW(HotWcontrol(PINpump, PINignt, PINservo, PINfreqCount{NEW}, ptrTempWater, ptrPressWater, ptrFlameRate, ptrLevelWater, ptrHeatServo, ptrOpmode, iSPtemp, iSPpress, iSPtime, iSPvol), @HWCstack) +1)
PUB Stop
IF Cog
COGSTOP(cog~ - 1)
PUB HotWcontrol(PINpump, PINignt, PINservo, PINfreqCount{NEW}, ptrTempWater, ptrPressWater, ptrFlameRate, ptrLevelWater, ptrHeatServo, ptrOpmode, iSPtemp, iSPpress, iSPtime, iSPvol)
LptrTempWater:= ptrTempWater 'Make life easier by copying into local public variables
LptrPressWater:= ptrPressWater
LptrFlameRate:= ptrFlameRate
LptrLevelWater:= ptrLevelWater
LptrHeatServo:= ptrHeatServo
LptrOpmode:= ptrOpmode
LPINpump:= PINpump
LPINignt:= PINignt
LPINservo:= PINservo
LPINfreqCount:= PINfreqCount
LiSPtemp:= iSPtemp
LiSPpress:= iSPpress
LiSPtime:= iSPtime
LiSPvol:= iSPvol
ReadGlobals
CASE iOpmode
0: Shutdown
1: HWcontrol
2: Time
3: Warm
4: Cold
5: Pon
6: Poff
7: Pilot
OTHER:
Shutdown
PRI Shutdown 'Shutdown pump, close servo, shut off igniter float outputs, COGSTOP
OUTA[LPINpump]:= 0
DIRA[LPINpump]~
ServoPos(0)
OUTA[LPINservo]:= 0
DIRA[LPINservo]~
Stop
PRI HWcontrol
'Setting up the first run of 'Milestones' as CNT values
'------------------------------------------------------------------------------------------------------------------------------------------------
iServoRate := 20*mSec 'Servo every 20ms
iServoPuls := 1100*uSec {50%} 'Servo Pos 1000..2000uS nominal pulse length ** will be recalculated by control algorithm
iServoPulsStart := CNT + iServoRate 'Define 'milestone' as CNT value
iServoPulsEnd := iServoPulsStart + iServoPuls 'Define 'milestone' as CNT value
iPumpRate := 50*mSec {20Hz} 'Pump Freq 5..60Hz = 200mS..16mS periode ** will be recalculated by control algorithm
iPumpPuls := 10*mSec 'Pump pulse length 10mS
iPumpPulsStart := CNT + iPumpRate 'Define 'milestone' as CNT value
iPumpPulsEnd := iPumpPulsStart + iPumpPuls 'Define 'milestone' as CNT value
iFreqCountIval:= 500*mSec 'Read, store and reset counter every 500mS
iFreqCountNow:= CNT + iFreqCountIval 'Define 'milestone' as CNT value
iAlgorithmIval:= 1000*mSec 'Run control algorithm /sec and update values
iAlgoritmNow:= CNT + iAlgorithmIval 'Define 'milestone' as CNT value
'Configure the counter and do a first frequency count to measure flame rate, then if not burning ignite it
'-------------------------------------------------------------------------------------------------------------------------------------------------
CTRA:= %01010 << 26 + LPINfreqCount 'Set up counter to POSEDGE mode (bit26-30, using pin LPINfreqCount (bit 0-5), and
FRQA:= 1 'using one count per edge to accumulate into PHSA
WAITCNT(clkfreq/2 + cnt)
FreqCount
IF iFlameRate < CiMinFlame 'Light the flame if not burning
Ignite(1)
'Set up some parameters for the 'milestone' loop
'-------------------------------------------------------------------------------------------------------------------------------------------------
DIRA[LPINservo]:= 1 'Define as outputs
DIRA[LPINpump]:= 1
iPumpStrokes:= 0 'Set volume counter to zero
iHeat:= 50 'Set initial values
iHeatNominal:= 50
iGainP:= 2
iGainI:= 4
iGainD:= 1
iTempWaterOld:= iTempWater
'Wait with starting pump until initial heating of water is finished
'-------------------------------------------------------------------------------------------------------------------------------------------------
iTempWater:= LONG[LptrTempWater]
REPEAT UNTIL iTempWater > 80 'LiSPtemp 'Wait until water is hot enough for brewing
iTempWater:= LONG[LptrTempWater] 'Keep updating the temperature measurements data
FreqCount
'Now start 'milestone' based hot water control routines
'--------------------------------------------------------------------------------------------------------------------------------------------------
REPEAT UNTIL iPumpStrokes > LiSPvol 'Continously check CNT to detect 'milestones' and perform timed actions - until setpoint volume
'Do the Servo
IF (CNT - iServoPulsStart) > 0 'If the iServoPulsStart milestone has been reached
OUTA[LPINservo]:= 1 'do what has to be done,
iServoPulsStart+= iServoRate 'then set up the next time milestone
IF (CNT - iServoPulsEnd) > 0
OUTA[LPINservo]:= 0
iServoPulsEnd:= iServoPulsStart + iServoPuls
'Do the pump
IF (CNT - iPumpPulsStart) > 0
OUTA[LPINpump]:= 1
iPumpPulsStart += iPumpRate
iPumpStrokes += 1 'Keep tally of total pump strokes (=volume)
IF (CNT - iPumpPulsEnd) > 0
OUTA[LPINpump]:= 0
iPumpPulsEnd:= iPumpPulsStart + iPumpPuls
'Do the frequency counter
IF (CNT - iFreqCountNow) > 0
FreqCount
iFreqCountNow += iFreqCountIval
'Do the control algorithm
IF (CNT - iAlgoritmNow) > 0 'Perform PID control of temperature and simple pump speed control
ControlAlgorithm
iAlgoritmNow += iAlgorithmIval
LONG[LptrFlameRate]:= -1 'Update of flamerate stops, so set false
Shutdown
'----------------- unfinished procedures-------------------
PRI Time 'Ignite (if not burning) heat, and pump setpoint time at max setpoint pressure, min and max setpoint temperature
PRI Warm 'Ignite (if not burning) and burn until setpoint temperature, then shut down
PRI Cold 'Run pump to setpoint time at max setpoint pressure
PRI Pon 'Pump on at nominal speed
PRI Poff 'Pump off
PRI Pilot 'Ignite (if not burning) heat and run at minimum flame
'--------------------- supporting procedures ----------------------
PRI ControlAlgorithm 'Regulating control with rudimentary PID functions
ReadGlobals 'Update measurements
'Do shutdowns
IF iOpmode== 0 'Check for stop command from parent
' Shutdown
IF iFlameRate < CiMinFlame 'Flameout shutdown
' Shutdown
IF iTempWater > 150 'Overheat shutdown
Shutdown
IF iPressWater > 1900 'Overpressure shutdown
Shutdown
'Propotional control
IF (LiSPtemp - iTempWater) > 2 'Deadband 2degC
iHeatP:= (iHeatNominal * iGainP * (LiSPtemp - iTempWater)/10)
IF (iTempWater - LiSPtemp) > 2
iHeatP:= (iHeatNominal / (iGainP * (1+ ||(LiSPtemp - iTempWater)/10)))
iHeatP:= 0 #> iHeatP <# 100
'Integrational control
IF (LiSPtemp - iTempWater) > 1 'Deadband 1 degC
iHeatI+= iGainI 'Increment detemines ramp-up/down rate
IF (LiSPtemp - iTempWater) < -1
iHeatI-= iGainI
iHeatI:= -30 #> iHeatI <# 30
'Derivative control
IF iHeatD > 1 'If there is a D-part, ramp it down at each iteration
iHeat-= 1
IF iHeatD < -1
iHeatD += 1
IF ||(iTempWaterOld - iTempWater) > 3 'Threshold temperature change for Derivative to kick in
iHeatD:= iGainD * (iTempWaterOld - iTempWater)
iTempWaterOld:= iTempWater
iHeatD:= -30 #> iHeatD <# 30
'Sum up and limit
iHeat:= 15 #> (iHeatP + iHeatI + iHeatD) <# 100
'Convert to servo value '100% = 950uSec, 0% = 2200 uSec
iServoRate:= 950 + (((100-iHeat)* (2200-950))/100)
LONG[LptrHeatServo]:= iHeat 'iServoRate
'Speed control
IF iPressWater > LiSPpress + 50
iFreqPump:= (iFreqPump - 5) #> 5 'Pump speed not allowed below 5Hz
IF iPressWater < LiSPpress
iFreqPump:= (iFreqPump + 5) <# 50 'Pump speed not allowed above 50Hz
iPumpRate:= (1000/iFreqPump)*mSec
PRI Ignite(Sec) 'Method to ignite gas burner
DIRA[LPINignt]~~ 'Set I/O pin to output direction
OUTA[LPINignt]:= 1 'Energize igniter
WAITCNT(clkfreq + cnt) 'Warm up igniter tip
ServoPos(950) 'Open gas valve fully
WAITCNT(Sec*clkfreq + cnt) 'Let glow for Sec
OUTA[LPINignt]:= 0 'Switch off Ignt
DIRA[LPINignt]~ 'Set I/O pin to input, ie. float
ServoPos(1200) 'Set gas valve mid burn
PRI ServoPos(position) 'Method to run a servo just long enough, then let it free, assuming the friction of the valves prevents movement
DIRA[LPINservo]~~ 'Set I/O pin to output direction
repeat 25 '1/2 S to allow servo to move to new pos
WAITCNT(clkfreq/50 + cnt) '50 hz update frequency
OUTA[LPINservo]:= 1 'Pulse high
WAITCNT(position*uSec + cnt) 'Servo pulse duration 0% eq 2220 uS, 100% eq 950mS
OUTA[LPINservo]:= 0 'Puse low
DIRA[LPINservo]~ 'Set I/O pin to input, ie. float
PRI ReadGlobals 'Method to copy values over from global variables owned by the parent method
iTempWater:= LONG[LptrTempWater]
iPressWater:= LONG[LptrPressWater]
iFlameRate:= LONG[LptrFlameRate]
iLevelWater:= LONG[LptrLevelWater]
iOpmode:= LONG[LptrOpmode]
PRI FreqCount
iFreqUV:= PHSA~ 'Capture counter accumulation and then post-zero
iFlamerate:= LONG[LptrFlameRate]:= iFreqUV 'Calculate real frequency and store in local and global variable
DAT
I just wanted to share it, in case there are others out there who need this idea.
Erlend

Comments
Quite some time ago, several years, I posted a very simple multitasking scheduler in two Parallax Contests. They permitted you numerous simultaneous and independent threads in a single cog. It also included a writeup on the principles behind its operation. Altough you aready have a solution, these generic concepts would do exactly what you require, and might be an interesting exploration for you.
With the reworked websites now, I can no longer locate these entries, so I cannot point you to them. Perhaps someone else still has the links. There were different entries for two separate years... Maybe 6 or so years ago ?
Cheers,
Peter (pjv)
was this one of them?
http://forums.parallax.com/showthread.php/120461-Single-Cog-Multi-Threading-Made-Easy-(assembler)?highlight=Multi-Threading
Thanks for looking that up; wherever did you find it? I can't locate those old contributions on the Parallax site. There were a lot of really good contributions from quite a number of folks. Collectively they make for a very informative read, and a good learning base for newcomers to the Propeller.
Cheers,
Peter(pjv)
It is also possible to set two counters running PWM or similar which run independently of whatever code is running in the cog. I have used this idea for setting the contrast and backlight on an LCD while the cog performs the rest of the LCD interface. Just have the cog setup the counters at the start, then run the code. Remember, if the cog is only running the counters you need to have the cog continue running. A simple wait loop is fine to minimise the power used.
My 1-pin TV uses the horizontal retrace to process a cutdown VT100 terminal for the TV display.
The FullDuplexSerial uses a task-swap concept to run multiple tasks.
All these concepts just add to the great 8-core propeller
While the transistor version works fine, it causes too much EMI for FCC Certification. The prop pin can output sufficient current without the transistor circuit.
Do an "Advanced Search", "Search Multiple Content Types",
Keyword "thread", and User Name "pjv".
This gives 31 results, Mike Cook's find was among them.
Perhaps your other one is also in the results. If not, try another keyword.
You are right about these forums being a good learning base for newcomers.
Certainly appreciate the forum members sharing their knowledge. Thanks.
Cheers,
Peter (pjv)
I was glad to be reminded of your multitasking routines.i need to run 3 pings in a single cog so will use your multitasking system and build 3 ping asm routines to run with it. I believe I can start all with a single out command turn ports around with a single command then use multitask routine to report measurements from each ping to global vars.
jim
I have never yet operated a Ping, but I suspect that a single routine firing off three times in round-robin form might consume less memory and operate as well as three independent codes. And then there would be more room left in the cog for other things.
The kernels posted in the contests were pretty primitive compared to what I have today, but still, they should do what you want.
Cheers,
Peter (pjv)
It certainly would consume less memory and could be done in parallel or round robin, although round robin would probably be better for pings. Re-entrant code is possible for PASM and spin.
Erlend