Shop OBEX P1 Docs P2 Docs Learn Events
(clkfreq/1_000_000 + cnt) and system clock frequency setting — Parallax Forums

(clkfreq/1_000_000 + cnt) and system clock frequency setting

Scott2HotScott2Hot Posts: 10
edited 2010-02-04 02:52 in Propeller 1
CON
  '_clkmode = xtal1 + pll2x       'Clock mode set for 10Mz.                       
  '_xinfreq = 5_000_000           'External oscillator frequency for precision timing.
  PB1 = 0                         'Push button pin.
  Servo = 11                      'Servo pin.
  Servo_C = 1500                  'Servo center of 1.5ms. 
  uS = (1_000_000)                'Value used to divide clock frequency into micro seconds.
  Period = 20_000                 'Servo PWM offtime in uS.
 
VAR
  long Servo_P                    'Servo pulse.
  long Stack[noparse][[/noparse]50]                  'Cog stack space.
 
 
PUB Main
  Servo_P := Servo_C              'Make servo pulse equal to servo center position.
 
  COGNEW (Servo_Cog, @Stack[noparse][[/noparse]0])   'Launch new cog to drive servo.
 
  dira[noparse][[/noparse]8] := 1                    'Bi-color led output.
  dira[noparse][[/noparse]9] := 1                    'Bi-color led output.
 
  Repeat
                                                        
    outa[noparse][[/noparse]8] := 0                  'Bi-color led is green.
    outa[noparse][[/noparse]9] := 1
  
    Repeat until ina[noparse][[/noparse]PB1] == 1    'Wait until push button is pressed.
 
    outa[noparse][[/noparse]8] := 1                  'Bi-Color led is red while running routine.
    outa[noparse][[/noparse]9] := 0
                                                        
    WAITCNT (clkfreq*1 + cnt)     'Wait one second after button pressed.
 
    Servo_P := 1000               'Send 1ms pulse to servo.
 
    WAITCNT (clkfreq*5 + cnt)     'Wait 5 seconds.
 
    Servo_P := 2000               'Send 2ms pulse to servo.
 
    WAITCNT (clkfreq*5 + cnt)     'Wait 5 seconds.
 
    Servo_P := Servo_C            'Set pulse for center position.
 
 
PUB Servo_Cog                     'New cog to refresh servo.
                                                        
  dira[noparse][[/noparse]Servo] := 1                'Set servo pin to output.
    
  repeat
    outa[noparse][[/noparse]Servo] := 1                                    'Turn pulse on.
    waitcnt((clkfreq/1_000_000) * Servo_P + cnt)        'Servo pulse on time
    outa[noparse][[/noparse]Servo] := 0                                    'Turn pulse off.
    waitcnt((clkfreq/1_000_000) * Period + cnt)         'Servo pulse off time.



Okay so I finally pulled my PELabs kit out and started to try to learn how to use the prop in a project. I am trying to drive a servo motor and ran into a problem·I cannot solve. My program works good dividing the clkfreq by 1meg to create a 1 microsecond unit and then multiple it by the servo pulse I want in·microseconds plus the clock count with the standard internal 12Mhz clock. So 1_500 will equal 1.5 miliseconds to center a servo. This worked pretty good·until I tried to change the clock from internal to external for better resolution of time. I have commented·the clock frequency settings out in the code above in the constant block (10Mhz in this case). After changing the clock frequency, the timing of my variable "Servo_P"·has changed. My thought is that clkfreq divided by 1_000_000 should remain in microseconds no matter what the actual·frequency·is that·I use. This is not the case. Can somebody please·point me in the right direction to my mistake with clkfreq timing?·

Comments

  • Dave FDave F Posts: 11
    edited 2010-02-03 04:09
    Working with servos is one of the more fun things you can do with the prop. However, unless you really want to investigate the nuts and bolts of creating pulse width modulated signals, the easiest way to get up and running is to download the Servo32V7 object by Beau Schwab at http://obex.parallax.com/objects/category/7/?n=100. This object adds servo control commands to your code for you.

    If you are indeed interested in working on PWM in Spin, Andy Lindsay has a good section on timing techniques in the counters module in the Propeller Education Kit Fundamentals PDF which is also available for free download from Parallax.

    One additional note: be careful of the current requirements of your servo when using the propeller education kit. If your servo draws too much current, it will brown out the propeller, which will then reset, which will then start the servo again, which will brown out... Don't ask me how I know this... If you have a large servo, use a separate power source for it, and tie the ground of it to the prop board. The signal itself draws very little current.

    Good luck with your project

    Dave
  • MagIO2MagIO2 Posts: 2,243
    edited 2010-02-03 08:54
    What's the deviation we talk about?

    In your repeat loop you do a lot of stuff:
    1. repeat instruction
    2. you set the output
    3. you calculate for which cnt to wait for with 1 division, 1 multiplication and one addition
    4. do the same as in 2./3. for the pause and go back to step 1

    So, when you decrease the clock frequency all this needs longer. Decreasing clock frequency by ~2% means the code will be ~2% slower. You should do the calculation of (clkfreq/1_000_000)*servo_P outside of the Servo_Cog. And use a cntwait-variable instead of adding cnt everytime. This will increase accuracy. (This is described in the Education Lab)

    In your main:
    
        WAITCNT (clkfreq*1 + cnt)     'Wait one second after button pressed.  
        Servo_P := (clkfreq/1_000_000) * 1000               'Send 1ms pulse to servo.
     
        WAITCNT (clkfreq*5 + cnt)     'Wait 5 seconds.
     
        Servo_P := (clkfreq/1_000_000) * 2000               'Send 2ms pulse to servo.
     
    in Servo_Cog:
     
    cntwait:=cnt
    out[noparse][[/noparse] servo ]:=0
    repeat
      cntwait:=cntwait+period
      waitcnt( cntwait )
      out[noparse][[/noparse] servo ]:=1
      cntwait:=cntwait+servo_p
      waitcnt( cntwait )
    
      out[noparse][[/noparse] servo ]:=0
    

    This way the runtime of instructions between the out-instructions are not added to the wait-time.

    Maybe this helps.
  • Scott2HotScott2Hot Posts: 10
    edited 2010-02-03 16:30
    I believe you answered the·solution to·my problem. After reading WAITCNT in the manual again. I found that there is a section for synchronized delay.·The code that you posted pretty much summed that up for me. I will give it a try this afternoon when I get home. I plan to use the 80Mhz frequency once I get it all working correctly. I have a Parallax USB oscilloscope on order to verify my servo timing.

    I wanted to use the Servo32V7 object but could not figure out how to use it to control my servo. If you know where to find a simple·example of how to use Servo32V7,·please let me know.

    Thank you!
  • JonnyMacJonnyMac Posts: 9,208
    edited 2010-02-03 16:32
    I think it's a bit tough to do precise servo control using waitcnt due to the instruction overhead. If you only want to control one or two servos, you can do it like this:

    pub servo(svopin) | t
    
      ctra := (%00100 << 26) + svopin
      frqa := 1
      dira[noparse][[/noparse]svopin]~~
    
      t := cnt
      repeat
        waitcnt(t += constant(MS_001 * 20))
        phsa := -svopos
    



    This is an adaptation of one of the programs in the counters app note (AN001) -- you can extend it to two servos if you need. I've attached a screen cap of the output; you can see that it refreshes right on 20ms using a synchronized waitcnt in the repeat loop. The position pulse is precise because the counter is doing the hard work setting the output.

    Going beyond two servos, you'll need to do it differently. Since you're already launching a another cog, you could use one of the servo objects in ObEx. Dare I say... this one's pretty good:

    -- obex.parallax.com/objects/445/

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Jon McPhalen
    Hollywood, CA

    Post Edited (JonnyMac) : 2/3/2010 4:53:23 PM GMT
  • MagIO2MagIO2 Posts: 2,243
    edited 2010-02-03 19:41
    @JonnyMac:
    If you work with a variable like I did and you did, the time difference comeing from instruction overhead is zero! Only the first time I clear the output the time may vary, but that's why I've choosen to have the first outa being a clear-bit instruction.

    Let's say we are in the middle of generation:
    the first waitcnt waits for 20ms. The output will be set to·1 at t1 = 20ms + dt1 (some runtime for returning from waitcnt and outa instruction)
    the second waitcnt waits for 1.5ms. The output will be set to·0 at t2 = 20ms + 1.5ms + dt2 (some runtime for return&outa)
    Where is dt1? It dissappeared because we calculate t (in your case and cntwait in my case) continuously adding either 20ms or 1.5ms.

    Now let's have a look at the instructions - and remember, the propeller is totally deterministic. So, the return time from waitcnt and the out instructions will definitely need the same time in both cases. (dt1 = dt2)
    So, let's calculate how long the impulse was:
    t2 - t1 = (20ms + 1.5ms + dt2) - (20ms +dt1) = 1.5ms (as dt2=dt1)

    So, the timing is as exact as the clock-frequency - either in spin and using waitcnt. You only need to take care that both parts (set and clear pin) are symmetrical (waitcnt directly followed by outa).



    Post Edited (MagIO2) : 2/3/2010 7:50:50 PM GMT
  • Scott2HotScott2Hot Posts: 10
    edited 2010-02-04 02:52
    My servo is running solid as a rock! I have been struggling for a couple of weeks trying to make this work. My servo·center position was drifting because of the timing error.·Now it is dead on. I can change the clock frequency and the servo pulse remain the same. This is basically my first program with Spin. I will verify my setup when the scope arrives. Thank you all for helping me figure this out!

    CON
      _clkmode=xtal1 + pll16x                               'Clock mode set for 80Mz.                       
      _xinfreq=5_000_000                                    'External oscillator frequency for precision timing.
      PB1=0                                                 'Push button pin.
      Servo=11                                              'Servo pin.
      Servo_C=1_500                                         'Servo center in micro seconds. 
      uS=1_000_000                                          'Divisor for micro second.
      Period=20_000                                         'Servo pulse off time in microseconds.
    VAR
      long Servo_P                                          'Servo pulse.
      long Stack[noparse][[/noparse]50]                                        'Cog stack space.
      long Off_Time                                         'Servo pulse off time variable.
    PUB Main
      dira[noparse][[/noparse]8]:=1                                            'Pin 8 is output for bi-color led.
      dira[noparse][[/noparse]9]:=1                                            'Pin 9 is output for bi-color led.
      Off_Time:=(clkfreq/uS)*Period                         'Set servo pulse off time.
      Servo_P:=(clkfreq/uS)*Servo_C                         'Make servo pulse equal to servo center position.
      COGNEW(Servo_Cog, @Stack[noparse][[/noparse]0])                          'Launch new cog to drive servo.  
      Repeat                                                'main routine to execute.
        outa[noparse][[/noparse]8]:=0                                          'Bi-color led is green.
        outa[noparse][[/noparse]9]:=1                                          'Bi-color led is green.
        Repeat until ina[noparse][[/noparse]PB1] == 1                          'Wait until push button is pressed. 
        WAITCNT(clkfreq*1 + cnt)                            'Wait for 1 second..
        outa[noparse][[/noparse]8]:=1                                          'Bi-Color led is red while running routine.
        outa[noparse][[/noparse]9]:=0                                          'Bi-Color led is red while running routine. 
        Servo_P:=(clkfreq/uS)*1000                          'Send 1ms pulse to servo. 
        WAITCNT (clkfreq*5+cnt)                             'Wait 5 seconds. 
        Servo_P:=(clkfreq/uS)*2000                          'Send 2ms pulse to servo.
        WAITCNT(clkfreq*5+cnt)                              'Wait for 5 seconds.
        Servo_P:=(clkfreq/uS)*Servo_C                       'Set servo back to center position.
    PUB Servo_Cog|cntwait
      dira[noparse][[/noparse]Servo]:= 1                                       'Set servo pin to output. 
      cntwait:=cnt                                          'Set variable equal to system clock.
      outa[noparse][[/noparse]Servo]:= 0                                       'Set servo output off.
      repeat                                                'Main routine to execute.
        cntwait:=cntwait+Off_Time                           'Set servo off time to 20 ms.
        waitcnt(cntwait)                                    'Wait for 20ms.
        outa[noparse][[/noparse]Servo]:=1                                      'Turn on pulse to servo.
        cntwait:=cntwait+Servo_P                            'Set servo on time to servo pulse variable.
        waitcnt(cntwait)                                    'Wait for pulse variable.
        outa[noparse][[/noparse]Servo]:= 0                                     'Turn off pulse to servo.
    
Sign In or Register to comment.