The Protocol¶
Published on 2023-04-02 in Sly Bug.
To talk to a smart servo you use a half-duplex UART protocol – meaning both RX and TX is done over the same wire, just not at the same time. The microcontroller sends a command, and then releases the pin and switches it to input to let the servo reply. Many microcontrollers have a special UART mode for this, but since it’s not supported in CircuitPython, for now I just connected the RX and TX pins together with 10kΩ resistors (to avoid having a short when both sides try to speak at the same time), and ignoring the looped back data whenever we send anything. I decided to use a raspberry pi pico for this, so later on I may implement proper half-duplex UART with the PIO assembly. For now this will do.
The datasheet for SCS0009 specifies the baudrate for the serial as 1000000 – it seems strange, not being a power of two or anything close, but it seems to work.
Now, there is a higher level protocol on top of that serial communication. We send and receive frames of data, and those frames have a well defined structure. An outgoing frame looks like this:
The first two bytes of a frame are 255 255 – this is to mark the start of the frame, but also to safeguard against missing the beginning of the transmission.
The third byte is the ID of the servo – which can be from 1 to 254, with 254 being the broadcast ID that addresses all servos at once. Note that it can’t be 255, so you can’t confuse it with the beginning of the frame.
The fourth byte is the length of the data that follows. This is so that you know how many more bytes to read from the serial connection before switching to output to send your reply, and so that you don’t need to use timeouts for this.
The fifth byte for an outgoing frame is the command. It can be 1 for ping, 2 for read, 3 for write, 4 for regwrite, 5 for action, 6 for reset and 83 for syncwrite. We will talk about those commands later.
Then you send the parameters for the command.
Finally, you send a checksum, calculated from the whole frame.
Once you send your frame, you switch to receiving, and wait for a reply frame. The reply frame has a slightly different, albeit similar structure:
First you get the two 255 255 bytes.
Then the ID of the servo that is replying.
The length of following data.
The status of the servo. This is 0 if everything is fine, but contains flags for different error conditions, like overheat or overload.
Then come any parameters of the reply.
And finally a checksum.
This protocol is share by all of the FeeTech servos, and probably many other smart servos out there. The examples in the documentation are invaluable for implementing the details, because they are not immediately clear from the descriptions.
Now that you have the higher level protocol, you will need one more thing: the register map of the particular servo you want to control. This is also part of the documentation, and it’s different for the different models of the servos. The most interesting registers for the SCS0009 are:
0x2A tartget location
0x2C running time
0x2E running speed
0x38 current location
0x3C current load
There are of course many more, including settings that are saved in the internal EPROM for the servo ID, its PID parameters or safety protections. We will not need those for now.
Now, let’s talk more about commands.
Ping is simple – it just makes the given servo send a response. It can be useful to test the communication, check the ID of the servo (if you only connect one and send a ping to the broadcast ID), or get its status.
Read and write let you get or set the content of the registers. The read takes the number of the register and the number of bytes to read from it (some registers are 2 bytes long), and write takes the number of the register and the values to write. Easy.
The regwrite is interesting, because it lets you schedule a change in the register without actually committing it yet. You can do it for several servos, and then when you are ready, confirm the change by sending the action command to the broadcast ID. This is useful when you need to move several servos together at once.
Reset is self-explanatory. It resets the servo to the settings it has saved in its EPROM.
Finally, syncwrite is a command that lets you send to the broadcast ID a big chunk of data, and each servo will fish from it the data for its registers. It’s faster then the regwrite+action way, but basically serves the same purpose – moving all the servos together at the same time.
That is pretty much all we will need. I will be making a CircuitPython library with all of this stuff next.
Special thanks to Abby from FeeTech for sending me all the necessary documentation! I would never figure it all out myself.