Shop OBEX P1 Docs P2 Docs Learn Events
Trouble with multiple cog methods reading same variables — Parallax Forums

Trouble with multiple cog methods reading same variables

ErlendErlend Posts: 612
edited 2015-01-31 12:43 in Propeller 1
Just when I believed I had a breakthrough-

I am making a gas burner hot water pumping process as part of an espresso machine. The code is organized such that the top level object defines a list of contigous varables that will hold all measured and calculated process varables. A pointer to this block of varables is passed to a ADC scanner object running in a separate object. Same manner for other I/O systems. These all do the job of feeding the variables with values.
In the same manner the pointer is passed to an object which displays the variables (for debugging) through PST. For the HotWaterControl, also running in a dedicated cog, pointers are passed as parameters to give the object access to such values as measured water temperature.
Trouble is, some times it works, sometimes not, so I got suspicious that the various objects were competing for access to the variables. Tried putting in some delays in the various repeat loops, but that did not fix it. As an example:
'Wait with starting pump until initial heating of water is finished
   '-------------------------------------------------------------------------------------------------------------------------------------------------
    REPEAT UNTIL LONG[LptrTempWater] > 90 'LiSPtemp                  'Wait until water is hot enough for brewing
      WAITCNT(clkfreq/2 + cnt)
does not (always) work, but sometimes triggers only when the temperature (as displayed through PST) reaches much higher, say 115 deg - but this varies.
I have already tried setting all Stack sizes very high.

Something I do not know that I should?


Erlend

Comments

  • Duane DegnDuane Degn Posts: 10,588
    edited 2015-01-30 09:00
    Erlend wrote: »
    I got suspicious that the various objects were competing for access to the variables.

    The technique you're using is very common and cogs can only access variables one (cog) at a time. A cog will never see a halfway written variable.

    The problem is likely something else. If you're willing to post an archive of your code and describe what you expect it to do and what it does do, some of us would likely be willing to take a look at it.
  • Mike GreenMike Green Posts: 23,101
    edited 2015-01-30 09:01
    Can't give you specific advice without complete source code. Most likely one of your cogs is trying to change one of the variables while another cog is trying to read it. Delays rarely help because the cogs are still running independently. Generally you can have a single cog changing a specific variable while other cogs may read the variable's value. Whenever there is a possibility of two cogs changing a variable's value, you need a lock so that only one cog can change it at a time.
  • Dave HeinDave Hein Posts: 6,347
    edited 2015-01-30 09:07
    We would need to see more of your code to figure this one out. Is there is only one cog that writes to LONG[LptrTempWater]? Is LptrTempWater pointing to a long-aligned address? You will not get the correct results if value of LprTempWater is not a multiple of 4. How is the structure defined? It should not be a local variable on the stack unless it is defined in the first method of the program.
  • ErlendErlend Posts: 612
    edited 2015-01-30 09:23
    Here goes-
    -the hotwatercontrol
    {{
     HeatWaterControl, Controls impulse pump and gas burner to feed hot water at pressure, ver.1, 
    
     
    -----------------------------------------------------------------------------------------------------------------------------------------------------------------------
     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
     
    
     Naming convention: Variables starts with:     s: String, i: Integer, f: Floating point, c: Character, d: Consecutive bytes containing data sets
                        Constant starts with:      Cs: String, Ci: Integer, Cf: Floating point, Cc: Character, Cd: Consecutive bytes containing data sets
                        DAT starts with:           Ds: String, Di: Integer, Df: Floating point, Dc: Composite, Dc: Character, Dd: Consecutive bytes containing data sets
                        PIN constant numbers are: 'PINfunction' wher function is an abbreviation of the signal/function
                        Pointers start with ptr followed by the variable core name, e.g. ptrVolume
                        Reserved words are always in capitals
                        Included objects are called by three or four letter lower case names
                        Methods (PUB and PRI) have first letter capital then lower case descriptive.
                        Local variables are free text beginning in lower case, or if they are copies of globals begin with capitol L 
    
    =========================================================================================================================================================================================
    
     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
            
    
              
    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
              
              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
        ServoPos(2220)
        OUTA[LPINpump]:= 0
        DIRA[LPINpump]~
        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 := 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:= 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 < 2                                   '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:= 2 
        iGainI:= 4 
        iGainD:= 1
        iTempWaterOld:= iTempWater      
      
       'Wait with starting pump until initial heating of water is finished
       '-------------------------------------------------------------------------------------------------------------------------------------------------
        REPEAT UNTIL LONG[LptrTempWater] > 90 'LiSPtemp                  'Wait until water is hot enough for brewing
          WAITCNT(clkfreq/2 + cnt)
    
    
       '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 < 2                                '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:= 80 #> (iHeatP + iHeatI + iHeatD) <# 100
    
       '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 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        
    
    {{
    
    Permission is hereby granted, free of charge, to any person obtaining a copy of this software and    
    associated documentation files (the "Software"), to deal in the Software without restriction,        
    including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
    and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
    subject to the following conditions:                                                                 
                                                                                                         
    The above copyright notice and this permission notice shall be included in all copies or substantial 
    portions of the Software.                                                                            
                                                                                                         
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
    LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  
    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER         
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    
    }}
    

    the adc
    {{
     ADCreaderConv, reads MCP3208 A/D converter over a SPI protocol, and calls "ScaleUnits*" to convert raw adc into engineering units value, does not do averaging or filtering
    
     Erlend Fjosna 2014
    -----------------------------------------------------------------------------------------------------------------------------------------------------------------------
     Reads MCP3208
    -----------------------------------------------------------------------------------------------------------------------------------------------------------------------
     Acknowledgments:
     Virtually all the code is originally by Jon "JonnyMac" McPhalen, it is only slightly modified by me.
    
    =======================================================================================================================================================================
     Hardware & wiring:
                                    3v3-5v0
                       MCP3208        &#61463;
                 &#9484;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9488;  &#9474;
        ch0 &#9472;&#61627;&#9472;&#9472;&#9472;&#9508;1  CH0     VDD 16&#9500;&#9472;&#9472;&#9515;                
        ch1 &#9472;&#61627;&#9472;&#9472;&#9472;&#9508;2  CH1    VREF 15&#9500;&#9472;&#9472;&#9496;           
        ch2 &#9472;&#61627;&#9472;&#9472;&#9472;&#9508;3  CH2    AGND 14&#9500;&#9472;&#9472;&#9488; 
        ch3 &#9472;&#61627;&#9472;&#9472;&#9472;&#9508;4  CH3     CLK 13&#9500;&#9472;&#9472;&#9474;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#61626;&#9472; clk
        ch4 &#9472;&#61627;&#9472;&#9472;&#9472;&#9508;5  CH4    DOUT 12&#9500;&#9472;&#9472;&#9474;&#9472;&#61629;&#61630;&#9472;&#9488; 3k3 
        ch5 &#9472;&#61627;&#9472;&#9472;&#9472;&#9508;6  CH5     DIN 11&#9500;&#9472;&#9472;&#9474;&#9472;&#9472;&#9472;&#9472;&#9531;&#9472;&#9472;&#61626;&#61627; dio
        ch6 &#9472;&#61627;&#9472;&#9472;&#9472;&#9508;7  CH6     /CS 10&#9500;&#9472;&#9472;&#9474;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#61626;&#9472; cs               
        ch7 &#9472;&#61627;&#9472;&#9472;&#9472;&#9508;8  CH7    DGND  9&#9500;&#9472;&#9472;&#9515;
                 &#9492;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9472;&#9496;  &#9474;
                                      &#61464;
    
    
    =======================================================================================================================================================================
    }}
    
    CON
            _clkmode = xtal1 + pll16x                                               'Standard clock mode * crystal frequency = 80 MHz
            _xinfreq = 5_000_000
    
     
    
    OBJ
       conv: "ScaleUnitsGS1"
      
    
    var
    
      long  cog                                                     ' cog running reader
    
      long  cs                                                      ' pins used
      long  clk
      long  dio
      long  channels
      long  p_adcvals                                               ' pointer to adc readings
     
      long  adc[8]                                                  ' adc readings
      long  stack[128]                                               ' memory for reader cog
      
    
    pub start(cspin, clkpin, diopin)
    
    '' Start free-running MCP3204/8 reader
    '' -- store readings in object var space
    '' -- assumes 8-channel MCP3208
    
      return startx(cspin, clkpin, diopin, 8, -1)
    
    
    pub startx(cspin, clkpin, diopin, nchans, p_analog)
    
    '' Start free-running MCP3204/8 reader
    '' -- store readings at user-specified location (parent var space)
    '' -- nchans is number of channels, 1..8
    '' -- p_analog is a pointer to a block of eight longs
    ''    * use -1 for local storage
          
      stop                                                          ' kill if already running
    
      longmove(@cs, @cspin, 3)                                      ' copy pins
    
      channels := 1 #> nchans <# 8                                  ' keep channel count legal
      
      if (p_analog < 0)
        p_adcvals := @adc                                           ' use internal array
      else
        p_adcvals := p_analog                                       ' use specified array
    
      cog := cognew(mcp3208, @stack) + 1                            ' start reader cog
    
      return cog
    
      
    pub stop
    
    '' Unload MCP3204/8 reader cog
    
      if (cog)                                                      ' if running
        cogstop(cog - 1)                                            '  stop 
        cog := 0
        longfill(p_adcvals, 0, channels)   
    
      
    pub read(ch)
    
    '' Get last read channel value
    
      if ((ch => 0) and (ch < channels))                            ' valid?
        return long[p_adcvals][ch]                                  ' return channel value
      else
        return 0
    
    
    pub address
    
    '' Returns address of adc results storage
    
       return p_adcvals
       
    
    pri mcp3208 | ch, mux, level                                    ' call with cognew()
    
      outa[cs] := 1                                                 ' output high
      dira[cs] := 1
    
      outa[clk] := 0                                                ' output low
      dira[clk] := 1
    
      repeat
        repeat ch from 0 to (channels-1)                            ' loop through channels
          outa[cs] := 0                                             ' activate adc  
    
          ' output mux bits, MSBFIRST
    
          dira[dio] := 1                                            ' dio is output
          mux := (%11000 + ch) << (32-5)                            ' single-ended mode
          repeat 5                                                  ' send mux bits
            outa[dio] := (mux <-= 1) & 1                            ' output a bit
            outa[clk] := 1                                          ' clock the bit
            outa[clk] := 0      
              
          ' input data bits, MSBPOST
           
          dira[dio] := 0                                            ' dio is input
          level := 0                                                ' clear work var  
          repeat 13                                                 ' null + 12 data bits
            outa[clk] := 1                                          ' clock a bit
            outa[clk] := 0                                           
            level := (level << 1) | ina[dio]                        ' input data bit
                                                                     
          outa[cs] := 1                                             ' de-activate adc
                                                                                                          
          'long[p_adcvals][ch] := level & $FFF                       ' update results array
          
          long[p_adcvals][ch] := conv.ScaleUnits((level & $FFF),ch )
        WAITCNT(clkfreq/9 + cnt)                                  'avoid jamming the global variables
         
    
    dat
    
    {{
    
      Terms of Use: MIT License
    
      Permission is hereby granted, free of charge, to any person obtaining a copy of this
      software and associated documentation files (the "Software"), to deal in the Software
      without restriction, including without limitation the rights to use, copy, modify,
      merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
      permit persons to whom the Software is furnished to do so, subject to the following
      conditions:
    
      The above copyright notice and this permission notice shall be included in all copies
      or substantial portions of the Software.
    
      THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
      INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
      PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
      HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
      CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
      OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
    
    }} 
    

    the value monitor
    {
    
    ValueMonitor, sits in the background and monitors global variables, printing the values to a serial terminal at 19200 baud. Ver.1
    TO BE: support for n number of varables, and support for display texts defined in DAT
    Erlend Fjosna 2013
    -----------------------------------------------------------------------------------------------------------------------------------------------------------------------
    A pointer to a block of LONG is passed as parameter, together with the number of LONGs to read from that block. The method continously reads these varaiables and
    displays them on a serial terminal.
    
    -----------------------------------------------------------------------------------------------------------------------------------------------------------------------
     Acknowledements
     Chris Gadd kindly shared the bare bone code for this monitor.
    
    
    
    =======================================================================================================================================================================
    =======================================================================================================================================================================
    }
    
    CON
            _clkmode = xtal1 + pll16x                                              
            _xinfreq = 5_000_000
    
    VAR        
              long  monitorStack[128]
    
              
    PUB start(intLong0{ptr}, intLongN)
    
        cognew(monitor(intLong0{ptr}, intLongN), @monitorStack)
    
    {need a stop + pluss a stop call}
    
    
    PUB monitor(intLong0{ptr}, intLongN) | pm
    
      dira[30]~~
    
      repeat
        waitcnt(cnt + clkfreq / 3)
        Tx($00)
        str(string("Water level [mm]: "))
        pm:= LONG[intLong0] [0]         
        Dec(pm)                      
        str(string($0D,"Water temp [C]: "))
        pm:= LONG[intLong0] [1]      
        Dec(pm)
            str(string($0D,"Water pressure [cBar]: "))
        pm:= LONG[intLong0] [2]      
        Dec(pm)
            str(string($0D,"Light int.[%]: "))
        pm:= LONG[intLong0] [3]      
        Dec(pm)
              str(string($0D,"Weight [g]: "))
        pm:= LONG[intLong0] [4]      
        Dec(pm)                                          
              str(string($0D,"Voltage [cV]: "))
        pm:= LONG[intLong0] [5]      
        Dec(pm)
              str(string($0D,"adc: "))
        pm:= LONG[intLong0] [6]      
        Dec(pm)                                          
              str(string($0D,"adc "))
        pm:= LONG[intLong0] [7]      
        Dec(pm)
                  str(string($0D,"HWC "))
        pm:= LONG[intLong0] [8]      
        Dec(pm)
                  str(string($0D,"QUAD "))
        pm:= LONG[intLong0] [9]      
        Dec(pm)
                      str(string($0D,"RFID "))
        pm:= LONG[intLong0] [10]      
        Dec(pm)
    
                          str(string($0D,"OutA "))               'NEW
        pm:= LONG[intLong0] [11]      
        Dec(pm)
                          str(string($0D,"OutB "))
        pm:= LONG[intLong0] [12]      
        Dec(pm)
                          str(string($0D,"InA "))
        pm:= LONG[intLong0] [13]      
        Bin(pm, 8)
                          str(string($0D,"InB "))
        pm:= LONG[intLong0] [14]      
        Bin(pm, 8)
                          str(string($0D,"Tray "))
        pm:= LONG[intLong0] [15]      
        Bin(pm, 8)
                          str(string($0D,"temp threshold "))
        pm:= LONG[intLong0] [16]      
        Dec(pm)
                          str(string($0D,"Flame "))
        pm:= LONG[intLong0] [17]      
        Dec(pm)
                          str(string($0D,"Heat "))
        pm:= LONG[intLong0] [18]      
        Dec(pm)                                                          
                       
    
                                      
                                
    
    PRI Str(stringPtr)
    
      repeat strsize(stringPtr)
        Tx(byte[stringPtr++])                                                       'Transmit each byte in the string
    
    PRI Dec(value) | i, x
    
      x := value == NEGX                                    'Check for max negative
      if value < 0
        value := ||(value+x)                                'If negative, make positive; adjust for max negative
        Tx("-")                                             'and output sign
    
      i := 1_000_000_000                                    'Initialize divisor
    
      repeat 10                                             'Loop for 10 digits
        if value => i                                                               
          Tx(value / i + "0" + x*(i == 1))                  'If non-zero digit, output digit; adjust for max negative
          value //= i                                       'and digit from value
          result~~                                          'flag non-zero found
        elseif result or i == 1
          Tx("0")                                           'If zero digit (or only digit) output it
        i /= 10                                             'Update divisor
    
    PRI Hex(value, digits)
    
      value <<= (8 - digits) << 2
      Tx("$")
      repeat digits                                         'do it for the number of hex digits being transmitted
        Tx(lookupz((value <-= 4) & $F : "0".."9", "A".."F"))'  Transmit the ASCII value of the hex characters
    
    PRI Bin(value, digits)
    
      value <<= 32 - digits
      Tx("%")
      repeat digits
        Tx((value <-= 1) & 1 + "0")    
        
    PRI Tx(txByte) | t, bit_delay
    
      bit_delay := clkfreq / 19200
    
      txByte <<= 2
      t := cnt
      repeat 10
        outa[30] := (txByte >>= 1) & 1
        waitcnt (t += bit_delay)
      outa[30]~~
    
    
    
    DAT
    DaaaBbbbb    BYTE  "string_data",0        
    
    {{
    
    Permission is hereby granted, free of charge, to any person obtaining a copy of this software and    
    associated documentation files (the "Software"), to deal in the Software without restriction,        
    including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
    and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
    subject to the following conditions:                                                                 
                                                                                                         
    The above copyright notice and this permission notice shall be included in all copies or substantial 
    portions of the Software.                                                                            
                                                                                                         
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
    LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  
    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER         
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    
    }}                                    
    

    the Main - very much a debug version
    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
            
    
            'pin assignments:        
      
            PinADCdata    = 7     
            PinADCclk     = 9     
            PinADCcs      = 8
    
            PinRIOslc     = 10               
            PinRIOsda     = 11
            
            PinQuadA      = 20
            PinQuadB      = 21
            PinRFIDser    = 22
            PinRFIDen     = 23
            PINfreqCount  = 24
            PINservo      = 25
            PINpumpImp    = 26
            PINignt       = 27
    
            
            'remote digital io assignments
            MCP32017addr  = %010
                             
            CintDinATrayIs  = 0       'Digital inputs on bank A
            CintDinAHolder  = 1
            CintDinATBN01   = 2
            CintDinATBN02   = 3
            CintDinAPIR01   = 4
            CintDinAMic     = 5
            CintDinALaser   = 6
            CintDinATBN04   = 7
           
            intDoutIrish   = 0        'Digital outputs on bank B
            'parameters
            
    
    
    VAR
            
            'Variables to hold I/O values
            '============================
            
            'ADC analogue input values - obeying the requirement for declaring 8 consecutive LONGs to make it easy for the ADC scanner object       
            LONG gAnLevelWater           
            LONG gAnTempWater            
            LONG gAnPressWater           
            LONG gAnLigth       
            LONG gAnWeightCup            
            LONG gAnVoltageSupply                                              
            LONG gAnCh6               
            LONG gAnCh7
            '----------------------------------------------------------------------------------------------------------------------------------
            
            LONG gHWCmode
            LONG gQuadPos
            LONG intMatch
            
            LONG intOutA                      'NEW
            LONG intOutB
            LONG intInA
            LONG intInB        
            LONG intTrayIs
            LONG intIrish        
            LONG gAnUVflame
            LONG gAnHeatServo
            
            BYTE strnRFID[11]
    
    
       
    OBJ
    
     adc     : "ADCreaderConv"
     val     : "ValueMonitor"
     hwc     : "HeatWaterControl"
     quad    : "QuadDecoder"
     rfid    : "RfidReader"
     vm2     : "TestVM2"
     oled    : "TestOLED"
     drw     : "DioReadWriter"
      
    PUB Main
    
    'start ad converter reader
      adc.startx(PinADCcs, PinADCclk, pinADCdata, 8, @gAnLevelWater)        'gAnLevelWater is the first in the block of 8 LONGs
      WAITCNT(clkfreq + CNT)
       
    'configure and start digital io readwriter                                                                            
       drw.Init(PinRIOslc, PinRIOsda, 2, %1111_1111, %0000_0000, %0000_0000, %0000_0000)
       drw.Start(PinRIOslc, PinRIOsda, 2, @intOutA, @intOutB, @intInA, @intInB)
       WAITCNT(clkfreq + CNT)                                                             'Wait for the Dio to rev up    
     
    'start monitor
       val.start(@gAnLevelWater, 10)
    
       intTrayIs:= intInA & |< CintDinATrayIs                                             'Reads one bit value from Long at given bit position
       intOutB:=   Wr_bit(intOutB, intDoutIrish, intIrish)                                'Writes one bit value into Long at given bit postion  
      
    
     'start rotary Gray decoder
       quad.start(PinQuadA, @gQuadPos)
    
     'start RFID reader
       rfid.start(PinRFIDser, PinRFIDen, @strnCodes, 7, 10, 0, @intMatch, @strnRFID)
    
     ' IF intMatch > 0
     '      vm2.demo  
    
       repeat until gQuadPos <> 0
      
    
    'start hotwatercontrol
       gHWCmode:= 1
    
    
     '            PINpump, PINignt, PINservo, PINfreqCount{NEW}, ptrTempWater, ptrPressWater, ptrFlameRate, ptrLevelWater, ptrHeatServo, ptrOpmode, iSPtemp, iSPpress, iSPtime, iSPvol
        hwc.Start(PINpumpImp, PINignt, PINservo, PINfreqCount, @gAnTempWater, @gAnPressWater, @gAnUVflame, @gAnLevelWater, @gAnHeatServo, @gQuadPos{@gHWCmode}, 93, 1500, 25, 3000)
     
       oled.demo
    
      'watchdog
      DIRA[5]~~
      repeat until gQuadPos == 3
          
        !OUTA[5]    
        WAITCNT(clkfreq/5 + cnt)
    
    
         
    PRI Wr_bit(target, pos, value)    'Writes value.0 to target.pos                                         'NEW
    
       if (value & 1)
         return target | (1 << pos)
       else
         return target & !(1 << pos)
     
    
     
    DAT
    strnCodes    BYTE  "0100A648BD", 0     {Erlend}
                 BYTE  "0100E3E9CB", 0     {Lisa}
                 BYTE  "8400338B3C", 0     {Mathias}
                 BYTE  "8400336BB8", 0     {Irene}
                 BYTE  "8400338FC0", 0     {Keith}
                 BYTE  "8400338778", 0     {Guest1}
                 BYTE  "8400338778", 0     {Guest2}             
                       
    intCodes     LONG  7
    

    This is still not all, but maybe enough?

    Erlend
  • StefanL38StefanL38 Posts: 2,292
    edited 2015-01-30 09:25
    why do you use pointers at all?
    surely it can be done. But it is another source of bugs if the value of the pointer is wrong.
    You have to check if you use the "@"-operator or not to access the right location in memory.

    As long as you keep all methods within ONE *.SPIN-file you can use all variables even ACROSS cogs

    If you have methods in different *.SPIN-files you can use "SetVar" und "GetVar" methods.

    As a general advice:
    Code in small steps. Code one method for one thing to do.
    Do extensive tests to each piece of your code. Testing all situations: value zero, value is max, value is min, value is out of allowed range
    After all these tests. Code next piece of code. In this manner you can be 99% sure that a bug is NOT inside previous written code
    but most of the time in the new method you are coding right now.

    All this testing takes time. But in the long run it will save a lot of time because you are not searching for bugs across hundreds of lines of code.
    As I'm coding now for more than 25 years me experience is: if it (should) go fast it will turn out to be real slow.

    here is a small demo-code that shows how variables can be accessed across cogs directly by their name.

    http://forums.parallax.com/showthread.php/113075-Variables-across-objects-again

    best regards

    Stefan
  • Heater.Heater. Posts: 21,230
    edited 2015-01-30 09:47
    Mike Green,
    Whenever there is a possibility of two cogs changing a variable's value, you need a lock so that only one cog can change it at a time.
    Perhaps this needs expanding on a bit.

    If two COGS are simply writing some values to the same HUB location the result in that location will be whatever the last COG wrote. It will make no difference if you use locks or not.

    But if they are doing a read/update/write operation, for example incrementing a counter, then locks will be required around the read and write other wise things will go wrong.

    Admittedly the first case above is not very useful as far as I can tell. Although if the writes and reads are some how ordered in time nicely, COG A writes, COG C reads, COG B writes, COG C reads, no locks would be required.
  • Duane DegnDuane Degn Posts: 10,588
    edited 2015-01-30 09:57
    Erlend wrote: »
    This is still not all, but maybe enough?

    I would much rather download an archive. Trying the read through the code in the browser does not sound like fun.

    The archive should have all the code needed to compile.
  • ErlendErlend Posts: 612
    edited 2015-01-30 10:42
    The whole thing_AllTestGaspresso - Archive [Date 2015.01.30 Time 19.33].zip for those of you who are want to help.
    I do work according to the principle of testing piece by piece, module by module, but I know from lifelong experience (from other technologies) that the real hard part is when bringing the pieces togheter into a system. I have tested basic stuff such as that the pointers point to where they should, and that the values arrive. When tested as small pieces. So, refering to my first post; the repeat loop does work as intended, when tested in isolation, but as part of the complete system it only occasionally works.

    I feel brave posting all this more or less half-cooked and untidy code. Thankfully the forum is very kind at heart.

    Erlend
  • Duane DegnDuane Degn Posts: 10,588
    edited 2015-01-30 11:27
    In the top post you say "sometimes it works and sometimes not".

    What does it do when it works and what does it do (or not do) when it doesn't?

    I do see a problem with your P control. I think you're getting a rounding error when the temperature is too hot.

    Line #286 of "HeatWaterControl".
           iHeatP:= (iHeatNominal / (iGainP * (1+ ||(LiSPtemp - iTempWater)/10)))
    

    This probably doesn't do what you want. The value of iHeatP will only change with every ten degrees of temperature difference.

    I think the equation below would give a little more resolution:
           iHeatP:= (iHeatNominal / (iGainP + ||(LiSPtemp - iTempWater) * iGainP/10)))
    

    With small values, you want to wait to divide until the end of the calculation. There's likely a better formula than the one above for the intended purpose since the number being divided by ten is still pretty small.

    I'm not sure if this at all related to the issue you're having since I don't know what "not working" means.
  • Mike GreenMike Green Posts: 23,101
    edited 2015-01-30 11:29
    Heater,
    You're right that, strictly speaking, multiple cogs can write to the same variable while other cogs are reading that variable as long as no cog makes a private copy of the variable value while it changes the value (read / modify / write). The private copy might be implicit (like in a register or stack). It's difficult to design multiple cog routines that keep in synchrony in a particular order of execution as you illustrated, but it can be done (usually with WAITCNT) ... not an exercise for anyone but a very experienced programmer used to working with multiprocessing (multiple processors with shared resources).

    The value monitor looks good.
  • ErlendErlend Posts: 612
    edited 2015-01-30 11:57
    There shouldn't be more than one method writing to the same varable, unless I have mis-coded somewhere. The intention is than one writes to, many read from. It all works, except when I want the heatwatercontrol to wait starting the main process control until the water has reached a desired initital temperature (=90). This one sometimes works, some other times only works after a significant time delay (seconds) - when the temperature has reached well over hundred. I cannot figure out why.

    Erlend
    Yes accuracy and control algorithms are basic still, I know. But they work.
  • Duane DegnDuane Degn Posts: 10,588
    edited 2015-01-30 12:38
    Erlend wrote: »
    It all works, except when I want the heatwatercontrol to wait starting the main process control until the water has reached a desired initital temperature (=90). This one sometimes works, some other times only works after a significant time delay (seconds) - when the temperature has reached well over hundred. I cannot figure out why.

    What's the indicator the main process control has started?
  • StefanL38StefanL38 Posts: 2,292
    edited 2015-01-30 12:43
    Hi Erlend,

    I'm not sure maybe you are doing at already this way.
    What I meant with testing was

    method A testing for itself
    adding method A to the whole thing
    testing it there
    writing method B testing it for itself
    adding it to the whole thing testing it in the whole thing.
    etc.

    Spin supports global variables and parameters
    Changing global variables sometimes causes strange behaviour if you loose the oversight over your code
    which parts of the code change the value of which variable at which time.
    And as the code in each cog runs independendly you can't predict which loop is at which point.

    Whenever a method just needs to read a value why not coding pass parameter by value?
    OK if the value is updated from one cog and another cog needs to read the value regularly
    instead of accessing a variable via a pointer accessing the variable via its variablename?

    If I understand right your method "HotWcontrol" runs through one time and thats all. There is no loop.
    hwc.start is called once and that's all

    Duane posted while I was still writing

    So how about monitoring inbetween-results of the caclulation
     iHeatP:= (iHeatNominal / (iGainP * (1+ ||(LiSPtemp - iTempWater)/10)))
    

    I mean monitoring
    LiSPtemp - iTempWater
    (LiSPtemp - iTempWater) / 10
    ||(LiSPtemp - iTempWater)/10)
    (1+ ||(LiSPtemp - iTempWater)/10))
    (iGainP * (1+ ||(LiSPtemp - iTempWater)/10))

    etc.

    best regards

    Stefan
  • Duane DegnDuane Degn Posts: 10,588
    edited 2015-01-30 12:56
    StefanL38 wrote: »
    I mean monitoring
    LiSPtemp - iTempWater
    (LiSPtemp - iTempWater) / 10
    ||(LiSPtemp - iTempWater)/10)
    (1+ ||(LiSPtemp - iTempWater)/10))
    (iGainP * (1+ ||(LiSPtemp - iTempWater)/10))

    I can't tell you how many times something like this has helped be catch integer math errors (which in hindsight are always obvious).

    My guess right now is there's an integer math issue causing the problem.
  • ErlendErlend Posts: 612
    edited 2015-01-30 13:12
    Duane Degn wrote: »
    What's the indicator the main process control has started?

    The top level objects starts the hotwater control in a separate cog, that method starts by inititializing variables, sets up the 'scheduler', and then does the following: 1) ignites the burner - 2) waits for the water to heat up - then 3) starts the PID control of the heater and speed control of the pump, which runs until a cup of espresso coffee is finished brewing. Step 3 is what I refer to as the 'main process'. My present trouble is that step 2 is governed by a repeat loop waiting for the temperature to be reached (around line 210-), simply by doing a comparison to the measured temperature which is held in a global variable - there is no complexity, it just LONG reads the value and does a > comparison. Problem is, it behaves unpredictable.

    @Stefan
    I do not want to go into a discussion of how I structure my code as I do, because then I need to describe all the features of the finished system as well as many other coding-philosophical things. I do not think it is a bad way to split my code into many independant modules, centered around a common set of global (measured and derived value) varaibles. It is a well proven concept. The mechanism of passing pointers to variables is also straight forward when the rules a so simple as in my code (one can write, many can read, assume asynch updating).
    The calculations you are pointing to are still to be polished - but they are not executed until later - until after the problem I described. Yes, I know the pains of integer math traps.

    Erlend
  • Duane DegnDuane Degn Posts: 10,588
    edited 2015-01-30 13:31
    Erlend wrote: »
    then 3) starts the PID control of the heater and speed control of the pump, which runs until a cup of espresso coffee is finished brewing. Step 3 is what I refer to as the 'main process'. My present trouble is that step 2 is governed by a repeat loop waiting for the temperature to be reached (around line 210-), simply by doing a comparison to the measured temperature which is held in a global variable - there is no complexity, it just LONG reads the value and does a > comparison. Problem is, it behaves unpredictable.

    What's your indicator the PID control is active?

    I think it would help if you added some sort of indicator to you know when the "main process" starts.
    'Wait with starting pump until initial heating of water is finished
       '-------------------------------------------------------------------------------------------------------------------------------------------------
        REPEAT UNTIL LONG[LptrTempWater] > 90 'LiSPtemp                  'Wait until water is hot enough for brewing
          WAITCNT(clkfreq/2 + cnt)
    
     [b] ' Light a LED or set some flag variable indicating the temperature has been reached.[/b]
       '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
    
  • ErlendErlend Posts: 612
    edited 2015-01-30 14:16
    I see your point Duane. When the 'main process' starts is indicated by that the impulse pump starts running. Anyway, I will do as you suggest, and then I should find if there is something holding up after the t>90 and pump starting. Only practical problem is that now it is too late at night at this side of the globe, so it'll have to wait until tomorrow morning.

    Thanks,
    Erlend
  • StefanL38StefanL38 Posts: 2,292
    edited 2015-01-31 00:58
    Hi Erlend,

    as the propeller-chip has no interrupts the behaviour of code is completely deterministic.
    old programmers wisdom:
    a programm does ALWAYS what the programmer has coded.
    If the program does some unexpected things it STILL does exactly what the programmer has coded
    only thing is the programmer coded something he does not fully understand and that's the reason why unexpected
    things happen.

    The only thing that really helps is to monitor every dammed detail of the code. Beginning at that place you find suspicious.
    Your monitor-method uses
      pm:= LONG[intLong0] [1]
    

    the "wait-for-temperature high enough"-loop uses
         REPEAT UNTIL LONG[LptrTempWater] > 90 'LiSPtemp                  'Wait until water is hot enough for brewing
          WAITCNT(clkfreq/2 + cnt)
    

    I guess you have thought lot's of times "LONG[LptrTempWater]" has exactly the same value as "LONG[intLong0] [1]"
    but did you really monitor it?

    You are using multiple cogs.
    This opens the possability that parts of the code execute from loop to loop in different amounts of time.
    For several test-runs
    Did you monitor all the code involved how far it is executed?
    You could do this with extra longs with via constants hardcoded adresses that are in memory far away from the end of your code
    Something like $7F80. Through using names of constants you make sure that the adress-value is REALLY constant.
    Each part of the code that runs in his own cog has this such a long. Then insert in your code assigning increasing values
    to these longs

    example
     PRI HWcontrol
    
        long[AbsAdressOf_hwc] := 1 
        '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
    ...
    ...
       'Wait with starting pump until initial heating of water is finished
       '-------------------------------------------------------------------------------------------------------------------------------------------------
        long[AbsAdressOf_hwc] := 2 
        REPEAT UNTIL LONG[LptrTempWater] > 90 'LiSPtemp                  'Wait until water is hot enough for brewing
          WAITCNT(clkfreq/2 + cnt)
    
        long[AbsAdressOf_hwc] := 3 
    

    or alternativly switching on/off different LEDs
    to get feedback which part of the code is executing where

    You are right: it's not nescessary to start a discussion about coding philisophies.
    But copying values to new variables opens the possability that values can be different

    indeed it is really strange that your loop
         REPEAT UNTIL LONG[LptrTempWater] > 90 'LiSPtemp                  'Wait until water is hot enough for brewing
          WAITCNT(clkfreq/2 + cnt)
    

    leaves the loop sometimes at a temperature of 115 degrees

    to me this would mean:
    - the code starts looping at 115 degrees
    - the value of LONG[LptrTempWater] is different from the monitored one LONG[intLong0] [1]

    best regards

    Stefan
  • ErlendErlend Posts: 612
    edited 2015-01-31 02:54
    Thank you Stefan, these are really good advices. I very much appreciate that you help me here. Hopefully I will find time later today to try out your ideas.

    Erlend
  • fridafrida Posts: 155
    edited 2015-01-31 04:26
    'start hotwatercontrol
       gHWCmode:= 1
    
    
     '            PINpump, PINignt, PINservo, PINfreqCount{NEW}, ptrTempWater, ptrPressWater, ptrFlameRate, ptrLevelWater, ptrHeatServo, ptrOpmode, iSPtemp, iSPpress, iSPtime, iSPvol
    '   PUB Start(PINpump,    PINignt, PINservo, PINfreqCount{NEW},  ptrTempWater,  ptrPressWater, ptrFlameRate,  ptrLevelWater,  ptrHeatServo,          [B]ptrOpmode[/B],  iSPtemp, iSPpress, iSPtime, iSPvol): Success
        hwc.Start(PINpumpImp, PINignt, PINservo,      PINfreqCount, @gAnTempWater, @gAnPressWater,  @gAnUVflame, @gAnLevelWater, @gAnHeatServo, [B]@gQuadPos{@gHWCmode}[/B],      93,     1500,      25,   3000)
    

    When I compare the call to hwc.Start(...) with PUB Start(...) I see a difference in the parameters.

    In PUB start there is ptrOpmode the opposite side has
    @gQuadPos{@gHWCmode}.

    As I see you are using @gQuadPos while {@gHWCmode} this is a comment.
    I guess that it should be @gHWCmode, but I could be wrong.
  • ErlendErlend Posts: 612
    edited 2015-01-31 07:35
    @Frida

    You've spotted right, but I am afraid it is only the result of me being half-way to setting up a varable handle to use for hotwatercontrol to write back it's status - for debugging purpose. The gQuadPos is an easy one to use for this purpose, as it is already being displayed by ValueMonitor. Sorry, but I am sharing half-baked code here.

    Erlend
  • ErlendErlend Posts: 612
    edited 2015-01-31 08:47
    Found it!

    Thanks all, for the help to trace the problem, and for helping me to take the approach of tracing the problem instead of drilling down into what I thought was the problem.
    It turns out that the scheme of sharing-reading global variables was fine, and also that the REPEAT UNTIL Temp_measured > Temp_setpoint worked fine.
    I inserted 'telltales' into the code to see at what stage it got stuck - it was just was after the temperature comparison code! So why didn't the pump start running? The 'milestone scheduler' code is all governed by the CNT values as it executes, and in my original code the 'milestones' values got referenced to CNT -- before the wait for the water to get hot REPEAT UNTIL loop. So when finally the water was hot enough that CNT value was minutes old. I assume therefore that some 32bit 'counting-around effect' caused the problem. I cannot explain exactly how. Maybe someone can? When I moved the code which sets up the milestones referenced to CNT to just before the 'scheduler' code, it all works perfect.

    Again, thanks for the help. Now I can go on to improve and tune the PID control algorithm.

    Erlend

    Present code - with telltales:
    PRI HWcontrol
    
        LONG[LptrOpmode]:=  10
            
    
       '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 < 2                                   'Light the flame if not burning
          Ignite(1)
          iFreqUV:= PHSA~                                   'reset counter
          WAITCNT(clkfreq/2 + cnt)
          FreqCount
    
        LONG[LptrOpmode]:=  11
    
        
       'Wait with starting pump until initial heating of water is finished
       '-------------------------------------------------------------------------------------------------------------------------------------------------
        REPEAT UNTIL LONG[LptrTempWater] > LiSPtemp         'Wait until water is hot enough for brewing
          WAITCNT(clkfreq/2 + cnt)
          
        LONG[LptrOpmode]:= 12
    
    
       '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:= 4 
        iGainD:= 1
        iTempWaterOld:= iTempWater
    
        LONG[LptrOpmode]:= 13
               
    
       '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:= 1000*mSec                          'Run control algorithm /sec and update values
        iAlgoritmNow:= CNT +  iAlgorithmIval                'Define 'milestone' as CNT value
        
    
        LONG[LptrOpmode]:=  14
                                                       
    
       '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
        
        LONG[LptrOpmode]:= 15
        
        Shutdown
    
  • Duane DegnDuane Degn Posts: 10,588
    edited 2015-01-31 08:59
    Erlend wrote: »
    helping me to take the approach of tracing the problem instead of drilling down into what I thought was the problem.

    I'm glad you found it.

    I'm sure you're not the first or last to look for a problem in the wrong place.

    I often use a multiple cog and multiple object serial driver with locks to find these sorts of bugs.
    Erlend wrote: »
    I assume therefore that some 32bit 'counting-around effect' caused the problem. I cannot explain exactly how. Maybe someone can?

    You can only test for delays less than (about) 26 seconds (at 80MHz). If the delay is longer than 26s, the interval will be negative and fail the ">" comparison used to see if a set time has passed.
  • StefanL38StefanL38 Posts: 2,292
    edited 2015-01-31 12:16
    Hi Erlend,

    congrats that you found it!
    the WaitCnt-command works as follows:

    the parameter of WaitCnt (usually something like (ClkFreq */ somefactor + cnt)
    gives a value which will be reached after some time in the future.

    The systemvariable cnt delivers the value of an always free-running 32bit counter which rolls over to zero in just one clocktick when the counter reaches his
    maximum value (which is 2^32)

    The WaitCnt command stops the cog completely until the value given as paremeter in the WaitCnt-command matches the value of the systemcounter
    with smaller and easier to understand numbers let's say maximum-value is 1000

    let's say cnt has a value of 600
    if you execute a WaitCnt with a value 500
    the freerunning counter has already a higher value.
    this means the next match will occur after counting up to 1000 then roll over to zero and start counting up again until 500 is reached.

    With the real system running at 80 MHz counting up will take 2^32 / 80.000.000 = 53,7 seconds
    So whenever code reacts after aprox one minute you have a "cnt has passed matching value already problem"
    and if code does not react soon wait for at least a bit more than a minute to see if the code reacts then.

    In Spin there is a minimumtime of 385 clockticks which it takes to interprete a WaitCnt-command.
    So if you want to create a pulse shorter than 1/80.000.000 * 385 = 4,8 microseconds it will not work in spin
    by using WaitCnt. You would have to do this in assember or by using the counter-modules.

    If you still can spend a cog how about using a software RTC like JonMcPhalens softrtc http://obex.parallax.com/object/322
    with milliseconds and "summarised seconds of day" as a timereference?

    best regards

    Stefan
  • Duane DegnDuane Degn Posts: 10,588
    edited 2015-01-31 12:41
    StefanL38 wrote: »
    the WaitCnt-command works as follows:

    Stefan,

    I don't think the issue was the waitcnt statement. I think it was the way time periods were compared using a start time more than 26 seconds from the current time.

    With waitcnt you can pause up to about 53 seconds but then comparing intervals the longest interval one can safely compare is about 26 seconds. Intervals longer than 26 seconds will be calculated as a negative number.
  • StefanL38StefanL38 Posts: 2,292
    edited 2015-01-31 12:43
    Hi Duane,

    you are right. In SPIN MSB is the sign. So it cuts down to half the time.
    best regards

    Stefan
Sign In or Register to comment.