- Implemented Euler to rotation matrix conversion for IMU data. - Added function to compute linear velocity from accelerometer data with gravity compensation. - Enhanced CSV and PNG saving functionality to automatically create directories. - Updated statistics printing to include linear acceleration and velocity metrics. - Expanded plotting functionality to visualize linear acceleration and velocity alongside existing data. - Added new PNG files for IMU quality visualization.
ESP32 IMU Bridge
This project bridges IMU data through an ESP32 to a PC, with optional gripper/relay control, real-time serial monitoring, CSV export, and signal-quality visualization.
Features
- Reads WIT-format IMU UART frames on the ESP32: acceleration, gyro, angle, and temperature.
- Streams custom binary packets from the ESP32 to the PC over USB serial at 50 Hz.
- Allows the PC to control the gripper with simple keyboard commands:
o: OPENc: CLOSEs: STOP
- Generates statistics, CSV files, and plots from either live serial data or saved text logs.
- Uses a physical switch on GPIO27 to trigger one-shot gripper open/close pulses.
Files
| File | Description |
|---|---|
main.py |
Main MicroPython program for the ESP32. It reads the IMU, controls DIO, and streams USB serial packets. |
esp_bridge.py |
PC-side Python wrapper class for integrating the ESP32 bridge into other programs. |
pc_reader.py |
PC-side real-time serial reader with keyboard gripper commands. |
visualise.py |
Sampling, statistics, CSV export, and plotting tool. |
test.py |
ESP32 UART pin scan/debug helper. |
imu_data.csv |
Example or exported IMU sample data. |
rcd.txt |
Example text log from pc_reader.py. |
imu_quality*.png |
Visualization output images. |
Hardware Wiring
IMU UART
| IMU | ESP32 |
|---|---|
| TX yellow wire | GPIO16 / RX2 |
| RX green wire | GPIO17 / TX2 |
| GND | GND |
The default IMU baud rate is 921600.
Digital Inputs
Default digital input pins in main.py:
DIN_PINS = (25, 26, 27, 32)
These inputs use Pin.PULL_UP, so normally:
- floating/open reads as
1 - connected to ground/closed reads as
0
GPIO27 is used as the physical toggle switch:
OFF -> ON: triggers a CLOSE pulseON -> OFF: triggers an OPEN pulse
Digital Outputs / Relay
Default output pins in main.py:
DOUT_PINS = (18, 19)
The relay is low-level triggered:
- GPIO HIGH: relay off
- GPIO LOW: relay on
Current output logic:
- GPIO18 / IN1 LOW: CLOSE
- GPIO19 / IN2 LOW: OPEN
- both GPIOs HIGH: STOP
ESP32 Setup
- Flash MicroPython firmware to the ESP32.
- Upload
main.pyto the ESP32 root filesystem. - Reset the ESP32.
After reset, the program stays quiet for about 3 seconds, then starts streaming binary packets over USB serial.
Example upload commands:
mpremote connect /dev/ttyUSB0 fs cp main.py :main.py
mpremote connect /dev/ttyUSB0 reset
Replace /dev/ttyUSB0 with your actual ESP32 serial port if needed.
PC Environment
Python 3 is recommended.
Install dependencies:
python3 -m pip install pyserial matplotlib numpy
If you only use pc_reader.py or esp_bridge.py, pyserial is enough.
Real-Time Reading And Control
Run:
python3 pc_reader.py /dev/ttyUSB0 --baud 115200
The output should look similar to:
# 793 50.0Hz DIN=[0, 1, 0, 0] DOUT=[1, 0] ACC=[...] GYRO=[...] ANGLE=[...]
Type a command in the terminal and press Enter:
o
c
s
These commands mean OPEN, CLOSE, and STOP.
Python API Usage
esp_bridge.py provides an ESP32Bridge class for use in other PC-side Python programs:
from esp_bridge import ESP32Bridge
with ESP32Bridge("/dev/ttyUSB0") as bridge:
bridge.open_gripper()
data = bridge.read_packet_with_frequency()
print(data)
bridge.stop_gripper()
Continuous reading example:
from esp_bridge import ESP32Bridge
with ESP32Bridge("/dev/ttyUSB0") as bridge:
for packet in bridge.iter_packets(seconds=10):
print(packet["freq_hz"], packet["acc_g"], packet["angle_deg"])
Sampling, Export, And Visualization
Sample from serial for 10 seconds, save a CSV file, and generate a plot:
python3 visualise.py --port /dev/ttyUSB0 --baud 115200 --seconds 10 --csv imu_data.csv --output imu_quality.png
Save files only without opening a plot window:
python3 visualise.py --port /dev/ttyUSB0 --baud 115200 --seconds 10 --csv imu_data.csv --output imu_quality.png --no-show
Read from a pc_reader.py text log and plot it:
python3 visualise.py --file rcd.txt --output imu_quality.png
If the output file already exists, visualise.py appends a timestamp to avoid overwriting it, for example:
imu_quality_20260701_131902.png
Packet Protocol
The ESP32-to-PC binary packet format is defined in main.py, pc_reader.py, and esp_bridge.py:
PACKET_FORMAT = "<2sBBBI10hB"
Field layout:
| Field | Type | Description |
|---|---|---|
| sync | 2s |
Fixed bytes A5 5A |
| version | B |
Currently 2 |
| din_mask | B |
4-channel digital input bit mask |
| dout_mask | B |
2-channel digital output bit mask |
| time_ms | I |
ESP32 ticks_ms() value |
| raw_values | 10h |
acc xyz, gyro xyz, angle xyz, temperature |
| checksum | B |
Low 8 bits of the sum of all previous bytes |
Raw value scaling:
| Data | Scaling |
|---|---|
| Acceleration | raw * 16 / 32768, unit g |
| Gyro | raw * 2000 / 32768, unit deg/s |
| Angle | raw * 180 / 32768, unit deg |
| Temperature | raw / 100, unit deg C |
Common Parameters
The most useful hardware and timing parameters are in main.py:
IMU_RX_PIN = 16
IMU_TX_PIN = 17
DIN_PINS = (25, 26, 27, 32)
DOUT_PINS = (18, 19)
IMU_BAUD = 921600
SEND_PERIOD_MS = 20
SWITCH_PULSE_MS = 2000
USE_REAL_DOUT = True
Before testing a real relay, you can set:
USE_REAL_DOUT = False
In this mode, the PC can still see DOUT state changes, but the ESP32 will not actually drive GPIO18/GPIO19.
Troubleshooting
Serial Port Not Found
On Linux, list possible serial ports with:
ls /dev/ttyUSB* /dev/ttyACM*
If you have permission issues, add your user to the dialout group or temporarily run the serial tool with sudo.
Frequency Is Not 50 Hz
Check the following:
- The ESP32 has entered the
main.pyloop. - The PC-side baud rate is
115200. - The IMU-to-ESP32 UART baud rate is
921600. - The USB serial cable and power supply are stable.
Data Is All Zero Or Angle Does Not Update
Check the IMU wiring and output format. The ESP32 parser currently handles WIT frames:
0x51: acceleration0x52: gyro0x53: angle
The program treats an angle frame as the point where one full IMU update is complete.
OPEN/CLOSE Direction Is Reversed
Depending on the relay and motor wiring, swap the relay inputs connected to GPIO18/GPIO19, or adjust the OPEN/CLOSE output logic in set_gripper().
Suggested Workflow
- Use
pc_reader.pyto confirm the PC receives stable 50 Hz packets. - Type
o,c, andsto confirmDOUTstate changes. - Enable real relay output with
USE_REAL_DOUT = Truewhen ready. - Use
visualise.pyto sample data and generate CSV/plot outputs for checking noise, frequency, and angle stability.