Gaugette

Building gauges and gadgets with Arduinos, Raspberry Pis and Switec stepper motors.

OpenXC - Hack Your Car

Permalink

OpenXC is a very cool initiative involving Ford Motor Company and Bug Labs aimed at creating an open development ecosystem for interfacing with in-car electronics. Looks like a pretty great project.

OpenXC Switec X25-based “Retro Gauge”

One of the OpenXC projects featured on their website is a retro gauge based around Switec X25 type micro-steppers.

They have published Eagle schematics for a gorgeous little combination analog/digital instrument gauge. Their in-gauge PCB cleverly incorporates an Arduino Pro Mini. They have also published STL files for a 3D-printable housing.
This all looks very slick to me.

Their firmware uses our very own Switec X25 library.

You can find detailed documentation and resources at their github repository.

Raspberry Pi Time Clock

Permalink

Gaugette Time Clock

This build combines a Raspberry Pi with a rotary-encoder, an RGB LED and an OLED character display to create a time clock that logs my time on tasks directly to a Google Docs spreadsheet.

Motivation

Whenever I have to record time against projects, I find it really hard to diligently keep my time records up to date. Maybe with a purpose-built time clock I will keep better records? Hey, it could happen!

Overview

The off-the-shelf components:

I used a common cathode LED from Sparkfun. You could use Adafruit’s common anode equivalent with minor code changes.

The theory of operation is pretty simple:

  • at start-up, it pulls a list of jobs from a Google Docs spreadsheet,
  • rotating the knob scrolls through the list of jobs,
  • clicking the knob logs a start time or end time in the spreadsheet.

The Case

The top of the case is made from a single block of wood.

The bottom of the block has been hollowed out to house the RPi board. The RPi sits in a plastic carriage that screws to the bottom of the block. I have not provided access to the HDMI, audio or video ports on the RPi since I’m not going to use them.

The carriage for the Raspberry Pi was designed in OpenSCAD and printed on a Makerbot Replicator. The RPi board doesn’t have mounting holes, so the carriage has edge-clips to grasp the board. The posts at the corners were originally intended to screw the carriage to the block, but I found that to be impractical, so I added the two tabs at the bottom and routed out matching recesses in the block. I left the posts there because they make the carriage look a little more interesting. Or because I was too lazy to remove them, I’ve heard it both ways.

The OpenScad sources and STL files for the base are available from Thingiverse.

The Circuit

I’m using two of the ground pins originally documented as Do Not Connect (DNC). The extra grounds are really convenient, and Eben has publicly committed to keeping these available in future board revisions.

It’s worth pointing out that I deliberately selected GPIO pin 5 for the push button because of the Raspberry Pi Safe Mode feature. If you have a recent firmware release, you can boot your RPi in safe mode by holding the knob down (shorting pin 5 to ground) when you power up. In truth my intention here is not so much to make safe mode available (I’ve never needed it) but to make sure that pin 5 is not unintentionally shorted to ground at boot time, as it could be if you used it for one of the quadrature encoder inputs. Yep, that happened. After I upgraded to the version of firmware that supports safe mode my box stopped booting. Lesson learned; be careful with pin 5, or disable safe mode by adding avoid_safe_mode=1 to /boot/config.txt.

For most of the GPIO connections I cut pre-terminated Female Female jumper wires in half and soldered the cut end to the component. I already had header pins on the SSD1306 OLED module, so I used 3” premade cables from Little Bird Electronics.
It is crucial to keep wiring short and well insulated so that it will all pack in neatly and without shorts when the case is closed up.

RGB LED Indicator

For this device I wanted a large, diffuse and interesting state indicator built around an RGB LED. I use the WiringPi soft PWM library to drive the LED, and the py-gaugette RgbLed class makes it easy to do animated colour transition loops. My first prototype was made from the back of a GU10 light bulb. The bulb glass is thick and diffuses the light nicely, and I thought the terminals would make cool capacitive switches.

I liked that a lot, but ultimately settled on a long oven light which had a more compact footprint. It gives elegant curving internal reflections which look quite nice.

When I cut the metal tip of the bulb off with a Dremel I expected to be able to remove the burned-out filament, but discovered this bulb (like most others I would guess) is made with an inner glass plug that encases the filament wires and seals the bulb. It isn’t easily removed, but has a hollow neck just big enough to receive an LED. So the filament stays. Maybe I should have used a new bulb!

I trimmed the red, green and blue leads on the LED quite short and soldered a 270Ω current-limiting resistor to each one. This keeps the resistors tucked up away from the board. I then added connector wires to each lead, pushed the LED up into the bulb neck and pumped some hot glue in to keep it all in place.

I’ve seen advice suggesting I should have selected different values for the resistors to to get optimal white balance. I didn’t bother. Colour balance is fine. I aint bovvered.

Rotary Encoder

I documented the encoder library in a previous post. I’m using the py-gaugette RotaryEncoder.Worker class to poll the encoder GPIOs in the background which keeps the application code very simple.

128x32 SSD1306 OLED Display

I’ve written about using these great little 128x32 OLEDs from Adafruit before. Mounting it in the block was a challenge. I used a router on the inside of the case to thin the material down to just a few mm over an area big enough to place the PCB. I then cut a rectangular hole for the display using a Dremel, file and scalpel, taking great pains not to crack the thin wood. I couldn’t see a practical way to use the mounting holes so I hot-glued the board in place. I positioned it with the display powered up showing a test pattern so I could line up the active part of the display with the hole.

The bezel was printed on a Makerbot Replicator and painted with Plaid brand copper Liquid Leaf. I’ve been looking for a metallic paint that wouldn’t dissolve the ABS. Liquid Leaf is xylene-based, which should be safe for ABS, although maybe not so safe for humans.

The effect of metallic paint on the printed surface is interesting; it highlights the individual filaments in the print. I like it. It would be possible to reduce the relief by brushing the surface with acetone before painting, but I think the sharp relief is good for what I’m doing here.

Cooling

It occurred to me fairly late in this build that I hadn’t provided for any air flow to help cool the processor. That got me to wondering, then to worrying… am I cooking my Pi? And is there no end to the bad pie puns?

Fortunately a recent firmware update provided a tool that allows us to measure the CPU temperature in code, so I did a little experiment. I recorded the temperature using vcgencmd.

while :; do /opt/vc/bin/vcgencmd measure_temp; sleep 3; done

I ran this loop from a cold boot for 25 minutes, first with the top off, then again (after letting the system cool down) with the case closed up tight. For the record the ambient temperature in my office was around 26°C.

The results show that the closed box adds about 4°C to the CPU temperature at idle. I tried removing the bulb from the centre of the cover to allow hot air to be convected away, but that made no measurable difference in temperature.

These temperatures were taken at idle. My application code runs around 20% CPU utilization. With application running and the lid off the temperature settles in around 46°C, and with the lid on at around 51°C.

Based on these results I’m happy to ignore air flow for now. 51°C isn’t worryingly high, and it looks like it would take a lot of work to improve air flow enough to make an appreciable difference.

Software

The code for all of the I/O devices used here is available in the py-gaugette library. I will release the application source soon; there are a few loose ends I want to tidy up first.

All Together Now

In the video below, the LED colours are as follows:

  • purple is idle - time not being logged
  • slowly pulsing blue is active - time is being logged against a task, pulses slowly
  • flashing is busy - updating the spreadsheet via Google Docs

The resulting spreadsheet looks like this:

Analog Gauge Stepper Breakout Board Available on Tindie

Permalink

Check out this great new Tindie project launched by The Renaissance Engineer Adam Fabio for a new breakout board designed for Switec motors and clones.

The breakout board incorporates flyback diodes to protect your circuit from inductive kickback, and the also serves as a convenient base to mount the motors. The kit includes the board, diodes, 6-pin header, a Switec X27.168 and 3d-printed needle.

If you want to get in on this initial offering act quickly - it closes in 12 days. The project has already exceeded the funding target. Nice work Adam!


Rotary Encoder Library for the Raspberry Pi

Permalink

Here’s a quick overview of the rotary encoder I/O class in the py-gaugette library.

The encoder I’m using is a 2-bit quadrature-encoded rotary encoder, available from Adafruit.
The datasheet is here.

The documentation for this encoder says that it gives 24 pulses per 360° rotation, which I interpreted to mean 24 resolvable positions, but after trying it I see that it has 24 detent positions, and between each detent is a full 4-step quadrature cycle, so there are actually 96 resolvable steps per rotation. This unit also includes a momentary switch which is closed when the button is pushed down. Takes a solid push to close the switch.

The diagram above shows how I’ve connected the rotary encoder to the Raspberry Pi. I’m using pin 9, one of the “Do Not Connect” (DNC) pins to get a second ground pin for convenience. Eben made a public commitment to keep the extra power and ground pins unchanged in future releases, so I think it is safe to publish circuits using them. Right? Here’s a pinout highlighting the functions of the DNC pins, along with the wiringpi pin numbers.

Decoder Logic

The implementation configures pins A and B as inputs, turns on the internal pull-up resistors for each, so they will read high when the contacts are open, low when closed. The inputs generate the following sequence of values as we advance through the quadrature sequence:

SEQBAA ^ B
0000
1011
2110
3101

One non-obvious detail here: the library uses the bitwise xor value A ^ B to efficiently transform the input bits into an ordinal sequence number. There is no reason behind the xor other than it gives us the bit sequence we want.

seq = (a_state ^ b_state) | b_state << 1

Because we are pulling our inputs high and shorting to ground when the contacts close, our inputs for A and B are actually inverted against those shown in Quadrature Output Table figure above. It turns out you can ignore this inversion in the decode logic; inverting the signals moves the quadrature half a cycle forward, but makes no difference at all to the decode logic.

Once the library has computed the sequence value, it determines the direction of movement by comparing the current sequence position against the previous sequence position, like this:

delta = (current_sequence - previous_sequence) % 4

which will yield values in the range 0 through 3, interpreted as follows:

deltameaning
0no change
11 step clockwise
22 steps clockwise or counter-clockwise
31 step counter clockwise

If we get a value of 2, we know we have missed a transition (oops!) but we don’t know if the encoder has turned clockwise or counter-clockwise. In this case the library assumes the direction is the same as the previous rotation. Of course if the encoder has actually moved 3 steps between reads, it will decode as 1 step in the wrong direction. You can only fix this by polling faster or using interrupts.

Polling Vs Interrupts

The current implementation of the RotaryEncoder class uses polling to monitor the inputs rather than using GPIO interrupts. It looks like the plumbing is in place within wiringpi to use interrupts on GPIOs, but I’ll leave that for another day. Instead I have included an optional worker thread class that can be used to monitor the inputs, leaving the main thread free to go about its business.

The push switch is handled by a separate Switch class that doesn’t do anything clever like debounce - it just reads and returns the current switch state.

Here’s how to read the rotary encoder and switch without starting a worker thread.

import gaugette.rotary_encoder
import gaugette.switch
A_PIN = 7
B_PIN = 9
SW_PIN = 8
encoder = gaugette.rotary_encoder.RotaryEncoder(A_PIN, B_PIN)
switch = gaugette.switch.Switch(SW_PIN)
last_state = None
while True:
delta = encoder.get_delta()
if delta!=0:
print "rotate %d" % delta
sw_state = switch.get_state()
if sw_state != last_state:
print "switch %d" % sw_state
last_state = sw_state

Spin the knob and you should see something like this:

$ sudo python rotary_worker_test.py
switch 0
rotate 1
rotate 1
rotate -1
...

Using the class as shown above, you must call encoder.get_delta() frequently enough to catch all transitions. A single missed step is handled okay, but 2 missed steps will be misinterpreted as a single step in the wrong direction, so if you turn the knob too quickly you might see some jitter.

To ensure the inputs are polled quickly enough, even if your application’s main thread is busy doing heavy lifting, you can use the worker thread class class to monitor the switch positions. Using the worker class is trivial; instantiate RotaryEncoder.Worker instead of RotaryEncoder with the same parameters, and call the start() method to begin polling - only lines 8 and 9 below have changed.

import gaugette.rotary_encoder
import gaugette.switch
A_PIN = 7
B_PIN = 9
SW_PIN = 8
encoder = gaugette.rotary_encoder.RotaryEncoder.Worker(A_PIN, B_PIN)
encoder.start()
switch = gaugette.switch.Switch(SW_PIN)
last_state = None
while 1:
delta = encoder.get_delta()
if delta!=0:
print "rotate %d" % delta
sw_state = switch.get_state()
if sw_state != last_state:
print "switch %d" % sw_state
last_state = sw_state

Here’s a video showing the rotary encoder at work. In this case I use the main thread to service the OLED scrolling, with the worker thread keeping an eye on the rotary encoder. There is another worker thread that manages the RGB led transitions.

Better Fonts for the SSD1306

Permalink

The first release of the SSD1306 support library py-gaugette used the 5x7 pixel fonts from the Adafruit GFX library. That’s a fine and compact font, but wouldn’t it be nice to have some pretty high-res fonts to take advantage of the memory and resolution we have to work with?

Generating Font Bitmaps

I started with The Dot Factory by Eran Duchan. Its a handy C# (Windows) tool for generating C and C++ bitmap files quickly, and source code is available. I modified it to generate Python code, and to add a kerning table to store the minimum number of pixels the cursor must move between any two characters to avoid collison.

The kerning isn’t completely right yet - I noticed that the underscore character can slip beheath other characters. I’ll need to look at that some more in due time - and I’d also like to replace the C# app for a command-line tool to generate the rasterized image files.

Each font is stored in a module like the following. The size suffix (16 in this case) indicates the character height in pixels. The descriptor table specifies the width of each character and the character’s offset in the bitmap.

# Module gaugette.fonts.arial_16
# generated from Arial 12pt
name = "Arial 16"
start_char = '#'
end_char = '~'
char_height = 16
space_width = 8
gap_width = 2
bitmaps = (
# @0 '!' (1 pixels wide)
0x00, #
0x80, # O
0x80, # O
0x80, # O
0x80, # O
0x80, # O
0x80, # O
0x80, # O
0x80, # O
0x80, # O
0x80, # O
0x00, #
0x80, # O
0x00, #
0x00, #
0x00, #
# @16 '"' (4 pixels wide)
0x00, #
0x90, # O O
0x90, # O O
0x90, # O O
0x90, # O O
0x00, #
0x00, #
0x00, #
0x00, #
0x00, #
0x00, #
0x00, #
0x00, #
0x00, #
0x00, #
0x00, #
# @32 '#' (9 pixels wide)
0x00, 0x00, #
0x11, 0x00, # O O
0x11, 0x00, # O O
0x11, 0x00, # O O
0x22, 0x00, # O O
0xFF, 0x80, # OOOOOOOOO
0x22, 0x00, # O O
0x22, 0x00, # O O
0x22, 0x00, # O O
0xFF, 0x80, # OOOOOOOOO
0x44, 0x00, # O O
0x44, 0x00, # O O
0x44, 0x00, # O O
0x00, 0x00, #
0x00, 0x00, #
0x00, 0x00, #
...
)
# (width, byte offset)
descriptors = (
(1,0),# !
(4,16),# "
(9,32),# #
...
)
# kerning[c1][c2] yeilds minimum number of pixels to advance
# after drawing #c1 before drawing #c2 so that the characters
# do not collide.
kerning = (
(1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,),
...
)

Note the font is a module, not a class, because it allows a very concise syntax:

from gaugette.fonts import arial_16
textSize = led.draw_text3(0,0,"Hello World",arial_16)

Supporting Horizontal Scrolling

In the process of testing these fonts, I realized I would like to be able to scroll horizontally, and the SSD1306 doesn’t have hardware support for that. Vertical scrolling is accomplished using the SET_START_LINE command, but the horizontal scrolling commands do not support scrolling through an image that is wider than the display. We need to do it in software.

It turns out that blitting memory from the Pi to the SSD1306 over SPI is pretty fast; fast enough to get a reasonable horizontal scroll effect by blitting complete frames from the Pi’s memory to the SSD1306. There’s just one thing - the default memory mode of the SSD1306 is row-major, and for horizontal scrolling we really want to send a vertical slice of the memory buffer over SPI. To avoid buffer manipulation I switched the Pi-side memory buffer to use column-major order, and use MEMORY_MODE_VERT on the SSD1306 when blitting.

To illustrate: the memory buffer is stored as a python list of bytes. Consider a virtual buffer that is 256 columns wide and 64 rows high. Using column-major layout we can address the 128 columns starting at column 100 using the python list addressing buffer[100*64/8:128*64/8].

buffer = [0] * 256 * 64 / 8 # buffer for 256 columns, 64 rows, 8 pixels stored per byte
start = 100 * 64 / 8 # byte offset to 100th column
length = 128 * 64 / 8 # byte count of 128 columns x 64 rows
led.command(led.SET_MEMORY_MODE, led.MEMORY_MODE_VERT) # use vertical addressing mode
led.data(buffer[start:start+length]) # send a vertical slice of the virtual buffer

Note that using column-major layout we cannot easily blit a horizontal slice of the virtual memory buffer into display ram, so we can’t use the same method for vertical scrolling. Stick with SET_START_LINE for vertical scrolling. The combination of these methods gives us fast horizontal and vertical scrolling.

An updated library with sample code is available on github.

Controlling an Adafruit SSD1306 SPI OLED With a Raspberry Pi

Permalink

Adafruit’s lovely little 128x32 monochrome SPI OLED module uses a SSD1306 driver chip (datasheet), and Adafruit have published excellent tutorials and libraries for driving this from an Arduino.

When asked in their forum about Raspberry Pi support, Adafruit have said that there is a huge backlog of libraries to port to the RasPi and (they) don’t have any ETA on the SSD1306.

I’m working on a project that was originally intended for an Arduino, but I’ve decided to switch to the Raspberry Pi, so I need to get this display working with the Pi. To that end, I’ve partially ported Adafruit’s SSD1306 library to Python for the Raspberry Pi. The port is partial in that:

  1. it only supports the 128x32 SPI module (unlike the original that supports the I²C and 128x64 modules) and
  2. it only supports pixel and text drawing functions (no geometric drawing functions).

Signal Levels

The SSD1306 operates at 3.3V, and the Adafruit module has built-in level-shifters for 5V operation. However I want to drive it at 3.3V from the Pi, and I wasn’t confident from the documentation that it would operate at 3.3V without modification. However the back-side silkscreen says very clearly 3.3 - 5V and I can confirm it works very happily with both Vin and signalling at 3.3V.

SPI Signals

In SPI nomenclature MISO is master in, slave out, MOSI is master out, slave in. The SSD1306 module is write-only using SPI, and so there is no MISO connection available, and MOSI is labelled DATA on the module. Of course SPI always reads while it writes, but it is fine to leave MISO disconnected. It will be pulled low, so if you ever looked at the data you would see all zeros.

The D/C (Data/Command) signal on the module is not part of the SPI specification, and it took a little experimenting to understand exactly what it is used for. The data sheet says “When it is pulled HIGH (i.e. connect to VDD), the data at D[7:0] is treated as data. When it is pulled LOW, the data at D[7:0] will be transferred to the command register.”

Initially I supposed data to include the argument bytes that follow the opcode when sending multi-byte commands. For example the “Set Contrast Control” command consists of a one-byte opcode (0x81) followed by a one-byte contrast value, so I was sending the first byte with D/C high, and pulling it low for the argument byte. Wrongo! That’s not what they mean by data; keep the D/C line high for all bytes in a command, and pull it low when blitting image data into the image memory buffer. Simple as that.

Platform

I’m running Python 2.7.3 on a Rasberry Pi Model B (v1) with the following software:

Wire Up

Here’s how I’ve wired it up. You can freely change the GPIOs for D/C and Reset.

Test Code

Note that pin numbers passed in the constructor are the wiring pin numbers, not the connector pin numbers! For example I have Reset wired to connector pin 8, which is BCP gpio 14, but wiringPi pin 15. It’s confusing, but just refer to the wiringPi GPIO table.

The python library for the SSD1306 has been rolled into the py-gaugette library available on github.

The test code below vertically scrolls vertically between two display buffers, one showing the current time, one showing the current date.

This sample code is included in the py-gaugette library.

import gaugette.ssd1306
import time
import sys
RESET_PIN = 15
DC_PIN = 16
led = gaugette.ssd1306.SSD1306(reset_pin=RESET_PIN, dc_pin=DC_PIN )
led.begin()
led.clear_display()
offset = 0 # buffer row currently displayed at the top of the display
while True:
# Write the time and date onto the display on every other cycle
if offset == 0:
text = time.strftime("%A")
led.draw_text2(0,0,text,2)
text = time.strftime("%e %b %Y")
led.draw_text2(0,16,text,2)
text = time.strftime("%X")
led.draw_text2(8,32+4,text,3)
led.display()
time.sleep(0.2)
else:
time.sleep(0.5)
# vertically scroll to switch between buffers
for i in range(0,32):
offset = (offset + 1) % 64
led.command(led.SET_START_LINE | offset)
time.sleep(0.01)

About Fonts

This test code uses the 5x7 bitmap font from the Adafruit GFX library scaled to x2 and x3. It works, but Steve Jobs would not approve! It isn’t taking advantage of the very high resolution of these lovely little displays. Larger fonts with kerning would be a great addition.

Using Google OAuth2 for Devices

Permalink

I’ve been working on a Raspberry Pi based device that needs to log data to a Google Docs spreadsheet. There are some fine classes like gdata and gspread that do just this.

However if you read the tutorial at Adafruit which uses the gspread library you will see that it uses the ClientLogin API for authentication, and so somewhere you need to include your Google username and password in plain text. There are several problems with this in the context of an embedded device:

  • You need to provide a way for the user to enter their credentials on the device which may have no keyboard or GUI,
  • The credientials are stored in a non-secure way,
  • The ClientLogin API is officially deprecated,
  • I personally live in fear of accidentally committing code with plain-text credentials to GitHub.

So I don’t want to use ClientLogin.

It turns out the Google’s OAuth2 implementation has a great alternative for embedded applications; OAuth 2.0 for Devices. Here’s the high-level overview of how the authentication works:

  1. Just once, register your application with the Google APIs console and get a client_key and client_secret,
  2. When your application runs for the first time, it sends an authentication request to Google which returns a short plain-text user_code,
  3. Your device shows the user_code to the user and gives them instructions to enter that code at http://www.google.com/device.
  4. Your application starts polling a Google web service waiting for confirmation that the user has approved the application,
  5. The user enters the code to Google’s form, and confirms the level of permissions the application has requested,
  6. Your application gets a response from the web service it has been polling with an access_token and a refresh_token.
  7. You pass this access_token in the header of all API requests

The access_token will expire after a relatively short time (1 hour?). When the token expires, it can be refreshed by making another API call using the refresh_token. This returns a fresh access_token but does not return an udpated refresh_token. The original refresh_token does not expire.

I could not find any documentation explaining how to use the access_token with any of the existing API libraries. As far as I can tell Google’s python gdata library doesn’t have explicit support for this form of authentication, but it turns out you can make it work if you send an appropriately formatted Authorization header to the service constructor like this:

headers = {
"Authorization": "%s %s" % (token['token_type'], token['access_token'])
}
client = gdata.spreadsheet.service.SpreadsheetsService(additional_headers=headers)

The extra header will be something like Authorization: Bearer 1/fFBGRNJru1FQd44AzqT3Zg. I’ve wrapped up what I’ve figured out so far into an oauth class in the python gaugette library “py-gaugette”.

Here’s an example of how it can be used to authenticate and add a row to the end of a spreadsheet. The library saves the authentication tokens to the local file system so that the user only has to authenticate the device once. The explicit token-expiration handling isn’t great. In ruby I could encapsulate that nicely using block parameters, but I haven’t yet figured out a clean abstraction for python. The user can revoke access to the device via this Google page.

import gaugette.oauth
import datetime
import gdata.service
import sys
CLIENT_ID = 'your client_id here'
CLIENT_SECRET = 'your client secret here'
SPREADSHEET_KEY = 'your spreadsheet key here'
oauth = gaugette.oauth.OAuth(CLIENT_ID, CLIENT_SECRET)
if not oauth.has_token():
user_code = oauth.get_user_code()
print "Go to %s and enter the code %s" % (oauth.verification_url, user_code)
oauth.get_new_token()
gd_client = oauth.spreadsheet_service()
spreadsheet_id = SPREADSHEET_KEY
try:
worksheets_feed = gd_client.GetWorksheetsFeed(spreadsheet_id)
except gdata.service.RequestError as error:
if (error[0]['status'] == 401):
oauth.refresh_token()
gd_client = oauth.spreadsheet_service()
worksheets_feed = gd_client.GetWorksheetsFeed(spreadsheet_id)
else:
raise
worksheet_id = worksheets_feed.entry[0].id.text.rsplit('/',1)[1]
now = datetime.datetime.now().isoformat(' ')
row = {
'project': 'datatest',
'start' : now,
'finish' : now
}
try:
gd_client.InsertRow(row, spreadsheet_id, worksheet_id)
except gdata.service.RequestError as error:
if (error[0]['status'] == 401):
oauth.refresh_token()
gd_client = oauth.spreadsheet_service()
gd_client.InsertRow(row, spreadsheet_id, worksheet_id)
else:
raise
print "done"

Analog Gauges Using I²C on the Raspberry Pi

Permalink

I’ve been intending to try driving Switec X25.168 motors using the MCP23008 I²C I/O port expander chip from an Arduino, but it occurs to me that it might be more interesting to try this on a Rasberry Pi. If it works, it will demonstrate a simple and very inexpensive method for driving analog gauges from the Raspberry Pi without the need for high-current drivers.

The MCP23008 provides 8 I/O lines controlled via an I²C interface.
The datasheet is available here. The pins are rated to source and sink 20mA each. That’s around half what an Arduino offers, but should be (just) enough to drive our little Switec steppers. These chips are available from Adafruit, along with the doubly awesome 16-port MCP23017.

Since this is my first Raspberry Pi project, I’ll include detailed setup notes.

Step 1 - Installing AdaFruit’s Occidentalis Kernel Image

Adafruit have prepared a modified version of the Wheezy Linux distribution for the RPi that includes I²C tools and drivers (plus SPI, one wire, and plenty of other hackable goodness.) The first step is to create a bootable SD card containing Adafruit’s Occidentalis image. The following instructions are for OSX.

Insert a 4GB SD card and figure out it’s device name using diskutil list. It is easy to recognise the SD card in the list below as /dev/disk2 because of the 4GB size.

euramoo:misc guy$ diskutil list
/dev/disk0
#: TYPE NAME SIZE IDENTIFIER
0: GUID_partition_scheme 320.1 GB disk0
1: EFI 209.7 MB disk0s1
2: Apple_HFS Macintosh HD 319.2 GB disk0s2
3: Apple_Boot Recovery HD 650.0 MB disk0s3
/dev/disk1
#: TYPE NAME SIZE IDENTIFIER
0: Apple_partition_scheme 12.3 MB disk1
1: Apple_partition_map 32.3 KB disk1s1
2: Apple_HFS Flash Player 12.3 MB disk1s2
/dev/disk2
#: TYPE NAME SIZE IDENTIFIER
0: FDisk_partition_scheme 4.0 GB disk2
1: Windows_FAT_32 58.7 MB disk2s1
2: Linux 3.9 GB disk2s2

Next unmount the disk with diskutil unmountDisk.

$ diskutil unmountDisk /dev/disk2
Unmount of all volumes on disk2 was successful

Download Occidentalis from AdaFruit. I’m using Version 0.2. Unzip it, verify the checksum, and copy it to the SD card. I know this a big file, but I was still surprised it took half an hour to write to the SD card.

$ mv ~/Downloads/Occidentalisv02.zip .
$ unzip Occidentalisv02.zip
Archive: Occidentalisv02.zip
inflating: Occidentalis_v02.img
$ shasum Occidentalis_v02.img
a609f588bca86694989ab7672badbce423aa89fd Occidentalis_v02.img
$ sudo dd bs=1m if=Occidentalis_v02.img of=/dev/disk2
Password:
...time passes
2479+1 records in
2479+1 records out
2600000000 bytes transferred in 2099.207512 secs (1238563 bytes/sec)

Step 2 - Test the Boot Image

Boot the RPi from the newly copied SD image and log in. The default username is ‘pi’, password ‘raspberry’. DHCP and ssh are enabled by default so you can login headless if you can figure out the IP address from the DHCP server.

Confirm that the I²C tools are installed and working by doing an I²C scan of bus 0. You should see nothing on the bus, as shown below.

pi@raspberrypi ~ $ sudo i2cdetect -y 0
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

Step 3 - Wire Up

The Raspberry Pi expansion port pinout is:

The MCP23008 pins are:

There are 4 wires connect the RPi to the MCP23008:

  • SDA RPi pin 3 to MCP23008 pin 2
  • SCL RPi pin 5 to MCP23008 pin 1
  • 5V RPi pin 2 to MCP23008 pin 18
  • GND RPi pin 6 to MCP23008 pin 9

Note that I’m powering the chip from 5V instead of the 3.3V because I want to power the motor windings at 5V. I have read that there is no problem connecting 3.3V and 5V circuits over I²C.

In addition you must make the following connections on the chip. It is very important that you do not let the address pins or the reset pin float!

  • MCP23008 address pins 3,4,5 to Vdd or Vss to set the bus address.
  • MCP23008 reset pin 6 to Vdd. Pull it high.

You do not need current limiting resistors on the address or reset pins and the RPi has built-in pull-up resistors on SDA and SCL so you do not need to provide them. Too easy, right?

The I²C 7-bit address will be binary [ 0 0 1 0 A2 A1 A0 ] so if all address lines are pulled to ground it will be at 0x20, if they are all high, it will be at 0x27. I²C addresses are sometimes confusingly left-shifted 1 bit and expressed as 8-bit addresses, which are 2x the 7-bit address, like this [ 0 1 0 A2 A1 A0 0 ]. Can’t we all just get along? Side note - if you are reading I²C protocol diagrams, I²C sends the most significant bit first.

Step 4 - Probe It

Power up and repeat the i2cdetect command. This time you should see device on the bus. Mine is at 0x20 because I pulled all of the address lines low.

pi@raspberrypi ~ $ sudo i2cdetect -y 0
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: 20 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

You can read the IODIR register (0) which should return all 1’s (all pins configured as inputs), and the OLAT register (10 or 0xA) which should return all 0’s.

pi@raspberrypi ~ $ sudo i2cget -y 0 0x20 0
0xff
pi@raspberrypi ~ $ sudo i2cget -y 0 0x20 10
0x00

Step 5 - Spin a Motor!

I wired up a VID29 to pins GP0 to GP3 on the driver chip and kludged together some C code to cycle through the 6 signal states used to drive the motor. I’m using a VID29 motor with the stops removed so I can let it spin continuously without hitting the stop. The test code is here.

$ sudo ./i2ctest
Register 0 = 255
Register 1 = 0
Register 2 = 0
Register 3 = 0
Register 4 = 0
Register 5 = 0
Register 6 = 0
Register 7 = 0
Register 8 = 0
Register 9 = 0
Register 10 = 0
done
error writing i2c gpio register
error writing i2c gpio register
...

Yeah, so those errors aren’t good. The results of reading the 10 registers look fine, but as soon as it starts trying to turn the motor we are getting errors. After some fiddling I confirmed that the errors only happen when the motor is attached, so it looks like the RPi’s 5V power rail isn’t able to supply the necessary current. This is not actually a surprise, and it isn’t a big problem either.

I happen to have a beefy 10A 5V supply on the bench, so used that to power the MCP23008. Problem solved, motor turns, errors are gone.

This is the final circuit.

Speed Tests

NOTE: Since writing this I discovered that the supply voltage used in these tests was down-regulated to around 3.9V instead of 5.0V, which accounts for the disappointing speeds noted below. I will repeat these tests when I get a chance. Initial tests indicate that at a full 5.0V the motor has roughly the same speed limits when driven by the MCP23008 as it does when driven directly from Arduino digital IO ports. It is also worth noting that the overhead for sending each motor pulse update over I²C is about 400μS. This could probably be improved using byte mode (to avoid sending the register address over I²C on each update) or by selecting a higher I²C baud rate.

I have previously measured the maximum speed that the Arduino can turn a VID29 stepper when driving directly from the I/O pins, and found it was a little over 500°/S.

I repeated these tests with the RPi and found a maximum speed of around 215 °/S, presumably lower because of the reduced current capacity. (Correction - it is due to an accidentally low supply voltage. See above.) I tried ganging two pins from the MCP23008 to each of the motor pins, thinking I might squeeze a little extra current out of the chip. That boosted the maximum speed a little, but still fell well short of the Arduino.

SystemMin μS/stepMax °/S
Arduino649513
RPi + MCP230081550215
RPi + MCP23008 paired pins1426233

Conclusions

The MCP23008 will indeed drive a Switec X25.168 (or clone) but the limited current capacity means that the motor’s maximum speed is noticably less than when driving from an Arduino.

If you aren’t bothered by the reduced maximum speed, you can drive lots of these little stepper motors without running out of I/O pins. One very tidy solution would be to use the Centipede Shield which would in theory drive up to 16 motors per shield - although at some point you will probably hit CPU limits trying to service the I/O.

Not surprisingly, the 5V power rail on the Raspberry Pi isn’t up to powering even one micro stepper, but the MCP28003 conveniently allows us to inject 5V power from a separate supply, so that isn’t a serious problem.