Lab 02a: Stepping Up

The questions below are due on Thursday February 15, 2018; 09:55:00 PM.


Partners: You have not yet been assigned a partner for this lab.
You are not logged in.

If you are a current student, please Log In for full access to the web site.
Note that this link will take you to an external site (https://oidc.mit.edu) to authenticate, and then you will be redirected back to this page.

Goals:

During the next two labs, we'll be focusing on a new piece of hardware, an inertial measurement unit (IMU). In Lab02A, we will first add the IMU to our embedded system and study its raw accelerometer outputs. Following that, we'll condition and process the raw data so that we can extract meaningful and useful information from it. In particular, we'll be counting the number of steps of a walking person by looking for peaks in our data. On Thursday, in Lab02B, we'll then integrate our step counter with a hosted web application for recording steps and comparing steps across the classroom.

1) Getting Started

Download the Code for today. This zip has several subfolders in it:

  1. mpu9255_esp32: This folder contains a library that allows the ESP32 microcontroller to conveniently communicate with the IMU. To use this library, it needs to be manually placed into your */Documents/Arduino/libraries folder and then you must restart Arduino completely. Please don't not do this and then ask why it isn't working. The library can then be accessed at any time in the future by including it at the top of your code: #include<mpu9255_esp32.h>.

  2. raw_plotter: This folder contains a minimal script to interface with the IMU that we'll use for visualizing the IMU's accelerometer data in real-time.

2) Adding the IMU

Up at the front you should grab an Inertial Measurement Unit (IMU). The actual IMU is the small 4mm-by-4mm chip mounted in the middle of the blue circuit board. Everything else on the board is a support component. The IMU chip contains three types of sensors: a three-axis (x, y, and z) accelerometer, a three-axis gyroscope, and a three-axis magnetometer. The accelerometer measures linear acceleration, the gyro measures rotational velocity, and the magnetometer measures magnetic fields. (Magnetometers are generally used with the the Earth's magnetic field to generate electronic compasses.) This is an extremely powerful set of sensors all within a tiny chip (again, 4mm-by-4mm). We live in amazing times! In 6.08 we'll be using the MPU9255 IMU by Invensense. The data sheet is on our Reference page.

For Lab today and on Thursday, we'll be using only the accelerometer functionality. We'll use the other sensors (the magnetometer and gyroscope) later on.

The IMU communicates with the ESP32 via the I2C (pronounced "I squared C") communication protocol. It needs four wires going to it—two for power and two for communication:

imu hookup

Hookup diagram for IMU. Two wires (3.3V and GND) provide power, and two wires (SDA and SCL) are used for bi-directional communication between the ESP32 and the IMU.

Place the IMU in this general area of your breadboard:

imu placement on board

Left: Where should we place our IMU on the board? The green box looks like promising real estate. Right: In preparation for placing another device there, I'm going to make sure my +3.3V and GND pins are extended down to the red/blue power rails.

Again, as with everything we do, try to keep your wiring clean! Clean boards are happy boards.

imu wiring

How I, Joe Steinmeyer, implemented my wiring. You can do it differently and nobody will judge you for it. (TA note: we might judge you just a little bit if we have to troubleshoot your circuit and your wiring is messy...)

2.1) Reading acceleration data

Assuming you have already installed the IMU library (see the points on the files above if you haven't), open up raw_plotter.ino from the Lab's zip distribution. Let's look at the code in chunks prior to just running it:

At the top of the file, we import a library to interface with our IMU. This is the one we just installed and made you restart Arduino for:

#include <mpu9255_esp32.h>

At the global scope of our file we then create an object of the MPU9255 class which contains interfacing instructions for the IMU. By accessing this object, we will also be able to get measurements from it:

MPU9255 imu; //imu object called, appropriately, imu

In our setup we will do some stuff that should start to become standard for us. This includes:

  • Start up serial communications
  • Initialize I2C communication (using the Wire library) for chatting with the IMU
  • Start up the IMU (see next code chunk)
  • Initialize a timer with a value

void setup() {
  Serial.begin(115200); //for debugging if needed.
  Wire.begin();  //begin i2c communication (how IMU and ESP talk)
  setup_imu(); //see function below
  primary_timer = millis(); //initialize timing variable
}

The setup_imu function makes sure the IMU is connected and, if it is, runs a few initialization tasks:

void setup_imu(){
  if (imu.readByte(MPU9255_ADDRESS, WHO_AM_I_MPU9255) == 0x73){
    imu.initMPU9255();
  }else{
    while(1) Serial.println("NOT FOUND"); // Loop forever if communication doesn't happen
  }
  imu.getAres(); //call this so the IMU internally knows its range/resolution
}

Finally our loop which contains the body of our code:

void loop() {
  imu.readAccelData(imu.accelCount);
  x = imu.accelCount[0]*imu.aRes;
  y = imu.accelCount[1]*imu.aRes;
  z = imu.accelCount[2]*imu.aRes;
  Serial.println(String(x)+","+String(y)+","+String(z));
  oled.clearBuffer();
  oled_print_at("X: "+String(x),0,15);
  oled_print_at("Y: "+String(y),0,30);
  oled_print_at("Z: "+String(z),0,45);
  oled.sendBuffer();
  while (millis()-primary_timer<LOOP_SPEED); //wait for time to move long enough
  primary_timer = millis(); //update timer
}

In the loop block, we use imu.readAccel() to read new accelerometer data. That method will update a three-element class array, imu.accelCount, which contains the acceleration data for each direction (accelCount[0] for x-directed accel, [1] for y-directed, etc.).

If you look at the IMU breakout board, you'll see how the directions are defined. z is up/down, while x and y are forward and side. The data stored in imu.accelCount is a signed integer between -32768 and 32767. This is because the IMU has on-board a 16-bit Analog-to-Digital Converter (ADC), so it stores its measured (analog) values as 16-bit signed (digital) integers. Those integers can be translated back to acceleration values by knowing the range of the accelerometer. In our case, it is set to +/-2 g, so that 32767 ~ 2 g, where 1 g = 9.81 m/s^2. That range can be changed by setting a variable within the IMU.

We can calculate the acceleration in g's much more easily with lines like this:

x =imu.accelCount[0]*imu.aRes;

We multiply the raw value by the scale factor (imu.aRes). The variable imu.aRes is a really convenient scaling factor. Similar lines of code can get us from raw gyroscope readings to angular velocity (in degrees per second) and from raw magnetometer readings to magnetic field (in milliGauss) when we need those later on.

The numbers we generate then have two things done with them. First, they are printed over Serial so that we can read them on the Serial monitor. In addition to just reading them, when you print lines of numbers separated by commas, you can:

  1. Close the Serial Monitor
  2. Open the Serial Plotter
  3. Watch the signals in real time.

This can often give a better feel for a signal that you're investigating.

We also display our three acceleration values on the OLED for convenience, which we do by printing them through another simple utility print function defined here:

void oled_print_at(String input, int x, int y){
  oled.setCursor(x,y);
  oled.print(input);
}

Try Now:

Do the values make sense? What happens when you move the system around, or change the orientation? What do the plots look like while you walk around? Does the orientation matter in terms of detecting the steps? Consider taking screenshots for discussions later on. Why is the az value at around 1 when the embedded system is flat on the table?

3) Inertial navigation

There is a lot of interest in using IMUs for navigation. After all, from Newton's Laws, if we know acceleration versus time, we should be able to infer position, right? We do this in 8.01, so why can't we do it to figure out where we are over time? Since the IMU is measuring acceleration, we should be able to integrate twice (in discrete time, just like in the exercises for Week 1) and figure out the position...right?

Unfortunately, no.

The reasons we can't just integrate twice are offsets and drifts. Put simply, if the acceleration values are off even a little bit from reality, then errors in position will compound proportionally to the square of time, t^2. In addition, any noise in the measurement will compound similarly.

For example, the IMU initialization/calibration procedure is supposed to remove a first set of offsets in the device. You see, even when there is no acceleration, the IMU does not report exactly 0.000000. It is off a little bit. The calibration function that we ran during setup measures that offset and subtracts it out. But the offset changes over time (due to temperature and other factors), and there is always noise that affects the values a little bit, so zero acceleration does not actually result in a true zero reading. All together, it means even sitting still we don't get zero values.

To gauge the importance of those offsets, if the x-directed acceleration when the device is sitting on a table is measured at 0.001 g instead of 0 g, what will be the change in position after 100 sec?Enter the distance traveled [in m].

So that's why we don't use inexpensive IMUs for inertial navigation. (People do make very expensive IMUs for inertial navigation, that have much lower noise, offsets and drifts.)

3.1) Forget Orientation

You should see that steps (and really any sort of movement) results in peaks and troughs along various axes of our accelerations. Our goal is to count steps, but we don't want to restrict ourselves to having to hold our labkit in just a certain way. In order to make our ability to measure these walking peaks better, we'd like to instead just look at the magnitude of total acceleration from all three axes combined, which is actually what most fitness trackers do. Change the code in raw_plotter.ino so it is only plotting the acceleration magnitude overall, and watch the plot as you (carefully!) place the system against your hip and walk around a bit. Are there features in the plots that indicate when a step occurs?

We have included the math.h library at the top of the .ino file for your convenience.

Finally, you may notice that the spikes in your system are a bit too spiky. One way to cut down on "noise" like this is to average the signal. Care must be taken in doing this, as, if you average too much, you'll completely ruin the signal— but the right amount of averaging can be shown (mathematically and in other actually legitimate ways) to remove unwanted types of signals. Now, instead of plotting the raw magnitude of the acceleration, run it through a three-way sliding average, which was discussed in Exercise 1 from last week which is a system that expresses the following difference equation:

y[n] = \frac{1}{3}\left(x[n]+x[n-1]+x[n-2]\right)

Set up your system so that both your raw acceleration magnitude and your averaged acceleration magnitude are plotted in the serial plotter and then ask for the checkoff below.

Checkoff 1:
Show and discuss your acceleration plots and calculations with a staff member.

Now it's time to count steps.

4) Step counter

One very simple step counting algorithm is to find peaks in the magnitude of the acceleration experienced by the device. Each isolated peak corresponds to a step. The goal then becomes to process our signal to first find peaks, and then count those peaks as steps. This is graphically shown below:

imu wiring

Progressing from Magnitude of Acceleration to Peaks to Steps.

Design a step counting algorithm that can at least reasonably count your steps by walking around the room. You can test your step counter by mimicking steps via gentle drops on a table, and once that looks good, by walking around with it and comparing the number of steps reported with the number of steps you actually took. You can carry your laptop around with you to power the wearable.

While you may find that delaying and taking long-apart measurements can get you to a decent response, we want to impose a limitation that you avoid halting the primary loop with time-consuming calls to delay. The loop's speed must stay at about 250 Hz so that we can integrate it with other code later on.

Ask for help! This is a fun problem and there are lots of right and even more wrong ways to do it!

Checkoff 2:
Show your step_count algorithm and discuss your approach with a staff member.

When done, go in peace. We'll pick this back up on Thursday and integrate it with some more state machines and a server and some other stuff...