Shop OBEX P1 Docs P2 Docs Learn Events
Am I beyond the speed limits of spin? — Parallax Forums

Am I beyond the speed limits of spin?

turbosupraturbosupra Posts: 1,088
edited 2010-10-28 20:01 in Propeller 1
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?



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

  • T ChapT Chap Posts: 4,223
    edited 2010-10-26 22:07
    Is it possible you are stepping on top of the previous debug?
  • Mike GreenMike Green Posts: 23,101
    edited 2010-10-26 22:12
    You may need to do your frequency measurement in assembly language.

    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.
  • turbosupraturbosupra Posts: 1,088
    edited 2010-10-26 22:13
    Tchap,

    What do you mean?

    Usually when I see debug conflicts I will see broken or strange characters on the serial output debugger application.
  • T ChapT Chap Posts: 4,223
    edited 2010-10-26 22:19
    Try commenting out this line Debug.Str(string("RPMS in measureFrequency are "))
  • Cluso99Cluso99 Posts: 18,069
    edited 2010-10-27 00:18
    I would guess spin is 50 times slower than pasm.

    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.
  • Christof Eb.Christof Eb. Posts: 1,247
    edited 2010-10-27 03:32
    Hi,
    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
  • turbosupraturbosupra Posts: 1,088
    edited 2010-10-27 07:31
    Tchap ... same thing

    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?
  • T ChapT Chap Posts: 4,223
    edited 2010-10-27 08:12
    What is the length of a pulse being generated? Have you looked at the pulses on a scope at the input when the error is occurring? Before chasing down the software, make sure you are able to see a nice pulse(definable end points) at the higher speeds on the input.
  • turbosupraturbosupra Posts: 1,088
    edited 2010-10-27 08:57
    Hi TChap,

    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?
    ' Configure counter module.
    ctra[30..26] := %01000 ' Set mode to "POS detector"
    ctra[5..0] := 17 ' Set APIN to 17 (P17)
    frqa := 1 ' Increment phsa by 1 for each clock tick
    
    
    


    ' Configure counter module.
    ctra[30..26] := %01000 ' Set mode to "POS detector"
    ctra[5..0] := 17 ' Set APIN to 17 (P17)
    frqa := 1 ' Increment phsa by 1 for each clock tick
    
    
    
    ctra[30..26] := %01000 ' Set mode to "POS detector"
    ctra[5..0] := 14 ' Set APIN to 14 (P14)
    frqa := 1 ' Increment phsa by 1 for each clock tick
    

    T Chap wrote: »
    What is the length of a pulse being generated? Have you looked at the pulses on a scope at the input when the error is occurring? Before chasing down the software, make sure you are able to see a nice pulse(definable end points) at the higher speeds on the input.
  • Mike GreenMike Green Posts: 23,101
    edited 2010-10-27 09:47
    There are two sets of control registers for the two counters in each cog, CTRA/PHSA/FRQA and CTRB/PHSB/FRQB. A specific cog is running your Spin code and the counters in that cog are used whenever you reference these register names, just like each cog has its own I/O registers, DIRA/OUTA/INA.

    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.
  • turbosupraturbosupra Posts: 1,088
    edited 2010-10-27 11:40
    Hi Mike,

    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
    
    
  • turbosupraturbosupra Posts: 1,088
    edited 2010-10-27 11:58
    Maybe
      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 count
    
    
  • turbosupraturbosupra Posts: 1,088
    edited 2010-10-27 14:36
    Mike,

    I found a post of yours and changed the code a little
    pub measureFrequency2
    
      ctra := 0   ' stop any existing counter use
      phsa := 0
      frqa := 0   ' initialize the counter
      ctra[30..26] := %01000        ' Configure bits 30-26 to be binarily set to detect a > 1.65v 
      ctra[5..0] := 1               ' Configure bits 5-0 to be binarily set to pin 1
      frqa := 1                     ' So that phasa will get 1 added to it each time the pin goes high
      return phsa   ' return the count 
    
    
  • Mike GreenMike Green Posts: 23,101
    edited 2010-10-27 15:28
    I don't think that will work for you. That code initializes the counter to count positive-going edges, but you don't show the code that comes back later and retrieves the count after a known period of time, then calculates the frequency from the number of pulses divided by the elapsed time.

    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?
  • turbosupraturbosupra Posts: 1,088
    edited 2010-10-27 19:40
    Hi Mike,

    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.


    Mike Green wrote: »
    I don't think that will work for you. That code initializes the counter to count positive-going edges, but you don't show the code that comes back later and retrieves the count after a known period of time, then calculates the frequency from the number of pulses divided by the elapsed time.

    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?
  • Mike GreenMike Green Posts: 23,101
    edited 2010-10-27 20:55
    Here's a partial example of what you'd need to launch a cog to count pulses repeatedly, calculate the rate (pulses per unit time), and update a global variable with the result. This assumes that you have some kind of debug object that displays text on a PC or some other display. Because the result is pulses per unit time, it's most likely to be a fraction so it's scaled so it can be represented in an integer.
    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 ticks
    
  • AribaAriba Posts: 2,690
    edited 2010-10-27 21:41
    Here is another solution, which measures the high time of one periode and writes this to a global variable.
    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
    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 counter
    

    Andy
  • turbosupraturbosupra Posts: 1,088
    edited 2010-10-27 22:11
    Hi Mike,

    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?
  • turbosupraturbosupra Posts: 1,088
    edited 2010-10-27 22:12
    Thanks Andy, I will experiment with this tomorrow evening also!


    Ariba wrote: »
    Here is another solution, which measures the high time of one periode and writes this to a global variable.
    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
    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 counter
    

    Andy
  • turbosupraturbosupra Posts: 1,088
    edited 2010-10-28 20:01
    Mike,

    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?


    Mike Green wrote: »
    Here's a partial example of what you'd need to launch a cog to count pulses repeatedly, calculate the rate (pulses per unit time), and update a global variable with the result. This assumes that you have some kind of debug object that displays text on a PC or some other display. Because the result is pulses per unit time, it's most likely to be a fraction so it's scaled so it can be represented in an integer.
    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 ticks
    
Sign In or Register to comment.