A need for read
About a year ago I purchased an ‘atomic IMU’ unit from Sparkfun ( for completeness, an IMU is an Inertial Measurement Unit, providing readings in (usually) >= 6 dimensions of global and angular acceleration ). Since then, I have used it in a few projects – a gyro mouse for a virtual reality headset (link), measuring the deceleration of a toy car for a physics demonstration; and as a nice test data-source for serial communication fiddling. Recently I had familiarized myself a little with the PyQt GUI framework, and was in a search for small project ideas to reinforce what I was learning – a serial reader for this device seemed adequate as it uses a few different GUI functions; real-time graph plots, buttons, timing, form input, dynamic resizing…
Stage 1. Python communication
First task was to actually get the data from the device into the computer’s memory. I’d previously done serial comms using windows sockets in C++, but I guessed that there’s probably an easier way in Python, it being the renown high-level language it is. Fortunately, a couple of minutes on Google lead me to PySerial, which was perfect. Opening the IMU was only a couple of function calls:
import serial serialPort = None def InitIMU(port, baudrate=115200): global serialPort serialPort = serial.Serial(port, baudrate, timeout=0.1) serialPort.close() #Sometimes the port needs to be closed first serialPort.open() serialPort.write('#') #Send the 'begin' signal
Yeah, I know, globals are bad. I don’t usually do this but my OOP in python isn’t fantastic so I just abused a global as a poor-man’s singleton :P. Anyway, after the IMU was open, I just read 16-bit integers, 2 bytes at a time, iterating over input readings into a custom storage class (between sentinels, this code is from inside a loop):
#Add a new, empty reading l.append( IMUReading() ) #Combines 2 bytes into an integer def ReadInt16(cnt): return 256*ord(bytes[cnt+1])+ord(bytes[cnt+2]) #Look at every even window to get the numbers, sticking them in the latest (empty) reading #Couldn't really come up with a cleaner way other than just incrementing the indices l[-1].count = ReadInt16(i);i+=2 l[-1].accel.x = ReadInt16(i);i+=2 l[-1].accel.y = ReadInt16(i);i+=2 l[-1].accel.z = ReadInt16(i);i+=2 l[-1].gyro.x = ReadInt16(i);i+=2 l[-1].gyro.y = ReadInt16(i);i+=2 l[-1].gyro.z = ReadInt16(i);i+=2
Again, nothing extravagant here, just reading data the way the IMU spec supplies them.
Stage 2: something to look at
Once the serial comms were working, I moved onto the GUI. Qt Designer is good like that, dragging spacers, buttons and windows onto the screen is swift and easy. In place of where the graphs would be in the final application, I dropped QGraphicsView objects. It was then, after some research, that I realised it would be easiest to use a 3rd-party graphing library rather than going from scratch with basic plotting functions. As it turns out, most people use Qwt for this – however I wasn’t convinced of it’s real-time performance; so instead I used PyQtGraph which — although not thoroughly documented, I found easy enough to use. For example, inside my main loop, retrieving, filtering and displaying data from the IMU every frame was only a few lines:
if IMUReader.serialPort != None: data.Update() accelxcurve.setData(list(data.filtered.accel.x)) accelycurve.setData(list(data.filtered.accel.y)) accelzcurve.setData(list(data.filtered.accel.z)) gyroxcurve.setData(list(data.filtered.gyro.x)) gyroycurve.setData(list(data.filtered.gyro.y)) gyrozcurve.setData(list(data.filtered.gyro.z))
After hooking input and button events to some functions and passing variables around, the application was working nicely. For the curious individual, the entire source code is available here, and requires Python 2.7 with PyQt, PyQtGraph and PySerial installed.