Sunday, June 10, 2018

Inertial Head Tracker

Summary

I'm a long time fan of flight simulators on PC. I'm looking forward to the day when VR headsets are able to provide a high-quality, immersive experience. However, VR is expensive and tough on computing power. In the past, I used an open source infrared headtracking system called FreeTrack. This provided so-so performance compared to the 1st party solution TrackIR, but at a fraction of the cost. Unfortunately, the FreeTrack setup had a lot of quality-of-life issues that really negated the benefits it brought to simulation. I propose a better way.

To solve the shortcomings of IR solutions and maintain low costs, I developed an inertial head tracker using easily available open source hardware and software. For a cost of approximately $40 USD, I created a highly accurate 3 degree of freedom (pitch, roll, yaw) inertial head tracker. Typical inertial motion solutions are plagued by accelerometer and gyro drift. Luckily, the Freescale FXOS8700CQ and FXAS21002C coupled with the "Mahony algorithm" developed by Robert Mahony et al. and library implementation by Paul Stoffregen have entirely solved this issue.

The result is a high-quality motion tracker with very smooth and predictable behavior at a modest cost. My immense thanks to all of the open source contributions that enabled this project--it was truly a slam dunk weekend project and I can't overstate how impressed I am with the result.

OpenTrack can also easily map head movements to mouse movements, which might make this project great for helping people with physical disabilities operate a computer. 



Implementation

Hardware

Software

Tools

  • Soldering iron, solder, wire/header

Instructions

  1. Solder the Teensy prop shield directly to the Teensy 3.2. Headers work great for this, since you can stack the two boards to make a small package without risking accidental shorts. I recommend soldering the prop shield under the Teensy controller, so you can access the reset button on the Teensy.
  2. Mount the Teensy stack to a hat, headset, or similar piece of headgear. It's important to complete the mechanical mounting of the Teensy stack before magnetometer calibration, since any nearby metals, magnets, or cables may impact the calibration.
  3. Install the required software and libraries.
  4. Connect the Teensy stack to your PC via USB and use the Arduino IDE to flash the "CalibrateSensors" example from the NXPMotionSense library. Ensure the board is set to "Teensy 3.2/3.1".
  5. Run MotionCal, and select the COM port for the Teensy in the drop down menu. Rotate the Teensy and headwear in complete circles until the gaps, variance, wobble, and fit error are low (5% or lower is a good goal). The calibration GUI should show a sphere or ellipsoid, and the "Send Cal" button will no longer be grayed out when the calibration is acceptable. Click "Send Cal" to save the calibration to EEPROM. It is critical that calibration be completed with the final mechanical setup. If using a headset, you may need to wear it and/or hold the speaker transducers apart as if you were hearing it to create a proper calibration.
  6. Flash the "MahonyIMU" example from the NXPMotionSense library. Change the USB type from "Serial" to "Flight Sim Controls + Joystick".
  7. Put your headset on and open the Arduino Serial Monitor. You will see the current values of heading, pitch, and roll in order. The values shown will vary with each setup depending on the mounting orientation of your Teensy and the direction your computer desk is facing. Note the heading value when you are looking straight ahead after about 1 minute has elapsed. The 1 minute wait is necessary for the algorithm to reduce the error with the magnetometer.
  8. Copy the below code to replace MahonyIMU example. Put the heading value in line 13 for the "headingcenter" variable. I coded "wraparound" handling if your center heading and range overlap 0. There is no such protection or calculation included for pitch and roll, which may be necessary if you mount your PCB upside down.
  9. Save the updated code to a safe place and flash the Teensy. It is now functioning as a USB joystick. You can view the output in the Arduino Serial Monitor for debugging if necessary, or launch the Windows "Set up USB Game Controllers" application to see windows receiving the data.
  10. Launch OpenTrack and adjust the input to use the Teensy. Depending on your luck, you may need to invert axes to match your movement. You can also adjust sensitivity, smoothing, and define curves in OpenTrack. For the output settings, set the interface option as "Use TrackIR, hide FreeTrack" to be compatible with most TrackIR compatible applications. I highly recommend setting a bind key for "Center" under "Options" to have the software adjust the resting center. This is to account for small differences in your positioning as every time you use the head tracker, you will have a slightly different resting position.

IMU HeadTracker Code

// Inertial Monitoring Unit (IMU) using Mahony filter.
//
// To view this data, use the Arduino Serial Monitor to watch the
// scrolling angles, or run the OrientationVisualiser example in Processing.

#include <NXPMotionSense.h>
#include <MahonyAHRS.h>
#include <Wire.h>
#include <EEPROM.h>

NXPMotionSense imu;
Mahony filter;
const int headingcenter = 0; // ADJUST THIS TO YOUR "RESTING" HEADING. MUST BE POSITIVE VALUE BETWEEN 0 AND 359.
const int headingrange = 70; // APPROXIMATE RANGE OF MOTION IN DEGREES +/- OF HEADING CENTER. MOVING OUTSIDE THIS CENTERS THE VIEW FOR RECOVERY.
int headingminimum;
int headingmaximum;
int joyheading;
int joypitch;
int joyroll;

void setup() {
  Serial.begin(9600);
  imu.begin();
  filter.begin(100); // 100 measurements per second
  if((headingcenter-headingrange)<0 bound="" calculate="" degrees="" else="" heading="" headingcenter="" headingminimum="headingcenter-headingrange;" headingrange="" if="" in="" lower="">360) // Calculate heading upper bound in degrees
  {
    headingmaximum=headingcenter+headingrange-360;
  }
  else
  {
    headingmaximum=headingcenter+headingrange;
  }
}

void loop() {
  float ax, ay, az;
  float gx, gy, gz;
  float mx, my, mz;
  float roll, pitch, heading;

  if (imu.available()) {
    // Read the motion sensors
    imu.readMotionSensor(ax, ay, az, gx, gy, gz, mx, my, mz);

    // Update the Mahony filter
    filter.update(gx, gy, gz, ax, ay, az, mx, my, mz);

    // print the heading, pitch and roll
    roll = filter.getRoll();
    pitch = filter.getPitch();
    heading = filter.getYaw();
    Serial.print("Orientation: ");
    Serial.print(heading);
    Serial.print(" ");
    Serial.print(pitch);
    Serial.print(" ");
    Serial.println(roll);


// Heading calculation code. Includes zero-wraparound handling if heading range overlaps 0/360 boundary.
    if(headingminimum>headingmaximum)
    {
      if((heading>headingmaximum)&&(heading(headingmaximum)))
    {
      joyheading = 512;
    }
    else
    {
      joyheading = (heading-(headingminimum))/((headingmaximum)-(headingminimum))*(1024-0)+0;
    }

    if(pitch==0)
    {
      joypitch = 512;
    }
    else
    {
      joypitch = (pitch-(-180))/(180-(-180))*(1024-0)+0;
    }
    
    if(roll==0)
    {
      joyroll = 512;
    }
    else
    {
      joyroll = (roll-(-180))/(180-(-180))*(1024-0)+0;
    }    
    Joystick.X(joypitch);
    Joystick.Y(joyroll);
    Joystick.Z(joyheading);

    Serial.print("Joyvalue: ");
    Serial.print(joyheading);
    Serial.print(" ");
    Serial.print(joypitch);
    Serial.print(" ");
    Serial.println(joyroll);
  }
}

Sunday, July 24, 2016

Open Source Reef Controller Update #1

I decided to begin this project some time ago, but shelved the idea until recently. Why? The time, effort, and cost of developing a system with the capabilities I wanted were prohibitive. I wanted more than just an aquarium data logger; I wanted a controller capable of handling lighting, dosing, and a number of additional safety measures. As time wore on, I decided to simplify things by completing one section at a time. First up, temperature data logging!

Temperature Data Logging

Hardware Setup

A Raspberry Pi 1 B+ is the foundation of the controller. I've added a wifi dongle to connect it to my home network and allow for easy access through SSH. Temperature measurement will be handled by a waterproof DS18B20 sensor. This sensor can be run at 3.3v and communicates with the Pi using the one-wire interface. The digital interface is a must, since the Pi does not have an analog to digital converter. A 4.7k pull up resistor is needed on the data line.

Software Setup

Luckily, the heavy lifting on the software side was already complete. On my Pi 1 B+ running Raspbian, I installed the w1thermsensor Python module. This can be done with the below commands or by downloading the source from the link.

sudo apt-get install python-w1thermsensor
python setup.py --command-packages=stdeb.command bdist_deb
Before the sensor can be queried, onewire must be enabled in the device tree overlay. This is done by modifying the boot config file.

To modfiy the boot config file:
sudo nano /boot/config.txt 

Add the line dtoverlay=w1-gpio to the bottom of the file and save. This enables onewire on pin 4 of the pi. On my setup, I'm using pin 6 which requires dtoverlay=w1-gpio,gpiopin=6. The chosen pin is generally arbitrary.

With the hardware and software installed, it's simple to query the sensor by running the example script in the w1thermsensor documentation. From a python terminal:

from w1thermsensor import W1ThermSensor 
sensor = W1ThermSensor()
temperature_in_celsius = sensor.get_temperature() 

ThingSpeak

In lieu of a functional GUI, I decided to create the controller as an IoT device. As such, recorded data and alerts will be pushed to the cloud service ThingSpeak. ThingSpeak is a free service with a number of graphing and data analysis tools that's great for this kind of project. My controller will log data here for viewing. As features are added, additional charts and alerts can be appended to the site.

I created a simple python script to measure the temperature with the sensor and push the result to ThingSpeak every 15 minutes by executing the below as a crontab job.
# templogger.py
# 
# Developed by cmarzano
# 5/26/2016

import httplib, urllib
from w1thermsensor import W1ThermSensor

sensor = W1ThermSensor()
temp = sensor.get_temperature(W1ThermSensor.DEGREES_F)

# Input your thingspeak channel API key below
apikey = 'yourkeygoeshere'

params = urllib.urlencode({'field1': temp, 'key':apikey})
headers = {"Content-type": "application/x-www-form-urlencoded","Accept": "text/plain"}
conn = httplib.HTTPConnection("api.thingspeak.com:80")
conn.request("POST", "/update", params, headers)
response = conn.getresponse()
print response.status, response.reason
data = response.read()
conn.close()
 

To run the script every 15 minutes in crontab, you'll need to add the below line to your crontab list after entering crontab -e with the path correctly filled in.

*/15 * * * * /usr/bin/python /absolute/path/to/templogger.py 

Some additional tweaking on the ThingSpeak side completes the temperature logger. Embedded below is the LIVE data from my tank. I have the temperature sensor slightly downstream of the heater, which causes some observable fluctuations when the heater activates. As I make hardware and software changes to the setup, automatic updates may periodically stop.


Sunday, July 17, 2016

3D Printing Redux

It's been quite some time since my last post! Shortly after my last 3D printer update, I completed the machine and began testing and tweaking. The machine worked, but overall performance wasn't up to my standards. I spent some time revisiting the design, and ultimately decided to tear it down and begin anew with higher quality components.

I designed a new printer, also a cubic foot H-bot, using OpenBuild's V-slot aluminum extrusion. The extrusion and accessories are fairly low cost, easy to work with, and high performing. Additionally, I decided to complete an overhaul of the electronics. I switched out the Printrboard for a Duet 0.8.5. This controller provides significantly more features than the Printrboard, such as native dual nozzle support, network control (similar to OctoPrint), and 24v power. Also, the DC powered 12" silicone heater was replaced with an AC version and a heatsinked solid state relay.

Additional electronic changes included use of the E3D PT100 temp sensor+board for the hotend, and the differential IR sensor for auto bed leveling. There were some hardships in getting the PT100 sensor functioning, since the E3D amplifier boards were designed for 5v electronics. To make things even more difficult, the Duet 0.8.5 did not have any software support for PT100 sensors (DC42 has since added some functionality).

At the time of writing this post, I completed the project about 9 months previously. There's still some work to be done on the printer for getting the second nozzle operating and watercooling the hotend. For now, enjoy some pictures of the build process!

Frame nearly finished at this stage. The black panels are a textured 1/4" HDPE called StarBoard. Cheap and robust!



Solid state relay and matching heatsink. Absolutely critical for keeping the relay functioning.

Electronics are in a compartment underneath the print area. Includes SSR and 24v power supply on the left with the Duet and 12v+5v power supply on the right.

The first powered test with all critical to function pieces wired. It works!

A 1/4" polycarbonate shell forms covers all sides but the front to limit temperature fluctuations in the build area. Eventually the enclosure will seal to prevent warping; but the hotend will need to be watercooled.

I printed a calibration cube before this to check basic functionality, then threw the iconic octopus on it. Finished it like a champ!

Thursday, January 8, 2015

3D Printer Mini-Update


Today I spent some time in the shop milling out some aluminum parts for the printer. The Shapeoko 2 made quick work of the two parts, taking about 40 minutes and 15 minutes for the large and small plates, respectively. The aluminum plates form a pulley mount for the H-gantry system, and will be substantially more rigid than their plastic counterparts. I'll have to make another plate assembly and hotend mount before I'm able to test out the parts.

For milling 6061 aluminum on the Shapeoko 2, I'm using:

  • DeWalt DW660 Cutting Tool
  • 1/8" 2 flute carbide endmill
  • 400 mm/s feed, 60 mm/s plunge, 0.2 mm depth per pass

Saturday, December 6, 2014

3D Printer Buildlog #5

At long last, the 3D printer project is beginning to come to a finish! Recent progress has been excellent, and a number of hardware and software problems have been tackled. Most notably, I completely reversed course on servo based automatic bed leveling and switched to a force sensing resistor (FSR) system.

Force sensing resistors are fantastic little devices which change resistance as a force is applied to the sensing area. They're fairly cost effective at around $5/ea too! Why'd I change from the mechanical switch mounted to the servo? Reliability. Once I began testing the servo leveling system, I found the results to be too erratic for my purposes. Additionally, the servo occupied a large amount of space near the hotend, sacrificing print volume.

The new system utilizes four FSR sensors, one under each corner of the build plate. Since FSRs don't work well between two flat surfaces, a felt floor pad is placed between each sensor and the bed. When leveling, the tip of the hotend contacts the build plate. The pressure applied to the plate drops the resistance in the sensors, which is detected by an ATtiny85 microcontroller. The ATtiny85 loops a simple rolling average filter, which outputs a signal to an NPN transistor that triggers the Printrboard endstop. An LED is simultaneously lit when the endstop is triggered for debugging purposes. See the circuit diagram below for details. Code and pictures will be posted to Github in the next update. The ATtiny85 was flashed with an Arduino Uno following the guide by High-Low Tech.


The potential fire hazard that was the former heated bed relay has also been replaced with a "Beefcake" relay from SparkFun. I would have preferred a quality solid state relay, but this will do.

Moving forward, my next tasks will be to tune and improve some of the mechanical aspects of the printer. The motor mounts for the H-gantry aren't as rigid as they could be, and the build platform has some wobbles that will likely affect printing. Once I begin doing some print tests, I'll need to check if racking is still an issue. If so, I will be switching to a CoreXY style belt arrangement. Now that I have the Shapeoko mill, any critical parts can now be cut in 6061 aluminum. That comes with its own set of challenges, which will be discussed another time!

Monday, October 20, 2014

Announcing a New Project: Open Source Reef Controller (OSRC)

I've begun working on another project! Aside from tinkering with electronics, I've enjoyed maintaining a "nano" saltwater aquarium in my spare time. Eventually I hope to transition to a larger tank to provide the best environment possible for my fish and corals. However, caring for larger tanks can be difficult and time consuming--lighting, flow, temperature, and a plethora of chemical concentrations have to be delicately managed to keep the tank's inhabitants alive and well.

To simplify maintenance, some folks have developed aquarium controllers that monitor important parameters and notify the aquarist if something is wrong. However! These systems typically cost a few hundred dollars and use proprietary hardware. This project aims to break the mold by providing a similar user experience at a fraction of the cost--all while using open source software (and hardware, where possible).

My current plan is to develop on a Raspberry Pi with a PiTFT touchscreen. Planned features include advanced lighting management, dosing and feeding control, and web/text based alerts.

Bonus picture of a happy shrimp!

Tuesday, August 12, 2014

Beyond 3D Printing

After using 3D printing technology for two years, I've begun to dream a little bigger. 3D printing has enabled me to quickly and accurately create the parts I design. However, some projects are simply better suited to different materials. With this in mind, I expanded my tool collection with a Shapeoko 2 CNC mill kit.

The Shapeoko is an open source, 3 axis mill solution. I've ordered and assembled the Shapeoko 2 ala Inventables. The overall kit quality was superb, and the assembly documentation was rarely lacking. The only hitch in the build was that my kit was short three screws, which I may have lost on my own. Inventables' customer service was quick to offer replacements, but I used some I had sitting in the shop instead. Good folks there.

Assembling and tuning took about 8 hours, with most of the time being spent tapping one of 18 forsaken holes in the aluminum "makerslide". I also spent some extra time on the wiring to keep everything tidy. Once assembled, the documentation walks the user through making a coaster with an embossed letter.

After the tutorial part, the Shapeoko gets quite a bit more difficult to use. This difficulty primarily comes from a shortage of easy to use, open-source CAM programs to turn DXF/DWGs or STLs into gcode. There is no magical tool like Slic3r--or I haven't found it yet. Closed-source solutions provide a profound boost in performance, at significant financial cost. :(



Software problems aside, the hardware works great. I promptly upgraded from the stock "spindle" (read: glorified dremel) to the popular Dewalt DW660. The DW660 sacrifices variable speed control for a significant jump in power. And it is completely worth it. The DW660 cuts through acrylic like butter with the stock bit. I haven't found the sweet spot for feeds and speeds yet, but I'm making progress. Once I gain more experience, I'll post the settings that work best for me on the materials I've tried--so far it's just acrylic.

The first few projects for the Shapeoko will likely be upgrades for itself. A dust-shoe and vacuum system to cut down on mess is top priority. I could 3D print one, but I'd rather machine it from clear acrylic so I can observe the tool. Afterwards, I'll be looking towards clamping solutions, and something to regain the Z travel lost by upgrading to the DW660. Eventually I'd like to mill aluminum for a number of projects, we'll get there!