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

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

1246

Comments

  • idbruceidbruce Posts: 6,197
    edited 2011-01-05 05:31
    Alright Guys

    Here is another modified version of MagIO2's original code. This one still runs in a cog like he had it.

    Bruce
  • idbruceidbruce Posts: 6,197
    edited 2011-01-05 09:05
    Hello Guys And Gals

    I am unfamiliar with Spins CASE conditional command. Could someone please examine the attached file and left me know if I am going about this correctly.

    Thanks

    Bruce
  • JonnyMacJonnyMac Posts: 9,208
    edited 2011-01-05 09:12
    You obviously didn't try to compile that else you'd seen a massive fail. What is it that you're trying to accomplish? I suggest you start with a compound if-then structure and once that works, convert it to case.

    In general a case structure looks like this:
    [b]case[/b] [i]expression[/i]
        value:
          [i]do_this_code[/i]
          
        value1..value2:
          [i]do_that_code[/i]
          
        [b]other[/b]:
          [i]nothing_matched_so_do_this[/i]
    

    Of course, the code elements can also be other case structures -- though that gets a little unwieldy.

    Edit: After staring at your code for a few minutes it seems to be wanting to do this:
    case steps
        5_000..5_999:
          steps //= 100             ' steps bumped down to 0..99
        1_000..4_999:
          steps //= 1_000           ' steps bumped down to 0..999
    


    Tip: Use PST to run test code to check it -- better to test new code independently before incorporating it into an otherwise working program. My default programming template has the FDS object built in so I can do this easy and, believe me, I do it all the time!
  • idbruceidbruce Posts: 6,197
    edited 2011-01-05 09:52
    Hmmm...

    I always stayed away from case statements and used the if conditions in the past. I don't know what I was thinking, I seemed to have remembered it differently. I thought they could be hierarchically ordered. :) Thanks Jon. I apologize Jon, after making several attempts to get the motor going with your code, I finally settled in to MagIO2's version. But that does not mean that I do not appreciate your effort, because I most certainly do. If it wasn't for the two of you, I would have still been trying to figure it out a month from now.

    Thanks Jon
  • JonnyMacJonnyMac Posts: 9,208
    edited 2011-01-05 09:56
    This is not related to your case question, rather a suggestion about your method that creates pulses. At the moment your code is depending on specific global variables which means you can only use it once. What happens when you decide to add a second motor? As the code stands you have to add a second method.

    You can re-use that code by passing the addresses of the variables you want to use instead of referencing them in your program. Your call changes from
    cognew(drivesteppin(MTR), @stack)
    

    to:
    cognew(drivesteppin(MTR1, @hightime1, @cntoffset1), @stack1)
    

    (this assumes you'll have more than one motor, hence the "1" at the end of variable names.) Now you method looks like this:
    pub drivesteppin(pin, hcpntr, copntr) | sync
    
      ctra := (%00100 << 26) + pin                                  ' PWM/NCO mode
      frqa := 1
      dira[pin] := 1                                                ' make output
    
      sync := cnt                                                   ' create loop timing sync point
      repeat
        phsa := long[hcpntr]                                        ' set pulse duration
        waitcnt(sync += long[copntr])                               ' let cycle finish
    

    And when you add another motor you simply call the same method with a new set of parameters:
    cognew(drivesteppin(MTR2, @hightime2, @cntoffset2), @stack2)
    

    By reusing code like this you only have to make one update if you want to change the behavior of the drivesteppin method.


    Note: I don't care if you use my code or not -- <EGO> though I think you benefit from at least working through it because I tend write clean, flexible, reusable code (most of the code I write is for public consumption so it has to be).</EGO>. I happen to be working on a motor drive project myself so assisting you with your code helps me help myself.
  • idbruceidbruce Posts: 6,197
    edited 2011-01-05 10:05
    Jon

    Thanks for the advice. That thought did cross my mind early in the morning. I will change that because reusable code is a wonderful thing. So what do I do, create a different set of variables for everytime that I know I am going to create a new cog? If so, can I reuse these variables? So what is your motor project?

    Bruce
  • Tracy AllenTracy Allen Posts: 6,666
    edited 2011-01-05 10:46
    @ Kuroneko,

    Your blanking method that backs up from NEGX with frqb=1 is clearly better than my original that played off frqa against frqb=frqa/2N. Better simply because it allows a lax time of nearly 1/2 minute. The computation of the initial phsb is a little tricky. I think this should do it, given a target frequency Fx within the burst and a number Nx of pulses within the burst.
    phsb := phsb - 417 - (clkfreq / Fx * Nx) - (binNormal(clkfreq // Fx, clkfreq, 30) * 4 ** Nx)
    
    Not sure. I may be over-complicating. I'll try it and report back. How did you settle on the 417 cycle lag? I measured execution time of frqa := longVariable at exactly 400 clock cycles (5 microseconds).
  • JonnyMacJonnyMac Posts: 9,208
    edited 2011-01-05 11:30
    So what do I do, create a different set of variables for everytime that I know I am going to create a new cog?

    Yes, because you would, anyway. The difference is that you don't need a separate method for the new set of variables. Call the original with links to the new variables and it will get launched into its own cog (technically, another Spin interpreter is launched and it references the code your specify -- multiple Spin cogs can use the same code because the hub allows only one cog at at time to have access).
    If so, can I reuse these variables?

    No; you need one set of (global) variables per motor. If you want you could use an array for indexed access to each motor.
    So what is your motor project?

    Mine is a pan/tilt controller for movie cameras but the PWM code method is identical. In my case, though, I use a fixed frequency (20kHz so the motors don't "whine" and screw up the audio track, and I can control two motors at once in the cog [ctra and ctrb]) and vary the positive side of the pulse; this varies the speed of the DC gear motor (the board uses an H-Bridge).
  • Tracy AllenTracy Allen Posts: 6,666
    edited 2011-01-05 18:42
    Okay, here is followup on post 73 that incorporates Kuroneko's comments in post 77.

    The attached program uses PST to ask the user for a frequency in Hz and a number of pulses, and then generates the requested burst. This uses the counters as before and is in Spin only, but works from 1 Hz up to 20 MHz with as few or as many pulses as you choose. A monitor routine running in a second cog verifies the number of pulses. An auxiliary pin assists with the monitor and also allows you to visualize the framing pulse on an oscilloscope.
  • idbruceidbruce Posts: 6,197
    edited 2011-01-05 21:08
    Jon

    Thanks for the tips. Sorry it took so long to get back, but I definitely had to get some sleep. Cameras, h-bridges, and gear motors, sounds like a cool project to me. Many moons ago, Parallax was selling some ICON H-BRIDGE products from Solutions Cubed for the Basic Stamp. I bought that and all the accessories to go with it. I just haven't got around to messing with it yet. It is all still brand new, packaged and put away for a future project. Too many irons, I must build a larger fire.

    Bruce
  • idbruceidbruce Posts: 6,197
    edited 2011-01-06 13:59
    Hello Everyone

    Yep it's me again with another problem. :(

    I just discovered that my so called solution is not working too well. In the attached code below, the loops account for 20,000 steps, however StepCount registers 51,375 pulses. 'What is going on here? Even if I eliminate the waitcnts, I am still way over on step count and the steppers do not end up where they should be. Is the CycleOffset to narrow?

    MagIO2:
    With this it should be easy to find the right timing.

    Bruce
  • MagIO2MagIO2 Posts: 2,243
    edited 2011-01-06 14:57
    StepCount is telling you the truth. Your missunderstanding is: You think the number of loop iterations is equal to the number of steps, but that's not true.

    The thing is, that the DriveStepPin and the Ramping are not in sync. The ramping always uses 4_000 for waiting. The DriveStepPin waits for whatever time delta it finds in CycleOffset.
    So, when starting with starting speed 8_000 the DriveStepPin will wait 5 times longer than the ramp-loop. But later on, when CycleOffset is going below 4_000 the DriveStepPin will wait for a shorter while than the ramping-loop.

    That's why the number of loop cycles does not match with the number of steps. That's what I meant with "find the right timing".

    But as the propeller is fully deterministic, there is a formula with which the number of steps can be calculated or a formula with which the waittime between the ramps could be calculated for the given ramp-parameters. But to find those formulas is work that still has to be done.

    Let me know if you need some help there. (But not today ... my local time is midnight ;o)
  • idbruceidbruce Posts: 6,197
    edited 2011-01-06 15:10
    MagIO2:
    You think the number of loop iterations is equal to the number of steps, but that's not true.
    I disagree.

    The waitcnt was the wrong value, the loops were correct. Okay maybe I was wrong, but now I am right. :)


    'Ramp Up
    REPEAT CycleOffset FROM StartingSpeed TO RunningSpeed STEP RampMultiplier '6_000 Steps
    WAITCNT(Counter += CycleOffset)
    'Maintain High Speed
    REPEAT StepRemainders
    WAITCNT(Counter += CycleOffset) '8_000 Steps
    'Ramp Down
    REPEAT CycleOffset FROM RunningSpeed TO StartingSpeed STEP RampMultiplier '6_000 Steps
    WAITCNT(Counter += CycleOffset)


    Bruce
  • idbruceidbruce Posts: 6,197
    edited 2011-01-06 15:32
    MagIO2

    Let me rephrase, " I am almost right, it appears to be picking up some steps.

    Bruce
  • MagIO2MagIO2 Posts: 2,243
    edited 2011-01-06 15:36
    Ok ... now you changed the waitcnt, BUT:
    1. with this change the speed increase is no longer linear. The faster the stepper runs, the faster the speed will increase.
    2. the loops are still not synced correctly (as far as I can interpret your new snippets without seeing the whole change)

    To have the COGs in sync both COGs need to wait for the same waitcnt (lets call that timebase) before they start the loops and then each one should maintain it's own copy of timebase + delta*n to stay in sync.
  • idbruceidbruce Posts: 6,197
    edited 2011-01-06 15:39
    MagIO2
    Ok ... now you changed the waitcnt, BUT:
    1. with this change the speed increase is no longer linear. The faster the stepper runs, the faster the speed will increase.

    The steppers run pretty darn good with that waitcnt.

    They are close, but no cigar just yet, but I have a new idea.

    Bruce
  • MagIO2MagIO2 Posts: 2,243
    edited 2011-01-06 15:53
    I found some problems with your parametrized version of the loop:
      repeat
        PHSA := LONG[StepPulseWidthPntr]
        StepCount++
        WAITCNT(Counter += LONG[CycleOffsetPntr])
    

    It's again to slow for the 2_000. In terms of SPIN-bytecode the array access is like adding runtime, as it needs additional HUB-RAM access. So, I'd suggest to go back to the loop using fixed variables. Those variables are meant to be used only by the CogDriveStepPin anyway. So it makes no sense to use a pointer and the object still can be used for several steppers, as each instance of the object will have it's own copy of those variables.
  • idbruceidbruce Posts: 6,197
    edited 2011-01-06 16:26
    MagIO2

    Okay I will look into it. Here is how close I am now to sinking with actual steps

    1,000,000 steps
    StpCnt = 999827 or StpCnt = 1000141

    Bruce
  • idbruceidbruce Posts: 6,197
    edited 2011-01-06 23:09
    Hello Everyone


    The step count and pulses are now in sync. There are two remaining problems with this driver, however, besides these two problems, the driver still needs error checking, aborts, etc.... I know you are dying to know what the two main problems are, so here they are.
    1. There is two extra pulses and therefore two extra steps. I looked for the cause of the extra steps and pulses, but I could not find it. I assume I am picking up a step for both the top and bottom REPEAT's within the DriveStepPin method. It could easily be resolved by subtracting 2 from the Steps variable, however, I would like to understand the cause of it.
    2. The DriveStepPin and CogDriveStepPin run in sync with each other. Of course that is not a problem, it is a good thing. The problem is that sometimes the clock rolls around before these functions continue. I sure could use some advice on fixing this problem.
    • In the future, I will be adding the CoglessStepperDriver code to the same file. The file will contain conditions on which method to execute based upon the number of the steps. With the current values of the parameters in StartingSpeed and RunningSpeed, the parameter Steps would be required to be at least 34,000. The current number of steps is 1,000,000, you may want to change this when testing. Just remember to keep it above 34,000 and it takes a while to get started.
    • Check it out, you don't need a stepper motor, I have FullDuplexSerial all ready to go, IO output is on PIN 3.
    It is not finished yet, but a good stepper driver is on the way!

    Bruce
  • MagIO2MagIO2 Posts: 2,243
    edited 2011-01-07 01:30
      repeat
        PHSA := LONG[StepPulseWidthPntr]
        StepCnt++
        WAITCNT(CounterPntr += LONG[CycleOffsetPntr])
    

    The waitcnt is wrong. If you want to access the Counter it has to be

    LONG[ CounterPntr ]+=LONG[ CycleOffsetPntr ]

    But that again adds runtime to the loop AND only one COG should really update the Counter. If both update the counter chance is high to have Counter += 2*CycleOffset there in the end.
    You seem to ignore my last post from yesterday (loop is to slow for 2_000).

    I thought about the driver and I still believe that PASM would be the better choice. A nice piece of PASM code that's doing the ramp up/down including emergency brake would be more usefull because it would run in one COG. For the current driver you need 2 COGs which can't do anything else. If you want to drive 2 steppers you need 4 COGs ....
    So, this driver might be perfect for you learning how to drive a stepper and it's maybe enough for projects where you only have 1 stepper, but it's not good for more, I suppose.

    But if you like we could make the SPIN version run perfectly and then convert it to PASM?!
  • idbruceidbruce Posts: 6,197
    edited 2011-01-07 06:44
    MagIO2
    LONG[ CounterPntr ]+=LONG[ CycleOffsetPntr ]
    I initially tried that but it threw out an error. I will try it aain.

    The 2000 is not very critical, I just wanted my motors to run much faster than they were currently running, In fact, I had to bump it up to 3000 because it misses steps at 2000, probably due to the reasons you state. But the motors still "ZING" and really fly compared to what I had before this thread started. As I mentioned in the previous post, the problem is that it waits way too long to begin execution. What's up with that, and how can that be resolved?

    I was not ignoring what you said. My main goal up to this point was to sync the two methods. Since that has now been achieved, it is time for fine tuning it. There is some confusion however, Jon explained one way and you explained another. Considering my lack of experience in programming with Spin, I am truly uncertain which way to go. Both of you are the experts compared to me. I would love for both of you to examine the code, and determine what is the best way to go to turn this into something nice. It was originally your sample that got me this far, as far as I am concerned, you can doctor this code anyway you like to make it better.
    But if you like we could make the SPIN version run perfectly and then convert it to PASM?!

    That would be excellent. I would really like to figure out how to do this with one cog, or use one cog to monitor several other cogs, but I am not that smart like you guys. Currently, my programming for these CNC's is inline and can only commit to single tasking. However, when I work out all the bugs, I want to start making use of the Propellers 8 cogs to improve production efficiency. The main reason I like Spin is becaise I can read it, PASM is all greek to me. Normally, I only like to use code that I can easily understand and easily alter. 9 times out of 10, if I don't completely understand the code, I don't use it. This is why I use my overly descriptive symbol names, so that I remember, understand, and can easily alter the code. In this particular circumstance, I would probably make an exception to that rule in this case, because my main goal is to finish the machines, and start producing. If there was truly a good stepper driver in the OBEX that pulsed a step pin like this source code, I would definitely use it, irregardless of the language it was written in.

    I am sure that you and many others here at the forum could turn this into a real nice driver that has good value, however it turns out, I would still like to continue participating in that endeavor.

    Code Screams:
    Bend me, shape me, any way you want me.

    Bruce
  • MagIO2MagIO2 Posts: 2,243
    edited 2011-01-07 07:43
    I'm not sure whether we tell you different things. I guess you refere to the 'remove hardcodes and use parameters'-kind of suggestion. And now I tell you to change the loop using it's own variables again. That's not directly the opposite.

    Ok ... let's start from the beginning:
    What you do is prototyping. It's absolutely feasible to use hardcodes and have the code less flexible in this phase. Once the code is running you should think in objects. (Definitely some more experienced people already think in objects from the very beginning.) In your case I think at the time we convert it to PASM is fine to start thinking in objects. When you start to make the code flexible you have to think about how to do that, so that the benefit for the code using your object is maximised. In our case that means the code should be able to drive not only one stepper.
    So, you have to find out how to do that. That's why Jonny suggested to use pointers to the variables that control the stepper COG because with using the exactly same variables you could only drive one stepper. Of course this works, but it's not the way you do it when thinking in objects.

    When you start using the driver as an object, you can define ONE variable and each instance of this object WILL have it's own private version of this variable. So, working with pointers in the object world only makes sense if you want the user of the object keep control over such memory. Of course there are usecases for this as well, but as far as I see the stepper driver does not need that.

    Main.spin
    OBJ
      stepper1: "MyStepperDriver"
      stepper2: "MyStepperDriver"
    
    PUB
      stepper1.start( Here you'd give all the really needed parameters for setup for example the max speed, the min speed, the pin ... )
      stepper2.start( ... )
    
      stepper1.RupStepRdown( 10_000, additional parameters which might be usefull )
    
    This is an example how we could use the stepper-driver. RupStepRdown would be our current function Ramp Up Step and Ramp Down. Other usefull functions would maybe not Ramp down but slow down do some more steps or Ramp Up Free Run ....

    As you now see,from a usage point of view, you are not interested in the internals. And that's why there is no need to let the user of the object manage the internal variables and pass pointers. The function RupStepRdown belongs to the object as well as the stepper-loop.

    In a few hours I can spend some time with the driver and then I can show you more.
  • idbruceidbruce Posts: 6,197
    edited 2011-01-07 07:48
    MagIO2

    That all sounded and looked very good.

    Bruce
  • idbruceidbruce Posts: 6,197
    edited 2011-01-07 07:57
    MagIO2
    I just found a major issue, Counter is only initialized as zero.
    Bruce
  • idbruceidbruce Posts: 6,197
    edited 2011-01-07 09:01
    Here is an updated version with some problems fixed. It now includes a timer for doing time test comparisons. With a StartingSpeed of 20,000 and RunningSpeed of 2,000, the stepper driver is producing 8.65 Revolutions Per Second.

    Bruce
  • idbruceidbruce Posts: 6,197
    edited 2011-01-07 09:54
    MagIO2

    Within a duplicate file, I converted CycleOffset back to a global instead of passing it as a pointer. Upon testing, it took approximately a minute longer to execute 1,000,000 pulses, and additionally, it did not keep an accurate count. As far as the speed test goes, it did not make much sense, because your sample code appears to run faster than my version, but I could be wrong.

    Bruce
  • JonnyMacJonnyMac Posts: 9,208
    edited 2011-01-07 11:12
    ..it took approximately a minute longer to execute...

    I was a little surprised by that so I whipped up a quick test -- turns out that accessing a global directly versus a pointer to it is significantly faster. At some point you're going to be faced with organizing your code so that it can be used in your project and others, so you may want to live with the slower speed -- or switch to PASM (I wrote a PASM demo for you a few days ago that does the motor drive stuff, it's in this thread).
    var
    
      long  global
    
    
    pub main | t, local, pntr
    
      term.start(RX1, TX1, %0000, 115_200)
      pause(1)
    
      term.tx(CLS)
      term.str(string("Timing Test", CR, CR))
    
      pntr := @global
    
      t := -cnt                                                     ' start timing
      'local := global                                              ' 320 ticks
      local := long[pntr]                                           ' 544 ticks
      t += cnt - 544                                                ' end timing
      
      term.dec(t)
      term.str(string(" ticks"))
    
      repeat
        waitcnt(0)
    
  • MagIO2MagIO2 Posts: 2,243
    edited 2011-01-07 11:45
    @Jonny: Hi again!

    Of course accessing globals or local variables is faster. If you do long[ addressVar ], the SPIN compiler has to read the content of addressVar from HUB-RAM and then can read/write the content of the memory location you want to access. By using direct variable names there is no need for the additional HUB-access.
  • JonnyMacJonnyMac Posts: 9,208
    edited 2011-01-07 12:55
    Makes perfect sense; the address of the global is hard-coded into the byte codes hence eliminating the hub read to get the address to retrieve the value from.
  • idbruceidbruce Posts: 6,197
    edited 2011-01-07 12:57
    Jon

    I am testing it right now. In this case the ponters are much faster and the count is accurate. Not true with globals.

    Bruce
Sign In or Register to comment.