Am I beyond the speed limits of spin?
turbosupra
Posts: 1,088
Somewhere between setting the outFreq setting of 1600 and 2000, my calculation formula fails and I start to get erroneous values. I need to be able to read up to an outFreq of 6600.
I've read that the minimum time to execute a command is 381 cycles, but I've never read about the maximum spin instructions that can be executed per clkfreq? This code works very well at lower outFreq's but is a little off at say an outFreq of 600 ... it should read 1000, but instead reads 960?
I've read that the minimum time to execute a command is 381 cycles, but I've never read about the maximum spin instructions that can be executed per clkfreq? This code works very well at lower outFreq's but is a little off at say an outFreq of 600 ... it should read 1000, but instead reads 960?
CON _clkmode = xtal1 + pll16x _xinfreq = 5_000_000 WMin = 381 'WAITCNT-expression-overhead Minimum c_CrankSensorInputPin = 1 c_CrankSensorOutputSimPin = 14 OBJ Debug : "FullDuplexSerial" VAR ' ########### PUB Main Variables ########### ' generateFrequency long rpm long toothcount long outFreq long genfreq_stack[20] ' measureFrequency long measureFrequencyTime long measureFrequencyTimePrevious long measureFrequencyTimePrevious2 long measureFrequencyTooth long measureFrequencyRPM long measureFrequencyTDCTime long measureFrequencyTDCTime2 long measureFrequencyTDCTime3 long low long measureFrequency_stack[20] PUB Main 'WAITPEQ (State, Mask, Port) 'dira[15] := 1 Debug.Start(31,30,0,115200) 'start cog with serial driver waitcnt((clkfreq * 3) + cnt) ' makes life easier when starting to debug with a 2 second pause to get the serial viewer active and receiving Debug.Tx(13) Debug.Tx(13) Debug.Tx(13) Debug.Str(string(" */\*/\*/\*/\*/\*/\*/\* START */\*/\*/\*/\*/\*/\*/\* About to enter main pub")) ' for debug purposes Debug.Tx(13) Debug.Tx(13) Debug.Tx(13) ' ####### Call all cogs here Cognew(generateFrequency, @genfreq_stack) Cognew(measureFrequency, @measureFrequency_stack) PUB generateFrequency | syncPoint, ticks dira[c_CrankSensorOutputSimPin] := 1 'make pin an output outFreq := 2000 ' (outFreq/36) * 60 = rpm or (rpm/60)* 36 = outFreq ticks := (clkfreq / (outFreq*2)) #> WMin 'calculate the number of ticks to wait to generate the desired frequency syncPoint := cnt 'take a picture of the current clock count. This way, we can always sync to this point. toothcount := 0 repeat !outa[c_CrankSensorOutputSimPin] 'toggle the pin toothcount := toothcount + 1 'Debug.Str(String("High and Low Tooth count is ")) 'Debug.Dec(toothcount) 'Debug.Str(String(" + ")) 'Debug.Dec(cnt >> 1) 'Debug.Tx(13) If toothcount => 68 toothcount := 1 outa[c_CrankSensorOutputSimPin] := 0 Debug.Str(String("----TDC----")) Debug.Tx(13) waitcnt(SyncPoint += (ticks*5)) waitcnt(SyncPoint += ticks) ' wait the appropriate number of clock counts to generate ' the proper square wave 'If toothcount == 72 OR toothcount == 144 OR toothcount == 226 OR toothcount == 288 OR toothcount == 360 OR toothcount == 432 { Debug.Str(String(" TDC in generateFrequency")) Debug.Tx(13) Debug.Dec(cnt >> 1) Debug.Tx(13) } PUB measureFrequency measureFrequencyTooth := 0 low := true repeat If ina[c_CrankSensorInputPin] == 1 AND Low == true Low := false measureFrequencyTimePrevious2 := measureFrequencyTimePrevious measureFrequencyTimePrevious := measureFrequencyTime measureFrequencyTime := cnt measureFrequencyTooth := measureFrequencyTooth + 1 'Debug.Str(String("measureFrequencyTooth count is ")) 'Debug.Dec(measureFrequencyTooth) 'Debug.Tx(13) If ((measureFrequencyTimePrevious - measureFrequencyTimePrevious2) * 2) < (measureFrequencyTime - measureFrequencyTimePrevious) 'Debug.Str(string("TDC in measureFrequency")) 'Debug.Tx(13) 'Debug.Dec(measureFrequencyTDCTime3) 'Debug.Tx(13) 'Debug.Dec(measureFrequencyTDCTime2) 'Debug.Tx(13) 'Debug.Dec(measureFrequencyTDCTime) 'Debug.Tx(13) measureFrequencyTDCTime3 := measureFrequencyTDCTime2 measureFrequencyTDCTime2 := measureFrequencyTDCTime measureFrequencyTDCTime := cnt measureFrequencyRPM := (measureFrequencyTDCTime - measureFrequencyTDCTime2) Debug.Str(string("RPMS in measureFrequency are ")) Debug.Dec(((clkfreq)/(measureFrequencyRPM))*60) ' needs to be clkfreq/2 because I'm removing 1/2 the time counter before it starts over with the clkfreq >> 1, I have it divided by 10 to keep the number whole and then multiplied by 6 because it really needs to be multiplied by 60 in total Debug.Tx(13) 'waitpeq(%000000, |< c_CrankSensorInputPin, 0) ElseIf ina[c_CrankSensorInputPin] == 0 Low := true 'Debug.Str(String("Low")) 'Debug.Tx(13)
Comments
Spin is an interpreted language and the amount of time needed to execute a Spin statement depends on the operations performed and on where variables are stored. There's a lot of optimizations for special cases of variable locations and constant values that both shorten the amount of storage needed for the interpretive code and speed up execution time. There's very little documentation on statement execution time as yet except specific cases like the minimum WAITCNT time.
It's possible to determine the exact execution time of a specific byte code sequence since the interpreter's source code is available for examination, but it would be a tremendous amount of work to create a document detailing the execution times for most operations and their variations. Figure a microsecond or several microseconds for most simple operations.
What do you mean?
Usually when I see debug conflicts I will see broken or strange characters on the serial output debugger application.
My zero-footprint debugger can calculate the number of pasm instructions being executed. However, it is not that easy to run for the uninitiated. Its in the OBEX. In reality my debugger is running the spin interpreter in an LMM style. It is capable of running pasm in the same fashion too.
you might consider two other possibilities other than to do it in assembler:
1. Use a counter to count the number of cycles for a given time.
2. Use PropBasic instead of PASM, which is very much faster than spin. As it compiles to PASM, you can reuse the routine later.
Good luck,
Christof
I'll have to look into using the counters, I have not used them directly before
Mike, do you agree that my issue is calculation speed?
I am watching the output of pin 14 on my propscope and then also measuring it as an input on pin 1, the duty cycle is 50% and the pulse length depends on the outFreq value that I set it to. It looks clean on the propscope, but my code falls apart after an outFreq of between 1600 and 2000. I need it to be accurate until at least an outFreq of 6600.
I'm trying to learn about the counter modules now and I'm reading through the PEkit on page 128.
I read that there is an A and B counter per cog? How does the code below know how to assign these values to the different ctra's in each cog? Would the second code block work?
I don't know whether calculation speed is indeed your limitation in Spin. You're talking about a process that occurs on the order of 6000 times a second. That gives about 160us to do everything. That's not a lot of Spin interpretive codes at 1-2us per operation.
You can carefully optimize your Spin code to minimize the time taken on each cycle or you can use other techniques (like using the cog counters). You can also switch to partially using assembly which is roughly 2 orders of magnitude faster.
I'd like to try the counter optimization first, before I try and learn assembly.
Would my code maybe be something like this? How would you use this to count the time in between leading edge triggers?
I'm having a hard time understanding how this buys me additional processing time if I still have to use the main cog to then assign frqa to something and do math calculations with it?
I found a post of yours and changed the code a little
How about backing up a step ...
How often do you want to update the frequency (RPM) reading? Is this a fixed time period or a minumum update rate? Do you have a spare cog that can do the frequency counter sampling while the rest of your program is doing other things?
I would like to poll the rpm reading each system cycle so that I can get the most accurate transition times that is why I thought the counter sounded good? Since I will see 6.6khz at most, I would speculate .1mhz would be plenty?
This is the first object I am writing for this project, so I have all of my cogs available at the moment. I have never used the counters before and would like to be able to use them as they sound quite advantageous.
The main cog calculates and displays then the frequency. The halfperiod variable is updated every periode of the signal! But the main cog displays it only every 1/2 second.
With Spin only this works up to ~30 kHz
Andy
Thanks for the code, I ran it and got the following
Frequency = -4843853
or
Frequency = 677439
I checked the countPulses loop and it's only looping every 53 seconds (which is also when it updates the return value) or so when the cnt register is rolling over. I will try and play with the code tomorrow to figure out why.
I think it is related to the waitcnt of that loop.
Is there any way to verify that the phsa count is correct? Or a way to mathematically estimate what it should be?
I am by far your inferior with the prop, but did you mean
"time #>= 400 " ?
I ran it with the set minimum command above and I'm getting a value in the 9000 range. I'm still not completely sure how this works so I'm not sure if that is the correct value when reading a 600hz signal?