Doing Wait Right?
Kirk Fraser
Posts: 364
The documentation for wait says:
WAITCNT (Value) 'Pause cog’s execution temporarily.
it doesn;t say what Value means or how it relates to real time.
The first Spin example:
waitcnt(3_000_000 + cnt)
CNT 'Current 32-bit System Counter value.
it doesn't say what changes CNT, crystal, ppl clock, code, or program steps.
does including the CNT variable mean the wait increases over time, then decreases?
it also doesn't clarify how the example relates to real time.
The Gold Standard example
http://www.parallaxsemiconductor.com/goldstandard
waitcnt(((clkfreq / 1_000 * Duration - 3932) #> 381) + cnt)
what a bunch of gibberish! What does "3932) #> 381)" mean or do?
The context indicates it has a millisecond duration but it doesn't say how.
The inclusion of CNT would suggest it is not a true millisecond conversion.
Phil Pilgrim suggested I write a PWM to vary within a freequency of 20KHz.
Here's my attempt so far, but without understanding how wait relates to real time.
Thanks for any help.
WAITCNT (Value) 'Pause cog’s execution temporarily.
it doesn;t say what Value means or how it relates to real time.
The first Spin example:
waitcnt(3_000_000 + cnt)
CNT 'Current 32-bit System Counter value.
it doesn't say what changes CNT, crystal, ppl clock, code, or program steps.
does including the CNT variable mean the wait increases over time, then decreases?
it also doesn't clarify how the example relates to real time.
The Gold Standard example
http://www.parallaxsemiconductor.com/goldstandard
waitcnt(((clkfreq / 1_000 * Duration - 3932) #> 381) + cnt)
what a bunch of gibberish! What does "3932) #> 381)" mean or do?
The context indicates it has a millisecond duration but it doesn't say how.
The inclusion of CNT would suggest it is not a true millisecond conversion.
Phil Pilgrim suggested I write a PWM to vary within a freequency of 20KHz.
Here's my attempt so far, but without understanding how wait relates to real time.
VAR m PUB testPWM startPause(20_000_000, 12) PWM(8, 2048, 12, 20_000_000) PRI startPause(frequency, bits) | t r t := 1/frequency 'time duration of one cycle r = 2^bits 'range of accuracy m := t/r 'multiplier to match max. delay to range ' Some other wait constants could be created here. PUB Pause(duration) {{Wait a duration, scaled into one cycle of a frequency }} waitcnt(duration) 'simple wait, no "+ cnt" here! What multiplier would make duration a real time? PUB PWM(pin, duty, bits, frequency) | hitime lotime {{Output a PWM pulse for 100 miliseconds, allowing time for other processing in cog. }} hitime := duty * m lotime := 2^bits - duty * m repeat outa[pin]~~ 'turn pin on Pause(hitime) 'wait for hi part of cycle outa[pin]~ 'turn the pin off Pause(lotime) 'wait for lo part of cycle until cnt > (CLKFREQ / 1000_000) 'repeat for 100 milliseconds. What would make this work ???
Thanks for any help.
Comments
So if it's important that you wait a specific time, your software needs to ask.
CLKFREQ
Command: Current System Clock frequency; the frequency at which each cog is running.
((PUB ┆ PRI))
CLKFREQ
Returns: Current System Clock frequency, in Hz.
Explanation
The value returned by CLKFREQ is the actual System Clock frequency as determined by the current clock mode (oscillator type, gain, and PLL settings)
and the external XI pin frequency, if any. Objects use CLKFREQ to determine the proper time delays for time-sensitive operations.
For example:
waitcnt(clkfreq / 10 + cnt) 'wait for .1 seconds (100 ms)
In PASM the clkfreq is stored at hub ram 0
rdlong MyHz, #0 'Get clock frequency
Thanks Tony, now what about that + cnt? What is incrementing and if it is incrementing then wouldn't the wait be 0.1 sec only once then keep incrementing past that?
CNT is an internal 32-bit counter that increments every tick of the system clock. It runs continuously. Waitcnt instructions are almost always referenced to CNT. In the example above (clkfreq/10 + cnt) means the current value of the system timer (cnt) + 1/10th of the clock frequency. The waitcnt halts the cog until the specified value is reached on the timer.
In your first post, this:
waitcnt(((clkfreq / 1_000 * Duration - 3932) #> 381) + cnt)
will delay Duration milliseconds. This is SPIN code, and SPIN is interpreted, so it takes significantly longer to interpret and execute the waitcnt command than it does with ASM. The 3932 is to compensate for the time that it takes the interpreter to process the computation portion of the command. The #>381 insures that you never do a waitcnt with a value less than 381 more than the current CNT, because if you do that the counter will have already counted past your target value before the timing starts, and that means you will hang there while the counter loops through it's entire 32-bit range, somewhere around a minute. Waitcnt waits for the system counter to be exactly equal to the target value
For example, in the Gold Standard snippet "#> 381" from jstjohnz's explination the code would not perform if the count happened to be getting ready to recycle back to zero within 381 counts. So using that would be risky for applications more serious than LED's.
So there is nothing harmful using cnt ...
So in my above example the startPause should put cnt in a temporary variable, test to see if it's ready to flip, and if so, reframe an offset value by subtracting. Something like this:
If the + cnt varialbe is eliminated, does waitcnt(value) work to delay by the value in all cases?
Also, your example, kuroneko, provides no implementing code so the formula involving waitcnt could be anything, with or without cnt and/or delay, making numbers irrelevant.
Pretend for example you have a surgery scheduled and the doctor is in Hawaii using a robot to communicate his movements to your body in your nearest hospital, and the robot has a Propeller chip. Now just a moment's delay made in error or an intended delay nullfied by obscure code could result in a delay of moving the surgeon's knife when you twitch, resulting in cutting a tiny virtually invisible nerve in a certian location resulting in permenent loss of function of a certian organ. Wouldn't that be dangerous?
That is why Gold Standard code should only be the very best, most flexible, easiest to read, validate, and verify. The current useage of CNT in the Gold Standard example is like abandoning both structured and object oriented programming and going back to spaghetti code with the goto statement.
The term "+ cnt" is a contributor to spaghetti code, like the goto statement. That is obvious from the above explinatitons without needing to know how waitcnt is implemented. Parallax might consider replacing the waitcnt statement with a true wait.
Say the current CNT is $FFF0_0000 and you precalculate the next waitcnt value by adding $1000_0000, then the result is $0FF0_0000. So the waitcnt command waits until CNT has counted up to $FFFF_FFFF, wraps over and count further to $0FF0_0000. Exactly at this point the waitcnt command terminates and the next command is executed.
Andy
[An opportunity squandered.]
"waitcnt(clkfreq / 10 + cnt) 'wait for .1 seconds (100 ms)"
Think of 'cnt' as 'now', and turn it around and you get
waitcnt(NOW + clkfreq/10)
which isn't particularly spaghetti.
-Tor
You're not understanding this correctly. The initial value of cnt is irrelevant. The system is simple, elegant, and reliable. You need to read some of the references that you were referred to. Perhaps that will help clear things up. You're making it more complicated than it is by worying about when the counter rolls over, that simply doesn't matter.
That is a valuable insight, thank you. However it is still a problem if you want to wait for 100 and NOW is $FFFF_FFF0 then you get a count that is 100-16. And not necessarily only once, but as the code executes in a loop, it may encounter that problem every pass giving random waits ranging from 0 to 100. This depends on the timing of the call to NOW which may vary based on other program execution branches. So while NOW doesn't look spaghetti in the suface SPIN code, it really is a random mess when used with waitcnt as usual. A random mess is spaghetti. Thanks to everyone for patience and help in developing this problem statement.
Is there a solution? For that let's look at Ariba's post. It shows how a count can start at any position, add the desired wait value, then achieve that wait by waiting until the if statement matches. Looks like it should always work by virtue of the math conversion.
Now let's compare the statements to see if there is still a problem. It looks like Ariba / Andy has the solution for the usual use of waitcnt. It works for any wait under $FFFF_FFFF counts. Any wait over that would be rare. Looking at the first post by jstjohnz the solution may fail for waits less than 381. It may also fail if the computation portion of the code is uncompensated. So any wait less than 3932 may fail? (It looks like my problem statement above was wrong, this is where the problem is.)
Next we should check on the Gold Standard PauseMS which uses various constants and one unusual operation, all without documentation, to provide a wait that gets around code execution delays. Based on the above solution, I'll assume the code mess works and my concerns about that were wrong. However the fact the code execution delays being prevented are undocumented (except in a post in this thread) and the means of preventing them is not obvious to the beginner (which Gold Standard code should first address) that code is spaghetti and should be rewritten. However in code it does provide valuable information, that any small wait may not be executed properly.
Any remaining questions? What happens to the code if its time of execution overruns a waitcnt? Possibly a delay of upto nearly $FFFF_FFFF before resuming execution. Can there EVER be a true wait which always works as simply as ariba / Andy wrote?
If kuroneko says something, you really ought to listen. Espcially when he's offering to help you with a question you asked.
He knows what he talks about, and I've not noticed him talk about things he doesn't know.
You blew it, dude.
A pause that starts from now or a interval timing (synchronized delay)
With interval timing you don't have to worry how long the code takes due do different branching.
Just as long you at least wait the time the longest route could take.
In spin that could be like this:
waitcnt(Time += 50_000) 'Wait for 10 ms interval.
In pasm the interval timing is built-in,
as Delay value prepares Time for the NEXT waitcnt Time.
code: waitcnt Time, Delay 'Wait for time window
The WAITCNT instruction pauses the cog until the global System Counter
equals the value in the Target register (Time),
then it adds Delta (Delay) to Target (value at address location Time label)
You start by coping current CNT to Time and
then you add the delay you want to it (Time that is) and finaly you do the waitcnt.
There is never a problem with CNT rollover as Time value will also roll over when you add delay to it.
Minimum delay,
waitcnt does only wait until equal and not until [equal or higher]
so if you miss the window it will take 54sec before you reach that cnt value again.
Max delay (based on 80MHz),
in pasm 54sec in Spin 27sec due to it treats numbers as signed integers. note edited the numbers
The roll-over time at 80MHz is about 54 seconds, and the max delay you can add with signed numbers is 27 seconds. If a resolution of 25ns is okay, then you can also wait up to 54 seconds in Spin:
waitcnt((delta>>1 + cnt>>1) << 1)
@Kirk Fraser
Yes you get 100-16, but that is the right value to wait for. Waitcnt waits for a certain absolute CNT value !
if you write WAITCNT(100) then it waits until the CNT system counter has the value 100. This happens only one time every 54 seconds.
If you write WAITCNT(cnt + 100) and cnt is $FFFF_FFF0 then it waits for the absolute cnt value of 100-16 ($0000_00054), which will take 100 clock cycles (This will not work In Spin, a delta of 100 is to short because the cnt is faster than the calculation).
You're right that you not get exact loop timings with this methode, it is only for waiting a time relative to the current moment.
For exact loops you just have to calculate the next point in time relative to the previous waitcnt value: Andy
Edit: For sure, the instructions inside the loop must not take more than 20us, otherwise the right CNT value is missed. With Spin you can not do much in 20us.
There have to be special rules for wait times longer than 24 hours, and for 12 hour clocks, and for time intervals so short that the act of asking is longer than the asked for wait.
So I will work on the SPIN code in my original post with all this information you've provided to get the high level approach correct. That may be good enough for my major test. Then move to assembler when I must guarantee results.
Tracy, the problem is my attention is flagging - and in my program I want to know all the flagging that goes on so I can make proper use of the time and still get the desired results out from the pin enough to trick whatever is connected to it into doing the right thing.
But as in any languages if the code running in the loop takes longer than interval you want when timing is no longer guaranteed.
So what timings do you want, microsecond or millisecond?
Pause or interval timing or both.
We are all going to help you understand this, even if it kills us.
You have to believe us, absolute, accurate delays are very easy with the prop, whether in spin or asm. We can even generate timing stringent video signals which is proof.
* You don't need to worry about the "wrap around" issue - it's taken care of by the way the counter math works.
To simplify lets pretend for a moment we have an 8 bit Prop, and CNT is an 8 bit counter, instead of a 32 bit counter. It counts from 0 to 255 then loops back to 0. Say at the instant your code needs its 'delay' executed, CNT is currently reading 250, and you want a fixed delay of 50. You will want to 'wait' until the CNT gets "300", correct? So you enter the command WAITCNT (50+CNT). The expression inside the bracket is evaluated at run time as 50+250, which evaluates to "44" in 8 bit representation (=256+44 with an irrelevent, discarded overflow flag). So at that instant we have effectively WAITCNT(44), with CNT sitting on 250 and counting up, waiting for a "match" where CNT=44. The prop sits idle (waiting) while CNT, which is clocked by the crystal or whatever clock source you have selected, is incremented (by hardware in the background), 251, 252, 253, 254, 255, 0, 1, 2.... 43, 44. When CNT hits 44 the match is made, and the next line of code is executed.
* The point is it doesn't matter what the actual value of CNT is, at the instant you enter the waitcnt command, you just add your delay on to "now" to work out the time (cnt) you are waiting for.
* The only difference between spin and pasm is with regard to the 'minimum' delay you can create, and this is because of the time spin takes to actually calculate the value inside the brackets. Hence you see that magic 381 in comments sometimes. If you need a shorter delay you generally need pasm.
* CNT overflows (rolls over) at 2^32 -1, something like 4.1 billion. Using a 80 MHz clock, this equates to about 53 seconds. If you ever see an unexplained delay of 53 seconds happen, its most likely because you've tried to wait for too short a time (< 381).
Keep asking until its clear, we're a helpful bunch
When executing a waitcnt, the propeller's attention is unflagging, and the response is guaranteed for either Spin or pasm. . It will not miss the appointed time even with the clock ticks of 12.5 nanoseconds. Spin is in no way inferior in that respect. It is true that pasm can do things faster around the waitcnt, but there is no issue with the reliability per se.
There is the constraint that has to do with intervals so short that the act of asking is longer than the asked for wait. Kuroneko pointed out that the shortest interval in pasm is #>5 cycles, a precise number, and in Spin it is also a precise number, #>381 cycles in the example given. You just have to remember that. What is 381 cycles? That is less than 5 microseconds when your Prop is clocked at 80 Mhz. Is that not quick enough? The minimum pasm delay is only 63 nanoseconds. But each within that time frame is equally reliable.
Amen to that brother.
The pause or interval distinction isn't clear to me.
Based on the posts that appeared as soon as I sent this one before this edit, it appears nothing smaller than 5 microseconds is possible. Therefore basing PWM on 20KHz is not possible at 12bit accuracy. Applying the squeeze... (1/2KHz) / 4096 = 0.1 us, 1/1KHz / 2048 = 4.8 us so it looks like that's close to 5 us. Therefore I can generate PWM at 1KHz with 11 bit accuracy. Ok.
or drop your 20kHz requirement to 19.531kHz and do it with a 5MHz xtal.