Shop OBEX P1 Docs P2 Docs Learn Events
Kind of multitasking within one cog - thanks for the idea — Parallax Forums

Kind of multitasking within one cog - thanks for the idea

ErlendErlend Posts: 612
edited 2015-01-29 12:24 in Propeller 1
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:
{{
 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

  • pjvpjv Posts: 1,903
    edited 2015-01-25 09:25
    Erlend,

    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)
  • Mike CookMike Cook Posts: 829
    edited 2015-01-25 10:04
    pjv wrote: »

    There were different entries for two separate years... Maybe 6 or so years ago ?


    was this one of them?

    http://forums.parallax.com/showthread.php/120461-Single-Cog-Multi-Threading-Made-Easy-(assembler)?highlight=Multi-Threading
  • pjvpjv Posts: 1,903
    edited 2015-01-25 15:15
    Mike, yes that was one. The other one, I cannot now recall for sure, but I think it was in a contest a year or two earlier.

    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)
  • Cluso99Cluso99 Posts: 18,069
    edited 2015-01-25 16:20
    FYI
    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 :)
  • JDatJDat Posts: 103
    edited 2015-01-25 19:09
    Clusto99! Can you show the schematics for contrast and brightness connections?
  • Cluso99Cluso99 Posts: 18,069
    edited 2015-01-25 19:56
    LCD contrast and backlight.jpg

    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.
    370 x 451 - 46K
  • No Static At AllNo Static At All Posts: 9
    edited 2015-01-27 11:04
    @pjv

    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.
  • pjvpjv Posts: 1,903
    edited 2015-01-27 19:26
    Thanks for the directions, NoStatic.

    Cheers,

    Peter (pjv)
  • RS_JimRS_Jim Posts: 1,765
    edited 2015-01-28 11:53
    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
  • pjvpjv Posts: 1,903
    edited 2015-01-28 12:41
    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)
  • kwinnkwinn Posts: 8,697
    edited 2015-01-28 19:39
    pjv wrote: »
    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.
  • ErlendErlend Posts: 612
    edited 2015-01-29 12:24
    For some reason I do not feel home in the assembler language. Probably a long term illness caused by excessive exposure to Pascal at early age. So PASM-based scheduling is not for me - yet.

    Erlend
Sign In or Register to comment.