Friday, March 6, 2020

Oxeye - A daisy chain servo controller

I finally decided to try out a robot arm. I have been looking at getting one (either that or one of those biped robots) for a while. I was also looking forward to building a controller for it from scratch.

One interesting problem is how to control several servos. Most solutions end up with this octopus like controller where wires from all the servo motors snake to a single controller board. This board has multiple servo outputs. But never enough. A much better way would be to use a bus structure.

Each servo will have its own little controller. Each controller will have an input and output port. Or the other way around – it is just a bus. Each controller is connected to the next one in a daisy chain. The first one is connected to the controller and the last one has one port unused. Depending on ease of wiring, in some cases a Y cable can connect multiple controllers to a single one. This should make the wiring a lot simpler – no tangle of connections to a single controller. It also simplifies the controller - there is only one port. This is roughly how smart servos work as well.

So how do they all talk on the bus? I chose a simple and crude option – a serial port. Multi-drop but nothing fancy. No RS-485 or using a single line for send and receive. No I2C, SPI or CAN. This means the servo controller could be a laptop with a USB-to-serial cable. Or an SBC. Or an ESP32. No specialised main servo controller! It can’t get any simpler than that.

All single servo controllers float their Tx line and enable them only when interrogated by the main controller. All this means the micro does not need any supporting chips either. The main controller does not need to worry about talking to multiple devices individually. It just sends out commands and expects a response.

The request-response protocol between the host and the servo controllers is based on an AT command set.

AT Command Set

I have used AT command set in the past. So often that I have a java program that generates C code implementing the state machine to process the input. Speed is not a requirement in this case. The servo controller can be tested and debugged using a simple terminal program. The protocol is geared towards a terminal. The command is terminated by a carriage return (0x0d). The servo controller responds with a line feed (0x0a), the response data and a carriage return (0x0d) and line feed (0x0a).

Command Response Description
Parameters
{m} – motor no. from 0 to 9
{ppp} – position as S99 where S is a + or –
{nn} – a positive value 99
Action Commands
ATM{m} OK Check if motor {m} exists
ATM{m}V v1.0 Get version no.
ATM{m}P={ppp} OK Move motor {m} to position {ppp}
ATM{m}F={ppp} OK Set future position {ppp} for motor {m}
ATM{m}T={nn} OK Set future position time {nn} for motor {m}
ATMP None Set future moves for all motors now
ATM{m}P? Ppp Get current position
Program Mode Commands
ATM={m} OK Set motor to an address of {m}
Config Commands
ATM{m}C={ppp} OK Set centre offset for motor {m}
ATM{m}H={ppp} OK Set high limit {ppp} for motor {m}
ATM{m}L={ppp} OK Set low limit {ppp} for motor {m}
ATM{m}C? Ppp Get centre offset for motor {m}
ATM{m}H? ppp Get high limit for motor {m}
ATM{m}L? ppp Get low limit for motor {m}
ATM{m}S={nn} OK Set max speed {nn} for motor {m}
ATM{m}A={nn} OK Set ramp up/down acceleration {nn} for motor {m}
ATM{m}S? nn Get max speed for motor {m}
ATM{m}A? nn Get ramp up/down acceleration for motor {m}
Future Commands
    Go limp
    Get current data

Using Action Commands

Each motor in a daisy chain has an address from 0 to 9 (parameter {m}).The motor position ranges from -99 to +99 (parameter {ppp}).To move motor 2 to position +20, use ATM2P=+20. Sometimes, every motor in the chain has to be positioned differently, all at the same time. Each motor is set to a future position. To set motor 2 to position -15 but not right away, use ATM2F=-15. Once all motors have been set to their future position, send ATMP. This is the cue for all motors to move to their correct positions. This command is executed by every controller on the bus and is one of the few without a response, for that reason. If you set a time period for each motor, say motor 2 using ATM2T=30, then the move will happen over the period.

Using Config Commands

The centre-zero position of a motor may not be where you expect it. You can set it using a config command. To set motor 3 so that the centre position is -6, use ATM3C=-06. The extreme limits of the motor may cause problems and safety dictates that it only moves within a subset of positions. To set the extreme limits for motor 4 between -60 and +65, use ATM4L=-60 and ATM4H=+65. Even if the position is set outside these limits, the servo controller will move only up to the safe limit.

When I tried setting the position of a servo placed on my desk, the jerk made it fall over! A better option is to set a ramp up and ramp down acceleration level along with a max speed. To set motor 1 to a speed of 55 and acceleration of 7, use ATM1S=55 and ATM1A=07.

The value of any of these parameters can be retrieved by using the right ? command. To get the centre position for motor 1, use ATM1C? and read the response.

Setting the Motor Address

Using these commands is all very well but how do you set the address for each motor for the first time? Glad you asked. All controllers are set to a default address of 0. Each controller has a pushbutton. To set a particular motor to an address of 3, send out the command ATM=3. The LED on all motor controllers will go from a slow blink to a fast blink. Motors with an address of 0 will blink faster (Program me! Program me!). Press the pushbutton on one of the motors and it will be set to that address and send an OK response, letting the host know that a motor has accepted the address. If at any point, the host wants to abort this mode, send out a normal command and all controllers will come out of program mode. To program each motor in a chain, send out its address and push the button on the right motor till all the motors have been assigned addresses.

A neat implementation would be for all motors in the chain to be assigned addresses. Then the host programs each address while indicating on the screen, the motor it wants to have that address. The user presses the pushbutton. That motor responds with an OK and the host moves to the next motor and so on till all motors have been assigned an address. An address of 0 is to be avoided as any new motor in that chain will wake up with that address.

Ideally, any motor with an address of 0 will either ignore all commands or execute them without sending a response - the former is preferred. In this case, this will be handled by the host, not assigning any motor that address and hence never sending out commands on that address. Note that this is not the same as a broadcast command as multiple motors can respond and their responses could clash.

Future Commands

One command I would like to implement in the future once I have hardware to support it is putting it in a 'go limp' mode. If power to the motor is shut off, you can twist it into the desired position. then you should be able to query the position and use that as a reference. This will need some way in the hardware to cut off power to the motor and tap the internal servo potentiometer to get the position. Another command would be to monitor the current consumed by the motor to detect motor stall. This will need hardware to sense the current. As the request-response does not allow the slave to initiate comms, the master will have to periodically interrogate each motor to find the max. current in the last n seconds.

No comments:

Post a Comment