Am I beyond the speed limits of spin?
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?
ctra[30..26] := %01010 ' Configure bits 30-26 to be set to detect a positive edge ctra[5..0] := 1 ' Configure bits 5-0 to be set to pin 1 frqa := 1 ' So that phasa will get 1 added to it each time the pin goes high phsa~ leadingedge1 := 1 repeat if phsa > leadingedge1 ' If the phsa value increments by one higher than the leadingedge1 variable, should happen at each positive edge detection time1 := cnt ' set a value to count difference := (time1 - time2) ' subtract the older time form the newer time leadingedge1 := phsa ' increment leadingedge1 so that the if statement is not true until another leading edge detection time2 := time1 ' assign another value to the time countI 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.
CON scale = 10000 ' Scale for the frequency so it can be integer VAR long globalFreq, freqStack[20] PUB main globalFreq := 0 cognew(CountPulses(clkfreq/10000),@freqStack) repeat waitcnt(clkfreq + cnt) ' Display result once a second debug.str(string("Frequency = ")) debug.dec(globalFreq) debug.str(string(13,10)) PUB CountPulses(time) | wait ' time = system clocks per timing cycle ctra[30..26] := %01000 ' Set POS Edge counting ctra[5..0] := 1 ' Use I/O pin 1 frqa := 1 ' Count 1 for each pulse time <#= 400 ' Set a minimum cycle time wait := cnt ' Will have "time" added to it repeat waitcnt(wait += time) ' Wait for the specified time result := phsa~ ' Get the pulse count and reset it globalFreq := scale * result / time ' # pulses / # clock ticksThe 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
CON _clkmode = xtal1 + pll16x _xinfreq = 5_000_000 pinnr = 1 VAR long halfperiod long stack[10] OBJ ser : "FullDuplexSerial" PUB main | freq ser.start(31,30,0,115200) cognew(PulsMeasure,@stack) dira[pinnr] := 1 ctra := %00100<<26 + pinnr 'NCO for freq out frqa := 6600 * 5369/100 '6600 Hz @80MHz repeat freq := clkfreq / 2 / halfperiod 'calc freq from pulstime ser.dec(freq) 'and display it ser.tx(13) waitcnt(clkfreq/2 + cnt) 'update every 1/2 second PUB PulsMeasure ctra := %01000<<26 + pinnr 'POS detect frqa := 1 repeat '___-----___ waitpeq(|<pinnr,|<pinnr,0) ' ^ waitpne(|<pinnr,|<pinnr,0) ' ^ halfperiod := phsa~ 'read exact pulswidth from counterAndy
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?