.Net Framework 2.0 control micro serial 8 servo controller

I am trying to use VS2005 to send serial command to the Pololu 8-servo controller. I have the code with “dec2bin” & “bin2dec” working. But each time I send to the controller, the red led will light up and yellow led will keep blinking. Is there anything wrong with the code: :?

Dim msb As Object
Dim lsb As Object
Dim strbinary As Object
Dim cmd As Object
		
cmd = Chr(&H80S) ' start byte   - 0x80 ' always
cmd = cmd & Chr(&H1S) ' Device ID    - 0x01 ' always
cmd = cmd & Chr(&H4S) ' command      - 0x04 ' we want command 4: Set Position, Absolute (2 data bytes)
cmd = cmd & Chr(num) ' servo num    - 0x.. ' 00-07

' data1        - 0x.. ' upper 7 bits - range is 500 through 5500
' data2        - 0x.. ' lower 7 bits

strbinary = "0000000000000000" & dec2bin(Value)

lsb = "0" & Mid(strbinary, Len(strbinary) - 6)
msb = "0" & Mid(strbinary, Len(strbinary) - 12, 6)

cmd = cmd & Chr(bin2dec(msb))
cmd = cmd & Chr(bin2dec(lsb))

SerialPort1.Open()
SerialPort1.WriteLine(cmd)
SerialPort1.Close()

Hello.

Looking at your code, it’s unclear to me what you’re doing, though I’m not very experienced with Visual Basic.
In general, it seems more complicated (and less readable) than it can be. Additionally, you shouldn’t need to convert between decimal and binary; a number is a number no matter how you represent it. The following is some visual basic code that will transmit a sample servo command 4:

SerialPort1.Open() 
Dim buffer As Byte() = {128, 1, 4, 0, 12, 79} 
SerialPort1.Write(buffer, 0, 6)

Since I don’t fully understand your code, the only thing I can truly identify as a problem is your use of the “WriteLine()” method. This will transmit your data along with extra bytes that indicate an end of line. Instead, use the “Write()” method. I also recommend you use an array of Bytes so you don’t have to use special characters to encode your data. Lastly, try to get it working in the simplest case of sending a hard-coded command packet before you try to generalize to command packets constructed from variables.

- Ben

I realized it might help if I also demonstrate how to calculate bytes data1 and data2 without having to go through the whole decimal-to-binary-to-decimal thing.

The value we’re trying to transmit is an absolute servo position that ranges from 500 to 5500. We can see that our servo position value will never need more than 13 bits to represent it (since position < 2^13 = 8192). We break this value up into two bytes as follows:

byte data1 = position bits 12:7 (six most-significant bits of 13-bit position)
byte data2 = position bits 6:0 (seven least-significant bits of 13-bit position)

In visual basic, we can represent this as:

Dim data1 As Byte
Dim data2 As Byte
Dim position As Integer

position = 1234    'set this as your absolute servo postion
data1 = Int(position / 128)    'a.k.a. " = Int(position / &H80)"
data2 = position And 127    'a.k.a. " = position And &H7F"

'we can then transmit this position to servo # servoNum with:
SerialPort1.Open() 
Dim buffer As Byte() = {128, 1, 4, servoNum, data1, data2} 
SerialPort1.Write(buffer, 0, 6) 

Dividing by 128 and discarding the remainder is equivalent to bitshifting the position value 7 bits to the right. Using the bitwise “And” operator to compute (position And 127) is equivalent to retaining only the seven least-significant bits of position while discarding the rest.

- Ben

Thanks Ben, the second post is working for my application. :bulb: I would like to know how may I change it if I want to add in a [Command 1: Set Speed] variable to the controls?

And do I always need to Open() and Close() Serialport in every command I send. Or I can leave it Open() for any command I send. :?

I’m glad you have the position code working for your application. I have added a sample “set speed” command to my original code (I haven’t tested it, but it should work):

Dim data1 As Byte
Dim data2 As Byte
Dim speed As Byte
Dim position As Integer
Dim buffer As Byte() = {128, 1, 0, 0, 0, 0}

SerialPort1.Open() 

speed = 29    '0 = "instant", 1 = slowest, 127 = fastest non-instant speed
buffer(2) = 1    'set command byte for command 1
buffer(3) = servoNum    'set servo-number byte
buffer(4) = speed    'set data1 byte for servo speed
SerialPort1.Write(buffer, 0, 5)    'send 5-byte command packet

position = 1234    'set this as your absolute servo postion
data1 = Int(position / 128)    'a.k.a. " = Int(position / &H80)"
data2 = position And 127    'a.k.a. " = position And &H7F"
buffer(2) = 4    'set command byte for command 4
buffer(3) = servoNum    'set servo-number byte
buffer(4) = data1    'set data1 byte for servo position
buffer(5) = data2    'set data2 byte for servo position
SerialPort1.Write(buffer, 0, 6)    'send 6-byte command packet

SerialPort1.Close()

As for your second question, you do not need to open and close the serial port for every command you send. Typically I open the serial port in my form’s constructor (or in the method associated with my form’s load event) and I close the serial port in my form’s destructor (or in the method associated with my form’s close event).

- Ben

Thank you very much, Ben. :smiley: The code works well on my project. The project that I am doing is a connection simulator using Hitec HS-81 servo and Servo-8 Serial controller. It simulates USB Plug connection for devices.

By the way, What is the actual usage for [Command5: Set Neutral]. Let’s say the fixture of the USB plug on the moving platform will come loose after prolong usage. I will need to calibrate it’s position again. Do I use this command to adjust it on my software instead :question: And can I reuse your position conversion code to set this value :question:

Very cool, thanks for the picture!

When you set the positions of your servos, you have two options:

  1. Use relative position commands (2 or 3) to set the position of servo relative to the absolute position you have set as “neutral”.

  2. Use absolute position commands (4) to tell the servo where to move in an absolute sense. Neutral and range settings have no effect on absolute position commands.

One advantage for going with option #1 is that you can recalibrate your controller by setting two values (neutral and range). If you go with option #2 and need to recalibrate, you have to go through your code and change the all of absolute values you are sending. If your servo control code isn’t all that complicated, this latter option wouldn’t necessarily be that bad, though.

The code for setting the neutral value would be identical to the code for setting an absolute position, except you’d set your command byte to 5 rather than 4. You can use the absolute-position conversion code I posted earlier to obtain your two data bytes. Then just make sure you set your servo positions with either command 2 (one data byte) or command 3 (two data bytes; you can reuse my absolute-position conversion code for this one, too).

- Ben

***I (Ben) idiocally edited VAG’s original post when I meant to reply to it (huge d’oh), but it went something like:

(I’m very sorry for overwriting your post, VAG. Feel free to repost/re-edit this if I didn’t summarize your original post well)

[Edited by ben to remove incorrect information]

Can you give me more information about what values you were trying to send? When you are using command 3, you are sending a position value that ranges from 0 - 255 (neutal position is represented by 127.5). This is different from setting an absolute position, which ranges from 500 to 5500. You can calculate the data bytes for command 3 as follows (this computation might be a little easier to understand conceptually):

if (position > 127)
{
    data1 = 1
    data2 = position - 127
}
else
{
    data1 = 0
    data2 = position
}

Using command 2 is the simplest since there’s only one data byte, and that data byte is equal to the position value. My suggestion would be to get things working in the following order:

  1. Get command 2 working the way you expect
  2. Use command 5 to change the neutral position and observe the effects on your command 2 calls
  3. Get command 3 working the way you expect (note that command 3 lets you command a servo to move through approximately 180°, which might be a larger range than your servo can handle)

- Ben (a.k.a. that guy who sucks at clicking the right buttons)

Hi Ben, I can get Command 2:Set Position, 7-bit to work with a range from 0 to 127. :slight_smile: But for the Command 3:Set Position, 8-bit, my code don’t seems to work properly. Can you show me an example with Command 3.

I also notice the turning angle for Command 3 is around 90 degree for my HS-81 servo. :frowning: Is this because of the range allowance for Neutral position? Will the angle be bigger for Command 3?

The following code is untested, but it seems like it should work.

Dim data1 As Byte 
Dim data2 As Byte 
Dim speed As Byte 
Dim position As Integer 
Dim buffer As Byte() = {128, 3, 0, 0, 0, 0} 

SerialPort1.Open() 

position = 180
data1 = Int(position / 128)    'a.k.a. " = Int(position / &H80)" 
data2 = position And 127    'a.k.a. " = position And &H7F" 
buffer(2) = 3    'set command byte for command 3 
buffer(3) = servoNum    'set servo-number byte 
buffer(4) = data1    'set data1 byte for servo position 
buffer(5) = data2    'set data2 byte for servo position 
SerialPort1.Write(buffer, 0, 6)    'send 6-byte command packet 

SerialPort1.Close() 

If this code doesn’t work, I’ll want you to conduct a few tests for me:

  1. Tell me exactly what behavior you’re observing and why it seems strange to you
  2. Insert a breakpoint somewhere in the code above and use the debugger to figure out what the values of data1 and data2 are. data1 should always be either 0 or 1, and data2 should always be < 128. VB 2005 has a very powerful debugger that can help you figure out exactly what values you’re sending
  3. Try running the code for position values < 128 and see if you get the same behavior as you do when you’re using Command 2 for those same position values. If something strange happens when you transition from position = 127 to position = 128, try to characterize the weirdness so I can get a better understanding of what’s going on.

From the Command 0 portion of the user’s guide:

So as you can see, the default range setting gives you a turn angle of around 90 degrees when using 7-bit position commands. You can extend this to approximately 180 degrees for 7-bit commands if you double the range (set the range value to 30).

- Ben

In a previous post I initially responded to your “how is the neutral value stored” question with an incorrect response. I have since edited that post. The neutral value is just stored in RAM, so it reverts back to its default value of 3000 every time the servo controller is reset.

If you are using a computer to communicate with your servo controller, the easiest approach is probably to just use absolute position commands (command 4). You can define in your code a variable called “neutral” to hold the absolute position you consider to be the neutral position. Then use command 4 to send absolute positions equal to:

relative_position + neutral, where relative_position is an integer between -2500 and 2500 that determines where the servo moves relative to your defined neutral position

Then you only have to change one value in your code to shift the neutral position of your servos. Does this make sense?

- Ben

Thanks Ben, the example you show me is working with greater range. :slight_smile: I think I will use Command 4:Set Position, Absolute and calculate my relative position in my application. As my project is always using PC to perform the controls. :wink:

Initially, I want to use command 5 as I have multiple units of the USB connection simulator which required calibration. Since the calibrated value is not stored into EEPROM itself. It’s not necessary for me to use Command 5, as the value formats to send using Command 3 is quite different from the absolute value. Anyway, this discussion helps me fully understand the use of Pololu servo controller. May be it will be usefull for my next Robotic Arm project. :smiley:

Thank you,
- Peter