RC servo motors with linux

You can use RC servo motors with any Arduino. Even with a Raspberry Pi, no need for special hardware – just connect the signal wire to a free GPIO and send a short every 20 ms or so, where the position of the motor is proportional to the length of the pulse (usually between 0 and 2 ms).

A “normal” computer doesn’t have GPIO pins so some kind of controller is needed. I have a Pololu Mini Maestro that can control several RC servo motors at once through USB or UART – in my case up to 24 motors.

It works great with linux – the Maestro Control Center is a .Net Framework application that runs fine with Mono but after initial setup a simple bash script is enough.

So the fastest (not the best) walk through:

Connect the Maestro with an USB cable and run ‘dmesg’ to see if it was recognized – two virtual serial ports should be created:

[ 1611.444253] usb 1-3: new full-speed USB device number 7 using xhci_hcd
[ 1611.616717] usb 1-3: New USB device found, idVendor=1ffb, idProduct=008c
[ 1611.616724] usb 1-3: New USB device strings: Mfr=1, Product=2, SerialNumber=5
[ 1611.616728] usb 1-3: Product: Pololu Mini Maestro 24-Channel USB Servo Controller
[ 1611.616732] usb 1-3: Manufacturer: Pololu Corporation
[ 1611.616735] usb 1-3: SerialNumber: 00094363
[ 1611.646820] cdc_acm 1-3:1.0: ttyACM0: USB ACM device
[ 1611.649542] cdc_acm 1-3:1.2: ttyACM1: USB ACM device
[ 1611.651109] usbcore: registered new interface driver cdc_acm
[ 1611.651112] cdc_acm: USB Abstract Control Model driver for USB modems and ISDN adapters

The first one (‘/dev/ttyACM0’) is the ‘Command Port’ and the second one (‘/dev/ttyACM’1) is the ‘TTL Serial Port’.

We download and extract the Maestro Servo Controller Linux Software from Pololu site. To run it we need Mono and libusb, the (excellent!) User Guide gives this command:

sudo apt-get install libusb-1.0-0-dev mono-runtime libmono-winforms2.0-cil

I already had libusb and with current Ubuntu (17.04) the Mono packages are different:

Package libmono-winforms2.0-cil is not available, but is referred to by another package.
This may mean that the package is missing, has been obsoleted, or
is only available from another source
However the following packages replace it:
 mono-reference-assemblies-2.0 mono-devel

so I ran instead

sudo apt install mono-devel

It will work but it will give an access error to the device. We can follow the manual and create an udev rule or just use sudo:

sudo mono MaestroControlCenter

We will probably find our Maestro ruuning in default serial mode, i.e. ‘UART, fixed baud rate: 9600’ but we change that to ‘USB Dual Port’ so we can control our servos directly from the shell without the Maestro Control Center:But now that we are here let’s also see Status:

We can control our servos from here if we want.

Now back to the command line – in ‘USB Dual Port Mode’ we can control our servos sending commands to the Command Port (i.e. ‘/dev/ttyACM0’). There are several protocols available, let’s just see the Compact Protocol:

echo -n -e "\x84\x00\x70\x2E" > /dev/ttyACM0

Four bytes are used:

  • the first byte is always “84h” and it means we are using the “set target” command
  • the second byte is the channel number so my Maestro can accept a number between 0 and 23
  • the third and fourth bytes is the length of the pulse but in a special format:

The value is in quarters of microseconds. So for a 1.5 ms (1500 µs) we will use 6000. Usually this is the middle (center) position of the servo.

Also «the lower 7 bits of the third data byte represent bits 0–6 of the target (the lower 7 bits), while the lower 7 bits of the fourth data byte represent bits 7–13 of the target». So

So 6000d = 1770h

The third byte is calculated with a bitwise AND:

1770h & 7Fh = 70h

And the fourth byte is calcultated with a shift and an AND:

1770h >> 7 = 2Eh

2Eh & 7Fh = 2Eh

So 1500 µs is represented as 70h 2Eh

Pololu makes life easier with a bash script, ‘maestro-set-target.sh’:

#!/bin/bash
# Sends a Set Target command to a Pololu Maestro servo controller
# via its virtual serial port.
# Usage: maestro-set-target.sh DEVICE CHANNEL TARGET
# Linux example: bash maestro-set-target.sh /dev/ttyACM0 0 6000
# Mac OS X example: bash maestro-set-target.sh /dev/cu.usbmodem00234567 0 6000
# Windows example: bash maestro-set-target.sh '\\.\USBSER000' 0 6000
# Windows example: bash maestro-set-target.sh '\\.\COM6' 0 6000
# CHANNEL is the channel number
# TARGET is the target in units of quarter microseconds.
# The Maestro must be configured to be in USB Dual Port mode.
DEVICE=$1
CHANNEL=$2
TARGET=$3

byte() {
 printf "\\x$(printf "%x" $1)"
}

stty raw -F $DEVICE

{
 byte 0x84
 byte $CHANNEL
 byte $((TARGET & 0x7F))
 byte $((TARGET >> 7 & 0x7F))
} > $DEVICE

So instead of echo’ing “\x84\x00\x70\x2E”  to the Command Port we can also use

./maestro-set-target.sh /dev/ttyACM0 0 6000

So now we can control a servo with common bash commands. For example this script makes the servo rotate from left to right then back in 20 increments then repeats it 4 times:

#!/bin/bash

sleep 5
for j in `seq 1 5`;
do
  for i in `seq 4000 200 8000`;
  do
    ./maestro-set-target.sh /dev/ttyACM0 0 $i
    sleep 0.1
  done
  for i in `seq 8000 -200 4000`;
  do
    ./maestro-set-target.sh /dev/ttyACM0 0 $i
    sleep 0.1 
  done
done

So we can now control up to 24 servo motors.

So let’s control a special servo motor, more related to my hobby:

That’s a 4DBrix Standard Servo Motor, a motor that 4DBrix sells for controlling LEGO train or monorail models. They also sell their own USB controller but since it is in fact a pretty common RC micro servo inside a 3D printed LEGO-compatible ABS housing we can also use the Maestro:

The same script:

These motors work fine with 4DBrix LEGO-compatible monorail parts:

But better yet… the Maestro also works fine with ev3dev:

I now realize it wasn’t very clever to show motors rotating slowly when the monorail switches only have two functional states so this new video looks a little better, with the three 4DBrix servo motors and the LEGO EV3 medium motor changing the state of the monorail switches every second:

So I can now control 24 RC Servo motors with my MINDSTORMS EV3.

Even better: we can add several Maestros through an USB hub… so why just 24 motors? With 126 Mini Maestros 24ch and an USB hub we could use 3024 motors 🙂

Relés USB

Adquiri um módulo USB-RLY16 que permite comandar 8 relés através de uma porta USB. A ideia original é controlar as minifiguras LEGO em palco pelo que com 8 relés poderei controlar 4 micromotores LEGO de forma independente e em ambos os sentidos (os Xutos&Pontapés são 5 pelo que 2 deles terão de partilhar o mesmo motor ou terei de arranjar outra forma para controlar um quinto motor).

O módulo é baseado num circuito FT232RL que emula uma porta série através da ligação USB. Em Linux o suporte a este tipo de devices já vem incluído no kernel sendo associado um device do tipo ‘/dev/ttyUSBn’. No meu PC com Ubuntu 14.04 funcionou à primeira, tal como no EV3 com ev3dev e estou 100% convencido que com Raspberry Pi e Raspbian acontecerá o mesmo.

São necessárias duas alimentações: uma para o accionamento dos relés e outra (ou outras) para os circuitos controlados pelos relés. Teoricamente podem ser as mesmas mas optei por separá-las:

  • uma pilha PP3 com ficha de 2.1mm para o accionamento dos relés
  • uma pilha PP3 em caixa LEGO para os motores

Nota: a pseudo-documentação afirma serem necessários pelo menos 12V/500 mA para o accionamento dos relés mas dei-me bem com os 9V de uma pilha alcalina PP3.

Ligando ao EV3:

$ dmesg
(...)
usb 1-1.4: new full-speed USB device number 6 using ohci
usbcore: registered new interface driver usbserial
usbcore: registered new interface driver ftdi_sio
usbserial: USB Serial support registered for FTDI USB Serial Device
ftdi_sio 1-1.4:1.0: FTDI USB Serial Device converter detected
usb 1-1.4: Detected FT232RL
usb 1-1.4: Number of endpoints 2
usb 1-1.4: Endpoint 1 MaxPacketSize 64
usb 1-1.4: Endpoint 2 MaxPacketSize 64
usb 1-1.4: Setting MaxPacketSize 64
usb 1-1.4: FTDI USB Serial Device converter now attached to ttyUSB0

Para comunicar com a placa é necessário configurar a porta série virtual (/dev/ttyUSB0) para comunicar a 19200 baud, 8 databits, 2 stop bits, sem paridade (19200 8N2) e sem hardware nem software handshaking. No Ubuntu usei o ‘puTTy’ mas como requer ambiente gráfico no ev3dev optei pelo ‘minicom’:

$ apt-get install minicom

O minicom tem um modo de setup:

$ minicom -s

depois de configurado (19200 8N2, no flow control) se carregarmos na tecla ‘d’ accionamos todos os relés e na tecla ‘n’ desactivamos todos (confirmem numa tabela ASCII que correspondem aos códigos decimais 100 e 110). Confirmado o funcionamento podemos sair (‘Control+A’ seguido de ‘Q’), a porta virtual permanece configurada até ao próximo reboot.

Podemos agora enviar comandos directamente da shell:

$ echo d > /dev/ttyUSB0
$ echo n > /dev/ttyUSB0

Em python podemos comunicar directamente com a porta virtual através da biblioteca pyserial (http://pyserial.sourceforge.net/):

$ apt-get install python-serial
import serial
from time import sleep

port = serial.Serial("/dev/ttyUSB0", baudrate=19200, bytesize=8, parity='N', stopbits=2, timeout=None, xonxoff=0, rtscts=0)
port.write("d");
sleep(1);
port.write("n");
sleep(1);
port.close()

O video acima corresponde ao script python abaixo:

import serial
from time import sleep


# da esquerda para a direita:
# motores A, B, C e D
#
# motor A e/f o/p
# motor B g/h q/r
# motor C i/j s/t
# motor D k/l u/v
#

def relays_all_off():
  port.write("n");

def relays_all_on():
  port.write("d");

### Motor A ###
def motor_A_Right():
  port.write("ep");

def motor_A_Left():
  port.write("of");

def motor_A_OFF():
  port.write("op");

### Motor B ###
def motor_B_Right():
  port.write("gr");

def motor_B_Left():
  port.write("qh");

def motor_B_OFF():
  port.write("qr");

### Motor C ###
def motor_C_Right():
  port.write("it");

def motor_C_Left():
  port.write("sj");

def motor_C_OFF():
  port.write("st");

### Motor D ###
def motor_D_Right():
  port.write("kv");

def motor_D_Left():
  port.write("ul");

def motor_D_OFF():
  port.write("uv");


port = serial.Serial("/dev/ttyUSB0", baudrate=19200, bytesize=8, parity='N', stopbits=2, timeout=None, xonxoff=0, rtscts=0)

motor_A_Left();
motor_B_Left();
motor_C_Left();
motor_D_Left();
sleep(1);
motor_A_OFF();
motor_B_OFF();
motor_C_OFF();
motor_D_OFF();
sleep(1);
motor_A_Right();
motor_B_Right();
motor_C_Right();
motor_D_Right();
sleep(1);
motor_A_OFF();
motor_B_OFF();
motor_C_OFF();
motor_D_OFF();
sleep(2);

motor_A_Left();
sleep(1);
motor_A_OFF();
sleep(1);
motor_B_Left();
sleep(1);
motor_B_OFF();
sleep(1);
motor_C_Left();
sleep(1);
motor_C_OFF();
sleep(1);
motor_D_Left();
sleep(1);
motor_D_OFF();
sleep(1);

port.close();