Lab 5

The questions below are due on Thursday March 12, 2026; 06:00:00 PM.
 
You are not logged in.

Please Log In for full access to the web site.
Note that this link will take you to an external site (https://shimmer.mit.edu) to authenticate, and then you will be redirected back to this page.

Learning Objectives

Today's lab will focus on assembling and testing your sensor boards. We'll assemble your boards and then undertake various activities with them.

Our accelerometer

This first board of yours is a 3-axis accelerometer, meaning it measures motion in X-, Y-, and Z-directions simultaneously. We'll also refer to it occasionally as an IMU, or inertial measurement unit.

Technically we're being inaccurate here: an IMU will typically also include a gyroscope and magnetometer. But among friends we'll use both terms: IMU and accelerometer.

We'll talk in lecture at some point about what's inside this chip, but it's pretty amazing. There is an actual tiny little mass on tiny little springs, and when that mass experiences acceleration, it moves (F=mA, remember?), deforming the springs, which we can measure electrically. These devices are insanely sensitive1

True IMUs measure not just acceleration but also rotation, magnetometry (to find North, for example). Ours is pretty basic (just acceleration), but it is still a super-duper powerful IC. It has alot on on-board computation to add value to an overall system. We're only going to touch the surface of what it can do, but if you end up using an IMU in your semester project, you'll want to dig in a bit deeper into what it can do.

OK, let's get started.

Plugin Installation

KiCad has a really neat plugin that can be activated which provides an easy-to-read "GUI" of your board and its components and elements. It can be very helpful in the assembly that you'll be doing. To install, first go into your main KiCad page and Tools -> Plugin and Content Manager.

Kicad Plugin

In there, look for the Interactive HTML BOM plugin. Install it.

Once installed open up your PCB that you want to use with the plugin. In the top panel there should now be a bright-green button you can push corresponding to this plugin. Click it.

In the window that comes up, make sure to check the options about including nets and traces. When ready, then go and click Generate BOM.

You should be transferred to your web browser (hopefully it doesn't block this) and it'll present you with a web page that is your board along with all your components. There's different features you can look at including parts that need to be assembled:

As well as your traces and other angles of your board.

This tool can be really help when assembling since it will tell you what parts go where in a very easy-to-read and visualize manner.

Sensor board assembly

We have our sensor boards back! This is very cool -- these are designs you made and you laid out and you messed up (jk...hopefully). Right now we just have the boards themselves, so we'll need to solder components onto them to make them functional circuits. We've already had several rounds of board assembly and component soldering, so you are all pros.

Surface Mount Assembly

As we did for the LDO board in lab01, we're going to assemble the surface mount components first, then do the through hole component.

First, get the parts!

  1. Mark down the values for the passives that you need from your KiCAD BOM. The passives (resistors and caps) are either laid out in the middle table in the 6.9000 parts organizer or in the reels of parts on the windowsills of EDS.

  2. Cut and take only what you need! But before you cut, use a sharpie to label the section of reel, since they all look the same after you cut them! Some things like the resistors will have codes on them to read and figure out values, but the capacitors will basically be un-figure-outable. Get the LED from the staff table.

  3. Get one FXLS8962 IC from the bin at the staff table. It's tiny. Do not lose it.

Some of you are wondering -- why am I getting an FXLS8962 IC when I designed my board for the FXLS8974? Because in-between us developing this lab for the FXLS8974 and us ordering the ICs, there was a run on the parts and some crazy back-order...NXP sold their IMU business to STMicro, and so on. As much as we hunted high-and-low for these parts, they are unavailable from any reputable supplier. So we found someone who had a bunch of FXLS8962, which is an older discontinued accelerometer. Luckily, it has the same pinout and almost exactly the same register structure (certainly for the registers we are interfacing with), so it is a drop-in replacement from our point of view. Phew. Supply chains matter!

Once we run out of FXLS8962 ICs, that it. These accelerometers are out of stock everywhere until who knows. So please be careful with them.

Apply Solder paste

  1. The next step is to place a thin bit of solder paste on the exposed pads of the SMT components with a syringe, same as in lab01. The main difference is that our passives are a bit smaller (0805 vs 1206), so put down less paste.

  2. The pads for the FXLS8962 IC are really close together. You won't be able to apply paste to each individual pad. So just smear a tiny bit of paste across the pads. As long as you don't put down too much, you'll be ok.

See the paste strip across the pads. It would be better to apply even less paste.

Feel free to ask the staff if you'd like a quick check on your paste job!

Place the parts

  1. Place the Rs and Cs on the pads with tweezers. The orientation of the resistors and (non-polarized) capacitors does not matter for our board.

  2. Place the LED. The LED does have an orientation. Most of our LEDs are from Broadcom (datasheet), but our green LEDs are from Lite-On (datasheet). They each have a line or mark denoting the cathode (negative) terminal. If you get the orientation wrong, it won't work!

  3. Place the IC. Take care to orient the chip so that the pin 1 marker on the chip is aligned with the pin 1 marker on the silkscreen of your board. You can look at the chip datasheet to see where pin 1 is. Get the pads pretty well aligned with the pads on the PCB, even though the solder paste will smush around.

Reflow

  1. Place it on the hot plate and reflow, like you did in lab01. Remember that the reflow isn't complete until a bit after the smoke appears. You want to wait for the paste to get shiny and liquid and the parts to snap into position.

Inspect

  1. Before attaching the through-hole components, inspect your solder job under a microscope. In particular, look at the FXLS8962 pads at and angle and ensure that there are no solder bridges between pads.

You can see that the solder is no longer bridging the pins, though it's hard to see if there truly any shorts.

  1. A good way to tell if there are solder bridges is to probe adjacent pins. Take a look at your board layout. Pin 1 on the IC is VCC. Pin 2 is GND. Take a multimeter and probe those two nets (use convenient TPs, or connector pins, whatever), you should read either open or something fairly large, like ~MOhm. If you see just a few Ohms or less, you have a short. Then go probe between pins 2 and 3, pins 3 and 4, and so on.

  2. If there are still solder bridges, you can go and try to reflow again. Or you can go to a soldering iron and suck up some of the solder with solder wick.

Thru-hole assembly

  1. Obtain a 2x3 IDC connector header and place it in your board in the correct orientation. Pin 1 on the connector should be oriented toward pin 1 on your PCB.

The connector has polarity! Match it to the polarity of your board.

  1. Solder the connector header using a soldering iron.

Make a cable

In the last lab we made up some JST cables. In this lab we're going to show you a different style of cable. Specifically, we will make a 6-conductor ribbon cable using IDC (insulation displacement contact) connectors.

Ribbon cables with IDC connectors are neat because you don't have to peal the insulation -- the connector has little teeth that cut thru the insulation to make the connection. It's super fast.

Making Our Own Cables!

  1. Peel off 6 conductors from a ribbon cable and cut into a ~6" (15 cm) long segment. You can use the gray ribbon cable or the multicolored one. I like to use colors because it helps me keep track of orientation, but you do you.

The ribbon cable after cutting.

  1. Grab two 6-pin IDC connectors. We have two types. One has a little triangle indicating pin 1. The other has two little bars that polarize the connector, allowing the connector to fit into the housing in only one orientation. I prefer the latter as it prevents connection mistakes and frying my board.

We have two types of connectors in the parts drawer. I prefer the one on the right, but you do you.

  1. Insert the cable into the opening of one connector, past the end, and keep it straight. Orient the cable & connector as shown in the image below. This orientation is ideally suited for the mainboard IDC connector housing.

The cable and connector are oriented a certain way.

  1. Clamp down on the connector to make close the contact. I like to use a set of pliers to do it. It's very satisfying.

Crimping the cable. Note this gif is for a 4-conductor cable, whereas we're using a 6-conductor cable this year.

  1. Now insert the second connector.

As some of you learned when making the JST cable in ex02, orientation matters. Same here. You must have pin 1 of one connector oriented so that it is connected to pin 1 of the other connector, else your cable will not work. We've provided a set of pictures below of assemblies that will and will not work, along with your recommended orientation.

Some ways will work. Some ways won't because pin 1 at one end will end up at pin 6 on the other end.

  1. Double-check the orientation of the second connector. Now is your change to fix it if it's backwards.

  2. Clamp down on the second connector. When you're finished, your cable will look like so:

A happy 6-conductor IDC ribbon cable.

  1. Finally, because we are not animals, use a diagonal cutter to cut the overhanging cable bit flush with the connector.

Giving the overhang a haircut.

Final assembly

Some soldering

Our 2x3 IDC connector has an SDA pin, and SCL pin, and an INT1 pin. We need to connect those to the appropriate pins on our ESP32C3. In the middle of your mainboard are little solder jumpers that allow you to connected SDA and SCL to IO3..7, and INT1 to IO0 or IO1.

  1. For I2C, choose the same I2C pins as you've been using for your BH1750 board. Use a soldering iron and some solder to connect the relevant solder jumpers.

  2. For IN1, choose a free pin, i.e., nothing should be connected to it on the breadboard. Solder the jumper for that pin.

Attachment and cabling

  1. Attach the accelerometer board to the last remaining attachment post on your mainboard. By now you know how to do this: one (1) M3 screw and you should be good.

  2. Attach the cable between the 2x3 IDC connector on your mainboard and the 2x3 IDC connector on your accelerometer board.

Add an LED

For the exercises that we'll undertake below, we'll need an LED.

  1. Find an LED in the lab (red is fine), and attach it and a suitable current-limiting resistor (~200 Ohm) to a free GPIO (I used GPIO4, but there are plenty of others) and your mini-breadboard.

Checkoff 1:
Ask a staff member to look over your assembled board.

Testing

Proof of life

OK, let's run some code to see this accelerometer working.

  1. Fire up VS Code.

  2. Instantiate a new ESP-IDF project, set the target to esp32c3.

  3. Unzip this fxls8974 library and place it into a Components folder in your project directory.

  4. Update the main CMakeLists.txt to:

idf_component_register(SRCS "main.cpp"
                    INCLUDE_DIRS "."
                    REQUIRES fxls8974
                    PRIV_REQUIRES esp_driver_gpio)
  1. Replace your main.cpp with the unzipped version of this code. Open it up and let's take a look.
// Initialize IMU with I2C configuration
if (!imu.begin(false, I2C_SCL, I2C_SDA)) {
    ESP_LOGE(TAG, "Failed to initialize FXLS8974");
    return;
}

Here we're initialize the accelerometer, using the I2C pins above.

// Proof of life!
ESP_LOGI(TAG, "FXLS8974 Product ID: 0x%02X", imu.getProductID());

// Set some acceleration parameters
imu.setFSR(2);
imu.setMagMode(1);
imu.setMode(1);
vTaskDelay(pdMS_TO_TICKS(10));  //tiny delay needed

AccelData accelData;    // instantiate data structure

Now our first method call, on getProductID. This is the one difference we've found between the FXLS8974 and the FXLS8962 that we are using -- they have different product IDs. No biggie.

Then we set some accelerometer parameters. setFSR sets the Full scale range in G's. So the max this accelerometer will measure in this setting is +/-2Gs. There are plenty of other settings. +/-2Gs is the most sensitive setting, but there are plenty of use cases where you don't want to be so sensitive, or you want to be able to measure larger accelerations (up to +/-16G for this part)

Then we setMagMode to 1, which causes the accelerometer to calculate the magnitude of the acceleration, in addition to the X-, Y-, and Z- components. So that we don't have to do floating-point math. Thanks, little dude.

Then is the important setMode method, which when set to 1 basically tells the IMU to get going and start measurements. We need a tiny delay afterward, but basically we're good to go.

accelData is a useful data structure to hold all the data from the IMU.

Finally, a while loop:

while(1) {
    if (imu.readAccelData(accelData) != 0) ESP_LOGW(TAG, "Data not ready");
    if (imu.readAccelMag(accelData) != 0)  ESP_LOGW(TAG, "Mag data not ready");

    ESP_LOGI(TAG, "Raw Accel: %d  %d  %d  %d",
            accelData.rawX, accelData.rawY, accelData.rawZ, accelData.rawMag);

    ESP_LOGI(TAG, "Accel: %.2f  %.2f  %.2f  %.2f",
            accelData.x, accelData.y, accelData.z, accelData.mag);

    vTaskDelay(pdMS_TO_TICKS(delayTime));
}

When we call the readAccelData and readAccelMag methods, we also check if the accelerometer has new data ready to read. This should rarely be an issue for our code, as the overall while loop is executing every 500 ms, much much slower than the accelerometer can measure (it's maximum data rate (also the default), called the "output data rate" is 3200 Hz). That said, I see occasional warning about the mag not being ready. Not a big deal.

Then we read the print the acceleration raw data (in bits) and the data in Gs.

  1. Build and flash the code and open the serial monitor. If everything worked, you should start to see accelerometer readings:
I (269) FXLS8974: FXLS8974 initialized successfully
I (269) IMU: FXLS8974 Product ID: 0x62
I (289) IMU: Raw Accel: -40  29  1020  1004
I (289) IMU: Accel: -0.04  0.03  1.00  0.98
I (789) IMU: Raw Accel: -40  20  984  992
I (789) IMU: Accel: -0.04  0.02  0.96  0.97
I (1289) IMU: Raw Accel: -40  49  1004  997
I (1289) IMU: Accel: -0.04  0.05  0.98  0.97
I (1789) IMU: Raw Accel: -16  8  1008  962
I (1789) IMU: Accel: -0.02  0.01  0.98  0.94

Make sure you understand all these numbers! The datasheet can help explain.

Checkoff 2:
When you get things working, find a staff member and allow them to join you in celebration of your first working PCB! Then explain what all those numbers mean.

Build a tilt detector, the hard way

One common use case for an accelerometer in the semester projects is to tell if the sensor node has tipped over. We can figure this out from the accelerometer readings, because gravity always points down, and the if your IMU is horizontal, then down is along the Z-axis of the chip. '' Throughout the next set of exercises, we want to evaluate different ways of detecting tilt, and in particular issues of latency. The LED will help us assess latency.

In our first approach to sensing tilt, write a function that returns true if the accelerometer orientation (and hence the mainboard) has tilted at least 45 degrees from horizontal.

bool isTilted(AccelData current) {

}

Now, insert that code into this slightly modified version of main.cpp (or create a new project if you prefer to keep the old code around).

You may need to update the ledPin variable depending on what you used for your LED.

In this code, when the isTilted function returns true, the code prints a message to the serial monitor and turns on the LED. The LED then turns OFF after a delay given by delayTime, which is currently 1000 ms. If the device is still tiled the next time through the loop, the LED will get turned back on. And so on.

Build, flash, and open the serial monitor. Now, if you did it correctly, the ESP32C3 should report if you tilt the mainboard and the LED should turn ON when that occurs. Try it and make sure it works when you tilt in various directions.

There are a few limitations of this approach. First, it assumes that the device was set up to be perfectly horizontal to begin with. It would be a bit smarter to take an initial reading of the angle and then check whether that angle changes substantially.

There are three bigger issues, though. First, we are detecting tilt via polling, meaning we keep looping and checking, which means the processor is always working, wasting power. Second, notice that there can be a delay between when you tilt and when the LED turns ON. This is due to the 1000 ms delay that we've inserted into our loop. Third, we are not taking full advantage of the IMU's capability, because, as it turns out, the IMU has an innate ability to detect tilt directly.

If the 1000 ms delay is causing latency between us tilting and the MCU detecting the tilt, why not just shorten the delay? Because the delay is a way of simulating a real-world situation where you want to be doing other things in your main loop, which might take time, and so you can't always fly through your main loop and just keep polling the tilt as fast as you'd like.

Build a tilt detector, a somewhat better way

The FXLS8974 (or, ok, the FXLS8962) has a built-in orientation detection subsystem. You can tell it exactly how much of an orientation change you'd like to detect (the default is 45 degrees) and it will set a register bit if that change occurs. It's very nice.

To use the orientation detection feature, we need to set bits across a few registers. Luckily for you, our library makes that easy.

If you add the following lines to the beginning your app_main where you configure the IMU:

imu.orientEnableDbcn(1);
imu.setorientDbCount(100);
imu.orientEnable(1);

The first two lines enable a debouncer, meaning that the orientation detection will only fire if the orientation persists for a period of time, in this case for 100 readings (which, at 3200 Hz, is around 31 ms). Then the last line turns on the orientation detection.

You'll also want to initialize an instance of the OrientData data structure (and you can get rid of the AccelData one, which we don't need anymore):

OrientData orient;

The key method call is getOrientation. Take a look at the library code for this method in fxls8974.cpp. Like many other methods in the library, we are either getting a register value or setting a register value.

In this case, what is the name of the variable corresponding to the register we are reading?

Enter the variable name

And what is the name of the corresponding register as per the data sheet?

Enter the register name

We next set a bunch of OrientData structure variables using various bitmasks.

Which bit is read to update the NewOrient variable?

Enter the bit number

Look through the datasheet to make sure you understand what that bit corresponds to. Also read and make sure you understand the other bits in that register (LO, LAPO, BAFRO). Basically, the internal computation of the IMU enables us to measure and read-out various types of orientation changes. It's pretty powerful.

Create a new ESP-IDF project. Within that project, write a new variant of your code that:

  • Uses the getOrientation method to detect tilt using polling
  • Writes the message "Orientation change detected!" and the new orientation upon detecting an orientation change, using the ESP_LOG library
  • If there is no orientation change, it does not print anything
  • Set the delayTime to around 50 ms to make the system more responsive

This code improves on our first version because we're using the IMU's on-board orientation detection, offloading floating point math from the MCU. In addition, now the LED will not stay ON if we're tilted. It turns ON during any change in orientation, which is pretty cool. However, we are still polling, and there is still substantial latency between tilting and the LED turning ON.

Checkoff 3:
Show your two tilt detector pieces of code working, and explain how they work.

Postscript: Luke, there is another

It turns we can do even better than this, and in the next problem set we'll do that. Stay tuned.


 
Footnotes

1In another class I taught in the distant past, we estimated the rms position noise of a first-generation accelerometer. It was <0.01 nm. So yes, insanely sensitive. (click to return to text)