← build log

SS Sensor Hub · Wave Simulator · downstairs bench

Sensor ESP32 — Halls + MPU Board Map

The downstairs ESP32-WROOM-32 (38-pin DevKit) that reads all 7 KY-003 Hall sensors and the MPU-6050, reporting to Blynk (quota-safe firmware, patched 2026-08-03). Pin map matches wave_sim_hall_sensors_v1.ino.

ESP32 DevKit — 38-pin, USB at bottom

Only the pins in use are highlighted. All hall inputs sit on the top half of the left column; I²C for the MPU is on the right column.

ESP32-WROOM-32 USB 3V3 → 3.3 V bus (all sensor VCC, red) EN VP·36 HALL X — white signal VN·39 HALL Y — white signal 34 HALL Z1 — white signal 35 HALL Z2 — white signal 32 HALL YAW — white signal 33 HALL PITCH — white signal 25 26 27 HALL ROLL — white signal 14 12 GND → ground bus (all sensor GND, black) 13 D2 D3 CMD 5V GND 23 22 MPU SCL — green TX0 RX0 21 MPU SDA — yellow GND spare ground 19 18 5 17 16 4 0 2 15 D1 D0 CLK
Input-only pins: 34, 35, 36 (VP), 39 (VN) have no internal pull-ups — that's fine here because the KY-003 modules carry their own onboard pull-ups. Firmware uses plain INPUT.

Hall sensors — 7× KY-003 @ 3.3 V

AxisGPIOSignal wireBlynk pinNotes
X36 (VP)whiteV0input-only pin
Y39 (VN)whiteV1input-only pin
Z134whiteV2input-only pin
Z235whiteV3input-only pin
Yaw32whiteV4
Pitch33whiteV5
Roll27whiteV6
Sensors read active-LOW (magnet present → LOW); firmware inverts so Blynk shows 1 = magnet detected. Status heartbeat on V7 every 5 min.

Flex-cable standard — coiled USB + breakouts

Every hall on the moving frame runs through a coiled USB cable with a USB breakout board at each end. The coil absorbs frame vibration mid-span; the breakout keeps strain off the ESP32 pins. Native USB wire colors already match the bench convention.

USB breakout VBUS D− D+ GND ← 3.3 V bus feed (NOT 5 V — KY-003 runs at 3.3 V) · red ← Hall digital signal → its GPIO · white ← unused spare — cap it ← ground bus · black
Rules:Continuity-test every coiled cable end-to-end before install — charge-only cables have no D− conductor. ② Anchor both ends so the coil flexes mid-span, never at the terminals.

MPU-6050 — I²C accelerometer / gyro

MPU pinESP32WireNotes
VCC3V3red3.3 V bus
GNDGNDblackground bus
SDA21yellowI²C data
SCL22greenI²C clock
Default I²C address 0x68 (AD0 low). Second MPU on the same bus → tie its AD0 high = 0x69. Future I²C sensors (VL53L0X, TCA9548A) share the same 21/22 bus and the same red/black/yellow/green convention.

Firmware — quota-safe (patched 2026-08-03)

wave_sim_hall_sensors_v1.ino · OTA hostname wavesim-downstairs · Serial 115200

BehaviorSettingBlynk cost
Hall updateson-change + 500 ms/channel cap~2 msgs per magnet event
Heartbeat (V7)every 5 min (was 5 s)~9k/month
Sensor scan20 Hzfree (local)
Bench test: flash patched firmware first, then wave a magnet past each sensor in order — X, Y, Z1, Z2, Yaw, Pitch, Roll — and watch V0–V6 flip in the app. A full session costs a few hundred messages.

Latest sketch — wave_sim_hall_sensors_v1.ino (patched 2026-08-03)

The exact firmware on the board. Credentials live in a separate secrets.h tab (gitignored, never published). Server copy: ~/waveforge/firmware/diagnostics/wave_sim_hall_sensors_v1/

▸ show full sketch (130 lines)
/*
 * WaveForge Wave Simulator — Hall Sensor + OTA Sketch
 *
 * Reads 7 Hall sensors and reports state to Blynk dashboard.
 * Supports ArduinoOTA for wireless firmware updates after first USB flash.
 *
 * Board: ESP32-WROOM-32 (38-pin DevKit)
 * Blynk: WaveForge Wave Simulator template
 *
 * SETUP: Copy secrets.h.template to secrets.h and fill in real values.
 *        secrets.h is gitignored and stays local.
 */

#include "secrets.h"

#define BLYNK_PRINT Serial

#include <WiFi.h>
#include <WiFiClient.h>
#include <BlynkSimpleEsp32.h>
#include <ArduinoOTA.h>

// Hall sensor pin assignments — matches physical wiring
const int PIN_HALL_X     = 36;  // VP — X axis
const int PIN_HALL_Y     = 39;  // VN — Y axis
const int PIN_HALL_Z1    = 34;  // D34 — Z axis sensor 1
const int PIN_HALL_Z2    = 35;  // D35 — Z axis sensor 2
const int PIN_HALL_YAW   = 32;  // D32 — Yaw
const int PIN_HALL_PITCH = 33;  // D33 — Pitch
const int PIN_HALL_ROLL  = 27;  // D27 — Roll

// Blynk virtual pins (datastreams)
#define V_HALL_X     V0
#define V_HALL_Y     V1
#define V_HALL_Z1    V2
#define V_HALL_Z2    V3
#define V_HALL_YAW   V4
#define V_HALL_PITCH V5
#define V_HALL_ROLL  V6
#define V_STATUS     V7

// Track previous sensor states so we only send updates on change
int prevX = -1, prevY = -1, prevZ1 = -1, prevZ2 = -1;
int prevYaw = -1, prevPitch = -1, prevRoll = -1;
// Rate cap: during vibration a hall can chatter at the 20 Hz scan rate.
// Cap each channel to one update per RATE_CAP_MS so quota survives long runs.
const unsigned long RATE_CAP_MS = 500;
unsigned long lastSent[7] = {0,0,0,0,0,0,0};
bool canSend(int ch) {
  unsigned long now = millis();
  if (now - lastSent[ch] < RATE_CAP_MS) return false;
  lastSent[ch] = now;
  return true;
}

BlynkTimer timer;

void sendSensorReadings() {
  // Invert each reading — sensors are active LOW
  int x     = !digitalRead(PIN_HALL_X);
  int y     = !digitalRead(PIN_HALL_Y);
  int z1    = !digitalRead(PIN_HALL_Z1);
  int z2    = !digitalRead(PIN_HALL_Z2);
  int yaw   = !digitalRead(PIN_HALL_YAW);
  int pitch = !digitalRead(PIN_HALL_PITCH);
  int roll  = !digitalRead(PIN_HALL_ROLL);

  if (x != prevX && canSend(0))         { Blynk.virtualWrite(V_HALL_X, x);         prevX = x; }
  if (y != prevY && canSend(1))         { Blynk.virtualWrite(V_HALL_Y, y);         prevY = y; }
  if (z1 != prevZ1 && canSend(2))       { Blynk.virtualWrite(V_HALL_Z1, z1);       prevZ1 = z1; }
  if (z2 != prevZ2 && canSend(3))       { Blynk.virtualWrite(V_HALL_Z2, z2);       prevZ2 = z2; }
  if (yaw != prevYaw && canSend(4))     { Blynk.virtualWrite(V_HALL_YAW, yaw);     prevYaw = yaw; }
  if (pitch != prevPitch && canSend(5)) { Blynk.virtualWrite(V_HALL_PITCH, pitch); prevPitch = pitch; }
  if (roll != prevRoll && canSend(6))   { Blynk.virtualWrite(V_HALL_ROLL, roll);   prevRoll = roll; }
}

void sendHeartbeat() {
  String msg = "Online — up " + String(millis() / 60000) + " min";
  Blynk.virtualWrite(V_STATUS, msg);
}

void setupOTA() {
  ArduinoOTA.setHostname("wavesim-downstairs");

  ArduinoOTA.onStart([]() {
    Serial.println("OTA update starting");
    Blynk.virtualWrite(V_STATUS, "OTA update starting");
  });

  ArduinoOTA.onEnd([]() {
    Serial.println("OTA complete");
  });

  ArduinoOTA.onError([](ota_error_t error) {
    Serial.printf("OTA error[%u]\n", error);
  });

  ArduinoOTA.begin();
  Serial.println("OTA ready");
}

void setup() {
  Serial.begin(115200);
  Serial.println("WaveForge Wave Simulator starting");

  // GPIO 34, 35, 36, 39 are input-only — no INPUT_PULLUP available
  // Hall modules have onboard pull-ups, so INPUT is correct
  pinMode(PIN_HALL_X, INPUT);
  pinMode(PIN_HALL_Y, INPUT);
  pinMode(PIN_HALL_Z1, INPUT);
  pinMode(PIN_HALL_Z2, INPUT);
  pinMode(PIN_HALL_YAW, INPUT);
  pinMode(PIN_HALL_PITCH, INPUT);
  pinMode(PIN_HALL_ROLL, INPUT);

  Blynk.begin(BLYNK_AUTH_TOKEN, WIFI_SSID, WIFI_PASS);

  setupOTA();

  timer.setInterval(50L, sendSensorReadings);    // 20 Hz sensor scan
  timer.setInterval(300000L, sendHeartbeat);     // 5 min heartbeat (was 5 s — Blynk quota!)

  Blynk.virtualWrite(V_STATUS, "Wave Sim online");
}

void loop() {
  Blynk.run();
  ArduinoOTA.handle();
  timer.run();
}