Shop OBEX P1 Docs P2 Docs Learn Events
XBee and Propeller controlled servo question — Parallax Forums

XBee and Propeller controlled servo question

xanaduxanadu Posts: 3,347
edited 2011-12-28 15:50 in Propeller 1
I have a robot with a motor contoller that has PWM inputs and a Propeller Proto Board. I have it working just fine using the XBee object. I added the Two_Servo_Assembly.spin I found in the OBEX to use a cog to keep the servos moving while the Prop does other tasks. I'm a little stuck right now and I'm not quite sure why this isn't working.

Right now I get the command echoed back to me, but the servo's don't turn. Everything works in the demo code, but when I move it into my code for listening for a keypress via XBee wireless something is going wrong.

The movement commands act as toggles, instead of holding down a key i would like to press it once, then listen for a stop command.

Any help would be great and thanks in advance!
CON
  _clkmode = xtal1 + pll16x
  _xinfreq = 5_000_000
' Set pins and Baud rate for XBee comms  
  XB_Rx     = 0              ' XBee Dout
  XB_Tx     = 1              ' XBee Din
  XB_Baud   = 9600
  
  right     = 12              'servo
  left      = 13              'servo

' Carriage return value
  CR        = 13
     
OBJ
  XB        : "XBee_Object"
   
VAR
  long Pause, Pulsout                  
  long position1, position2                'The assembly program will read these variables from the main Hub RAM to determine
Pub  Start | DataIn
XB.start(XB_Rx, XB_Tx, 0, XB_Baud)         ' Initialize comms for XBee
XB.Delay(1000)                             ' One second delay
Pause := clkfreq/1_000
Pulsout := clkfreq/500_000
XB.AT_Init                                 'fast AT Command mode
XB.Tx("1")                                 'ready
Repeat
  DataIn := XB.RxTime(1000)                ' Wait for byte with timeout
  If DataIn == "d"
    position1:=180_000                     'Start sending 2.25ms servo signal high pulses to servomotor 1  (CCW position)                                                
    position2:=40_000                      'Start sending 0.5ms servo signal high pulses to servomotor 2   (CW position)
        XB.Tx("d")
   
    
  If DataIn == "w"
    position1:=180_000                     'Start sending 2.25ms servo signal high pulses to servomotor 1  (CCW position)                                                
    position2:=40_000                      'Start sending 0.5ms servo signal high pulses to servomotor 2   (CW position)
        XB.Tx("w")
   
    
  If DataIn == "s"
    position1:=180_000                     'Start sending 2.25ms servo signal high pulses to servomotor 1  (CCW position)                                                
    position2:=40_000                      'Start sending 0.5ms servo signal high pulses to servomotor 2   (CW position)
        XB.Tx("s")
   
    
  If DataIn == "a"
    position1:=180_000                     'Start sending 2.25ms servo signal high pulses to servomotor 1  (CCW position)                                                
    position2:=40_000                      'Start sending 0.5ms servo signal high pulses to servomotor 2   (CW position)
        XB.Tx("a")
   
    
  If DataIn == "u"
    position1:=180_000                     'Start sending 2.25ms servo signal high pulses to servomotor 1  (CCW position)                                                
    position2:=40_000                      'Start sending 0.5ms servo signal high pulses to servomotor 2   (CW position)
        XB.Tx("u")
    
    
  Else
   XB.Tx(".")                               'waiting
PUB Demo                                                                                                                 
  p1:=@position1                           'Stores the address of the "position1" variable in the main Hub RAM as "p1"
  p2:=@position2                           'Stores the address of the "position2" variable in the main Hub RAM as "p2" 
  cognew(@TwoServos,0)                     'Start a new cog and run the assembly code starting at the "TwoServos" cell         
  'The new cog that is started above continuously reads the "position1" and "position2" variables as they are changed by
  ' the example Spin code below
     
DAT
'The assembly program below runs on a parallel cog and checks the value of the "position1" and "position2" variables in the
' main RAM (which other cogs can change at any time). It then outputs two servo high pulses (back to back) each corresponding
' to the two position variables (which represent the number of system clock ticks during which each pulse is outputed) and
' sends a 10ms low part of the pulse. It repeats this signal continuously and changes the width of the high pulses as the 
' "position1" and "position2" variables are changed by other cogs.
TwoServos     org                         'Assembles the next command to the first cell (cell 0) in the new cog's RAM                                                                                                                   
Loop          mov       dira,ServoPin1    'Set the direction of the "ServoPin1" to be an output (and all others to be inputs)  
              rdlong    HighTime,p1       'Read the "position1" variable from Main RAM and store it as "HighTime"
              mov       counter,cnt       'Store the current system clock count in the "counter" cell's address 
              mov       outa,AllOn        'Set all pins on this cog high (really only sets ServoPin1 high b/c rest are inputs)               
              add       counter,HighTime  'Add "HighTime" value to "counter" value
              waitcnt   counter,0         'Wait until cnt matches counter (adds 0 to "counter" afterwards)
              mov       outa,#0           'Set all pins on this cog low (really only sets ServoPin1 low b/c rest are inputs)
              
              mov       dira,ServoPin2    'Set the direction of the "ServoPin2" to be an output (and all others to be inputs)  
              rdlong    HighTime,p2       'Read the "position2" variable from Main RAM and store it as "HighTime"
              mov       counter,cnt       'Store the current system clock count in the "counter" cell's address    
              mov       outa,AllOn        'Set all pins on this cog high (really only sets ServoPin2 high b/c rest are inputs)            
              add       counter,HighTime  'Add "HighTime" value to "counter" value
              waitcnt   counter,LowTime   'Wait until "cnt" matches "counter" then add a 10ms delay to "counter" value 
              mov       outa,#0           'Set all pins on this cog low (really only sets ServoPin2 low b/c rest are inputs)
              waitcnt   counter,0         'Wait until cnt matches counter (adds 0 to "counter" afterwards)
              jmp       #Loop             'Jump back up to the cell labled "Loop"                                      
                                                                                                                    
'Constants and Variables:
ServoPin1     long      |<      12 '<------- This sets the pin that outputs the first servo signal (which is sent to the white
                                          ' wire on most servomotors). Here, this "7" indicates Pin 7. Simply change the "7"
                                          ' to another number to specify another pin (0-31).
ServoPin2     long      |<      13 '<------- This sets the pin that outputs the second servo signal (could be 0-31). 
p1            long      0                 'Used to store the address of the "position1" variable in the main Hub RAM
p2            long      0                 'Used to store the address of the "position2" variable in the main Hub RAM
AllOn         long      $FFFFFFFF         'This will be used to set all of the pins high (this number is 32 ones in binary)
LowTime       long      800_000           'This works out to be a 10ms pause time with an 80MHz system clock. If the
                                          ' servo behaves erratically, this value can be changed to 1_600_000 (20ms pause)                                  
counter       res                         'Reserve one long of cog RAM for this "counter" variable                     
HighTime      res                         'Reserve one long of cog RAM for this "HighTime" variable
              fit                         'Makes sure the preceding code fits within cells 0-495 of the cog's RAM

{Copyright (c) 2008 Gavin Garner, University of Virginia
MIT License: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and
this permission notice shall be included in all copies or substantial portions of the Software. The software is provided
as is, without warranty of any kind, express or implied, including but not limited to the warrenties of noninfringement.
In no event shall the author or copyright holder be liable for any claim, damages or other liablility, out of or in
connection with the software or the use or other dealings in the software.}                 

Comments

  • JonnyMacJonnyMac Posts: 9,198
    edited 2011-12-28 13:06
    You don't seem to be calling the method called Demo which would launch the servo controller cog.

    Just a note: White space is your friend. Listings with generous spacing -- that is, blank lines between distinct sections -- are always easier to read and troubleshoot. Also, the case structure would be useful in your program.

    In this example you can see that case is a little easier on the eyes. It also let's you use upper and lower case input (as I did with the "d") much more easily than the if (condition) structure.
    case datain
        "d", "D":
          position1 := 180_000
          position2 :=  40_000
          xb.tx(dataIn)
    
        "w":
          position1 := 180_000
          position2 :=  40_000
          xb.tx(dataIn)
    
        "s":
          position1 := 180_000
          position2 :=  40_000
          xb.tx(dataIn)
    
        "a":
          position1 := 180_000
          position2 :=  40_000
          xb.tx(dataIn)
    
        "u":
          position1 := 180_000
          position2 :=  40_000
          xb.tx(dataIn)
    
        other:
          xb.tx(".")
    

    You do realize that every command uses the same servo position, right? Perhaps that's why you're not seeing any movement.

    And, finally, for what it's worth... there are better servo objects available than what you're using, including my own:
    -- http://obex.parallax.com/objects/445/

    A well-crafted object should not require you to changes its source to use it as is the case with that dual-servo object you're using. Well behaved objects should allow you to specify which pins to use and, to the greatest extent possible, be clock frequency agnostic. Mine and others do both.
  • xanaduxanadu Posts: 3,347
    edited 2011-12-28 13:10
    When I c&v the code it removes all of my blank spaces :/ I'm calling 'demo' right after the XBee code, is that wrong? Thanks again!
  • xanaduxanadu Posts: 3,347
    edited 2011-12-28 13:16
    This code works, but there is this one minute delay before it does anything. Once the motor starts turning you can turn it off with the "u" key. Both XBee and the Servo code work great on their own, I missing something here...
    CON
      _clkmode = xtal1 + pll16x
      _xinfreq = 5_000_000
    ' Set pins and Baud rate for XBee comms  
      XB_Rx     = 0              ' XBee Dout
      XB_Tx     = 1              ' XBee Din
      XB_Baud   = 9600
      
      right     = 12              'servo
      left      = 13              'servo
    
    ' Carriage return value
      CR        = 13
      
         
    OBJ
      XB        : "XBee_Object"
     
    
       
    VAR
      long Pause, Pulsout                  
      long position1, position2                'The assembly program will read these variables from the main Hub RAM to determine
     
    
    Pub  Start | DataIn
    XB.start(XB_Rx, XB_Tx, 0, XB_Baud)         ' Initialize comms for XBee
    XB.Delay(1000)                             ' One second delay
    Pause := clkfreq/1_000
    Pulsout := clkfreq/500_000
    XB.AT_Init                                 'fast AT Command mode
    XB.Tx("1")                                 'ready
    
    Repeat
      DataIn := XB.RxTime(1000)                ' Wait for byte with timeout
      If DataIn == "d"
        position1:=180_000                     'Start sending 2.25ms servo signal high pulses to servomotor 1  (CCW position)                                                
        position2:=40_000                      'Start sending 0.5ms servo signal high pulses to servomotor 2   (CW position)
            XB.Tx("d")
       
        
      If DataIn == "w"
        position1:=180_000                     'Start sending 2.25ms servo signal high pulses to servomotor 1  (CCW position)                                                
        position2:=40_000                      'Start sending 0.5ms servo signal high pulses to servomotor 2   (CW position)
            XB.Tx("w")
       
        
      If DataIn == "s"
        position1:=180_000                     'Start sending 2.25ms servo signal high pulses to servomotor 1  (CCW position)                                                
        position2:=40_000                      'Start sending 0.5ms servo signal high pulses to servomotor 2   (CW position)
            XB.Tx("s")
       
        
      If DataIn == "a"
        position1:=180_000                     'Start sending 2.25ms servo signal high pulses to servomotor 1  (CCW position)                                                
        position2:=40_000                      'Start sending 0.5ms servo signal high pulses to servomotor 2   (CW position)
            XB.Tx("a")
       
        
      If DataIn == "u"
        position1:=0_000                       'stop                                                
        position2:=0_000                       'stop
            XB.Tx("u")
        
        
      Else
       XB.Tx(".")                              'waiting
      
    
       
    DAT
    'The assembly program below runs on a parallel cog and checks the value of the "position1" and "position2" variables in the
    ' main RAM (which other cogs can change at any time). It then outputs two servo high pulses (back to back) each corresponding
    ' to the two position variables (which represent the number of system clock ticks during which each pulse is outputed) and
    ' sends a 10ms low part of the pulse. It repeats this signal continuously and changes the width of the high pulses as the 
    ' "position1" and "position2" variables are changed by other cogs.
    TwoServos     org                         'Assembles the next command to the first cell (cell 0) in the new cog's RAM                                                                                                                   
    Loop          mov       dira,ServoPin1    'Set the direction of the "ServoPin1" to be an output (and all others to be inputs)  
                  rdlong    HighTime,p1       'Read the "position1" variable from Main RAM and store it as "HighTime"
                  mov       counter,cnt       'Store the current system clock count in the "counter" cell's address 
                  mov       outa,AllOn        'Set all pins on this cog high (really only sets ServoPin1 high b/c rest are inputs)               
                  add       counter,HighTime  'Add "HighTime" value to "counter" value
                  waitcnt   counter,0         'Wait until cnt matches counter (adds 0 to "counter" afterwards)
                  mov       outa,#0           'Set all pins on this cog low (really only sets ServoPin1 low b/c rest are inputs)
                  
                  mov       dira,ServoPin2    'Set the direction of the "ServoPin2" to be an output (and all others to be inputs)  
                  rdlong    HighTime,p2       'Read the "position2" variable from Main RAM and store it as "HighTime"
                  mov       counter,cnt       'Store the current system clock count in the "counter" cell's address    
                  mov       outa,AllOn        'Set all pins on this cog high (really only sets ServoPin2 high b/c rest are inputs)            
                  add       counter,HighTime  'Add "HighTime" value to "counter" value
                  waitcnt   counter,LowTime   'Wait until "cnt" matches "counter" then add a 10ms delay to "counter" value 
                  mov       outa,#0           'Set all pins on this cog low (really only sets ServoPin2 low b/c rest are inputs)
                  waitcnt   counter,0         'Wait until cnt matches counter (adds 0 to "counter" afterwards)
                  jmp       #Loop             'Jump back up to the cell labled "Loop"                                      
                                                                                                                        
    'Constants and Variables:
    ServoPin1     long      |<      12 '<------- This sets the pin that outputs the first servo signal (which is sent to the white
                                              ' wire on most servomotors). Here, this "7" indicates Pin 7. Simply change the "7"
                                              ' to another number to specify another pin (0-31).
    ServoPin2     long      |<      13 '<------- This sets the pin that outputs the second servo signal (could be 0-31). 
    p1            long      0                 'Used to store the address of the "position1" variable in the main Hub RAM
    p2            long      0                 'Used to store the address of the "position2" variable in the main Hub RAM
    AllOn         long      $FFFFFFFF         'This will be used to set all of the pins high (this number is 32 ones in binary)
    LowTime       long      800_000           'This works out to be a 10ms pause time with an 80MHz system clock. If the
                                              ' servo behaves erratically, this value can be changed to 1_600_000 (20ms pause)                                  
    counter       res                         'Reserve one long of cog RAM for this "counter" variable                     
    HighTime      res                         'Reserve one long of cog RAM for this "HighTime" variable
                  fit                         'Makes sure the preceding code fits within cells 0-495 of the cog's RAM
    
    {Copyright (c) 2008 Gavin Garner, University of Virginia
    MIT License: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
    documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
    rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
    persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and
    this permission notice shall be included in all copies or substantial portions of the Software. The software is provided
    as is, without warranty of any kind, express or implied, including but not limited to the warrenties of noninfringement.
    In no event shall the author or copyright holder be liable for any claim, damages or other liablility, out of or in
    connection with the software or the use or other dealings in the software.}                 
    
  • JonnyMacJonnyMac Posts: 9,198
    edited 2011-12-28 13:30
    I'm calling 'demo' right after the XBee code, is that wrong?

    You're not calling the Demo method, you simply have it built into your code. Your code loops back to the repeat before then because Demo is an atomic block of code (it's a public method) -- it doesn't run inline as you're believing it does. In the editor you should see Demo having a different background color that Start; this is the clue that they are separate sections of code.
    This code works, but there is this one minute delay before it does anything.

    The delay usually means you have a waitcnt rollover error.
    I missing something here.

    So are those of us that could help you.... This is common problem in the forums: a well-intentioned poster says, "My program doesn't work." but doesn't provide a full explanation of what it should do. Most of us are good programs, not so good at mind reading. ;) A programmer's intentions are not always obvious from code.

    Also, if you attached an archive of your project then others could look at the other objects to see if there are some issues. I suspect the .delay method in the XBee object may have a problem that is causing your start-up delay.
  • xanaduxanadu Posts: 3,347
    edited 2011-12-28 13:48
    JonnyMac wrote: »
    The delay usually means you have a waitcnt rollover error.



    So are those of us that could help you.... This is common problem in the forums: a well-intentioned poster says, "My program doesn't work." but doesn't provide a full explanation of what it should do. Most of us are good programs, not so good at mind reading. ;) A programmer's intentions are not always obvious from code.

    .

    Sorry for the lack of info, I know what you mean.

    What I'm trying to do is toggle two continous rotation servos using an XBee and Prop Proto Board. I need to be able to press the "a" key to start two servo's turning, then press the "u" key to stop them. I have setup the code to make is work like a push button, but not a toggle switch. This project came over from a Basic Stamp board because the Basic stamp can't run the servos and listen for a stop command at the same time. Just imagine a BOE Bot (w/prop board), controlled via XBee, and all that needs to move are the wheels, but they need to toggle on/off.

    I thought I was going about this pretty simple. It seems like no matter what I do part of the code will work, and part will not.

    I'll check out the waitcnt error. Is this something viewport would help a lot with?
  • JonnyMacJonnyMac Posts: 9,198
    edited 2011-12-28 13:58
    That helps, but what do the letters mean? "F" for forward, "R" for reverse would make sense -- I'm just not finding the logic. Once I know that I'll whip up a version using my servo driver that you can try.

    I don't use Viewport, so I don't know if it's helpful or not.
  • xanaduxanadu Posts: 3,347
    edited 2011-12-28 14:00
    JonnyMac wrote: »
    You don't seem to be calling the method called Demo which would launch the servo controller cog.

    Just a note: White space is your friend. Listings with generous spacing -- that is, blank lines between distinct sections -- are always easier to read and troubleshoot. Also, the case structure would be useful in your program.

    In this example you can see that case is a little easier on the eyes. It also let's you use upper and lower case input (as I did with the "d") much more easily than the if (condition) structure.
    case datain
        "d", "D":
          position1 := 180_000
          position2 :=  40_000
          xb.tx(dataIn)
    
        "w":
          position1 := 180_000
          position2 :=  40_000
          xb.tx(dataIn)
    
        "s":
          position1 := 180_000
          position2 :=  40_000
          xb.tx(dataIn)
    
        "a":
          position1 := 180_000
          position2 :=  40_000
          xb.tx(dataIn)
    
        "u":
          position1 := 180_000
          position2 :=  40_000
          xb.tx(dataIn)
    
        other:
          xb.tx(".")
    

    You do realize that every command uses the same servo position, right? Perhaps that's why you're not seeing any movement.

    And, finally, for what it's worth... there are better servo objects available than what you're using, including my own:
    -- http://obex.parallax.com/objects/445/

    A well-crafted object should not require you to changes its source to use it as is the case with that dual-servo object you're using. Well behaved objects should allow you to specify which pins to use and, to the greatest extent possible, be clock frequency agnostic. Mine and others do both.

    Yes, I know they all output the same but go and stop works fine for now. I guess I'll check for other servo drivers. Thanks a lot!
  • xanaduxanadu Posts: 3,347
    edited 2011-12-28 14:12
    The letters are; w=forward, a=left, s=backward, d=right, u=stop

    I'd love to see it with your servo driver, I'm looking at it now. As you can already tell I need a lot more time with the Prop book, but this is on the side for fun and I'd like to get it going so thanks!
  • JonnyMacJonnyMac Posts: 9,198
    edited 2011-12-28 14:16
    Here's what your code *might* look light using my servo driver (I used letter commands that make sense to me).
    con
    
      _clkmode = xtal1 + pll16x
      _xinfreq = 5_000_000                                          ' use 5MHz crystal
    ' _xinfreq = 6_250_000                                          ' use 6.25MHz crystal
    
      CLK_FREQ = ((_clkmode - xtal1) >> 6) * _xinfreq
      MS_001   = CLK_FREQ / 1_000
      US_001   = CLK_FREQ / 1_000_000
    
    
    con
    
      RX1   = 31                                                    ' programming/debug port
      TX1   = 30
      SDA   = 29
      SCL   = 28
    
      L_SVO = 13                                                    ' servos
      R_SVO = 12
    
      XBT   =  1                                                    ' xbee serial io
      XBR   =  0
      
    
    con
    
      ' servo control values (microseconds)
    
      L_FWD  = 2_250
      L_STOP = 1_500
      L_REV  = 0_750
    
      R_FWD  = 0_750 
      R_STOP = 1_500
      R_REV  = 2_250 
    
    
    obj
    
      xbee  : "xbee_object_2"
      servo : "jm_servo_8x"
     
    
    var
    
    
    pub main | c
    
      xbee.start(XBR, XBT, %0000, 9_600)                            ' start XBee coms 9600 baud
      servo.start(2, R_SVO)                                         ' use two servos
      pause(1000)
    
      xbee.at_init                                                  ' initialize xbee
      xbee.tx("1")                                                  ' indicate ready
    
      xbee.rxflush                                                  ' clear trash from xbee
      repeat
        c := xbee.rxtime(250)
        case c
          "f", "F":                                                 ' forward
            servo.set(0, R_FWD, 0)
            servo.set(1, L_FWD, 0)
            xbee.tx("F")
    
          "x", "X":                                                 ' stop
            servo.set(0, R_STOP, 0)
            servo.set(1, L_STOP, 0)
            xbee.tx("X")
    
          "b", "B":                                                 ' backward
            servo.set(0, R_REV, 0)
            servo.set(1, L_REV, 0)
            xbee.tx("B")
         
          "l", "L":                                                 ' left
            servo.set(0, R_FWD, 0)
            servo.set(1, L_STOP, 0)
            xbee.tx("L")  
      
          "r", "R":                                                 ' right
            servo.set(0, R_STOP, 0)
            servo.set(1, L_FWD, 0)
            xbee.tx("R") 
    
    
    con
    
    pub pause(ms) | t
    
    '' Delay program ms milliseconds
    
      t := cnt - 1088                                               ' sync with system counter
      repeat (ms #> 0)                                              ' delay must be > 0
        waitcnt(t += MS_001)
        
    
    pub high(pin)
    
    '' Makes pin output and high
    
      outa[pin] := 1
      dira[pin] := 1
    
    
    pub low(pin)
    
    '' Makes pin output and low
    
      outa[pin] := 0
      dira[pin] := 1
    
    
    pub toggle(pin)
    
    '' Toggles pin state
    
      !outa[pin]
      dira[pin] := 1
    
    
    pub input(pin)
    
    '' Makes pin input and returns current state
    
      dira[pin] := 0
    
      return ina[pin]
    


    I've attached the project for you to play with (it compiles, but you'll have to test). Also, I'm using "instant" servo commands, that's the ", 0" in the third parameter of the set method. In practical use you may want to change this to 100 or greater so that the servo ramps to the new position. That's one of the nice features of my servo driver; it includes position/speed ramping without using more than one cog.
  • xanaduxanadu Posts: 3,347
    edited 2011-12-28 14:28
    Wow, thank you! This looks a lot better. I'm going to go over it and upload it and understand how it works a little more right now :)
  • xanaduxanadu Posts: 3,347
    edited 2011-12-28 15:25
    Yes, this works without any bugs, excellent! I was so happy I tripped over my project and a thirty cent fuse saved my a--.

    Here's a video of it in action - http://www.youtube.com/watch?v=XSe5nsexfQk so much to do.... so little time. Thanks again JohnnyMac!!
  • JonnyMacJonnyMac Posts: 9,198
    edited 2011-12-28 15:50
    You're welcome, Xahnadu (There's no "h" in my name, either! <grin>) -- glad you're able to move forward to more exciting aspects of the project.
Sign In or Register to comment.