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