Shop OBEX P1 Docs P2 Docs Learn Events
CNT adding up to trouble — Parallax Forums

CNT adding up to trouble

ErlendErlend Posts: 612
edited 2015-04-29 23:44 in Propeller 1
So, since I put together the code I have been running this 'scheduler-based' servo control for some while, assuming the twichting of the servo was due to EM noise that I would fix when doing up good wiring. Not so. Some length of screened wire later I am homing in on the trouble - the algorithm.
Something is not good in the way CNT is used here. I suspect the repeaded adding up overflows the numbers. The symptom is that cyclically - each few tens of iterations - the servo jitters into a couple of other positions before it goes back to the correct one for a while.
My brain is not wired for these kind of flip-around counter math. I need help.

Here's the essential code, causing trouble even when everything else is commented out:
'Setting up the first run of 'Milestones' as CNT values
   '------------------------------------------------------------------------------------------------------------------------------------------------
    iServoRate := 20*mSec                               'Servo every 20ms
    iServoPuls := 1200*uSec                             '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

 '--------------------------------------------------------------------------------------------------------------------------------------------------
    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
'---------------------------------------------------------------------------------------------------------------------------------------------------

The principle being that the switch on and switch off servo signal points are defined by means of CNT value,
and that each time the next points are set up by adding to the CNT derived value. But it obviously adds up to trouble.

Erlend

Here's the full module code: (with commenting out of most of it)
{=========================================================================================================================================================================================

 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
        
          CiSecsPerDegC_x10  = 2                                        ' S_x10 time to heat 1 degC at full blast
          
VAR        
          LONG  HWCstack[512]
          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
          LONG  iHeatFirst

          LONG  iProcessState
          
          BYTE  cog
          BYTE  iPumpWait
          
                    
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
    ServoPos(2220)
    OUTA[LPINpump]:= 0
    DIRA[LPINpump]~
    OUTA[LPINservo]:= 0
    DIRA[LPINservo]~
    Stop


PRI HWcontrol

   '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 < 200                                 'Light the flame if not burning
      Ignite(1)
      iFreqUV:= PHSA~                                   'reset counter
      WAITCNT(clkfreq/2 + cnt)
      FreqCount

    
   'Set up some parameters for the 'milestone' loop   
   '-------------------------------------------------------------------------------------------------------------------------------------------------
    DIRA[LPINservo]~~                                  'Define as outputs
    DIRA[LPINpump]~~

    iPumpStrokes:= 0                                    'Set volume counter to zero
    iHeat:= 50                                          'Set initial values
    iHeatNominal:= 50
    iGainP:= 4 
    iGainI:= 10 
    iGainD:= 5
    
    iTempWater:= LONG[LptrTempWater]                    'The temperature when the process begins
    iHeatFirst:= (CiSecsPerDegC_x10 * (LiSPtemp - iTempWater))/10    
                                                        'Calculate how long to heat initially, to reach pump start temp - in Sec
                                                        
    iTempWaterOld:= iTempWater                          'Ref for the D-part of control algorithm
    
    IF iTempWater > LiSPtemp
       iPumpWait:= FALSE                                'A flag used to avoid pumping when water is not warm enough
    ELSE
       iPumpWait:= TRUE
           

   'Setting up the first run of 'Milestones' as CNT values
   '------------------------------------------------------------------------------------------------------------------------------------------------
    iServoRate := 20*mSec                               'Servo every 20ms
    iServoPuls := 1200*uSec                             '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:= 2000*mSec                          'Run control algorithm /sec and update values
    iAlgoritmNow:= CNT +  iAlgorithmIval                'Define 'milestone' as CNT value
                                                   

   '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
        IF NOT iPumpWait                                   'Only drive pump if not iPumpWait
          OUTA[LPINpump]:= 1                               
          iPumpStrokes += 1                                'Keep tally of total pump strokes (=volume)
        iPumpPulsStart += iPumpRate     

      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
    
    LONG[LptrOpmode]:= 15
    
    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 - executes 1/sec

    ReadGlobals                                       'Update measurements

   'Do shutdowns  
    IF  iOpmode== 911                                 'Check for stop command from parent
       LONG[LptrOpmode]:= 0
       Shutdown
    IF  iFlameRate < 100                              'Flameout shutdown
       LONG[LptrOpmode]:= -2
       Shutdown
    IF iTempWater > 150                               'Overheat shutdown
       LONG[LptrOpmode]:= -3
        Shutdown
    IF iPressWater > 1500                             'Overpressure shutdown
       LONG[LptrOpmode]:= -4
        Shutdown
 
    
   'Do pump enable/disable control
    IF iTempwater > LiSPtemp                          'Only allow pump to run when water is warm enough
      iPumpWait:= FALSE
    IF iTempwater < LiSPtemp - 5                      'Allow some hysteresis
      iPumpWait:= TRUE        

        
   'Do temperature control
    LONG[LptrOpmode]:= iHeatFirst
   
    IF iHeatFirst-- > 0                              'While still counting down the HeatFirst duration (1/Sec)
        iHeat:= 100                                   'run heating at full blast, unless..
        IF (LiSPtemp - iTempWater) < 10               'temperature is approaching setpoint
            iHeat:= 60                                'then run heat at half blast

    ELSEIF LiSPtemp - iTempWater > 5                  'simplistic propotional control
            iHeat:= 100                                          

    ELSEIF iTempWater - LiSPtemp  > 5                 'simplistic propotional control
            iHeat:= 60                                          
               
    ELSEIF iTempWaterOld > iTempWater                 'if temperature is dropping
            iHeat:= 100                               'run heating at full blast, unless...
            IF iTempWater - LiSPtemp  > 30            'temperature is really too high
                iHeat:= 60                            'run heating at minimum            

    ELSEIF iTempWater > iTempWaterOld                 'if temperature is rising
            iHeat:= 60                                'run heating at minimum, unless...
            IF LiSPtemp - iTempWater > 5              'temperature is really too low
                iHeat:= 100                           'run heating at full blast
                
    iTempWaterOld:= iTempWater


   'Convert to servo value                           '100% = 950uSec, 0% = 2200 uSec
    iServoPuls:= ( 100*950 + ( (100 - iHeat) * (2200 - 950) ) )/100
    LONG[LptrHeatServo]:= iServoPuls
    iServoPuls*= uSec 

   'Speed control
    IF NOT iPumpWait
      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(1100)                                  'Set gas valve low 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


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
           DaaaBbbbb    BYTE  "string_data",0        

{{
«1

Comments

  • ErlendErlend Posts: 612
    edited 2015-04-26 01:44
    It's not the first time a solution dawns upon me just as I have given up and go ask for help: avoid the adding up by using a 'clean slate' CNT value for each time:
        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 := CNT + iServoRate       ' INSTEAD OF:   iServoPulsStart+= iServoRate                    'then set up the next time milestone
    
          IF (CNT - iServoPulsEnd) > 0
            OUTA[LPINservo]:= 0
            iServoPulsEnd:=  iServoPulsStart + iServoPuls
    

    Erlend

    EDIT: some testing later: not quite solved, there is still a rytmic twitch - every .8sec or so. So small it is not a problem, but it is still there.
    EDIT: this may actually be noise, it is synch with writes to the terminal
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2015-04-26 03:33
    Try this:
    time0 := cnt
    repeat until iPumpStrokes > LiSPvol
      time0 += iServoRate
      time1 := time0 + iServoPulse
      waitcnt(time0)
      outa[LPINservo]~~
      waitcnt(time1)
      outa[LPINservo]~
    

    -Phil
  • ErlendErlend Posts: 612
    edited 2015-04-26 03:41
    Try this:
    time0 := cnt
    repeat until iPumpStrokes > LiSPvol
      time0 += iServoRate
      time1 := time0 + iServoPulse
      waitcnt(time0)
      outa[LPINservo]~~
      waitcnt(time1)
      outa[LPINservo]~
    

    -Phil

    Cant do that (waitcnt), there are three more tasks to be tended to (see full listing), pulsing the impuls pump, reading the flame counts, and trigging the control algorithm.

    Erlend
  • davidsaundersdavidsaunders Posts: 1,559
    edited 2015-04-26 04:14
    Erlend wrote: »
    Cant do that (waitcnt), there are three more tasks to be tended to (see full listing), pulsing the impuls pump, reading the flame counts, and trigging the control algorithm.

    Erlend
    The simple use of waitcnt is the best way, as your code will cause some small jitter. You should have the servo control in its own cog, or make certain that it gets back to the servo control well under the 20ms, and use waitcnt once there.

    Using an if count is likely the cause of your jitter.

    I could be wrong, though SPIN is not very deterministic.
  • ErlendErlend Posts: 612
    edited 2015-04-26 05:36
    The simple use of waitcnt is the best way, as your code will cause some small jitter. You should have the servo control in its own cog, or make certain that it gets back to the servo control well under the 20ms, and use waitcnt once there.

    Using an if count is likely the cause of your jitter.

    I could be wrong, though SPIN is not very deterministic.

    The problem is not about Spin not making it around - there is plenty of time to spare between each 20mS DIRA operation. I suspect it is something about the way I use CNT.
    P1 only has 8 cogs, and I don't have one to spare.

    Erlend
  • T ChapT Chap Posts: 4,223
    edited 2015-04-26 05:54
    I seem to recall a long time ago I tricked a counter(or 2?) into working well with a servo, set it and forget it until you want to change the position. I can't remember where the code is but you can experiment with a counter to see if you can output a pulse that will work a close enough on/off times.

    An alternative to waitcnt may be to count up and use PHSA as your "waitcnt", but in this case you do not hold up the other code.

    IF PHSA > iServoPulsStart
    'OUTA[LPINservo]~~
    'PHSA~
    IF PHSB > iServoPulsEnd
    'OUTA[LPINservo]~
    'PHSB~



    You may be able to slightly improve the timing of your loop.

    OUTA[LPINservo]:= 1 OUTA[LPINservo]~~

    OUTA[LPINservo]:= 0 OUTA[LPINservo]~
  • kwinnkwinn Posts: 8,697
    edited 2015-04-26 06:04
    A glitch every 0.8 seconds seems to point to a problem other than the waitcnt. If the problem was due to cnt rollover it would occur every 56 seconds if the prop ran at 80MHz.

    Can you put all four tasks (servo pulsing, pulsing the impuls pump, reading the flame counts, and trigging the control algorithm) in a single loop so you can use a simple waitcnt for timing?
  • T ChapT Chap Posts: 4,223
    edited 2015-04-26 06:32
    What about restricting the two servo lines so that only one line can run at a time. Use elseif for the servo end line. It may be that there is a timing issue with these able to fire back to back. Have you looked at the servo pulse on a scope? It may be that the loop is causing an on/off state to fall outside of what the servo will accept. I would test the servo with simple on off loop first, see how it performs with simple code. Then work backwards to get it to work with waitcnt or phsa.
    PUB Rotate
      repeat
        outa[18] := 1
        waitcnt(cnt + 1_362_000)
        outa[18] := 0
        waitcnt(cnt + 200_000)
    

    As another test, try your existing code but insert a line with a fixed waitcnt period for servo off instead of CNT.
  • JonnyMacJonnyMac Posts: 9,105
    edited 2015-04-26 06:36
    I don't know if it will help you but I made a little timer object that takes care of the math with waitcnt. You still need to service the timer before the ~53 second roll-over; the object just makes the code look friendlier.
    timer.start
    

    ...
    if (timer.millis => 20)
        timer.adjust(-20)
        do_something
    


    I use .adjust() to back-up the timer two where it should be in case some process took longer than expected; as long as the timer duration was not violated, this should get things back in sync.

    BTW, if you only have one and have a servo you can use one of the counters to generate a servo pulse. You'll get much better precision in the pulse and the code will be simpler.
    pub setup_servo(pos)
    
    '' Setup servo for use with ctra
    
      svotimer.start                                                 ' start servo timer
    
      frqa := 1                                                      ' configure counter for servo pulses                                                  
      phsa := -(pos * US_001)               
      ctra := (%00100 << 26) | SVO_PIN      
      dira[SVO_PIN] := 1                                             ' make output
    
    
    pub process_servo(pos)
    
      if (svotimer.millis => 20)                                     ' 20ms timer expired?
        svotimer.adjust(-20)                                         ' yes, back up for next
        phsa := -(pos * US_001)                                      ' generate new pulse
    


    Code has been updated (26 APR 2015)
  • AribaAriba Posts: 2,690
    edited 2015-04-26 10:48
    Erlend

    I think Spin is just not fast enough to handle all your tasks without havy jitter.
    The worst case is the Servopulse, because it has the shortest time, so you should replace the pulselength task with a Propeller counter.

    In general for such a task scheduler the loop must run as fast as possible to get low jitter. You should not have long routines inside this loop. So your ControlAlgorythm as one of the tasks is problematic, because it will stall the scheduler loop for a long time when the 'iAlgoritmNow' is reached. You should make the regulator as short as possible and split it maybe up in several shorter tasks.
    There is a chance that it works anyway when the Servopulse is contolled by a counter and for the other tasks a jitter is not a big problem.

    Here is an untested codepart that uses counter B for the servopulse:
    'Setting up the first run of 'Milestones' as CNT values
       '------------------------------------------------------------------------------------------------------------------------------------------------
        iServoRate := 20*mSec                               'Servo every 20ms
        iServoPuls := 1200*uSec                             'Servo Pos 1000..2000uS nominal pulse length    ** will be recalculated by control algorithm
        iServoPulsStart := CNT + iServoRate                 'Define 'milestone' as CNT value
        CTRB := %00100<<26 + LPINServo                      'Setup counter B for Servopuls output
        FRQB := 1
    
     '--------------------------------------------------------------------------------------------------------------------------------------------------
        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
            PHSB := -iServoPuls                             'start a Servopuls (ends automatically with the help of a counter)
    
    '---------------------------------------------------------------------------------------------------------------------------------------------------
    

    Andy
  • ErlendErlend Posts: 612
    edited 2015-04-26 10:54
    kwinn wrote: »
    A glitch every 0.8 seconds seems to point to a problem other than the waitcnt. If the problem was due to cnt rollover it would occur every 56 seconds if the prop ran at 80MHz.

    Can you put all four tasks (servo pulsing, pulsing the impuls pump, reading the flame counts, and trigging the control algorithm) in a single loop so you can use a simple waitcnt for timing?

    The glitch isn't so regular after all, but it is that frequency range, and with a slightly varying rythm to it (almost jazz). If no one can spot any real errors in my coding, maybe there is none, maybe it is some noise after all.
    I could put it all in a waitcnt loop as you say, as long as the lesser frequent operations can be done at multiples of the more frequent ones, but that isn't the case. The servo is fixed frequency, variable pulse length, the pump is fixed pulse length, varable frequency.
    Besides, I do think this particular coding scheme is an elegant one (thanks to @Ariba), so I would rather debug it fully, istead of choosing some other scheme.

    Erlend
  • ErlendErlend Posts: 612
    edited 2015-04-26 11:04
    Ariba wrote: »
    Erlend

    I think Spin is just not fast enough to handle all your tasks without havy jitter.
    The worst case is the Servopulse, because it has the shortest time, so you should replace the pulselength task with a Propeller counter.

    In general for such a task scheduler the loop must run as fast as possible to get low jitter. You should not have long routines inside this loop. So your ControlAlgorythm as one of the tasks is problematic, because it will stall the scheduler loop for a long time when the 'iAlgoritmNow' is reached. You should make the regulator as short as possible and split it maybe up in several shorter tasks.
    There is a chance that it works anyway when the Servopulse is contolled by a counter and for the other tasks a jitter is not a big problem.

    Here is an untested codepart that uses counter B for the servopulse:
    'Setting up the first run of 'Milestones' as CNT values
       '------------------------------------------------------------------------------------------------------------------------------------------------
        iServoRate := 20*mSec                               'Servo every 20ms
        iServoPuls := 1200*uSec                             'Servo Pos 1000..2000uS nominal pulse length    ** will be recalculated by control algorithm
        iServoPulsStart := CNT + iServoRate                 'Define 'milestone' as CNT value
        CTRB := %00100<<26 + LPINServo                      'Setup counter B for Servopuls output
        FRQB := 1
    
     '--------------------------------------------------------------------------------------------------------------------------------------------------
        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
            PHSB := -iServoPuls                             'start a Servopuls (ends automatically with the help of a counter)
    
    '---------------------------------------------------------------------------------------------------------------------------------------------------
    

    Andy

    Hey Andy - you put me onto this path, months ago. And I am convinced it is a good one. I have tried to both remove and add algorithms into the loop, but it has no effect, indicating there is lots of margins for the code to make it around before 20mS. But now (as I am writing) you put me onto an idea: make the code such that the slower tasks can not be triggered inside the execution of a servo pulse, i.e. not inside the microseconds duration of the pulse. During the 20ms intervals between pulses there should be plenty of time. I think this is an Eureka!
    Ow you one,
    Erlend
  • ErlendErlend Posts: 612
    edited 2015-04-26 11:07
    JonnyMac wrote: »
    I don't know if it will help you ...

    Your postings ALWAYS help me. This goes into my solutions library for when I meet upon the next challenges.

    Erlend
  • AribaAriba Posts: 2,690
    edited 2015-04-26 11:16
    Yes maybe you can execute the regulation routine only if the next servostart and the next pump event is far away (needs a bit more checking).
    But I would also generate the servopulse time with a counter, this is the most critical part. And as you see in the above example, it's even simpler and faster.

    Andy
  • ErlendErlend Posts: 612
    edited 2015-04-26 12:21
    Ariba wrote: »
    Yes maybe you can execute the regulation routine only if the next servostart and the next pump event is far away (needs a bit more checking).
    But I would also generate the servopulse time with a counter, this is the most critical part. And as you see in the above example, it's even simpler and faster.

    Andy
    The counter is extremely elegant, yes - but I need it for some other part.

    Erlend
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2015-04-26 12:34
    The time between servo pulses can vary a lot without issue. Why not use your scheme for the time between pulses, but when it's time to send a pulse, use waitcnt to time it. You will be absent from the control loop for only 2 ms or less during the two waitcnts (out of 20 or more milliseconds), i.e. less than 10%.

    -Phil
  • pjvpjv Posts: 1,903
    edited 2015-04-26 12:34
    Erlend;

    There is another answer, provided you can move the timing pieces of the code into a PASM cog. You say you have no cogs left over, but I would bet that you can free one up by moving more code running in other cogs into that same assembler cog. Sort of concentrating all the timing things into a single cog. To effect this, that assembler cog would run a real time scheduler kernel which I can give you. That would allow up to 8 individual timers/threads to operate simultaneously, yet independently of each other. In other words, a change in the timing of one will not (appreciably) affect the others.

    The scheduler I have is very SPIN friendly, and interfaces bidirectionally to other cogs through a single long in hubram with single reads or writes. It allows any of the 8 threads in a cog to be suspended, resumed, updated, deleted, or (soon) to be replaced with other code, all while the unaffected threads continue to operate normally.

    You probably could expand this concept into other cogs, each running such a scheduler, concentrating more code into each cog, freeing up space to do yet more things in your application. But it would mean you need to convert those codes into PASM, and that might not be your preference.

    Let me know if this is of interest..... I am absolutely convinced it will easily solve your problem.

    Cheers,

    Peter (pjv)
  • T ChapT Chap Posts: 4,223
    edited 2015-04-26 13:06
    What do you mean by you need "it" for other purposes? There are 16 counters and it is fairly easy to affect counters associated with other cogs if you can't use the counters in main.
  • ErlendErlend Posts: 612
    edited 2015-04-26 14:16
    T Chap wrote: »
    What do you mean by you need "it" for other purposes? There are 16 counters and it is fairly easy to affect counters associated with other cogs if you can't use the counters in main.

    True, I could use a counter in another cog, but I would like each cog process to be as selfsufficient as possibly, I believe that makes for a cleaner design.

    Erlend
  • ErlendErlend Posts: 612
    edited 2015-04-26 14:19
    The time between servo pulses can vary a lot without issue. Why not use your scheme for the time between pulses, but when it's time to send a pulse, use waitcnt to time it. You will be absent from the control loop for only 2 ms or less during the two waitcnts (out of 20 or more milliseconds), i.e. less than 10%.

    -Phil
    Yes, that is probably the most straightforward and simple way. I will try it out as an alternative to flagging to other tasks that a pulse is executing.

    Erlend
  • ErlendErlend Posts: 612
    edited 2015-04-26 14:25
    pjv wrote: »
    Erlend;

    There is another answer, provided you can move the timing pieces of the code into a PASM cog. You say you have no cogs left over, but I would bet that you can free one up by moving more code running in other cogs into that same assembler cog. Sort of concentrating all the timing things into a single cog. To effect this, that assembler cog would run a real time scheduler kernel which I can give you. That would allow up to 8 individual timers/threads to operate simultaneously, yet independently of each other. In other words, a change in the timing of one will not (appreciably) affect the others.

    The scheduler I have is very SPIN friendly, and interfaces bidirectionally to other cogs through a single long in hubram with single reads or writes. It allows any of the 8 threads in a cog to be suspended, resumed, updated, deleted, or (soon) to be replaced with other code, all while the unaffected threads continue to operate normally.

    You probably could expand this concept into other cogs, each running such a scheduler, concentrating more code into each cog, freeing up space to do yet more things in your application. But it would mean you need to convert those codes into PASM, and that might not be your preference.

    Let me know if this is of interest..... I am absolutely convinced it will easily solve your problem.

    Cheers,

    Peter (pjv)

    Thanks. I was never a friend with ASM, not even PASM. My whole project is written in Spin.

    Erlend
  • davidsaundersdavidsaunders Posts: 1,559
    edited 2015-04-26 15:04
    Erlend wrote: »
    The problem is not about Spin not making it around - there is plenty of time to spare between each 20mS DIRA operation. I suspect it is something about the way I use CNT.
    P1 only has 8 cogs, and I don't have one to spare.

    Erlend

    No argument on the having plenty of time.

    Though use waitcnt instead of the if statement to reduce jitter.
  • AribaAriba Posts: 2,690
    edited 2015-04-26 15:25
    Another idea to try: Just don^t execute the ControlAlgorithm as long as the Servo pulse is active. With this code modification:
    'Do the control algorithm
          IF (CNT - iAlgoritmNow) > 0 and OUTA[LPINservo] == 0   'Perform PID control of temperature and simple pump speed control 
            ControlAlgorithm
            iAlgoritmNow += iAlgorithmIval 
    
    the Control Alorothm will be delayed until te Servopulse is done, but the other tasks work as before.

    Andy
  • JonnyMacJonnyMac Posts: 9,105
    edited 2015-04-26 17:34
    While working with a friend/client today on a complex gaming product, we found reason to update the timer module. The code update is attached to post #10.
  • ErlendErlend Posts: 612
    edited 2015-04-28 11:01
    Fixed.
       'Setting up the first run of 'Milestones' as CNT values
       '------------------------------------------------------------------------------------------------------------------------------------------------
        iServoRate := 20*mSec                               'Servo every 20ms
        iServoPuls := 1200*uSec                             'Servo Pos 1000..2000uS nominal pulse length    ** will be recalculated by control algorithm
        iServoPulsStart := CNT + iServoRate                 '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
                                                       
    
       '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 := CNT + iServoRate              'then set up the next time milestone
            WAITCNT(CNT+ iServoPuls)                        'keep any other task out of it until servo pulse has been finished
            OUTA[LPINservo]:= 0    
      
        'Do the pump        
          IF (CNT - iPumpPulsStart) > 0
            IF NOT iPumpWait                                   'Only drive pump if not iPumpWait
              OUTA[LPINpump]:= 1                               
              iPumpStrokes += 1                                'Keep tally of total pump strokes (=volume)
            iPumpPulsStart := CNT + iPumpRate     
    
          IF (CNT - iPumpPulsEnd) > 0
            OUTA[LPINpump]:= 0
            iPumpPulsEnd:= iPumpPulsStart + iPumpPuls
             
        'Do the frequency counter
          IF (CNT - iFreqCountNow) > 0
            FreqCount                                       
            iFreqCountNow:=  CNT + iFreqCountIval
    
        'Do the control algorithm
          IF (CNT - iAlgoritmNow) > 0                        'Perform PID control of temperature and simple pump speed control 
            ControlAlgorithm
            iAlgoritmNow:= CNT +  iAlgorithmIval    
    

    Thanks to you the problem is fixed. I experimented with the various proposed solutions and decided on using WAITCNT for the servo pulse.

    Erlend
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2015-04-28 12:05
    Have you accounted for overhead in your value for iServoPuls? If not, the following will give you a more accurate pulse width:
        'Do the Servo
          IF (CNT - iServoPulsStart) > 0                    'If the iServoPulsStart milestone has been reached
            iServoPulsStart := CNT + iServoRate              'then set up the next time milestone
            t0 := cnt + clkfreq / 10000
            t1 := t0 + iServoPuls
            waitcnt(t0)
            OUTA[LPINservo]:= 1        
            waitcnt(t1)                      
            OUTA[LPINservo]:= 0    
    

    -Phil
  • ErlendErlend Posts: 612
    edited 2015-04-28 12:32
    Have you accounted for overhead in your value for iServoPuls? If not, the following will give you a more accurate pulse width:
        'Do the Servo
          IF (CNT - iServoPulsStart) > 0                    'If the iServoPulsStart milestone has been reached
            iServoPulsStart := CNT + iServoRate              'then set up the next time milestone
            t0 := cnt + clkfreq / 10000
            t1 := t0 + iServoPuls
            waitcnt(t0)
            OUTA[LPINservo]:= 1        
            waitcnt(t1)                      
            OUTA[LPINservo]:= 0    
    

    -Phil

    I don't quite see - why does it help to delay the pulse start by clkfreq/10000?

    Erlend
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2015-04-28 13:20
    The delay doesn't help (or hurt) your program. It's there to accommodate the time required to compute t1. It could probably be shorter than that, but .1 ms is but a fraction of the pulse width anyway, so the extra time hardly matters.

    -Phil
  • ErlendErlend Posts: 612
    edited 2015-04-28 23:49
    The delay doesn't help (or hurt) your program. It's there to accommodate the time required to compute t1. It could probably be shorter than that, but .1 ms is but a fraction of the pulse width anyway, so the extra time hardly matters.

    -Phil

    OK, now I get it. Thanks.

    Erlend
  • JonnyMacJonnyMac Posts: 9,105
    edited 2015-04-29 10:51
    Bear in mind that with this style
    IF (CNT - iServoPulsStart) > 0
    


    is only good for delays less that 2^31 ticks (about 26ish seconds); if you need longer periods, my little time object will make things easy. I'm now using it for everything as it hides the ugly math! :)
Sign In or Register to comment.