Pretty basic question about constant CNT
From my reading and understanding, correct me if I am wrong please ...
CNT will go 2^32 or 4294967296 and roll over every 53.6870912 seconds at 80mhz
I can correlate time to any divisor of 80mhz then, for example 1/6.67 or .15 seconds equals 12_000_000 cycles?
Is the CNT constant signed? If it is, would I have to write || CNT or write || variable := CNT ?
Part of this is that I should have a better understanding of this constant and also is that I'm trying to deal with roll over and if I convert it to milliseconds, I can go from 53.68 seconds to 49.71 days with a 2^32 register.
I'm a little confused as to why Parallax didn't make only the system CNT at 64 bit register? If my math was right, we'd go from 53 seconds to 7300 years?
Thanks for reading!
CNT will go 2^32 or 4294967296 and roll over every 53.6870912 seconds at 80mhz
I can correlate time to any divisor of 80mhz then, for example 1/6.67 or .15 seconds equals 12_000_000 cycles?
Is the CNT constant signed? If it is, would I have to write || CNT or write || variable := CNT ?
Part of this is that I should have a better understanding of this constant and also is that I'm trying to deal with roll over and if I convert it to milliseconds, I can go from 53.68 seconds to 49.71 days with a 2^32 register.
I'm a little confused as to why Parallax didn't make only the system CNT at 64 bit register? If my math was right, we'd go from 53 seconds to 7300 years?
Thanks for reading!

Comments
CNT is unsigned and you can use it for any timing up to about 50 seconds with an 80MHz system clock.
Typically, you save the current value of CNT at the beginning of a timing interval, then compute (CNT - savedCNT) to get the elapsed time in system clocks. You use CLKFREQ as a reference for computing other time units. For example, to get the elapsed time in milliseconds, you'd compute ( (CNT - savedCNT) / (CLKFREQ / 1000) )
I made an object for reading a tachometer and had difficult time with CNT.
Thanks for the more in depth explanation.
My code is still a little flakey, I will post it when it smooth.
Charlie
Simply calculating (CNT - savedCNT) / (CLKFREQ / 1000) leads to negative values after 26 seconds
if the negative sign would be eliminated by the "||"-operator I would get values that DECREASE instead of INCREASE.
SPIN treats longs as SIGNED longs. There must be a possability to get not only 26.000 milliseconds as result but 53.000 milliseconds
So how is the math done in two's complement to get the full 53-second range that the counter offers?
best regards
Stefan
But if the difference is higher then $7FFF_FFFF Spin sees the number as negativ that's why it works only up to 26 seconds with Spin.
With Assembly you can also do unsigned math.
A solution with Spin is this:
milliseconds := (CNT - savedCNT) >> 1 / (CLKFREQ/500)
The highest bit (the sign bit) is forced to zero by shifting all bits to the right. The resolution is halfed but that really not matters here.
Andy
@Stefan
I get negative values as well, somehow the math still works on my output calculations though
@BluHair
What kind of car is this?
Here is my code for others trying to do this, it includes error checking where it dumps values that have too great of a change, or are too far out of range.
Sorry if this breaks forum etiquette as far as attaching code, for some reason I cannot upload .zip files today to the forum?
You may need to change the math formula at the bottom, depending on how your ECU outputs to your tachometer.
CON _clkmode=xtal1+pll16x _xinfreq=5_000_000 '#########ConstantsforRPMs c_EngineRunningPin=1'MAKESURETHISPINISAWAYFROMINTERFERENCEORAPWMPIN(maybegroundedwithapulldownresistor) c_IgnitionOnPin=2 c_StarterPin=25 c_WaitingforRPM c_Starterturningover c_DetectFirstRPMPulse c_Default OBJ Debug:'FullDuplexSerial''Ifyoudon'tusethis,youshoulditmakeslifemucheasier,it'slocatedinParallax'sattachments VAR '###################VariablesforRPMs longtimeoflastpulse longpulsecount longRPMPulseonerisingedge longRPMPulsetworisingedge longRPMPulseonetrailingedge longRPMPulsetwotrailingtime longrpmtime longrpms longrpmhighlow longrpmcount longcaroff longstarterturningovertime longRPMPulseonerisingtime longRPMPulsetworisingtime longrpmpulsehigh longrpmpluselow longRPMPulseonetrailingtime longrpmpulselow longrpmcalculatedtime longrpm_stack[20] longRPMDebugupdatetime longrpmcalculatedtimeprevious longrpmcalculatedtimeerror PUBMain Debug.Start(31,30,0,115200)'startcogwithserialdriver waitcnt((clkfreq*3)+cnt)'makeslifeeasierwhenstartingtodebugwitha2secondpausetogettheserialvieweractiveandreceiving Debug.Tx(13) Debug.Tx(13) Debug.Tx(13) Debug.Str(string('*/\*/\*/\*/\*/\*/\*/\*START*/\*/\*/\*/\*/\*/\*/\*Abouttoentermainpub'))'fordebugpurposes Debug.Tx(13) Debug.Tx(13) Debug.Tx(13) '#######Defineinputsandoutputshere dira[c_EngineRunningPin]:=0'pinisaninput dira[c_IgnitionOnPin]:=0'pinisaninput ina[c_EngineRunningPin]:=1'pinlooksforahighvalue ina[c_IgnitionOnPin]:=1'pinlooksforahighvalue dira[c_StarterPin]:=1'temporarilyhavepinsettoanoutput '#######Callallcogshere Cognew(Measure_RPM,@RPM_stack) Cognew(Debug_Output(@Debug_Stack2,@Debug_Stack3,@Debug_Stack4),@Debug_Stack1) Debugupdatetime:=0 repeat 'loopsfromhere If(Debugupdatetime+clkfreq)=<cntORDebugupdatetime==0 '#######Calldebugvalueshere 'Debug_Output(p_Stringbeforevariable,p_variable,p_IfNewLine) 'Debug_Output(string('='),ina[],true) Debug.Tx(13) Debug.Str(string('***********')) Debug_Output(string('Keyison?='),ina[c_IgnitionOnPin],false) Debug_Output(string('Engineison?='),ina[c_EngineRunningPin],false) Debug_Output(string('Starterison?='),ina[c_StarterPin],false) Debug_Output(string('TrunkPulseisseen='),ina[c_PulseInputPin],true) Debug_Output(string('RPMValueis='),rpms,false) Debug_Output(string('CaseRPMcountisincasenumber='),rpmcount,false) {Debug_Output(string('='),ina[],true) } Debug.Str(string('***********')) Debug.Tx(13) Debug.Tx(13) Debug.Str(string('Debugupdatetimeis=')) Debug.Dec(Debugupdatetime) Debug.Tx(13) Debugupdatetime:=cnt PUBMeasure_RPM dira[c_EngineRunningPin]:=0'pinisaninput dira[c_IgnitionOnPin]:=0'pinisaninput ina[c_EngineRunningPin]:=1'pinlooksforahighvalue ina[c_IgnitionOnPin]:=1'pinlooksforahighvalue rpmcount:=c_WaitingforRPM RPMPulseonerisingedge:=false repeat'loopsfromhere caserpmcount'casename c_WaitingforRPM:'1stcase Ifina[c_IgnitionOnPin]==1 Debug.Str(string('Ignitionison,waitingforRPMpulse')) Debug.Tx(13) WaitPeq(0,|<c_EngineRunningPin,0)'waitforrpmpinlow Debug.Str(string('Lowdetected')) Debug.Tx(13) WaitPeq(|<c_EngineRunningPin,|<c_EngineRunningPin,0)'waitforrpmpinhigh Debug.Str(string('Highdetected')) Debug.Tx(13) rpmhighlow:=cnt'setavariabletocntafterthefirsthighlow rpms:=0 Debug.Str(string('High/Lowdetected')) Debug.Tx(13) rpmcount:=c_Starterturningover'gotonextcasescenario Else waitcnt((clkfreq/2)+cnt)'waitfor1/10thofasecond Debug.Str(string('Ignitionisoff,waitingforRPMPulse')) Debug.Tx(13) rpms:=0 RPMPulseonerisingedge:=false'initializethisvaluetofalse RPMPulseonetrailingedge:=false'initializethisvaluetofalse rpmcount:=c_WaitingforRPM'loopbacktothiscasestatementuntiltheignitionturnson c_Starterturningover:'2ndcaseusethisifyoufeedthestartersolenoidwireintothepropasareference Ifc_StarterPin==1'ifkeyisinstartposition rpms:=0 WaitPeq(|<c_StarterPin,|<c_StarterPin,0)'waitforkeynottobeinstartposition Debug.Str(string('Waiting2secondsafterstartersignalgenerated')) Debug.Tx(13) Waitcnt((clkfreq*2)+cnt) starterturningovertime:=cnt Else rpmcount:=c_DetectFirstRPMPulse'nostartersignal,lookforfirstrpmpulse c_DetectFirstRPMPulse:'3rdcase Ifc_IgnitionOnPin==0'ifignitionpinisoff,gobacktowaitingforrpm Debug.Str(string('Ignitionisoff,goingbackto*WaitingforRPM*')) Debug.Tx(13) rpmcount:=c_WaitingforRPM Elseifc_StarterPin==1'gobacktostartercrankingovercase,ifstarterpinishigh rpmcount:=c_Starterturningover Debug.Str(string('Goingbacktoc_Starterturningover')) Debug.Tx(13) Else Ifina[c_EngineRunningPin]==1 rpmpulsehigh:=||cnt WaitPeq(0,|<c_EngineRunningPin,0)'waitforpinlow Ifina[c_EngineRunningPin]==0 rpmpulselow:=||cnt WaitPeq(|<c_EngineRunningPin,|<c_EngineRunningPin,0)'waitforpinhigh RPMPulseonerisingtime:=cnt WaitPeq(0,|<c_EngineRunningPin,0)'waitforpinlow WaitPeq(|<c_EngineRunningPin,|<c_EngineRunningPin,0)'waitforpinhigh RPMPulsetworisingtime:=cnt rpmcalculatedtime:=RPMPulsetworisingtime-RPMPulseonerisingtime rpmcalculatedtimeerror:=false Ifrpmcalculatedtime=>6_015_037ORrpmcalculatedtime=<240_000 rpmcalculatedtimeerror:=true Debug.Str(string('RPMcalculatedERRORvaluetooloworhigh,settingerror=true')) Debug.Tx(13) ElseIfrpmcalculatedtime=<rpmcalculatedtimeprevious-700_000ORrpmcalculatedtime=>rpmcalculatedtimeprevious+700_000 rpmcalculatedtimeerror:=true Debug.Str(string('RPMcalculatedERRORdeltachangetoogreat,settingerror=true')) Debug.Tx(13) rpmcalculatedtimeprevious:=rpmcalculatedtime Ifrpmcalculatedtimeerror==true rpms:=0 Debug.Str(string('RPMcalculatedERRORrpmcalculatedtimeerror=1,dumpingvalues')) Debug.Tx(13) Else rpms:=(((clkfreq/rpmcalculatedtime)*60)/2)'thereare2pulsesperrevolution,ifyourECUdoesthisdifferentlyyou'llhavetochangeyourmathhere c_Default: Debug.Str(string('Inthedefaultstatement,goingbacktowaitingforRPM')) Debug.Tx(13) waitcnt((clkfreq/4)+cnt) rpmcount:=c_WaitingforRPMJon Williams has a nice object in the exchange that works flawlessly if you are simply measuring the output waveform from the PCM:
obex.parallax.com/objects/479/
Good Luck.
Post Edited (AJM) : 8/2/2010 7:46:50 PM GMT
I've had mixed results with using the || operator as well.
Does anyone have a full proof way of doing this?
This crude but you get the idea, I just tested this and get 2 sets of 0 - 2147483647 back to back. This may need to be cleaned up on the last digits.
VAR Long NewCnt
PUB GetCNT
...If CNT < 0
.....NewCNT := 2,147,483,647 - (||CNT + 1)
.....Return NewCNT
...elseIf CNT => 0
.....Return CNT
If you just need a time stamp every 52 seconds, you could scan for a certain CNT value in a loop, when the loop sees a value withing the window, then do something. In a SPIN loop you may miss a specific CNT number, so if the timing requirements can handle a little tolerance per iteration (will not accumulate an error), then you can check for a small window of time in CNT to be sure not to miss it:
If CNT > 1 AND CNT < 5000
do something
repeat tmp := cnt dosomethng Pre_time_Elapsed = Cnt - Tmp If Pre_Time_Elapsed < 0 NEXT else Real_Time_Elapsed = Pre_Time_ElapsedAs you can see in the example above, all I do is test to see if the calculated time elapsed is <0 and if so, then just skip this measurement cycle. You can optimize the way I have coded this to reduce lines. I purposely expanded it a little to make it easier to read.
this will only work if
1. The worst case lenght of a measurement cycle is less than 50 seconds or so (Cnt Rap time)
2. You can afford to allow the time elapsed measure to skip an update every 50 seconds (Cnt Rap Time)
The prop will send a binary number to the BCD, and turn on ONE of the LED's (via the anode) for 1ms, then it will cycle to the next LED, send a different binary number to the BCD and turn that LED on for 1ms and so on ... This way I use 10 pins for my 6 LED's instead of 24.
When spin goes negative, it hoses the whole operation. One of the lines of code I tried but couldn't use because of the signed stuff is
For example, let's say you start timing with cnt at $FFFF_FFFE and end at $0000_0002. In signed math that's simple enough; $FFFF_FFFE is -2 so 2 - (-2) = 4. But with unsigned math it's 2 - 4294967294 or -4294967292. That's not what you want! But that's a signed number and you're using unsigned math; rolling has the effect of adding (or subtracting in the other direction) $1_0000_0000 to the result to get it within the limits, so -4294967292 + 4294967296 = 4. Same result!
CON _clkmode = xtal1 + pll16x _xinfreq = 5_000_000 OBJ Debug : "FullDuplexSerial" VAR long c_unsignedcnt PUB Main Debug.Start(31,30,0,115200) 'start cog with serial driver waitcnt((clkfreq * 3) + cnt) ' makes life easier when starting to debug with a 3 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) repeat if cnt > 0 c_unsignedcnt := cnt Debug.Str(String("if ")) Debug.Tx(13) elseif cnt < 0 c_unsignedcnt := (2_147_483_648 + (2_147_483_648 + cnt)) Debug.Str(String("elseif ")) Debug.Tx(13) Debug.Tx(13) Debug.Str(String("unsigned count is ")) Debug.Dec(c_unsignedcnt) Debug.Tx(13) Debug.Dec(||cnt) Debug.Tx(13)I don't understand the asm to be perfectly honest, but I can tell you that the complement ideal does not work when I write code such as
If half of cnts cycle is a negative number, this repeat loop will not run for ~26 seconds.
So either there has to be a better way to calculate time, or a better way for me to write my loops, both of which I'm not sure how to do.
This does go negative. You should first get an understanding of what these values really are before doing the math. Print out the value 2_147_483_648 alone and you will see that SPIN considers that number to be 0. So you are really saying in that formula:
0 + (0 + CNT) which goes negative just like it always did. I posted code above that perfectly cycles from 0 to 2_147_483_647, not sure why you are changing the formula when the one posted works.
2_147_483_647 is the maximum number allowed in signed numbers with SPIN. -2_147_483_648 is the lowest. If you write 2_147_483_648 then SPIN considers it 2_147_483_647 + 1.
There are 16 counters on the Prop that are amazing at doing 32 bit counting locked to the system clock. You should take a look at the App note for the counters, they may make things easier for you.
Thanks for replying, I tried your way but didn't have success, but I will try again.
I'm getting closer with the bitwise shift operator >>, but I will try your way first.
The fact that CNT goes - completely hoses the whole thing no matter what I seem to try.
Although that may work, it still does not work within my loops
repeat while (variable + clkfreq) => (cnt >> 1)
If I want the code to do something for a second, and then update
How do you write your loops to take advantage of your newcnt?
Yes, that's pushing past the rollover without recovering properly if cnt has rolled over. The way you do it is this:
cnt - snapshotofcnt > clkfreq
That will work.
it is still not clear to me, what you want to do.
The negative cnt values are only a problem if you try to measure time intervals longer then 27 seconds (up to max. 54 seconds).
But from what you describe, I think you will just do a 1ms delay.
For that exists the waitcnt command.
So for measuring a time interval:
and for doing a 1ms delay:
Andy