Wednesday 25 February 2015

Turning the GPS up to 10(Hz)!

Until today I had been using the Adafruit Ultimate GPS pretty much straight out of the box.  This meant that it was running at 9600 BAUD, with 1Hz updates, including the satellite SKY records.  That's a whole lot of data to send over a 9600 BAUD serial line.  In practice that meant that NTP wasn't very happy with the GPS output, and one update per second was nowhere near enough for logging data on a motorcycle travelling at 100+mph (on the track of course).

The GPS is capable of 1/5/10Hz updates, but defaults to 1Hz.  It's also capable of BAUD rates up to 115200, but defaults to 9600.  In order to increase the update frequency, you really have to push the BAUD much higher. A number of people also recommend dropping the SKY records to reduce the throughput.  I looked into this and, unlike some microcontrollers, the Raspberry Pi is quite happy with 115200 BAUD, so the SKY records get to stay for now!

All you need to do to change the BAUD rate is to send the GPS the appropriate command and it will respond with a confirmation string.  Same again for the 10Hz updates.  The NMEA sentences that you need to send are well documented in the PDF.  You need is a checksum generator to find the magic characters for the end of the sentence, and after that, it's a simple bit of Python code to access the serial port and send the relevant commands.


#!/usr/bin/python

from serial import Serial
import time
outStr = ''
inStr = ''

serialPort = Serial("/dev/ttyAMA0", 9600, timeout=2)
if (serialPort.isOpen() == False):
  serialPort.open()
serialPort.flushInput()
serialPort.flushOutput()
outStr = '$PMTK251,115200*1F\r\n'
print "outStr = " + outStr
serialPort.write(outStr)
time.sleep(0.1)
inStr = serialPort.read(serialPort.inWaiting())
print "inStr =  " + inStr
serialPort.close()


serialPort = Serial("/dev/ttyAMA0", 115200, timeout=2)
if (serialPort.isOpen() == False):
  serialPort.open()
serialPort.flushInput()
serialPort.flushOutput()
outStr = '$PMTK220,100*2F\r\n'
print "outStr = " + outStr
serialPort.write(outStr)
time.sleep(0.1)
inStr = serialPort.read(serialPort.inWaiting())
print "inStr =  " + inStr
serialPort.close()

Friday 20 February 2015

Losing time

This week didn't quite go according to plan, I was planning to be test-driving the logger by now, but it hasn't happened.  Why?  Well, mainly because of the very recent change in Raspbian Wheezy to use kernel 3.18.7+.  This brings about a huge change - device tree support.  I had never even heard of device trees before and to be honest didn't know I needed to know about them.  As will all things technology there has been a huge amount of banging my head on the desk, and the odd sweary word!! Why did it affect this project?  Well, i2c is now controlled by device-tree rather than just loading modules.  This change meant that i2c no longer loaded at all in fact.

It turns out that raspi-config has been updated to use device trees, and using it to enable i2c at boot works fine.  If you go to the raspi-config advanced menu, i2c can now be enabled.  Just choose enable and load modules on boot.  This means that the RTC should again be available on the bus at boot.

This doesn't entirely solve the problem though - you actually have to get raspbian to use it, something it's not overly keen on doing. What I had been doing was this, but I wan't happy with editing the hwclock.sh script to that extent, and disabling the udev calls didn't sit well either.  Even with this, the RTC wasn't loaded until quite a way into the boot process.  There must be a better way...

(I'll spare you details of the three days I spent delving into device trees, and get straight to the point)

There is a device tree overlay provided as part of 2015-02-16-raspbian-wheezy:
/boot/overlays/ds1307-rtc-overlay.dtb

This overlay is specifically designed to load the rtc_ds1307 kernel module, which supports many of the common RTCs, including my DS3231 unit.  All one needs to do is to call it from /boot/config.txt:
dtoverlay=ds1307-rtc

Believe it or not, you will now have a running RTC straight away at boot time!  The i2c drivers will be loaded and /dev/rtc0 will be ready and waiting for you.

That's not quite the end of the story though, you'll find that the system time is still set to Jan 1st 1970 after boot. Thankfully, Sob's post pointed me to an issue with the udev helper /lib/udev/hwclock-set.  It's only designed to set the timezone, not the system time.  Thankfully that's a trivial fix; just change --systz to --hctosys.  When booting you should find your Pi now has the correct time copied from the RTC to the system time.

The last time related issue is to do with the PPS pin on the GPS.  Now that device tree is in effect, enabling PPS on a GPIO pin is no longer done with a parameter in /boot/cmdline.txt.  Instead there's an overlay for that too!.  Just add dtoverlay=pps-gpio,gpiopin=18 (or whichever pin you want to use) to /boot/config.txt and the PPS pin will be configured and /dev/pps0 will be up and running right after boot.

It's been a bit of a bumpy ride this week, but it's been well worth it!




Monday 16 February 2015

Hardware assembly v1.0

Until now, I've been testing the components individually, and all has gone really quite well.  So, the next logical step is to put all of the hardware together in a form that is portable enough to take it out for a blast to see how the GPS/accelerometer work under real conditions (well in the warmth and safety of the car to start with).  So, I present to you, hardware build v1.0.  
























I haven't even powered it up yet, and there's a lot of config to do on this RPi as it's pretty much factory defaults!

The components shown above are:
  • RPi B+ in a PiBow coupe case
  • Camera in an ABS plastic housing
  • Wifi dongle (remote admin/GoPro control)
  • Bluetooth dongle (OBD dongle)
  • Adafruit Ultimate GPS (direct to Pi UART, PPS connected)
  • DS3231 RTC (to help with disconnected startup) hidden behind the big bunch of wires!
  • 10DOF board (lean angle, accelleration, temperature)
As soon I get my hands on one of these I can get on with the final hardware assembly.  That's assuming that I don't start adding more components like this with one or more of these attached for brake position sensor, or these for tyre temperature monitoring!

Thursday 12 February 2015

Which way is up??

Another significant component of the datalogger is an Inertial Measurement Unit (IMU).  There are many many models out there, from the really expensive to the really cheap.  I decided to start with a 10DOF (degrees of freedom) board based on these chipsets: L3GD20 LSM303D BMP180.  It has a temperature and barometric pressure sensor in addition to the usual compass, accelerometer and gyro sensors. Delivered from HK for £5.88 in a couple of weeks, not bad!  I have since ordered an MPU-9150 board for £4.90 to see how that behaves. The choice of both boards was based on the support matrix for RTIMULib, which is probably going to be the library of choice.

The board came with an unsoldered header, but I'm getting the hang of this soldering malarkey, so it was soon a soldered header!  These boards all use the I2C, so the wiring is simple: power (+3.3v), ground and the I2C lines (SDA/SCL).  I2C is on GPIO 2 (SDA) and GPIO 3 (SCL).









To check that the RPI is seeing it, use i2cdetect on I2C bus 1:

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

There are quite a few things on the bus, most of them are the chips on this board, but there's also a RTC that I added to help out with the GPS timing.

After downloading and installing RTIMULib, it was time for a bit of python:
import sys, getopt

sys.path.append('.')
import RTIMU
import os.path
import os
import time
import math
import datetime

SETTINGS_FILE = "RTIMULib"
print("Using settings file " + SETTINGS_FILE + ".ini")
if not os.path.exists(SETTINGS_FILE + ".ini"):
  print("Settings file does not exist, will be created")

s = RTIMU.Settings(SETTINGS_FILE)
imu = RTIMU.RTIMU(s)
pressure = RTIMU.RTPressure(s)

print("IMU Name: " + imu.IMUName())
print("Pressure Name: " + pressure.pressureName())

if (not imu.IMUInit()):
    print("IMU Init Failed");
    sys.exit(1)
else:
    print("IMU Init Succeeded");

if (not pressure.pressureInit()):
    print("Pressure sensor Init Failed");
else:
    print("Pressure sensor Init Succeeded");

poll_interval = imu.IMUGetPollInterval()
print("Recommended Poll Interval: %dmS\n" % poll_interval)

raw_input("Press Enter when ready\n>")

while True:
  if imu.IMURead():
    (x, y, z) = imu.getFusionData()
    data = imu.getIMUData()
    (data["pressureValid"], data["pressure"], data["temperatureValid"], data["temperature"]) = pressure.pressureRead()
    fusionPose = data["fusionPose"]
    os.system('clear')

    print "timestamp: " + str(data["timestamp"])

    print "accelValid: " + str(data["accelValid"])
    print "accel: " + str(data["accel"])
    print "accel x: " + str(data["accel"][0])
    print "accel y: " + str(data["accel"][1])

    print "compassValid: " + str(data["compassValid"])
    print "compass: " + str(data["compass"])

    print "fusionPoseValid: " + str(data["fusionPoseValid"])
    print "fusionPose: " + str(data["fusionPose"])
    print("r: %f p: %f y: %f" % (math.degrees(fusionPose[0]), math.degrees(fusionPose[1]), math.degrees(fusionPose[2])))
    print("r: %i p: %i y: %i" % (int(math.degrees(fusionPose[0])), int(math.degrees(fusionPose[1])), int(math.degrees(fusionPose[2]))))
    print("x: %f y: %f z: %f" % (x,y,z))

    print "fusionQPoseValid: " + str(data["fusionQPoseValid"])
    print "fusionQPose: " + str(data["fusionQPose"])

    print "gyroValid: " + str(data["gyroValid"])
    print "gyro: " + str(data["gyro"])

    print "pressureValid: " + str(data["pressureValid"])
    print("Pressure: %f" % (data["pressure"]))

    print "temperatureValid: " + str(data["temperatureValid"])
    print("Temperature: %f" % (data["temperature"]))

    time.sleep(0.2)



pi@pi0 ~ $ python imu_fields.py
Using settings file RTIMULib.ini
IMU Name: LSM9DS0
Pressure Name: BMP180
IMU Init Succeeded
Pressure sensor Init Succeeded
Recommended Poll Interval: 4mS

Press Enter when ready
>
timestamp: 1423748093614330
accelValid: True
accel: (1.0306559801101685, 0.04684799909591675, -0.16396799683570862)
accel x: 1.03065598011
accel y: 0.0468479990959
compassValid: True
compass: (43.95816421508789, 25.756946563720703, -5.119741439819336)
fusionPoseValid: True
fusionPose: (2.216735363006592, -1.4253621101379395, 2.5034217834472656)
r: 127.009581 p: -81.667233 y: 143.435503
r: 127 p: -81 y: 143
x: 2.216735 y: -1.425362 z: 2.503422
fusionQPoseValid: True
fusionQPose: (-0.44976526498794556, 0.4893990159034729, 0.551458477973938, 0.5040767192840576)
gyroValid: True
gyro: (0.15618298947811127, 0.15292565524578094, -0.16467183828353882)
pressureValid: 1
Pressure: 1013.469971
temperatureValid: 1
Temperature: 21.500000

That seems to work nicely, and is easily capable of 50Hz updates, and may well do 100Hz if I decide to go to that level.