Shift registers are the component that allows a microcontroller with only a handful of digital pins, to output dozens upon dozens of digital signals simultaneously. It does this by taking a sequence of 8 bits of information (a bit is a single unit of information; either a 1 or a 0) and output single bits to 8 different pins. This can used, for example, to control 8 LEDs with only 3 pins!
The 3 most important data pins on a shift register are Clock, Data, and Latch. While each of these pins may go by different names, they have the same function:
Storage Clock (RCLK, latch) will copy the bit configuration in the shift register to the outputs. This allows you to shift in any combination of 1's and 0's in any order before the data is represented on the 8 output pins.
Data (SER) provides the serial (bit-by-bit) data to be stored in the shift register.
Shift Register Clock (SRCLK) drives the timing that the shift register expects to recieve data by. It will shift bits further "into" the shift register each clock cycle, and simultaenously read the Data line for a new bit. In other words, each clock cycle on this pin will shift a new bit in and shift the oldest bit out.
There are other pins that also affect the operation of the shift register, such as SRCLR and OE. And then there's also the ability to daisy-chain several shift registers together to make (for example) a 16-bit shift register out of two 8-bit shift registers (that's 16 LEDs, still with only 3 pins)! To read more on shift registers and how to use them with Arduino, see Arduino's documentation here.
The magnetometer is the heart of this project. The IC chosen for this compass is the HMC5883L, which is a 3-axis magneto-resistive sensor made by Honeywell. The magneto-resistive effect is a property of Permalloy (nickel-iron) in which its resistance, and therefore voltage drop under a constant current, changes based on the magnetic field present along its axis. By having three strips of this material in the IC, this magnetometer can measure magnetic field on all 3 axes.
The data for each axis is passed along via I2C to the Arduino, where it is intepretted as floats (decimals). The z-axis is ignored in this project; only the x and y axes are taken into account. Before the raw values can be used, they must be calibrated. The most notable source of error is from hard-iron offsets, which come from surrounding metal and electronics. To fix this, we must first understand what kind of output we want from the magnetometer. Ideally, we would have a perfect circle of readings centered around an origin. The circle should then have North, East, South, and West at the points which it crosses the +y, +x, -y, and -x axes respectively as shown.
This circle of readings produces an accurate directional compass as shown in the lower right.
The issue arises when this circle is not centered. Metal and electronics close to the sensor can create magnetic fields that push and pull the circle away from where it should be. The drawing below shows an example of a hard-iron offset, shown in green.
With this hard-iron offset, the compass directions in the bottom right are produced. Obviously these are inaccurate and will not provide an accurate heading. To fix this, we need to move the entire circle of readings by the amount it is offset.
Calibration of the magnetometer involves finding how large the circle is in the x and y directions. By taking half of these values, we can find where the center of the circle is versus where the actual origin is. We can then take the difference between the origin and center of the circle, and then offset every magnetometer reading by this amount. This will effectively undo the hard-iron offset, resulting in accurate readings. To get a 360 degree heading, arctangent is performed on ( y / x ).
This model of magnetometer was chosen due to its popularity, low cost, and relative simplicity. Only two axes of the 3-axis magnetometer were needed, but the HMC was common and low cost enough that it was still the better choice; plus, the extra axis adds room for expandability!
If you were to choose this component for your own new project going forward, be careful, as it has been "end-of-lifed" (discontinued). Because it is no longer being manufactured, the price will begin to increase over time.
PCA9306 Level Shifter
This I2C level shifer was chosen for its low cost and ease of use. It is made specifically for I2C level shifting, so it was the perfect choice for communication between the 3.3V HMC5883L and 5V Arduino. Without this component, sending data over I2C from the Arduino could damage the magnetometer via overvoltage.
The magnetometer necessarily had to be in the center of the circle, so all other components were simply spread out around it to allow for easy routing of traces. A keepout area under the magnetometer was suggested by the datasheet to prevent power planes from influencing the magnetic field. Additionally, it recommended traces able to handle up to one amp to the external capacitors.