diff --git a/README.md b/README.md new file mode 100644 index 0000000..b516060 --- /dev/null +++ b/README.md @@ -0,0 +1,273 @@ +# 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`: OPEN + - `c`: CLOSE + - `s`: 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`: + +```python +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 pulse +- `ON -> OFF`: triggers an OPEN pulse + +### Digital Outputs / Relay + +Default output pins in `main.py`: + +```python +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 + +1. Flash MicroPython firmware to the ESP32. +2. Upload `main.py` to the ESP32 root filesystem. +3. Reset the ESP32. + +After reset, the program stays quiet for about 3 seconds, then starts streaming binary packets over USB serial. + +Example upload commands: + +```bash +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: + +```bash +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: + +```bash +python3 pc_reader.py /dev/ttyUSB0 --baud 115200 +``` + +The output should look similar to: + +```text +# 793 50.0Hz DIN=[0, 1, 0, 0] DOUT=[1, 0] ACC=[...] GYRO=[...] ANGLE=[...] +``` + +Type a command in the terminal and press Enter: + +```text +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: + +```python +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: + +```python +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: + +```bash +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: + +```bash +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: + +```bash +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: + +```text +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`: + +```python +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`: + +```python +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: + +```python +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: + +```bash +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.py` loop. +- 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`: acceleration +- `0x52`: gyro +- `0x53`: 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 + +1. Use `pc_reader.py` to confirm the PC receives stable 50 Hz packets. +2. Type `o`, `c`, and `s` to confirm `DOUT` state changes. +3. Enable real relay output with `USE_REAL_DOUT = True` when ready. +4. Use `visualise.py` to sample data and generate CSV/plot outputs for checking noise, frequency, and angle stability.