Shop OBEX P1 Docs P2 Docs Learn Events
I need a Spin counter expert to examine some code for me PLEASE :) — Parallax Forums

I need a Spin counter expert to examine some code for me PLEASE :)

idbruceidbruce Posts: 6,197
edited 2011-01-08 19:00 in Propeller 1
Hello Expert

I have a stepper driver object within the object exchange, and I created it while having very little knowledge of clock speeds or counters, and this is still the case. Regardless of my lack of knowledge in these areas, the driver works really well to a point. I have been pushing the software, stepper motors, and the electronics to find it's breaking point and I finally reached it.

Now I wonder why it is breaking.

Am I exceeding the limit of Spin counters with my parameters and code?

Or is it due to a lack of sufficient voltage in the motor coils, which happens to be 50VDC? I am not looking for a reply to this question.

The following parameter values are the last ones that are successful.
nMaxFreqDurHZ := 16_000 (If I go below this value the motors fail to run)
nMinFreqDurHZ := 9_000 (If I go below this value the motors fail to run)
nMinPulseWidth := 1_000_000
Here are some global variables that are used:
Long nTotalStepsDivByTwo
Long nStepsRemaining
Long nCurrentFrequency
Long nCounterTime
Long nStepPulseWidth
Long nTotalRamps
PUB G251DriveStepper(nMaxFreqDurHZ, nMinFreqDurHZ, nMinPulseWidth, nRampingFreq, nStepPin, nTotalSteps)
  nCounterTime := cnt
  nStepPulseWidth := clkfreq / nMinPulseWidth
  nCurrentFrequency := nMaxFreqDurHZ
  nTotalStepsDivByTwo := nTotalSteps / 2
  nStepsRemaining := nTotalSteps
  ctra[30..26] := %00100 ' Configure Counter A to NCO
  ctra[5..0] := nStepPin
  frqa := 1
  dira[nStepPin]~~
 
  repeat while nStepsRemaining > 0    
    {Ramp Up}
    if nCurrentFrequency > nMinFreqDurHZ and nStepsRemaining > nTotalStepsDivByTwo
      nCurrentFrequency := nCurrentFrequency - nRampingFreq      
      nTotalRamps := nTotalSteps - nStepsRemaining
    {Ramp Down}
    if nTotalRamps => nStepsRemaining and nCurrentFrequency < nMaxFreqDurHZ
      nCurrentFrequency := nCurrentFrequency + nRampingFreq
      nTotalRamps--
    nCounterTime += nCurrentFrequency
    phsa := -nStepPulseWidth
    waitcnt(nCounterTime)
 
    nStepsRemaining--

Any effort you may put into deciphering this code and giving me a response will be GREATLY appreciated.

I have the need for speed.

Bruce
«13456

Comments

  • ElectricAyeElectricAye Posts: 4,561
    edited 2011-01-01 13:47
    Bruce,

    I'm in no state of mind to read code today - happy new year, ugg... but I wanted to at least make sure you have already seen the application note on counters in case that might help you.

    Go to this page and look for appnote, AN001
    http://www.parallax.com/tabid/832/Default.aspx

    How many rpms is your motor maxing out on? Is it possible it's simply hitting its no load limit? I tried to find a torque-speed curve for the Applied Motion motors which I think you're using (true?) but their website seems bereft of such data.
  • StefanL38StefanL38 Posts: 2,292
    edited 2011-01-01 14:07
    For driving a steppermotor you have to create a certain amount of pulses.

    In your code you have to do "babysitting" of the counter. There are some applications where you can set and forget the counter.
    This is not the case with steppermotors as you have to create a certain amount of step-pulses.

    So then it can be done much easier without the counters

    A simple-repeat-loop that toggles the step-pulse-IO-pin and two waitcnt-command and you are done

    To get further help you should specifiy the maximum-frequency you want to create.

    You should write if it is important to count every single step or if it doesn't matter to stop a couple hundred steps earlier or later.

    best regards

    Stefan
  • idbruceidbruce Posts: 6,197
    edited 2011-01-01 14:29
    StephanL38

    Thanks for the response, however, I noticed that you did not answer the question.
    Am I exceeding the limit of Spin counters with my parameters and code?



    In answer to you questions:
    1. Step counting is crucial for my application
    2. For a G251 Gecko Drive, the minimum step pulse width is 1us
    3. For a G251 Gecko Drive, the advertised step pulse rate is 0Hz - 300kHz
    I don't understand what you mean by me needing to babysit my counter. I pass the parameters and it works like a charm. That code is a good driver. I just want more speed. I am no expert by any means, but I have been told that counter driven PWM will blow the doors off standard Spin waitcnts like you suggest.

    As I previously mentioned, the code works and it works well. It can be fully tailored to suit ones particular needs by altering the parameters. Test and you will see.

    As a final note, if you look at the code, you will see that you need to scroll to see the last parameter which indicates the number of required step pulses.

    ## Edited the step pin and required pulses are fully visible as the last two parameters

    Bruce
  • jazzedjazzed Posts: 11,803
    edited 2011-01-01 15:47
    idbruce wrote: »
    Am I exceeding the limit of Spin counters with my parameters and code?
    There are two counters per cog A & B. Your example only uses A (CTRA, FRQA, PHSA, etc...).
  • idbruceidbruce Posts: 6,197
    edited 2011-01-01 15:48
    Jazzed

    I agree

    Bruce
  • idbruceidbruce Posts: 6,197
    edited 2011-01-01 15:52
    Okay guys

    I will make it easier for you. Is this code to fast for a Spin counter?
    PUB Easier2Understand | nCounter, nPulseWidth 
     
      nCounter := cnt
      nPulseWidth := clkfreq / 1_000_000
      ctra[30..26] := %00100 ' Configure Counter A to NCO
      ctra[5..0] := 22
      frqa := 1
      dira[22]~~
     
      repeat 10    
        nCounter += 8_000
        phsa := -nPulseWidth
        waitcnt(nCounter)
    

    :) Bruce
  • Cluso99Cluso99 Posts: 18,069
    edited 2011-01-01 15:57
    As a way to check your spin code for speed, insert statements to copy the cnt value at certain points in the code. If you place these into hub, another cog can examine them or you could use the serial object to display these values to the pc. If you have a pin to spare (and of course a cog) then you could use 1pin TV (see my obex object tv debug 1pin).
  • idbruceidbruce Posts: 6,197
    edited 2011-01-01 16:06
    Cluso99

    Thanks for responding. You and I posted right at the same time. Did you see the previous post? Do you have a link to that object?

    Bruce
  • JonnyMacJonnyMac Posts: 9,208
    edited 2011-01-01 16:49
    I just want more speed

    Keep in mind that Spin takes time to execute so there may come a point where your inner-loop calculations take more time that your loop wants to use; in this case you will miss the waitcnt target that sets the output frequency of the driver.
  • idbruceidbruce Posts: 6,197
    edited 2011-01-01 16:59
    Jon

    Now that was clear, concise, and made a lot of sense, and I guess that is what I was asking, only you summed it up. Cluso99 suggested monitoring the pulse utilizing an object that runs in another cog. I tried to find his object, but then remembered some source code in PEKitLabs Chapter 7 Counter Modules. I just looked at the code, and it appears it will be very easy to alter to do some speed tests, and find out if it is failing in the inner loop calculations as you so elequently stated. I truly believe that is the situation. You are a smart guy :)

    Bruce
  • jazzedjazzed Posts: 11,803
    edited 2011-01-01 17:46
    Normally one would get the CNT value in the loop and add the "future" CNT poll value just before the event starts. I.E.
    repeat
      future := 10000
      future += CNT
      ' do something that takes less than future*100000/(clkfreq/10000) ns OR future*12.5ns at 80MHz
      doSomeMethod
      ' wait for excess time to pass
      waitcnt(future)
    

    A typical and concise statement just for waiting 1ms is waitcnt(clfreq/1000+cnt).
    This is obviously a little different from your case, but the utility of it is often overlooked by many.

    Happy New Year
  • agfaagfa Posts: 295
    edited 2011-01-01 17:55
    Bruce,

    I tried both of your samples using a scope. Your first sample actually functioned with lower values than you noted. Your 2nd sample also functioned, I had to increase the repeat value to see it though. Sounds like your pushing your hardware beyond their limits.

    agfa
  • idbruceidbruce Posts: 6,197
    edited 2011-01-01 17:56
    Jazzed
    Normally one would get the CNT value in the loop and add the "future" CNT poll value just before the event starts. I.E

    Not according to PEKitLabs-v1.2 :smile:

    Bruce
  • jazzedjazzed Posts: 11,803
    edited 2011-01-01 18:03
    PEKitLabs is merely an introduction. You will be able to discern better practices with more experience.
  • idbruceidbruce Posts: 6,197
    edited 2011-01-01 18:09
    agfa

    WOW! That is just too cool! I truly appreciate you taking the time test the code on your scope. That is nice to know. I am just curious how far you pushed the first piece of code. Okay, so now it is determined that the inner loop is not slowing it down in my case, but I am certain that it will reach a breaking point as JonnyMac so nicely stated. I guess I will have to live with the results and go forward. I am assuming at this point that it is due to insufficient voltage for more speed. In the future, I will have to get a larger power supply. I truly thought that 16A 50VDC would be enough for my needs, but I was wrong :frown:

    Thanks Again agfa

    Bruce
  • agfaagfa Posts: 295
    edited 2011-01-01 18:14
    I only tested in steps of 1000

    nMaxFreqDurHZ down to 13,000
    & nMinFreqDurHZ to 7000

    agfa
  • idbruceidbruce Posts: 6,197
    edited 2011-01-01 18:19
    agfa

    Once again thanks for responding back, like I said just way too cool. 7000 and 13000, at least I know those numbers are good. Actually the machines move fairly fast already, I was just hoping for better production.

    Thanks

    Bruce
  • ElectricAyeElectricAye Posts: 4,561
    edited 2011-01-01 20:50
    idbruce wrote: »
    ... I am assuming at this point that it is due to insufficient voltage for more speed. In the future, I will have to get a larger power supply. I truly thought that 16A 50VDC would be enough for my needs, but I was wrong ....

    Before getting a different power supply, I would check how your stepper motors are reacting to such high speeds. It seems to me that you are more likely encountering a speed limit caused by the induction of the motor. The coils can charge and discharge only so fast. Beyond that, you can whip her all you want, but the donkey she no go no more.
  • idbruceidbruce Posts: 6,197
    edited 2011-01-01 20:59
    ElectricAye

    I have some torque speed curves from Applied Motion around here somewhere. I think they were achieving around 10 rps with about 50 volts. The G251, has 10 uSteps per full step. Like I said, I am not that good with counters, so I am really uncertain how many pulses I am sending per second. If I were to compare it to Applied Motions torque speed curves, to obtain the 10 revs per second I would have to actually be sending 20,000 pulses per second. I do not know exactly how fast they are turning, but it is pretty fast.

    Bruce
  • StefanL38StefanL38 Posts: 2,292
    edited 2011-01-02 00:01
    Hi Bruce,

    the specs of your steper-card says MINIMUM-length of step-pulses 1 microsecond
    to make it work reliably I would increase the pulse-length.

    Your way to use a counter in NCO-mode means you set the counter and do some changes to the frequency but the counter is free running.
    Your code has no EXACTLY control about how many step-pulses were created.

    So if you want to control the amount of steps EXACTLY you have to "babysit" the counter
    how many pulses where created. Therefore the speed of your loop has to be faster
    than the pulse-frequency. And if this is the case you can do it without the counters.

    I measured the frequency with a scope and it is 8-9 kHz.

    I include a code-example where an EXACT amount of step-pulses is created at a maximum-frequency of 19.5 kHz without using the counters.

    Steppermotors loose torque with increasing rpm. And it will drop down to ZERO if the speed is just high enough.

    So if you need really high rpms I suggest that you write what you want to to in the end.

    I have seen it so many times. If it is clear what you want to do in the end often other solutions that work much better can be found.
    CON
      _clkmode = xtal1 + pll16x 
      _xinfreq = 5_000_000      
    
      StepX_Pin_no   = 1
    
      RampSteps = 10
    
      DelayMultiplier = 1200 'start with a value of 20 max is around 1200 = 19,5kHz
    
      SpeedSteps      = 100_000
    
      DelayMaxIdx     = 150 'amount of steps to ramp up/down
    
    
    VAR
      long Delay[DelayMaxIdx]  'array that contains the waitcnt-values for ramping
      long DelayIdx
    
         
    OBJ
      Debug : "FullDuplexSerial"
    
      
    PUB Main
      Debug.Start(31,30,0,115200)
      Debug.Str(string("Debug.Start(30,31,0,115200) done",13))
    
      dira[StepX_Pin_no] := 1
    
      'set delay-values for ramp
      repeat DelayIdx from 0 to DelayMaxIdx - 1
      
        Delay[DelayIdx] := ClkFreq / ( (DelayIdx + 1) * DelayMultiplier + 500)    
    
        Debug.Str(string("Delay[DelayIdx]="))
        Debug.Dec(Delay[DelayIdx])
        Debug.Tx(13)
        Debug.Tx(13)
        waitcnt(ClkFreq / 100 + cnt)
    
      repeat
        'rampup
        repeat DelayIdx from 0 to DelayMaxIdx - 1
          repeat RampSteps 'repeat steppulses 
            outa[StepX_Pin_no]   := 0
            waitcnt(Delay[DelayIdx] + cnt)
            outa[StepX_Pin_no]   := 1
            waitcnt(Delay[DelayIdx] + cnt)
    
        'hold maximum speed    
        repeat SpeedSteps
          outa[StepX_Pin_no]   := 0
          waitcnt(Delay[DelayMaxIdx - 1] + cnt)
          outa[StepX_Pin_no]   := 1
          waitcnt(Delay[DelayMaxIdx - 1] + cnt)
    
        'rampdown  
        repeat DelayIdx from DelayMaxIdx - 1 to 0  
          repeat RampSteps
            outa[StepX_Pin_no]   := 0
            waitcnt(Delay[DelayIdx] + cnt)
            outa[StepX_Pin_no]   := 1
            waitcnt(Delay[DelayIdx] + cnt)
    

    best regards

    Stefan
  • MagIO2MagIO2 Posts: 2,243
    edited 2011-01-02 03:25
    Just because I read "scope" so often ... "I have no scope" ... "I measured with the scope" ... why don't you simply use another COG/Counter to read the output of the driver? Then you'd exactly know the frequency and you could even check the pulse-width.
  • StefanL38StefanL38 Posts: 2,292
    edited 2011-01-02 04:47
    Hi MagIO2,

    I did with the scope because it was easier for me to add five lines of code switch on the scope and connect the probe.
    (takes a minute)

    I estimate if I do it myself it will take 4-5 hours to implement it. (maybe I'm wrong) but this estimation keeps me away from
    writing the code myself.

    If you could provide a democode that shows how to implement this code into another I would use it.
    Do you have the code handy and would not mind taking 10 minutes to add comments?

    best regards

    Stefan
  • JonnyMacJonnyMac Posts: 9,208
    edited 2011-01-02 06:42
    Another valid reason to use a 'scope is that you're pulling yourself out of the test equation. If we're honest, every one of us has made a mistake with test code that has cost us time.
  • idbruceidbruce Posts: 6,197
    edited 2011-01-02 06:57
    StefanL38

    There is an example in PEKitLab1.2 that includes a test for two pins, however it could be altered for just one. Here are the necessary files.

    Bruce
  • ElectricAyeElectricAye Posts: 4,561
    edited 2011-01-02 07:51
    StefanL38 wrote: »
    Hi MagIO2,

    I did with the scope because it was easier for me to add five lines of code switch on the scope and connect the probe....


    Hi Stefan,
    I think MagicIO2's comment about scopes was aimed at Bruce, not you. Based on Bruce's previous comments, it sounds like he doesn't really know what his output has been maybe because he doesn't have a scope, so I think MagicIO2 was suggesting Bruce use the Propeller counters for getting some idea of rpm, etc. It's awesome that you guys have gone through all the trouble to help Bruce with your scopes, etc.

    In addition to JonnyMac's comment about the importance of using a scope for things like this, Stefan's point about putting a load on the motor is also excellent: Bruce can set up his system so it runs beautifully in the "no load" condition, but once a load is put on the motor, he might not really get what he expects.
  • idbruceidbruce Posts: 6,197
    edited 2011-01-02 08:06
    ElectricAye
    I think MagicIO2's comment about scopes was aimed at Bruce, not you. Based on Bruce's previous comments, it sounds like he doesn't really know what his output has been maybe because he doesn't have a scope,
    This is correct, I do not have a scope, and I am writing test code now for testing everything using counters.
    It's awesome that you guys have gone through all the trouble to help Bruce with your scopes, etc.
    I could not agree more, thanks very much guys.
    Stefan's point about putting a load on the motor is also excellent: Bruce can set up his system so it runs beautifully in the "no load" condition, but once a load is put on the motor, he might not really get what he expects.
    Okay here is the purpose of all this. I am setting up a couple of CNC machines to produce and package a product. Irregardless of the load, I want to know the maximum obtainable speed for my steppers that can achieved with the hardware that I possess. If the maximum obtainable speed does not push the load, then I can reduce the speed until it does push the load, which would result in maximum speed for pushing the load. When I have a achieved this, then production will be at its fullest capability.

    I will be adding test code here very shortly for the G251DriveStepper object :)

    Bruce
  • ElectricAyeElectricAye Posts: 4,561
    edited 2011-01-02 08:58
    idbruce wrote: »
    ... If the maximum obtainable speed does not push the load, then I can reduce the speed until it does push the load, which would result in maximum speed for pushing the load. When I have a achieved this, then production will be at its fullest capability....


    Bruce,
    just be aware that if you're operating on the hairy edge of what a stepper motor can do, then it might skip steps if it encounters a transient load for some reason, and if it skips steps, then it won't be where you think it is. This is why some people add encoders to steppers, just to be safe. A cheaper but perhaps more risky method to keep track of the stepper is to have it periodically "re-zero" itself by having it move its "whatever-its-actuating" over to a homing switch or limit switch of some kind. Because loads can be unpredictable, your safe operational envelope might be difficult to discern without thorough testing.
  • LawsonLawson Posts: 870
    edited 2011-01-02 09:03
    jazzed wrote: »
    Normally one would get the CNT value in the loop and add the "future" CNT poll value just before the event starts. I.E.
    repeat
      future := 10000
      future += CNT
      ' do something that takes less than future*100000/(clkfreq/10000) ns OR future*12.5ns at 80MHz
      doSomeMethod
      ' wait for excess time to pass
      waitcnt(future)
    

    A typical and concise statement just for waiting 1ms is waitcnt(clfreq/1000+cnt).
    This is obviously a little different from your case, but the utility of it is often overlooked by many.

    Happy New Year

    Jazzed: The timing loop you give above will NOT execute in the specified 10000/80MHz time (or 125uS). It'll execute in something more like 10500/80MHz time (or 131uS) because the "repeat", "future := 10000" and about half of the "future += CNT" is outside of the precisely timed code block. Precise timing can be restored by measuring this overhead and compensating for it, but it's just easier to roll the overhead into the precisely timed code. This second technique is what IDbruce is using. My favorite way of doing this is summarized in the code snip-it below
    period := clkfreq/8000
    mark := CNT + period
    
    repeat
      waitcnt(mark)
      'do stuff that needs exceptionally precise timing
      mark += period
      'do stuff that takes less than 10000 clocks at 80Mhz
    

    Lawson
  • LawsonLawson Posts: 870
    edited 2011-01-02 09:21
    StefanL38 wrote: »
    *snip*
    Your way to use a counter in NCO-mode means you set the counter and do some changes to the frequency but the counter is free running.
    Your code has no EXACTLY control about how many step-pulses were created.

    So if you want to control the amount of steps EXACTLY you have to "babysit" the counter
    how many pulses where created. Therefore the speed of your loop has to be faster
    than the pulse-frequency. And if this is the case you can do it without the counters. *snip*

    I've also used this counter trick a lot. It lets me generate a precise pulse from 1 to ~2^30 clocks long with a single instruction while my code continues on doing other stuff. I agree that you do have to keep setting the counter (or stop it when you are done) but this is rarely a problem since the counter will take 2^31 clock cycles to roll over and set the output. (~25.8 seconds at 80MHz) Even then it will take the counter another 2^31 clock cycles to clear the output.

    With regards to speed, I've had Spin loops using WAITCNT running at up to 64KHz. BUT, this loop did almost nothing. I.e. like the code fragment below. So if you want to get up to the 300KHz maximum pulse rate of the Geko drives you'll need to use assembly. (though configuring one counter for edge counting and another as a 0-300KHz NCO might be made to work from Spin)
    ctra[30..26] := %00100 ' Configure Counter A to NCO
    ctra[5..0] := nStepPin
    frqa := 1
    dira[nStepPin]~~
    pulse_width := -300
    period = clkfreq/64000
    mark := cnt + period
    
    repeat
      waitcnt(mark)
      phsa := pulse_width
      mark += period
    

    Lawson
  • idbruceidbruce Posts: 6,197
    edited 2011-01-02 09:34
    Lawson

    From your code, are you saying that spin loops are faster than code that uses a counter? This is how I have interpreted what you said.

    By the way, thanks for your input, I have been reading it.

    Bruce
Sign In or Register to comment.