Lab 9: Mapping

Overview

This lab focuses on mapping a static environment by taking our robots and performing a 360° in-place rotation and collecting data from our time-of-flight (ToF) sensors. I used the orientation-based PID control approach via DMP yaw estimates from the ICM-20948 IMU with PID tuning.

At each angular increment(for this lab it was 25°), the robot paused and collected several ToF readings. These readings were collected with timestamp and heading. After completing the scan, the data is transmitted over BLE for post-processing into a global map.

Control Strategy and Angle Wrapping

To perform accurate rotations, the robot uses a PID loop around the DMP-derived yaw. The yaw is calculated from quaternions:

double siny = 2.0 * (qw * qz + qx * qy);
double cosy = 1.0 - 2.0 * (qy * qy + qz * qz);
yaw_out = atan2(siny, cosy) * 180.0 / PI;

To ensure the yaw does not wrap unexpectedly at ±180°, the PID loop applies this correction:

if (yaw_error > 180) yaw_error -= 360;
if (yaw_error < -180) yaw_error += 360;

This guaranteed smooth control and allows the robot to turn reliably to target angles.

Dynamic PID Tuning

PID gains are adjusted dynamically based on yaw error:

float abs_err = abs(error);
Kp = 0.3 * abs_err;
if (Kp > 0.12) Kp = 0.12;

This approach avoided overshooting at small errors and ensured faster convergence at larger angles.

Command Handling

The robot responds to BLE commands via a handleCommand() function that supports:

  • DO_MAPPING – perform a full 360° scan, collecting readings_per_angle samples at each `increment_deg`(I used 25 degrees for this lab as the instructions said so)

  • POINT_TO_ANGLE – rotate to a specified angle ( redoing lab 6 if you will)

  • STOP – interrupt ongoing behavior. (Sometimes when tuning, I really needed to stop, but also still exampline data).

For mapping, the following loop is executed:

for (int angle = 0; angle < 360; angle += increment_deg) {
  yaw_target = angle;
  while (millis() - start_time < 5000) {
    pidYawControl(yaw_target);
    if (abs(yaw_error) <= err_thresh) break;
  }
  stopMotors();
  delay(200);
  for (int i = 0; i < readings_per_angle; i++) {
    tof.startRanging();
    delay(70);
    readDMPYaw(yaw);
    recordReading();
  }
}

Measurement Logging

Each reading consists of:

  • Timestamp (miliseconds)

  • Unwrapped yaw angle (in degrees)

  • ToF distance (in mm)

These are stored in arrays:

yaw_log[log_index] = yaw;
tof_log[log_index] = dist;
time_log[log_index] = millis();

After the full scan, readings are sent back over BLE:

tx_string.clear();
tx_string.append(time_log[i]); tx_string.append("|");
tx_string.append(yaw_log[i]); tx_string.append("|");
tx_string.append(tof_log[i]);
tx_characteristic_string.writeValue(tx_string.c_str());

Robot Video

Here is a video of the pid loop running with the data collection.

Post-Processing

On the Python side, each BLE message is parsed into:

time = float(data_components[0]) / 1000
yaw = float(data_components[1]) * np.pi / 180
distance = float(data_components[2]) / 1000

Each point is translated into global coordinates from a known pose:

\[\begin{split}x = x_0 + r \cos(\theta) \\ y = y_0 - r \sin(\theta)\end{split}\]

Polar and Cartesian plots are generated to verify individual scans.

Here is the individual plots for (0,3)

Polar Plot (0,3) Polar Plot with connectivity (0,3)

Here is the individual plot for (5,3)

Polar Plot (5,3) Polar Plot with connectivity (5,3)

Here is the individual plot for (5,-3)

Polar Plot (5,-3) Polar Plot with connectivity (5,-3)

Here is the individual plot for (-3,2)

Polar Plot (-3,-2) Polar Plot with connectivity (-3,-2)

If you make plots that are scaled the same and place them on the map at their location, you can make a global map. See below.

Global Mapping

The robot is placed at 4 distinct positions (-3, -2), (0, 3), (5, -3), (5, 3)), and performs a full 360° scan at each. These are combined into a merged global map:

Merged Map

Here is the image with the lines drawn on top

Merged Map with Lines

Here are the segments in list form. They can be used next lab.

List 1

(-3, 4.5)  (6.2, 4.5)
(-3, 4.5)  (-3, 1)
(6.2, 4.5)  (6.2, -4.5)
(0, -4.5)  (6.2, -4.5)
(0, -4.5)  (0, -2.2)
(-5.5, -4.5)  (-5.5, 1)
(-5.5, -4.5)  (-0.5, -4.5)
(-0.5, -4.5)  (-0.5, -2.2)
(-5.5, -4.5)  (-0.5, -4.5)
(-0.5, -2.2)  (0, -2.2)
(-5.5, 1)  (-3, 1)

List 2

(2.2, -0.5)  (4.2, -0.5)
(2.2, 1.8)  (4.2, 1.8)
(2.2, -0.5)  (2.2, 1.8)
(4.2, -0.5)  (4.2, 1.8)

Reflection

I decided to use the DMP for this lab as I did not for lab 6 and getting it working and in a way redoing lab 6 was the hardest part. It was very rewarding once complete though.

Acknowledgements

Thanks to the course staff for feedback on motor tuning. Special thanks to Anunth Ramaswami for letting me film a video on his robot after I broke one of my motor drivers(I had to adjust my constant values). Thanks to Anunth Ramaswami, Aravind Ramaswami, and Aidan McNay for answering my random questions about the DMP. Chat GPT was used to generate a barebones template for this write up as usual, plot points, and also help debug my code sometimes