Dennis Salzner - 2015-04-06-Mo-ServoMotorPCA9685PwmDriverFT232RLbitbangI2CPythonFlaskWebserver

<< Back Home

Fig.1: Driver board

Fig.2: Pulse width modulation

Fig.3: Web interface

Controlling servo motors using the PCA9685 PWM driver via I2C from a Python Flask Webinterface using an FTDI FT232RL in BitBang-Mode

An easy method for controlling servo motors from your computer's USB port

What?

I've adapted the I2C script for reading the compass module (see here for details on I2C and FTDI FT232RL bit-banging), so that I can now use it to talk to a PCA9685 PWM driver IC. This enables me to control pulse width modulated servo motors (motors that can be set to a specific angle), which are connected to the PCA9685, from Python - very easily. From there it's easy to add a Webinterface with sliders that control the motors.

When?

This is what I was originally heading for when I started working on the compass module. I wanted to control servo motors for a four-legged walking robot. Reading the compass module is easier than controlling the PWM driver, so I decided to do that first and then use the functioning I2C code and adapt it for the pwm controller.

Why?

Being able to easily control servo motors from Python will be very useful. I have a number of projects planned that use servo motors. One of which is a walking robot that will use servo motors to control its legs. Another idea is to track a moving object by panning and tilting a webcam.

Background?

FTDI, bit-banging and I2C

(see here for details on I2C and FTDI FT232RL bit-banging),

Servo motors

A servo motor is a DC motor with added mechanics and electronics. The added mechanics are a gear box, for increasing the motors torque, and a limit, to constrain the range it can turn. Typical servo motors can turn within a range of +90 to -90 degrees. The added electronics are a potentiometer, which is connected to the motor. So when the motor rotates, so does the potentiometer. By measuring the potentiometer one can determine the angle at which the motor has currently rotated. An additional circuit inside the servo receives a "command" to what angle it should turn and it then rotates the motor until the potentiometer read-out matches that angle.

PWM modulation

Most servos are PWM modulated. What this means is that this "command" to the servo's circuit board is a pulse-width modulated (PWM) signal. A PWM signal is a digital signal which is normally on logic 0. It then ramps up to 1 for a specified amount of time and then back down to 0 (see Fig.2). The specified amount is the pulse-width. This pulse-width has to be set accordingly to the desired angle we want the motor to rotate to.

PWM driver

Now, I can't accuratly control the width of a signal from the FTDI FT232RL in BitBang-mode, because it is far too slow and inaccurate. But I can make it speak I2C in software - just like I did for the compass module. There are PWM driver integrated circuits which receive commands via I2C and handle the pulse-width-modulation themselves. So all I have to do is tell this chip, via I2C, which pulse-width I want it to modulate. This will enable me to control the servo motor.

How?

Connecting it

The connection between the FTDI FT232RL break-out board and the PCA9685 break-out board is as you would expect: Vcc and V+ go to the FTDI's Vcc. Ground goes to Ground. And the V+ and GND in the center of the PCA9685 break-out are hooked up in the same way - V+ goes to the FTDI's Vcc and Ground to its Ground. I am in fact running the servos off of the usb ports power.

This works for me using either one TowardPro MG996R or one TowerPro SG90 servo - both don't need too much power. But hooking up 16 of those MG996Rs will definitely cause trouble. But USB-Ports are in fact protected - this depends on your computer's mainboard. Generally, you do need to go to great lengths to fry them, but they will eventually shut down.

The PCA9685's I2C SCL line goes to TXD on the FT232RL and SDA goes to RXD. Additional 1,8k pull-ups go from RXD and TXD to Vcc.

Servo Library

I've adopted the I2C code I wrote for the compass module in order to speak to the PCA9685. This code is used by a web interface, so it has to be thread-safe. This is because the webserver will start a new thread for each request and so access to variables which are outside of the functions is not guaranteed.

The easiest way to make the code thread-safe is to make it free of "side-effects". That is to remove all variables that are outside of functions and pass everything the functions needs to the function via parameters. By doing this the code will have no "state" (or memory) of its own and every function call will work no matter in what order or what else is happening.

Of course accessing the FTDI chip locks the USB device associated with it. So we need to make sure we grab and then release it in every call. Other calls which try to grab the device, when another thread already has it, will fail silently. But calling the function again will work as soon as the previous thread has finished - so this is okay for now.

See more
1
2
3
4
5
6
7
# [...]
from pylibftdi import BitBangDevice
# [...]
def init(chipAddresse, freq):
# [...]
def move(chipAddresse, port, duration):
# [...]

The code can easily be tested in the Python interactive shell by issuing "python" in the terminal/command prompt (Linux: may need root privilidges to access the USB device) and enter these commands:

 1
 2
 3
 4
 5
 6
import servo
servo_chipAddresse = 0b10000000
servo_freq = 60
servo_port = 0

servo.init(servo_chipAddresse, servo_freq)

This is also how I found out what values I need for my servos - by changing the duration and measuring the PWM signal on the oscilloscope to match the datasheet of the motors - but the values differ. Especially on the cheap SG-90 servos (these can even break themselves, if they attempt to rotate too far).

servo.move(servo_chipAddresse, servo_port, 180) # TowardPro MG996R min - 600us
servo.move(servo_chipAddresse, servo_port, 470) # TowardPro MG996R max - 1800us

servo.move(servo_chipAddresse, servo_port, 130) # TowerPro SG-90 min - 520us
servo.move(servo_chipAddresse, servo_port, 650) # TowerPro SG-90 max - 2500us

Web-Interface

I stumbled across Python Flask the other day and found that it is an excellent way to quickly build user interfaces. It starts a small webserver and loads up HTML templates from a subdirectory, which are sent to the web browser, when it accesses the server. Inside these templates is code to communicate back with Python. So you can have a button on the website that runs Python code in the background once its clicked on - perfect for my needs.

This also gives me the ability to drop the code - as is - onto an embedded network-enabled computer and be able to control it from outside. And I don't need to write seperate client and server-side code. Plus I can open it from my smartphone.

Big-banging: No microcontroller code. Flask: No client/server code and no custom protocols. All in all: No nothing - just one short Python script (and one for the servos).

Python Flask requires a special folder structure:
app
app/main.py			#  code for Flask, loads the webserver 
app/servo.py			#  code for controlling the servos
				
app/static
app/static/jquery-ui-1.11.4	# JQuery library. This is what I use for user interface elementes such as a slider
				
app/templates
app/templates/steuerung.html	# the template for the servo control webpage
The file servo.py is the code from above. This is main.py:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# D.Salzner 2015-04-05-So

from flask import Flask, render_template, request, url_for
app = Flask(__name__)

import servo
servo_chipAddresse = 0b10000000
servo_freq = 60
servo_port = 0

servo_max = 650
servo_min = 130

@app.route('/')
def steuerung():
	servo.init(servo_chipAddresse, servo_freq)
	return render_template('steuerung.html')

@app.route('/slider_changed', methods=['POST'])
def slider_changed():
	print "[] slider_changed"
	wert = request.values.get("wert")
	setzen = ((((servo_max - servo_min) / 180.0) * float(wert)) + servo_min)
	servo.move(servo_chipAddresse, servo_port, int(setzen))
	
if __name__ == '__main__':
	app.run()

I won't go into too much detail on how it works (out of scope), but its fairly basic. Once the web browser accesses the webpage Flask will initialize the servos and send the html template back. Within this template is a slider, which automatically initiates an HTTP Post request to the server. This is caught in the "slider_changed" function. Then the servos are moved accordingly.

Now the slider goes from 0 to 180 degrees, so I need to adapt the range to that of the servos, but that's really all there is to it.

The template looks like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<html>
	<head>
		<title>Steuerung</title>
		<link rel="stylesheet" href="static/jquery-ui-1.11.4/jquery-ui.css">
		<script src="static/jquery-ui-1.11.4/external/jquery/jquery.js"></script>
		<script src="static/jquery-ui-1.11.4/jquery-ui.js"></script>
		<script type=text/javascript>
			$(function() {
				$( "#slider" ).slider({
					min: 0,
					max: 180,
					step: 1,
					change: function(event, ui) {
					//slide: function(event, ui) {
						//alert("slider triggered");
						$("#textfield").val(ui.value);
						$.post("/slider_changed", {wert: ui.value} );
					}
				});
			});
		</script>
	</head>
	<body>
		<div id="slider"></div>
		<input id="textfield" type="text" value="0"></input>
	</body>
</html>

(I just syntax highlighted html in html...)

And then I can run this by issuing:

python main.py

It will open a webserver on port 5000, which I can access from my browser under the url http://127.0.0.1:5000/. Moving the slider on that page moves the servo.

Progress?

All is up and running (see Video).