Some XLoBorg code to get you started

Here’s code to create a graphical compass for your XLoBorg from PiBorg. It’s a work in progress, working fine for 2-dimensional headings (ie: keep the Pi on a flat, level surface). It’s got the code in for 3-dimensional headings using the accelerometer as well as the magnetometer to enable the Pi to be tilted but this is *ahem* not quite working yet (my maths is a bit screwy it seems). I’ll post an update when it’s working good ’n proper.

I’ve included support for Declination which is pretty important to get an accurate compass heading. Just define your location(s) as constants near the top and edit the declinationAngle = BRISTOL / 1000 to equal your location and hey presto.

I also found a very interesting PDF from Honeywell all about how these combined accelerometer + magnetometer compasses work. Definitely worth a read.

# Calculate and generate a graphical display of the compass heading
# calculated from the output of XLoBorg from www.piborg.com

# Author: Colin Deady, 2012.
# Released under Creative Commons Attribution Share Alike,
http://creativecommons.org/licenses/by-sa/2.5/

# Credit to Bryan Oakley for the MyApp class code that generates the clock face:
#
http://stackoverflow.com/questions/6161816/tkinter-how-to-make-tkinter-to-refresh-and-delete-last-lines
# Released under Creative Commons Attribution Share Alike (
http://stackexchange.com/legal, http://creativecommons.org/licenses/by-sa/2.5/)

# WARNING: THIS IS NOT TO BE USED FOR ANYTHING IMPORTANT!
# THIS IS PURELY PROOF OF CONCEPT FOR FUN.
# NO liability is accepted WHATSOEVER for inaccurate data generated by this program.
# If you use it to navigate the Pacific / Sahara / other place of your choice and
# end up falling over the edge of the world and the last thing you see are some giant
# elephants then that is your get, not mine.

import Tkinter as tk; import time
from math import cos,sin,pi, atan, atan2, floor, asin
import sys

# Load the XLoBorg library
import XLoBorg

# Tell the library to disable diagnostic printouts
#XLoBorg.printFunction = XLoBorg.NoPrint

# Start the XLoBorg module (sets up devices)
XLoBorg.Init()

# define a target direction that we can use to rotate the Pi towards
# this could be built upon to be the target angle that a robot steers towards
targetDirection = 120

# Declination - very important!
# http://en.wikipedia.org/wiki/Declination
# http://www.loveelectronics.co.uk/Tutorials/8/hmc5883l-tutorial-and-arduino-library
# http://www.magnetic-declination.com/
BRISTOL = -2.2
declinationAngle = BRISTOL/1000

# create a "clock face" upon which we can render the compass heading of XLoBorg
class MyApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)

self.size=300

self.title("Compass")
self.w = tk.Canvas(self, width=320, height=320, bg="#111", relief= "sunken", border=10)
self.w.pack()

self.w.create_oval(10,10,330,330, fill="cyan", tags="compassbg1")
self.w.create_oval(70,70,270,270, fill="blue", tags="compassbg2")
self.w.create_line(0,0,0,0, fill="red", width="3", tags="compass")
self.w.create_line(0,0,0,0, fill="yellow", width="3", tag="target")

legendbg = "#fff"
uzr1 = tk.Label(self, text="N", bg=legendbg )
uzr1.place(x=160, y=12)
uzr2 = tk.Label(self, text="S", bg=legendbg )
uzr2.place(x=160, y=311)
uzr3 = tk.Label(self, text="E", bg=legendbg )
uzr3.place(x=317, y=160)
uzr4 = tk.Label(self, text="W", bg=legendbg )
uzr4.place(x=12, y=162)

e = tk.Button(self,text="Quit", command=self.Quit)
e.pack()

self.update_compass()

def update_compass(self):

# Read and render the raw compass readings, correcting by our manual calibration values
compass = XLoBorg.ReadCompassRaw()
print str(compass[0])+","+str(compass[1])+","+str(compass[2])
mX = compass[0] - -248
mY = compass[1] - 370
mZ = compass[2] - 1384
# print "mX:"+str(mX)+",mY:"+str(mY)

# Read the raw accelerometer readings
accel = XLoBorg.ReadAccelerometer()
print str(accel[0])+","+str(accel[1])+","+str(accel[2])
aX = accel[0]
aY = accel[1]
aZ = accel[2]

# some notes on tilt compensation
# http://www.loveelectronics.co.uk/Tutorials/13/tilt-compensated-compass-arduino-tutorial

# =======================================================================================
# The following code does NOT yet work... it should but my maths is a bit screwy
# When I manage to fix it then we'll have a compass that copes with being tilted, huzzah!
rollRadians = asin(aY)
pitchRadians = asin(aX)
# up to 40 degree tilt max:
# http://www.loveelectronics.co.uk/Tutorials/13/tilt-compensated-compass-arduino-tutorial
cosRoll = cos(rollRadians)
sinRoll = sin(rollRadians)
cosPitch = cos(pitchRadians);
sinPitch = sin(pitchRadians);

vX = mX * cosPitch + mZ * sinPitch;
vY = mX * sinRoll * sinPitch + mY * cosRoll - mZ * sinRoll * cosPitch;
# =======================================================================================

# get the heading in radians
heading = atan2(vY,vX)

# correct for declination
heading +=declinationAngle

# Correct negative values
if (heading < 0):
heading = heading + (2 * pi)

# Check for wrap due to declination and compensate
if(heading > 2*pi):
heading -= 2*pi

# convert to degrees
heading = heading * 180/pi;

# get the base degrees
heading = floor(heading)
print heading

angle = heading*pi*2/360
ox = 165
oy = 165
x = ox + self.size*sin(angle)*0.45
y = oy - self.size*cos(angle)*0.45
self.w.coords("compass", (ox,oy,x,y))

angleTarget = targetDirection*pi*2/360
xt = ox + self.size*sin(angleTarget)*0.45
yt = oy - self.size*cos(angleTarget)*0.45
self.w.coords("target", (ox,oy, xt, yt))

gapAngle = abs(targetDirection - heading) # we want the angle without the sign
# if the gap angle is more than 180 degrees away then it is closer than we think
if gapAngle > 180 :
gapAngle = 360-gapAngle
if gapAngle < 0 :
print "Target is " + str(gapAngle) + " degrees anticlockwise"
elif gapAngle > 0 :
print "Target is " + str(gapAngle) + " degrees clockwise"
else:
print "Target acquired!"

self.after(500, self.update_compass)

def Quit(self):
self.after(700,self.destroy())

app = MyApp()
app.mainloop()

Comments

Raspberry Pi Python Cookbook: now taking pre-orders

Raspberry Pi Cookbook for Python Programmers
Those great people over at Packt Publishing have just started taking pre-orders for Raspberry Pi Cookbook for Python Programmers. Written by Tim Cox of Pi Hardware fame this is looking to be a great resource that teaches exactly what it says on the tin.

Tim is a regular contributor to and member of the editorial team for The MagPi. He also jointly runs The MagPi’s stand at the BCS Bristol Boot Camp events providing various demos and tutorials that show off his technowizardry. Consequently he knows his stuff.

Packt state that the book will provide information on creating 3D worlds and expanding the Pi via interfacing with hardware amongst many topics (those two just particularly grabbed my attention), and all focussed on Python 3, which I think helps keep things nice and consistent.

More info when I’ve a copy in my hands.
Comments

Nokia 3310 LCD board for Raspberry Pi

I recently ordered a Nokia 3310 LCD shield from Texy. The beauty is it comes pre-assembled with 5 micro switches and the 84x48 LCD so no soldering required. The price was pretty superb too at £14 + £4 postage. I looked up the list price of just the LCD and it is about £10 hence 8 pounds extra for a fully assembled board delivered to my door is a bargain.

I have some interesting plans a-foot for this little board.

To get it up and running I needed to install WiringPi from Gordons Projects to work with Texy’s sample code. Be sure to install WiringPi for Python though else you’ll sit there confused like I did wondering what went wrong ;)

Texy_Nokia3310LCD

Something that is most interesting with the 3310 LCD is that it has no internal font set. Consequently you have to define all characters in hex up-front. Luckily (and VERY thoughtfully) Texy provides sample Python code to introduce the functionality of the board which includes a default fairly small font. Calling:

text(‘Hello world’)

will display the text string on-screen. One of the first things I did was to port a pretty good large font to Python from C, the latter courtesy of Petras Sadulkis. The large font takes up three rows per character meaning I had to get it to loop through each row of the array which takes me into my favourite world of multi-dimensional arrays :) (ask me sometime why I love multidimensional arrays so much...) You can see the board outputting the current time (HH-MM, SS.ms) using this larger font.

Here’s some code to get large text working for you for numbers and a few characters (add this into Texy’s sample code), and I apologise in advance for the gratuitous hex:

def display_largechar(char, character_position, display_on_row, font=LARGEFONT):
  try:
    gotorc(0 + display_on_row, character_position)
    for value in font[char][0]:
      lcd_data(value)
    gotorc(1 + display_on_row, character_position)
    for value in font[char][1]:
      lcd_data(value)
    gotorc(2 + display_on_row, character_position)
    for value in font[char][2]:
      lcd_data(value)
    lcd_data(0) # Space inbetween characters.
  except KeyError:
    pass # Ignore undefined characters.


def largetext(string, display_on_row, font=LARGEFONT):
  character_position = 0
  for char in string:
    display_largechar(char, character_position, display_on_row, font)
    character_position += 2

# Based on http://mbed.org/users/snatch59/code/N3310LCD/
LARGEFONT = {
  '0': [
    [0x00,0x00,0xc0,0xe0,0x70,0x30,0x30,0x30,0x70,0xe0,0xc0,0x00,0x00,0x00,0x00,0x00],
    [0x00,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x00,0x00,0x00,0x00],
    [0x00,0x07,0x1f,0x38,0x70,0x60,0x60,0x60,0x70,0x38,0x1f,0x07,0x00,0x00,0x00,0x00],
  ],
  '1': [
    [0x00,0x00,0x00,0xc0,0xc0,0xc0,0xf0,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
    [0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
    [0x00,0x00,0x00,0x60,0x60,0x60,0x7f,0x7f,0x60,0x60,0x60,0x00,0x00,0x00,0x00,0x00]
  ],
  '2': [
    [0x00,0xe0,0x60,0x70,0x30,0x30,0x30,0x30,0x60,0xe0,0x80,0x00,0x00,0x00,0x00,0x00],
    [0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xc0,0xf0,0x3f,0x1f,0x00,0x00,0x00,0x00,0x00],
    [0x00,0x70,0x78,0x7c,0x6e,0x67,0x63,0x61,0x60,0x60,0x60,0x60,0x00,0x00,0x00,0x00],
  ],
  '3': [
    [0x00,0xe0,0x60,0x70,0x30,0x30,0x30,0x30,0x30,0x60,0xe0,0xc0,0x00,0x00,0x00,0x00],
    [0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x18,0x3c,0x7c,0xe7,0xc3,0x00,0x00,0x00,0x00],
    [0x00,0x38,0x30,0x70,0x60,0x60,0x60,0x60,0x70,0x38,0x1f,0x0f,0x00,0x00,0x00,0x00],
  ],
  '4': [
    [0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xc0,0x60,0xf0,0xf0,0x00,0x00,0x00,0x00,0x00],
    [0x00,0xe0,0xf0,0xdc,0xce,0xc7,0xc1,0xc0,0xc0,0xff,0xff,0x00,0x00,0x00,0x00,0x00],
    [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7f,0x7f,0x00,0x00,0x00,0x00,0x00],
  ],
  '5': [
    [0x00,0x00,0xf0,0xf0,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x00,0x00,0x00,0x00,0x00],
    [0x00,0x00,0x1f,0x1f,0x18,0x18,0x18,0x18,0x30,0xf0,0xc0,0x00,0x00,0x00,0x00,0x00],
    [0x00,0x30,0x70,0x60,0x60,0x60,0x60,0x70,0x38,0x1f,0x0f,0x00,0x00,0x00,0x00,0x00],
  ],
  '6': [
    [0x00,0x00,0x80,0xc0,0xe0,0x60,0x30,0x30,0x30,0x30,0x30,0x00,0x00,0x00,0x00,0x00],
    [0x00,0xfc,0xff,0x33,0x18,0x18,0x18,0x18,0x18,0x30,0xf0,0xc0,0x00,0x00,0x00,0x00],
    [0x00,0x07,0x1f,0x38,0x70,0x60,0x60,0x60,0x60,0x30,0x1f,0x0f,0x00,0x00,0x00,0x00],
  ],
  '7': [
    [0x00,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0xb0,0xf0,0xf0,0x00,0x00,0x00,0x00,0x00],
    [0x00,0x00,0x00,0x00,0xc0,0xf0,0x78,0x1e,0x07,0x01,0x00,0x00,0x00,0x00,0x00,0x00],
    [0x00,0x60,0x78,0x3e,0x0f,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00],
  ],
  '8': [
    [0x00,0x80,0xe0,0x60,0x30,0x30,0x30,0x30,0x30,0x60,0xe0,0xc0,0x00,0x00,0x00,0x00],
    [0x00,0x87,0xe7,0x6c,0x18,0x18,0x30,0x30,0x38,0x6c,0xc7,0x83,0x00,0x00,0x00,0x00],
    [0x00,0x0f,0x3f,0x38,0x70,0x60,0x60,0x60,0x60,0x30,0x1f,0x0f,0x00,0x00,0x00,0x00],
  ],
  '9': [
    [0x00,0x80,0xc0,0x60,0x30,0x30,0x30,0x30,0x70,0xe0,0xc0,0x00,0x00,0x00,0x00,0x00],
    [0x00,0x0f,0x3f,0x30,0x60,0x60,0x60,0x60,0x60,0x30,0xff,0xff,0x00,0x00,0x00,0x00],
    [0x00,0x00,0x00,0x60,0x60,0x60,0x60,0x70,0x38,0x1e,0x0f,0x03,0x00,0x00,0x00,0x00],
  ],
  ':': [
        [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
        [0,00,56,56,56,56,56,56,56,56,56,0,0,0,0,0],
        [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
  ],
  '+': [
        [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
        [0,0,64,64,64,64,64,254,254,64,64,64,64,64,0,0],
        [0,0,0,0,0,0,0,15,15,0,0,0,0,0,0,0]
  ],
  '-': [
        [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
        [0,64,64,64,64,64,64,0,0,0,0,0,0,0,0,0],
        [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
  ],  
}

from datetime import datetime

# clear the screen then display the time HH:MM on one row and SS:ms on another
cls()
now = datetime.now()
largetext(now.strftime("%H:%M"),0)
largetext(now.strftime("%S.%f")[:5],3)

With the large font in place and the time display working I can move on to adding a stop watch, along with some configuration menus for brightness, contrast and a few other parameters. Once this is done the fun begins as I’ll be using it to remotely control my two Nikon cameras using the time and stop watch functionality to control when the Pi triggers the cameras to take a photo.

This is a great board to experiment with. It brings out the hex in all of us;) Luckily there are tools online to help out with generating hex images in case you need them:

http://www.quinapalus.com/hd44780udg.html (online character generator - this is pretty nifty)
http://en.radzio.dxp.pl/bitmap_converter/ (LCD Assistant: windows tool for converting monochromatic bitmaps to hex data arrays)

As you can probably tell I’m very pleased with this shield. If I was being picky I would say, as Texy notes, it “almost” fits within the credit-card form factor of the Pi, with the red board under the LCD just poking out a little. I’m hoping this can be sorted as it means the board won’t fit inside tight-fitting cases designed for the Pi. But apart from that, this shield gets a thumbs up from me.
Comments

The MagPi issue 7 is out

The MagPi issue 7 has been released. What’s more I have two articles published in this issue: an interview with Mike Thompson who created Raspbian, and my first ever published computer program! I took over The Python Pit this month to demonstrate how one can implement command line arguments to make configuring an application that is about to be run easier than having to manually edit the program code.

I am especially pleased with the program I wrote as it generates graphical output (see image below) that I used to draw by hand when I was a child. Back then I don’t even think it occurred to me to write a program to do this!

lineimage

The program supports a number of command line arguments to change the output. Run:
line_generator.py -h
to see all of the options.

For your viewing pleasure, here’s the output of the above:

usage: line_generator_edit.py [-h] [-s SCALE] [-t STEP] [-r {y,n}]

Render shape

optional arguments:
-h, --help show this help message and exit
-s SCALE Render size, default=2, 200x200px)
-t STEP Step value (default=5): lower the value for denser lines
-r {y,n} Render line by line (slower) (y) or only display finished object
(faster) (n)? (default=y)


Comments

The MagPi issue 8 is out

The MagPi issue 8 has been released, featuring my second ever published program when I take over The Python Pit again. This time around I show how to use Python’s subprocess to create desktop widgets. Think: those things what Windows Vista and 7 has and you won’t be far off.

The Python program has an intentional flaw, but I only reveal general details on this to the readership. This is by design as one of the things I always liked about programs in those 1980s computer magazines (you know, the one’s that listed the code and you had to type it yourself back in the days before cover cassettes / disks or discs became the norm) was that you often had to finish the program off, or improve it to get it to work _just_right_.

If you are reading this I’ll let you know a bit more detail to help you out.

As I note at the end of the article, refreshing the Pygame screen for each widget occurs at the same time as each checks for new content. The problem this causes is that if I drag another window on top of either Pygame window it will blank the output until the next check for content, which could be hours away. The fix is quite straightforward and involves adding an if statement that uses datetime to determine when to check for new content, and changing time.sleep(28800) and time.sleep(3600) to both be time.sleep(1). This means that each pygame widget’s screen will refresh every second (change this to 0.1 for faster refresh, every tenth of a second, at the cost of higher CPU usage by Python) BUT a check for new content will still only happen periodically when the new datetime value is suitably different from the old datetime value. You do NOT just want to change to time.sleep(1) on its own as this will cause both URLs to be queried every second for new content which is much too frequent.
Comments