CNT adding up to trouble
Erlend
Posts: 612
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:
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)
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
{{

Comments
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 + iServoPulsErlend
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
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
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
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]~
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?
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.
...
if (timer.millis => 20) timer.adjust(-20) do_somethingI 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 pulseCode has been updated (26 APR 2015)
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
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
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
Your postings ALWAYS help me. This goes into my solutions library for when I meet upon the next challenges.
Erlend
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
Erlend
-Phil
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)
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
Erlend
Thanks. I was never a friend with ASM, not even PASM. My whole project is written in Spin.
Erlend
No argument on the having plenty of time.
Though use waitcnt instead of the if statement to reduce jitter.
'Do the control algorithm IF (CNT - iAlgoritmNow) > 0 and OUTA[LPINservo] == 0 'Perform PID control of temperature and simple pump speed control ControlAlgorithm iAlgoritmNow += iAlgorithmIvalthe Control Alorothm will be delayed until te Servopulse is done, but the other tasks work as before.Andy
'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 + iAlgorithmIvalThanks to you the problem is fixed. I experimented with the various proposed solutions and decided on using WAITCNT for the servo pulse.
Erlend
'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
OK, now I get it. Thanks.
Erlend
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!