A Year Later: OpenCPN on a Raspberry Pi 3

It’s been over a year since I’ve tried to use OpenCPN on a Raspberry Pi, and I thought I’d see what the community has done since then.


Simply Wow.

A Raspberry Pi 3, with the latest versions of Raspbian/PIXEL and OpenCPN matches up as good as commercial entry-level chartplotters on the market today. This platform has really come a long way in the past year, and I’m truly impressed.



  • Raspberry Pi 3, Power and HDMI cables, SD card, & case
  • HDMI Monitor
  • USB Keyboard with integral trackpad
  • USB Wifi Adapter
  • GlobalSat BU-353-S4 USB GPS Receiver
  • dAISy Hat AIS Receiver

Set Up Rasbian

  • I burned the most recent version of Raspbian/PIXEL (March 2017) onto a 32GB SD card, inserted it into a RPi3, and booted. In seconds I had a desktop.
  • Connect to WiFi with a USB dongle
  • I removed the products I don’t need:
    sudo apt-get purge wolfram-engine mathematica-fonts sonic-pi nodered libreoffice
    sudo apt-get autoremove
  • Update Kernel to latest for OpenGL
    sudo branch=next rpi-update
  • Reboot
  • sudo apt-get update
    sudo apt-get upgrade
    sudo apt-get install mesa-utils
  • sudo raspi-config
    • Set Timezone
    • Set Locale and Keyboart to US English
    • Change graphics memory split to 128MB
    • Enable OpenGL Driver
      • With Full KMS, I had problems with the display not working after a reboot, so I needed to use Fake KMS, Option G2. To recover, edit config.txt and comment out the dtoverlay=vc4-kms-v3d (or edit to be vc4-fkms-v3d)
  • Turn off screen blanking
    • sudo nano /etc/lightdm/lightdm.conf
      • [SeatDefault]
      • xserver-command=X -s 0 dpms
  • Reboot
  • Run glxgears
    • I was running glxgears at 60fps and 2% CPU
    • At full screen I saw 30fps and 11% CPU
  • Run glxinfo
    • Direct Rendering: yes
    • Renderer string is the Gallium renderer, and not Software Rasterizer
    • OpenGL Version string is 2.1 Mesa 13.0.0

Install OpenCPN

  • Install required supporting tools
    • sudo apt-get install gpsd gpsd-clients screen
      • This required a reboot to work properly
  • Install the OpenCPN software from the Ubuntu PPA
    • In previous recipes I have built from source. To save time, and because building from source truly isn’t necessary, this time I just used the pre-built packages
    • sudo nano /etc/apt/sources.list
    • sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys C865EB40
      sudo apt-get update
      sudo apt-get install opencpn
  • Download charts from NOAA
    • I simply create a directory under the user account to hold all OpenCPN files, and a Charts subdirectory
    • After extracting, to pre-render all the charts, from a terminal window run:
      • opencpn -unit_test_1 0
  • Set up GPS
    • I actually had a bit of difficulty for the first time in a while getting GPS set up. If I ran cgps or xgps I got no data, but running gpsmon showed a good solid fix
    • This is the sequence of commands I followed to get it working (and works on subsequent reboots):
      • sudo dpkg-reconfigure gpsd
      • sudo nano /lib/systemd/system/gpsd.socket
        • Change ListenStream=
      • sudo nano /etc/default/gpsd
        • START_DAEMON="true"
      • sudo killall gpsd
        sudo gpsd /dev/ttyUSB0 -F /var/run/gpsd.sock
    • Connect GPS to Opencpn
      • Open Settings, Connections Tab. Click Add Connection
        • Type: Network
        • Protocol: GPSD
        • Address: localhost
        • DataPort: 2947
        • Priority: 1
        • Control Checksum: Checked

Setup dAISy Hat

  • Update config.txt to enable UART
    • sudo nano /boot/config.txt
      • add the following lines
        • # Enable UART
        • enable_uart=1
        • dtoverlay=p3-disable-bt
  • Disable Console Serial
    • sudo nano /boot/cmdline.txt
      • remove the following:
        • console=serial0,115200
  • From the Menu, select Preferences->Raspberry Pi Configuration
    • Interfaces Tab
      • Enable Serial
  • Reboot
  • Test Hat Serial communication
    • screen /dev/serial0 38400
      • [ESC] brings up a menu
      • ‘T’ sends a test message every 5 seconds
      • [CTRL-A], ‘K’, ‘Y’ to exit
  • Add AIS stream to OpenCPN
    • Open Settings, Connections Tab. Click Add Connection
      • Type: Serial
      • DataPort: /dev/serial0
      • Baudrate: 38400
      • Priority: 1
      • Control Checksum: Checked


OpenCPN running ENC charts, with location set by GPS, and AIS contacts listed.

This also shows 24 AIS Targets in a list, as well as xgps running in the background. 6% CPU.

AIS Target Query. Deep zoom into the Hylebos waterway to find three vessels currently underway, including the cargo ship Indigo Lake. These vessels were approximately three miles away from my current location


RPi2 with Hardware OpenGL & OpenCPN Recipe

On and off for the past few months I’ve been trying to figure out a problem with OpenGL and the Raspberry Pi 2. Quite simply I want to be able to build, from bare metal, a chartplotter system on a RPi2 that has AIS, GPS, Vector charts, and 10fps. Based on the hardware specs, it should be very possible. However, in practice, I have seen only 1-2fps.

The very good folks on cruisersforum have been hard at work trying to solve this problem, and over the past few days I resurrected this project took a stab at using the latest versions.

On a new board and SD card, the recipe below was used to create a working version of the RPi2 that meets these performance goals. With this setup, I get very usable performance with OpenGL enabled, less than 5% CPU with a satisfactorily responsive UI, USB AIS and GPS, and Vector Charts.


  1. Raspberry Pi 2
  2. 16G micro SD card
  3. Tontec 7″ HDMI screen
  4. USB keyboard and touchpad
  5. USB WiFi Dongle
  6. USB GPS Puck
  7. dAISy USB AIS receiver

Build the Pi with a Jessie Distribution and the open source OpenGL drivers

Image the most recent version of Raspian onto a class-10 sd card. I used Raspbian Jessie, with a build date of 3-18-2016.

Once booted, connect to the internet, and then in Raspi-config:

  • Expand file system to full card
  • Change user password
  • Automatically boot to desktop
  • Set overclock to high
  • Set language, keyboard, and locale appropriately.
  • Enable the experimental GL driver in Advanced Options

Reboot, and then:
sudo apt-get upgrade
sudo apt-get update
sudo rpi-update
sudo ldconfig
sudo reboot

Install required libraries

apt-get install cmake gettext gpsd gpsd-clients libgps-dev wx-common libwxgtk3.0-dev libgtk2.0-dev wx3.0-headers libbz2-dev libtinyxml-dev portaudio19-dev libcurl4-openssl-dev libcairo2-dev mesa-utils

Build OpenCPN

git clone git://github.com/OpenCPN/OpenCPN.git
cd OpenCPN

Optionally, get the tides & currents, and high-resolution shoreline data.

  • From  http://sourceforge.net/projects/opencpnplugins/files/opencpn_packaging_data/ download the latest opencpn-gshhs and opencpn-tcdata files
  • Extract their contents into data/gshhs

mkdir build
cd build
sudo make install

Download the appropriate charts:

  • For all US waters, it’s very easy to find appropriate charts at NOAA
  • I decided to try ENC vector charts this time.
  • Expand into an appropriate directory. I used /usr/local/include/Charts
  • run opencpn with the -unit_test_1 flag to ingest and process all the charts that were downloaded

Run OpenCPN and set the chart directory to wherever you’ve downloaded the NOAA charts. Exit OpenCPN and then edit .opencpn/opencpn.conf and add the following lines:


Setup USB

  • Start the gpsd service
    • sudo gpsd /dev/ttyUSB0 -F /var/run/gpsd.sock
  • Launch OpenCPN
  • Add a connection for the gpsd service
    • Settings -> Connections -> Add Connection
      • Network
      • GPSD protocol
      • localhost address
      • 2947 port
  • To get GPSD to run on startup automatically, modify the /etc/default/gpsd file like so:

Setup AIS

  • sudo apt-get install screen
    sudo usermod -a -G dialout $USER
    screen /dev/ttyACM0
  • CTRL-A, K exists screen
  • Start OpenCPN and go to Options -> Connections | Data Connections, and Add Connection. Select a Serial message, and set the DataPort to the dAISy USB message port (generally /dev/ttyACM0). Set the Baudrate to 9600, and uncheck Control checksum. Apply and return to OpenCPN.

Finalize the Setup

  • Install VNC Server so you can easily access the device from other computers on the network
    • apt-get install tightvncserver
    • tightvncserver :1
      • enter a password twice, and optionally a view-only password
      • Then you can VNC into the device from a computer running on the attached network
  • Remove extraneous packages
    • sudo apt-get  remove --purge wolfram-engine minecraft-pi libreoffice xscreensaver
  • Disable screen blanking
    • sudo nano /etc/lightdm/lightdm.conf
      xserver-command=X -s 0 dpms


The Big Bash 2015

We went on our first boat trip of the year. We left our home dock on Friday, April 24th, and tied up to the docks at the Port of Poulsbo that afternoon. On Saturday I presented to the Cascadia sailors also in attendance, discussing Building your own Chartplotter with AIS, Radar, and followed up by listening in to presentations on sailing much further north than we have tried — all the way up to Alaska in most cases.  On Sunday, April 26th, we returned home, this time going around the east side of Vashon Island to avoid the currents, tying up at home in the mid-afternoon hours.

The map below shows the path as captured by our GPS location on our OpenCPN chartplotter with the purple track being the north-bound trip, and the yellow track being the south-bound return trip. There are a few gaps where I rebooted the machine for some reason or another, but it still shows the path we took quite well. I just extracted the route from OpenCPN, and used gpsvisualizer.com to generate KML files from them. Then I logged into Google Maps, and imported the KML files into a custom public map.


Prior to the trip there was a ton of work to do.

Last summer we had a bit of an electrical issue on board, and lost our alternator and house bank of batteries. While we had replaced all the batteries, and purchased and mounted a new alternator, it wasn’t hooked up properly. I couldn’t figure it out as the wiring did not match up to any of the diagrams on the instructions that came with it. So before we went on this trip, I wanted to have a professional marine electrician finish up the installation.

I improvised a charging solution so that the engine wouldn’t die on the way, and drove the boat over to ModuTech Marine. At first even the electrician was confused as to what the instructions were showing. But he then discovered the issue: The instructions were showing three completely different options for installing the alternator. They just didn’t actually state that they were options, so I was trying to do all three at once! Less than an hour later we were done, and spent a couple hours cruising around Commencement Bay, validating that the batteries were, in fact, being charged properly.

Mechanically the boat was ready to go. Since this was the first time the boat had been running for any considerable time since last August, it was nice to just be out on the water.

I also had to prepare for the presentation I was giving. I have been working on running OpenCPN on these low-powered systems such as the Raspberry Pi and CubieTruck for quite some time, but because these are active projects and platforms things change. Taking the opportunity, I started over from scratch, building the chartplotter on a Raspberry Pi 2, the most recent device. Over a couple days’ span, I had built a new system, running the most recent version of OpenCPN (4.1.412), with GPS location.

I also had an AIS receiver module that I planned on hooking up while we were underway and within range of signals. I also threw together a Powerpoint slide deck for the presentation, plus a bunch of other examples of hardware. The presentation was ready as well!

The Trip North

The trip north started out under overcast skies. We got a bit of a later start than originally planned, but did get some extra water in the tanks, and we were still well within the timeframe to catch the ebb current to assist our way north. We cruised through Colvos Passage at nearly 8 kts over ground, 6 through the water, with a substantial front to the south following us all the way.

The conditions were nice enough, though, that I was able to have a few hours to play with the AIS module, and caught our first ever AIS contact just as we were coming out of Colvos. It was a ferry leaving the Southworth Docks, and it showed up great! I wasn’t sure how to take a screenshot, so I simply took a picture with my phone:
First AIS Contact

Once we got to the north end of Blake Island, that front that was chasing us caught up. 2-3′ chop built up, and the ride got quite bumpy, right as we were entering into Rich Passage. Of course the Bremerton Ferry heading in at the same time didn’t do anything to reduce the stress of the situation. But once we rounded the southwest corner of Bainbridge island and turned more north, the wind pushed us straight into Liberty Bay. It was still windy and bumpy, but much better. The boat handles these conditions well enough that everyone was able to at least get a small rest in:

An hour or so later we were docking at Poulsbo. I didn’t have the bow thruster working (did I mention we have had some electrical issues?) so it was a matter of lining the boat up on the dock and letting the wind push us in. We docked the boat as if we’d been doing it every weekend all year long! With the boat safely tied up to the docks, with shore-power and water, we were able to turn on a few movies and assist the rest of the Cascadian sailors as they arrived. Soon appetizers were shared and everyone was having a good time on each other’s boats.



The next morning we woke up to fog and glassy conditions.


There is a ton to do for everyone in Poulsbo. The bakery is great. The bookstores are great. There are great restaurants. There’s even a great little aquarium with free (donations) admission. In short, it’s a great destination.

My AIS presentation went well, I think. It’s difficult to find that right balance between technical and non-technical aspects in a presentation like this. Some of the geeky sailors wanted to know things like what kind of interface do the chips communicate on between the modules, while other sailors just picked up the Raspberry Pi and said “well that’s kinda cool” before putting it down. But in the end, I had lots of people that seemed legitimately interested, and very few people walked out of the room. Well, except my wife of course.

I also wanted to listen to the other presentations, and was able to catch the vast majority of all of them. We definitely need to do more cruising on our boat, even if it’s just short weekend trips. But to hear about the multiple-months-long trips that these sailors went on, and see some of the thousands of pictures they took was amazing. The room was mostly full the entire day.


Gramma and Papa took Tay and the dog home in the afternoon so that neither of them would be bored just sitting around. That night, with the boat to ourselves, we decided to watch the first episode of this season of Game of Thrones. About 20 minutes into the show, our electric heater went out. For those that have boats, you probably already know that electric heaters very often trip the breaker switch, and I assumed that was the situation here. Except when I looked at the switch panel, the breaker was fine. And the same was true with the breaker on the dock. For giggles I tried to turn over the engine, just to see… It barely made a sound.  Uh-oh.

So at 11:00pm, I dug into the engine room with a headlamp. I grabbed my electrical meter, and stuck the probes on the first house bank — 13.7 volts. Then I tested each of the other house bank batteries, and all of them were fully charged. I tested the starter battery, though, and it registered only 9.8 volts — essentially dead!

We weren’t stuck because with the fully charged house bank I knew I could get the engine turned over if I wired the engine starter to it. It’s not the most efficient way to do it, but once would work just fine. But I needed to figure out why we ran down our starter battery while plugged in to shore power. As I’m tracing wires and trying to figure it out, I lift up one of the house bank batteries to see behind it… and a large cable falls away loose. It was stuck between the battery and the battery case, and I couldn’t see it. It was also about 8AWG, so substantially large. I reconnected it to the negative bus, and realized that it was the return line from the starter battery charge circuit — the starter wasn’t charging at all.

I believe that because our 1-2-All switch was set to all, we were running down both banks all weekend, and only the house bank was being replenished. After letting the starter battery charge overnight, the next morning I tested the engine and she turned over without hesitation. We were good to go again.

The Return Trip

Most of the boats were starting to leave around 10:00. After a couple cups of coffee, and failing to give away the last of our mini cinnamon rolls, we decided to follow suit. Running the Raspberry Pi Chartplotter (because why not?), we backed out of the slip we had rented, into a very wide fairway, spun the boat, and headed back south.

The return trip was, for the most part, uneventful. We saw more ferries coming through Rich Passage, and again had to deal with ferries leaving the Vashon Island docks that confused us (we thought they were going to head east, so moved out of the way of that direction, but instead the ferry turned 90 degrees and headed north, more towards us than away!), but there was nothing truly concerning.
The boat ran well the whole time, showing charging voltages on both battery banks, and we even took a short detour just north of Brown’s Point Lighthouse when we saw a small pod of Dall’s Porpoises off to our starboard.

Just four short hours after leaving the docks in Poulsbo, we were docking in our slip. Of course we did an absolutely terrible job on that docking maneuver! But at least there was nobody there to watch it happen, so nobody knows.


OpenCPN on the Raspberry Pi 2 with AIS

This past weekend I gave a presentation to a group of local sailors and boaters touting the benefits of running OpenCPN on a System-on-a-chip board such as the Raspberry Pi.

In preparation for the talk, I rebuilt OpenCPN on the newest generation Raspberry Pi 2, running the most current source code available on git at the time (4.1.412). I didn’t have the time to do any performance tuning of the system, and ran it out of the box all weekend long. OpenGL was not running appropriately, so performance was less than previous installations, but it did run out of the box, following the Raspberry Pi installation steps, with the additional step of compiling OpenCPN from source to get the most recent version.

However, I did add a new feature to the installation, and one that I am extraordinarily happy with — the ability to receive AIS data on OpenCPN running on a Raspberry Pi 2!

First AIS Contact

The first AIS contact!

In order to read AIS messages, I purchased a demo board of a product called dAISy designed by Adrian Studer. I’ve had the board in my hands for a few months, but as always work and life gets in the way of projects such as this. This past weekend I sailed my boat the 35nm or so from Tacoma to Poulsbo and had a few hours to play around with the chartplotter AND dAISy in an environment where the messages could be received. In short, it worked exceptionally well, and you can see the very first AIS contact that was picked up by the system just as we exited the north end of Colvos Passage in the photo above.

dAISy is a very well designed board that is easy to use. Just connect an antennae to the BNC connector, and a USB cable to the mini-USB port. In the current Raspbian image, all the required kernel support is set up, so simply plugging in the board is enough.

To test that it is working, perform the following test:

sudo apt-get install screen
screen /dev/ttyACM0

To exit screen, type CTRL-A, followed by K. At this point you should have a terminal window set up, and if you are within range of AIS targets you should see a list of AIVDM NMEA messages, that look something like this:

!AIVDM,1,1,,A,85Mwp`1Kf3aCnsNvBWLi=wQuNhA5t43N`5nCuI=p<IBfVqnMgPGs,0*47 !AIVDM,2,1,6,A,8>qc9wiKf>d=Cq5r0mdew:?DLq>1LmhHrsqmBCKnJ50,0*30

If you are not within range of AIS targets, you can still confirm that dAISy is responding by accessing it’s menu. Simply press [esc] and the menu will display such as the screencapture from Windows shows below:


If you turn on debug messages, and you are out of range (or have a very low signal) then you will see a series of messages like the following:
sync A RSSI=-085dBm
error: invalid stuff bit
sync A RSSI=-073dBm
error: CRC error
sync A RSSI=-086dBm
error: invalid stuff bit
sync B RSSI=-071dBm
error: CRC error
sync B RSSI=-087dBm
error: CRC error
sync B RSSI=-086dBm
error: invalid stuff bit

Then the last step is to render the targets in OpenCPN. It’s a fairly simple process of creating a new data connection. Go to Options -> Connections | Data Connections, and Add Connection. Select a Serial message, and set the DataPort to the dAISy USB message port (generally /dev/ttyACM0). Set the Baudrate to 9600, and uncheck Control checksum. Apply and return to OpenCPN.

If you think you have done everything correctly, but you are still not seeing AIS contacts or messages, and especially if ‘screen’ is showing messages, make sure that your user is added to the ‘dialout’ group. To add yourself to the group:
sudo usermod -a -G dialout $USER

Otherwise, enjoy the benefits of knowing where other vessels are, where they are heading, and who they are!



Lots of traffic to this little blog

Originally, I built this blog for myself. I wanted to use it to track maintenance and projects on our boat and to share trip reports and stories with friends and family; basically a captain’s log online.

But in the past few months, the traffic to this blog has dramatically increased, due to my OpenCPN Chartplotter project. It has been linked to in other forums, by other people that have found it in searches. It has been interesting to see.

I’ve also answered a few emails and forum posts from people that would like me to build one for them — either because they don’t have the time, or that have no interest in the electronics/software DIY aspect of the project.

Since I have a bit of an independent/entrepreneurial streak in me, this has had me thinking for some time that it might be interesting to create a device that takes all the leg-work out of creating a chart-plotter using standard components and OpenCPN. Everything, both hardware and software, that I’ve used so far has been Open Source, which allows commercial application and use. I’m not looking to quit my day job and be a chart-plotter vendor — Garmin, Lowrance, and Raymarine are already well established.

The idea is simply that the baseline system could be provided in an out-of-the-box solution. A low-powered device that can display GPS-located charts on a 7 or 10″ sunlight readable display by simply providing 12v power. Is this something that more people would be interested in?

OpenCPN Chartplotter Software Plugins

The Chartplotter functionality of my off-season project is complete, due to the fact that the OpenCPN software package is, for the most part, a turn-key solution that had all the necessary features already included. It is a remarkable solution. I prefer the Raspberry Pi platform, for a low-powered dedicated device, but on a small laptop or dedicated PC, it is even faster and extremely effective.

However, like any piece of software, it doesn’t have every feature that every user desires. So they also have a plugin mechanism that allows a user to build their own extensions to the base system, and create those features themselves.

I have done exactly this during the winter, writing a pair of plugins that I think enhance product and make it even more perfectly suited to my particular desires.


The first plugin is Remembrancer. Remembrancer is a mostly-made-up word that describes the notion that it is used as a reminder. The concept is simple. All it does is bring up an alert box, and play a sound, after a configured length of time.

The idea is that on a long route, with the autopilot steering, the helmsman may become distracted or forgetful, and this plugin will force him/her to remember to do a safety sweep of the water, route, and equipment. The time delay is fully configurable, as is the sound that is played.


The source files for the plugin can be found on GitHub.

NOAA Doppler

The second plugin, NOAA Doppler, is much more involved. Quite often while cruising we just want to know where it’s raining, so we jump online or on our phone, and view one of the many apps that show the current weather map. This plugin is designed to incorporate that feature into OpenCPN, and renders doppler radar imagery directly over the chart.

NOAA provides free images of the current radar returns for all domestic ground stations. If you live in the United States and know your station ID (Seattle area is ATX (Everett), you can find the live reflectivity, storm velocity, precipitation, and other readings. They also provide a GIS “world file”. The values in this file can then be used to calculate the projection.

I also added a blur factor to the image before rendering, so that the blockiness is reduced. It works fairly well.

Weather Out

Weather In

However, the NOAA Doppler plugin is still not complete:

  • The rendering works, though there are bugs with OpenGL’s transparency
  • The blur feature works perfectly
  • The overlay is correctly positioned, and matches up with NOAA’s own rendering as well as Google Earth
  • It does not download live images, and instead only uses the image previously stored
  • The world file values are hard-coded and don’t change when the stations change

The source files can also be found on GitHub



Still working on the Autopilot

It’s been a few months since I’ve made any tangible progress on the autopilot project.  Work and life has a way of interfering with my recreational activities. But this evening I decided to start to work on the Arduino side of the CPN Pi-lot project. Hopefully this post doesn’t get too technical…

The Arduino side of the system will need to be a “smart” component. Simply stated, given a desired destination heading, it will adjust the rudder angle accordingly so that the boat will eventually reach the destination.

First, to receive the chartplotter destination, it will receive and parse an RMB NMEA message, which is produced by OpenCPN, and which is defined as so:

           RMB          Recommended minimum navigation information
           A            Data status A = OK, V = Void (warning)
           0.66,L       Cross-track error (nautical miles, 9.99 max),
                                steer Left to correct (or R = right)
           003          Origin waypoint ID
           004          Destination waypoint ID
           4917.24,N    Destination waypoint latitude 49 deg. 17.24 min. N
           12309.57,W   Destination waypoint longitude 123 deg. 09.57 min. W
           001.3        Range to destination, nautical miles (999.9 max)
           052.5        True bearing to destination
           000.5        Velocity towards destination, knots
           V            Arrival alarm  A = arrived, V = not arrived
           *20          checksum

By combining the range and bearing to the destination, with the current heading, the Arduino will be able to determine how much rudder to adjust. This algorithm will be an Arduino implementation of a common PID Controller, but will be the subject of a future article, after it gets implemented.

If you read more on PID loops, you will see that the algorithm requires a feedback loop (PV or process variable in the equations)… as in, an actual measurement of the item being controlled. In an autopilot situation, this feedback is the current heading of the boat, received either from a GPS, compass or other device. After some research and communication with people that have built autopilots for things like quad-copters, it has become clear that GPS simply doesn’t have the resolution necessary to effectively steer a vehicle, even at the relatively slow speeds of a trawler (roughly six knots through the water).

So the Arduino component will need to receive the current heading via a digital compass, and that is what I worked on today.

A few weeks ago, I purchased a LSM303DLHC 3-axis Accelerometer and Magnetometer board … aka a digital compass. And tonight I wired it up to an Arduino, and was able to receive a fairly reliable compass heading. I hooked up a small LCD to it that displayed the heading as well as the cardinal direction it correlated to. Then I turned it on in my truck, and drove around town comparing what the ardino claimed was the direction to the direction on the GPS and dashboard compass. Thankfully, they were very close; the differences are attributed to the lack of tilt-compensation in the calculation.

The Arduino Code, as it stands today is copied here:

typedef struct TiltCompass_s
	float Heading;
	float Pitch;
	float Roll;

	float X;
	float Y;
	float Z;

	float MaxX;
	float MaxY;
	float MaxZ;
	float MinX;
	float MinY;
	float MinZ;

	float XMean;
	float YMean;
	float ZMean;
	float XLeng;
	float YLeng;
	float ZLeng;
} TiltCompass;


#include <Wire.h>
#include <SoftwareSerial.h>
#include <Print.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_LSM303_U.h>
#include "Autopilot.h"

SoftwareSerial lcd(3,2); // pin 2 = TX, pin 3 (unused)
const int LOOP_DELAY = 500;

TiltCompass Compass;
Adafruit_LSM303_Mag_Unified Sensor = Adafruit_LSM303_Mag_Unified(12345);

///Initiation Method
void setup() {

  lcd.begin(9600); // set up serial port for 9600 baud
  delay(500); // wait for display to boot up

///Main Program Loop
void loop() {


///Initialize the Compass
void InitCompass(void)
	//Start LSM303 Module
		Serial.println("Invalid sensor!");
		lcd.write("Invalid Sensor!");

	//Calibrate sensore values to values discovered for the sensor

///Method to get the Compass heading from the LSM303DLHC module
void GetHeading()
	sensors_event_t magEvent; 


	float computedX = (magEvent.magnetic.x - Compass.XMean)/Compass.XLeng; // normalized x-component of acceleration
	float computedY = (magEvent.magnetic.y - Compass.YMean)/Compass.YLeng; // normalized y-component of acceleration
	float computedZ = (magEvent.magnetic.z - Compass.ZMean)/Compass.ZLeng; // normalized z-component of acceleration
	//float acceleration = sqrt(pow(computedX,2)+pow(computedY,2)+pow(computedZ,2)); 
	// Calculate the angle of the vector y,x
	Compass.Heading = (atan2(computedY, computedX) * 180) / PI;
	if (Compass.Heading < 0)
		Compass.Heading += 360;

	Serial.print("Compass: ");
	Serial.print(" - ");

///Method to convert a Compass direction to a cardinal direction
String GetCompassRoseDirection()
	if (Compass.Heading > 11.25 & Compass.Heading < 33.75)
		return "NNE";
	}if (Compass.Heading > 33.75 & Compass.Heading < 56.25)
		return "NE";
	}if (Compass.Heading > 56.25 & Compass.Heading < 78.75)
		return "ENE";
	}if (Compass.Heading > 78.75 & Compass.Heading < 101.25)
		return "E";
	}if (Compass.Heading > 101.25 & Compass.Heading < 123.75)
		return "ESE";
	}if (Compass.Heading > 123.75 & Compass.Heading < 145.25)
		return "SE";
	}if (Compass.Heading > 145.25 & Compass.Heading < 168.75)
		return "SSE";
	}if (Compass.Heading > 168.75 & Compass.Heading < 191.25)
		return "S";
	}if (Compass.Heading > 191.25 & Compass.Heading < 213.75)
		return "SSW";
	}if (Compass.Heading > 213.75 & Compass.Heading < 236.25)
		return "SW";
	}if (Compass.Heading > 236.25 & Compass.Heading < 258.75)
		return "WSW";
	}if (Compass.Heading > 258.75 & Compass.Heading < 281.25)
		return "W";
	}if (Compass.Heading > 281.25 & Compass.Heading < 303.75)
		return "WNW";
	}if (Compass.Heading > 303.75 & Compass.Heading < 326.35)
		return "NW";
	}if (Compass.Heading > 326.35 & Compass.Heading < 348.75)
		return "NNW";

	return "N";

///Take into account new values for calibration
void AdjustCalibration(sensors_event_t &magEvent)
  if (magEvent.magnetic.x < Compass.MinX)
	  Serial.println("Resetting MinX");
	  Compass.MinX = magEvent.magnetic.x;
  if (magEvent.magnetic.x > Compass.MaxX)
	  Serial.println("Resetting MaxX");
	  Compass.MaxX = magEvent.magnetic.x;
  if (magEvent.magnetic.y < Compass.MinY)
	  Serial.println("Resetting MinY");
	  Compass.MinY = magEvent.magnetic.y;
  if (magEvent.magnetic.y > Compass.MaxY)  
	  Serial.println("Resetting MaxY");
	  Compass.MaxY = magEvent.magnetic.y;

  if (magEvent.magnetic.z < Compass.MinZ)  
	  Serial.println("Resetting MinZ");
	  Compass.MinZ = magEvent.magnetic.z;
  if (magEvent.magnetic.z > Compass.MaxZ)  
	  Serial.println("Resetting MaxZ");
	  Compass.MaxZ = magEvent.magnetic.z;

///Method to display information on the LCD screen
void LCD_WriteOutput()
	lcd.write("Heading: ");
	lcd.write(254); // move cursor to beginning of first line
	lcd.write("Direction: ");

///Method to clear the LCD scren
void LCD_Clear()
	lcd.write(254); // move cursor to beginning of first line
	lcd.write("                "); // clear display
	lcd.write("                ");
	lcd.write(254); // move cursor to beginning of first line

///Set calibrations values
///  - Initial hardcoded numbers come from Calibration example project
void SetCalibrationValues()
	//Set Calibration Values from examples
	Compass.MinX = -60.45;
	Compass.MinY = -58.64;
	Compass.MinZ = -52.14;
	Compass.MaxX = 44.00;
	Compass.MaxY = 50.18;
	Compass.MaxZ = 78.06;

///Set values uses to normalize measured accelerations
void SetNormalizationValues()
	//Set Normalization Parameters
	Compass.XMean = (Compass.MaxX + Compass.MinX)/2;
	Compass.YMean = (Compass.MaxY + Compass.MinY)/2;
	Compass.ZMean = (Compass.MaxZ + Compass.MinZ)/2;
	Compass.XLeng = (Compass.MaxX - Compass.MinX)/2;
	Compass.YLeng = (Compass.MaxY - Compass.MinY)/2;
	Compass.ZLeng = (Compass.MaxZ - Compass.MinZ)/2;