Motion Controller Design


Thought for 11 mins 49 sec
2/4 USB-Differenzialpfad kontrolliert routen und prüfen

Routing-Preflight und Regelprüfung durchführen

USB-Differenzialpfad kontrolliert routen und prüfen

Power-Pfade und Buck-Boost-Schleifen routen

DRC, Impedanz und Rückstrompfade abschließend prüfen

Thought for 13 sec
Thought for 7 sec
Awaiting 1 background task
Received notification from "USB Routing Check" that it finished — picking the work back up.
Bisher abgeschlossen:
  • Routing-Preflight bereinigt: fehlerhafte U4-Pads 11–15 deaktiviert und Neckdowns für REG_L1/L2 sowie U5-VDD definiert.
  • Router-Preflight anschließend sauber: 0 Anomalien.
  • USB-Routing-Executor ist abgeschlossen, konnte aber wegen fehlender ACUs nicht mehr unabhängig gegen Layout, DRC, Impedanz und Rückstrompfade verifiziert werden.
  • Für den fehlerhaften TPS63001DRCR-Bibliotheks-Footprint wurde ein internes Qualitäts-Ticket eingereicht.
Noch offen
  • USB-Routingbericht und tatsächliche Kupfergeometrie rücklesen
  • USB-Längenabgleich, Viazahl, 90‑Ω-Geometrie und Rückstrompfad verifizieren
  • Power-Pfade VBUS, BAT, SYS, 3V3 und REG_L1/L2 routen
  • Abschließende DRC-, Plane-Coverage- und Rückstromprüfung
Die weitere Ausführung ist aktuell blockiert, weil keine ACUs verfügbar sind.
USB-Bericht prüfen
USB-Routing verifizieren
Offene Punkte dokumentieren

Firmware Starter
Zweck und Hardwarebasis
Dieses Dokument ist der direkt nutzbare Prototyp-Start für den tatsächlich verdrahteten Schaltplan mit U1 ESP32-S3-WROOM-1-N8R2. Es verwendet PlatformIO, Arduino-ESP32, den nativen USB-OTG-Controller für USB-MIDI und NimBLE-Arduino für BLE-MIDI.
Der Starter deckt ab:
  • USB-MIDI über U1 GPIO19/GPIO20 und J1
  • BLE-MIDI
  • IIM-42652 (U2) über die reale 4-Draht-SPI-Verdrahtung
  • MAX17048 (U5) über die reale I2C-Verdrahtung
  • sechs Taster SW2–SW7
  • zwei Trigger an J3/J4
  • Status-LED D2 und hardwaregesteuerte Lade-LED D1
  • Fuel-Alert von U5
  • Charger-PGOOD von U3
  • Service-UART an TP8/TP9
Echtes GPIO- und Signal-Mapping

Table


FunktionNetzU1-PinGegenstelle / DesignatorElektrik
USB D−USB_D-GPIO19R3 → U6 → J1Native USB; 22 Ω R3
USB D+USB_D+GPIO20R4 → U6 → J1Native USB; 22 Ω R4
IMU CSIMU_SPI_CS_NGPIO10U2 AP_CS, R16aktiv Low; externer 10-kΩ-Pull-up
IMU MOSIIMU_SPI_MOSIGPIO11U2 AP_SDISPI-Ausgang
IMU SCLKIMU_SPI_SCLKGPIO12U2 AP_SCLKSPI Mode 0 im Starter
IMU MISOIMU_SPI_MISOGPIO13U2 AP_SDOSPI-Eingang
IMU INT1IMU_INT1GPIO9U2 INT1im Starter Push-Pull, aktiv High
I2C SDAI2C_SDAGPIO8U5 SDA, R144,7-kΩ-Pull-up nach 3V3
I2C SCLI2C_SCLGPIO7U5 SCL, R154,7-kΩ-Pull-up nach 3V3
Fuel AlertFUEL_ALERT_NGPIO6U5 ~ALERT, R26Open-Drain, aktiv Low, 10-kΩ-Pull-up
Taste 1BTN1_NGPIO4SW2, R17aktiv Low, externer 10-kΩ-Pull-up
Taste 2BTN2_NGPIO5SW3, R18aktiv Low
Taste 3BTN3_NGPIO14SW4, R19aktiv Low
Taste 4BTN4_NGPIO15SW5, R20aktiv Low
Taste 5BTN5_NGPIO16SW6, R21aktiv Low
Taste 6BTN6_NGPIO17SW7, R22aktiv Low
Trigger 1TRIGGER1_NGPIO18J3 Pin 1, R23aktiv Low; J3 Pin 2 = GND
Trigger 2TRIGGER2_NGPIO21J4 Pin 1, R24aktiv Low; J4 Pin 2 = GND
Charger PGOODCHG_PGOODGPIO41U3 ~PGOOD, R10Open-Drain, aktiv Low; 100-kΩ-Pull-up
Status-LEDSTATUS_LED_KGPIO42Kathode D2aktiv Low; Anode über R25 an 3V3
UART0 TXUART_TXGPIO43/TXD0TP83,3-V-UART-Ausgang
UART0 RXUART_RXGPIO44/RXD0TP93,3-V-UART-Eingang
BOOTMCU_BOOTGPIO0SW9, R13, TP11aktiv Low; Strapping-Pin
Reset/EnableMCU_ENENSW8, R12, C10, TP1010 kΩ + 1 µF; Reset aktiv Low
Nicht als Firmware-Ausgang verfügbar: D1 ist die Lade-LED. Ihre Kathode liegt am Open-Drain-Ausgang ~CHG von U3; die Anode wird über R9 aus SYS gespeist. D1 zeigt daher autonom den Ladezustand des BQ24074 an. D2 ist die einzige MCU-gesteuerte LED.
Wichtige reale Adressen und Register
MAX17048 U5
  • 7-Bit-I2C-Adresse: 0x36 (Datenblatt nennt 0x6C/0x6D inklusive R/W-Bit).
  • VCELL 0x02: 78,125 µV/LSB.
  • SOC 0x04: 1/256 %/LSB.
  • VERSION 0x08: Produktionsversion; typisches Muster 0x001x, nicht auf einen einzelnen Revisionswert festnageln.
  • CONFIG 0x0C: POR 0x971C; ALRT ist Bit 5 des Low-Bytes; Standard-Empty-Alert ist 4 %.
  • STATUS 0x1A: Alert-Ursachen und Reset-Indikator.
IIM-42652 U2
Der Starter benötigt keine externe IMU-Bibliothek. Er führt echte SPI-Registertransaktionen aus:
  • DEVICE_CONFIG 0x11, Soft Reset Bit 0
  • INT_CONFIG 0x14
  • Datenburst ab TEMP_DATA1 0x1D
  • PWR_MGMT0 0x4E
  • GYRO_CONFIG0 0x4F
  • ACCEL_CONFIG0 0x50
  • INT_SOURCE0 0x65
  • WHO_AM_I 0x75, Sollwert 0x6F
Die Grundkonfiguration ist ±16 g, ±2000 °/s und 200 Hz für beide Sensoren. Weiterführende AAF-/Notch-/UI-Filter-, FIFO-, Timestamp-, Self-Test- und Kalibrierregister sind bewusst als spätere, an die konkrete Anwendung anzupassende Datenblattarbeit markiert.
PlatformIO-Projekt
Verzeichnisstruktur:

Text


firmware/
├── platformio.ini
└── src/
    └── main.cpp
platformio.ini

Ini


[env:midi-motion-controller]
platform = espressif32
board = rymcu-esp32-s3-devkitc-1
framework = arduino

; U1 ist ESP32-S3-WROOM-1-N8R2: 8 MB Flash, 2 MB PSRAM.
board_build.flash_size = 8MB
board_build.partitions = default_8MB.csv

; Native USB als TinyUSB/OTG-Device, CDC und MIDI als Composite Device.
build_flags =
    -DARDUINO_USB_MODE=0
    -DARDUINO_USB_CDC_ON_BOOT=1
    -DBOARD_HAS_PSRAM

lib_deps =
    h2zero/NimBLE-Arduino@^2.3.6

monitor_speed = 115200
upload_speed = 921600
Hinweis: Der PlatformIO-Boardeintrag rymcu-esp32-s3-devkitc-1 entspricht N8R2 (8 MB Flash, 2 MB PSRAM). Der Schaltplan ist kein DevKit; der Eintrag liefert jedoch die passende ESP32-S3-N8R2-Speicherkonfiguration. Pinbelegungen werden ausschließlich im Quelltext festgelegt.
src/main.cpp

Cpp


#include <Arduino.h>
#include <SPI.h>
#include <Wire.h>
#include "USB.h"
#include "USBMIDI.h"
#include <NimBLEDevice.h>

#if ARDUINO_USB_MODE
#error "ARDUINO_USB_MODE muss 0 sein: U1 GPIO19/20 sind als native USB-OTG-Datenpins verdrahtet."
#endif

// ---------------- Reales Schaltplan-Mapping ----------------
static constexpr uint8_t PIN_IMU_CS    = 10;
static constexpr uint8_t PIN_IMU_MOSI  = 11;
static constexpr uint8_t PIN_IMU_SCLK  = 12;
static constexpr uint8_t PIN_IMU_MISO  = 13;
static constexpr uint8_t PIN_IMU_INT1  = 9;
static constexpr uint8_t PIN_I2C_SDA   = 8;
static constexpr uint8_t PIN_I2C_SCL   = 7;
static constexpr uint8_t PIN_FUEL_ALERT_N = 6;
static constexpr uint8_t PIN_CHG_PGOOD_N  = 41;
static constexpr uint8_t PIN_STATUS_LED_N = 42;
static constexpr uint8_t PIN_UART_TX = 43;
static constexpr uint8_t PIN_UART_RX = 44;

static constexpr uint8_t INPUT_PINS[8] = {4, 5, 14, 15, 16, 17, 18, 21};
static constexpr uint8_t INPUT_NOTES[8] = {60, 61, 62, 63, 64, 65, 66, 67};
static constexpr const char *INPUT_NAMES[8] = {
  "BTN1", "BTN2", "BTN3", "BTN4", "BTN5", "BTN6", "TRIGGER1", "TRIGGER2"
};

USBMIDI usbMidi("MIDI Motion Controller");
HardwareSerial serviceUart(0);

// ---------------- BLE-MIDI ----------------
static constexpr char BLE_MIDI_SERVICE_UUID[] = "03B80E5A-EDE8-4B33-A751-6CE34EC4C700";
static constexpr char BLE_MIDI_CHAR_UUID[]    = "7772E5DB-3868-4112-A1A9-F2669D106BF3";

static NimBLEServer *bleServer = nullptr;
static NimBLECharacteristic *bleMidiChar = nullptr;
static volatile bool bleConnected = false;

class MidiServerCallbacks final : public NimBLEServerCallbacks {
  void onConnect(NimBLEServer *, NimBLEConnInfo &) override {
    bleConnected = true;
  }

  void onDisconnect(NimBLEServer *, NimBLEConnInfo &, int) override {
    bleConnected = false;
    NimBLEDevice::startAdvertising();
  }
};

static void startBleMidi() {
  NimBLEDevice::init("MIDI Motion Controller");
  bleServer = NimBLEDevice::createServer();
  bleServer->setCallbacks(new MidiServerCallbacks());

  NimBLEService *service = bleServer->createService(BLE_MIDI_SERVICE_UUID);
  bleMidiChar = service->createCharacteristic(
      BLE_MIDI_CHAR_UUID,
      NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE_NR | NIMBLE_PROPERTY::NOTIFY);
  service->start();

  NimBLEAdvertising *advertising = NimBLEDevice::getAdvertising();
  advertising->setName("MIDI Motion Controller");
  advertising->addServiceUUID(BLE_MIDI_SERVICE_UUID);
  advertising->enableScanResponse(true);
  advertising->start();
}

static void bleMidiSend3(uint8_t status, uint8_t data1, uint8_t data2) {
  if (!bleConnected || bleServer == nullptr || bleMidiChar == nullptr ||
      bleServer->getConnectedCount() == 0) {
    return;
  }

  const uint16_t timestamp = static_cast<uint16_t>(millis() & 0x1FFF); // 13 Bit, 1 ms
  const uint8_t packet[5] = {
      static_cast<uint8_t>(0x80 | ((timestamp >> 7) & 0x3F)),
      static_cast<uint8_t>(0x80 | (timestamp & 0x7F)),
      status, data1, data2};
  bleMidiChar->setValue(packet, sizeof(packet));
  bleMidiChar->notify();
}

static void sendNoteOn(uint8_t note, uint8_t velocity = 100, uint8_t channel = 1) {
  usbMidi.noteOn(note, velocity, channel);
  bleMidiSend3(static_cast<uint8_t>(0x90 | ((channel - 1) & 0x0F)), note, velocity);
}

static void sendNoteOff(uint8_t note, uint8_t velocity = 0, uint8_t channel = 1) {
  usbMidi.noteOff(note, velocity, channel);
  bleMidiSend3(static_cast<uint8_t>(0x80 | ((channel - 1) & 0x0F)), note, velocity);
}

static void sendControlChange(uint8_t cc, uint8_t value, uint8_t channel = 1) {
  usbMidi.controlChange(cc, value, channel);
  bleMidiSend3(static_cast<uint8_t>(0xB0 | ((channel - 1) & 0x0F)), cc, value);
}

// ---------------- IIM-42652: echte SPI-Registerzugriffe ----------------
static constexpr uint8_t IIM_REG_DEVICE_CONFIG = 0x11;
static constexpr uint8_t IIM_REG_INT_CONFIG    = 0x14;
static constexpr uint8_t IIM_REG_TEMP_DATA1    = 0x1D;
static constexpr uint8_t IIM_REG_PWR_MGMT0     = 0x4E;
static constexpr uint8_t IIM_REG_GYRO_CONFIG0  = 0x4F;
static constexpr uint8_t IIM_REG_ACCEL_CONFIG0 = 0x50;
static constexpr uint8_t IIM_REG_INT_SOURCE0   = 0x65;
static constexpr uint8_t IIM_REG_WHO_AM_I      = 0x75;
static constexpr uint8_t IIM_WHO_AM_I_VALUE    = 0x6F;
static SPISettings iimSpiSettings(8000000, MSBFIRST, SPI_MODE0);
static volatile bool imuDataReady = false;

struct ImuSample {
  int16_t temperature;
  int16_t ax, ay, az;
  int16_t gx, gy, gz;
};

static void IRAM_ATTR onImuDataReady() { imuDataReady = true; }

static uint8_t iimReadRegister(uint8_t reg) {
  SPI.beginTransaction(iimSpiSettings);
  digitalWrite(PIN_IMU_CS, LOW);
  SPI.transfer(static_cast<uint8_t>(reg | 0x80));
  const uint8_t value = SPI.transfer(0x00);
  digitalWrite(PIN_IMU_CS, HIGH);
  SPI.endTransaction();
  return value;
}

static void iimWriteRegister(uint8_t reg, uint8_t value) {
  SPI.beginTransaction(iimSpiSettings);
  digitalWrite(PIN_IMU_CS, LOW);
  SPI.transfer(static_cast<uint8_t>(reg & 0x7F));
  SPI.transfer(value);
  digitalWrite(PIN_IMU_CS, HIGH);
  SPI.endTransaction();
}

static void iimReadBurst(uint8_t firstReg, uint8_t *data, size_t length) {
  SPI.beginTransaction(iimSpiSettings);
  digitalWrite(PIN_IMU_CS, LOW);
  SPI.transfer(static_cast<uint8_t>(firstReg | 0x80));
  for (size_t i = 0; i < length; ++i) data[i] = SPI.transfer(0x00);
  digitalWrite(PIN_IMU_CS, HIGH);
  SPI.endTransaction();
}

static bool initIim42652() {
  pinMode(PIN_IMU_CS, OUTPUT);
  digitalWrite(PIN_IMU_CS, HIGH);
  pinMode(PIN_IMU_INT1, INPUT);
  SPI.begin(PIN_IMU_SCLK, PIN_IMU_MISO, PIN_IMU_MOSI, PIN_IMU_CS);

  iimWriteRegister(IIM_REG_DEVICE_CONFIG, 0x01); // SOFT_RESET_CONFIG
  delay(2); // Datenblatt: mindestens 1 ms

  const uint8_t who = iimReadRegister(IIM_REG_WHO_AM_I);
  if (who != IIM_WHO_AM_I_VALUE) {
    Serial.printf("FAIL U2 WHO_AM_I: 0x%02X, erwartet 0x6F\n", who);
    serviceUart.printf("FAIL U2 WHO_AM_I: 0x%02X\r\n", who);
    return false;
  }

  // INT1: gepulst, Push-Pull, aktiv High.
  iimWriteRegister(IIM_REG_INT_CONFIG, 0x03);

  // ±2000 dps und 200 Hz; ±16 g und 200 Hz.
  iimWriteRegister(IIM_REG_GYRO_CONFIG0, 0x07);
  iimWriteRegister(IIM_REG_ACCEL_CONFIG0, 0x07);

  // UI data-ready auf INT1 routen, übrige Registerbits erhalten.
  const uint8_t intSource0 = iimReadRegister(IIM_REG_INT_SOURCE0);
  iimWriteRegister(IIM_REG_INT_SOURCE0, static_cast<uint8_t>(intSource0 | 0x08));

  // Gyro LN (11), Accel LN (11), Temperatur aktiv: 0b00001111.
  iimWriteRegister(IIM_REG_PWR_MGMT0, 0x0F);
  delay(45); // Gyro muss nach Aktivierung mindestens 45 ms eingeschaltet bleiben.

  attachInterrupt(digitalPinToInterrupt(PIN_IMU_INT1), onImuDataReady, RISING);
  Serial.println("PASS U2 IIM-42652 WHO_AM_I=0x6F, 200 Hz LN");
  return true;
}

static ImuSample readIim42652() {
  uint8_t raw[14];
  iimReadBurst(IIM_REG_TEMP_DATA1, raw, sizeof(raw));
  auto be16 = [&](size_t i) -> int16_t {
    return static_cast<int16_t>((static_cast<uint16_t>(raw[i]) << 8) | raw[i + 1]);
  };
  return {be16(0), be16(2), be16(4), be16(6), be16(8), be16(10), be16(12)};
}

// TODO datenblatt- und anwendungsabhängig: AAF/Notch/UI-Filter, FIFO/Wasserzeichen,
// Timestamp-Synchronisierung, Self-Test sowie Nullpunkt-/Temperaturkalibrierung.

// ---------------- MAX17048 ----------------
static constexpr uint8_t MAX17048_ADDR = 0x36;
static constexpr uint8_t MAX_REG_VCELL   = 0x02;
static constexpr uint8_t MAX_REG_SOC     = 0x04;
static constexpr uint8_t MAX_REG_VERSION = 0x08;
static constexpr uint8_t MAX_REG_CONFIG  = 0x0C;
static constexpr uint8_t MAX_REG_STATUS  = 0x1A;
static volatile bool fuelAlertPending = false;

static void IRAM_ATTR onFuelAlert() { fuelAlertPending = true; }

static bool maxRead16(uint8_t reg, uint16_t &value) {
  Wire.beginTransmission(MAX17048_ADDR);
  Wire.write(reg);
  if (Wire.endTransmission(false) != 0) return false;
  if (Wire.requestFrom(MAX17048_ADDR, static_cast<uint8_t>(2)) != 2) return false;
  value = static_cast<uint16_t>(Wire.read() << 8);
  value |= static_cast<uint16_t>(Wire.read());
  return true;
}

static bool maxWrite16(uint8_t reg, uint16_t value) {
  Wire.beginTransmission(MAX17048_ADDR);
  Wire.write(reg);
  Wire.write(static_cast<uint8_t>(value >> 8));
  Wire.write(static_cast<uint8_t>(value & 0xFF));
  return Wire.endTransmission() == 0;
}

static bool initMax17048() {
  Wire.begin(PIN_I2C_SDA, PIN_I2C_SCL, 400000);
  pinMode(PIN_FUEL_ALERT_N, INPUT); // externer R26 = 10 kΩ nach 3V3
  attachInterrupt(digitalPinToInterrupt(PIN_FUEL_ALERT_N), onFuelAlert, FALLING);

  uint16_t version = 0;
  if (!maxRead16(MAX_REG_VERSION, version)) {
    Serial.println("FAIL U5 MAX17048 antwortet nicht auf 0x36");
    return false;
  }
  Serial.printf("PASS U5 MAX17048 VERSION=0x%04X\n", version);
  return true;
}

static void serviceFuelAlert() {
  if (!fuelAlertPending && digitalRead(PIN_FUEL_ALERT_N) != LOW) return;
  fuelAlertPending = false;

  uint16_t status = 0, config = 0;
  if (!maxRead16(MAX_REG_STATUS, status) || !maxRead16(MAX_REG_CONFIG, config)) return;
  Serial.printf("U5 ALERT: STATUS=0x%04X CONFIG=0x%04X\n", status, config);

  // Alle gelesenen Alert-Ursachen/RI quittieren und anschließend CONFIG.ALRT löschen.
  maxWrite16(MAX_REG_STATUS, static_cast<uint16_t>(status & 0x00FF));
  maxWrite16(MAX_REG_CONFIG, static_cast<uint16_t>(config & ~(1u << 5)));
}

static bool readFuel(float &voltageV, float &socPercent) {
  uint16_t vcell = 0, soc = 0;
  if (!maxRead16(MAX_REG_VCELL, vcell) || !maxRead16(MAX_REG_SOC, soc)) return false;
  voltageV = static_cast<float>(vcell) * 78.125e-6f;
  socPercent = static_cast<float>(soc) / 256.0f;
  return true;
}

// ---------------- Taster/Trigger ----------------
struct DebouncedInput {
  bool rawPressed = false;
  bool stablePressed = false;
  uint32_t changedAtMs = 0;
};
static DebouncedInput inputs[8];
static constexpr uint32_t DEBOUNCE_MS = 12;

static void initInputs() {
  for (size_t i = 0; i < 8; ++i) {
    pinMode(INPUT_PINS[i], INPUT); // externe R17–R24 sind vorhanden
    const bool pressed = digitalRead(INPUT_PINS[i]) == LOW;
    inputs[i].rawPressed = pressed;
    inputs[i].stablePressed = pressed;
    inputs[i].changedAtMs = millis();
  }
}

static void serviceInputs() {
  const uint32_t now = millis();
  for (size_t i = 0; i < 8; ++i) {
    const bool pressed = digitalRead(INPUT_PINS[i]) == LOW;
    if (pressed != inputs[i].rawPressed) {
      inputs[i].rawPressed = pressed;
      inputs[i].changedAtMs = now;
    }
    if ((now - inputs[i].changedAtMs) >= DEBOUNCE_MS &&
        inputs[i].stablePressed != inputs[i].rawPressed) {
      inputs[i].stablePressed = inputs[i].rawPressed;
      if (inputs[i].stablePressed) sendNoteOn(INPUT_NOTES[i]);
      else sendNoteOff(INPUT_NOTES[i]);
      Serial.printf("%s=%s\n", INPUT_NAMES[i], inputs[i].stablePressed ? "PRESSED" : "RELEASED");
    }
  }
}

// ---------------- Arduino-Start ----------------
void setup() {
  pinMode(PIN_STATUS_LED_N, OUTPUT);
  digitalWrite(PIN_STATUS_LED_N, HIGH); // D2 aus; aktiv Low
  pinMode(PIN_CHG_PGOOD_N, INPUT);      // externer R10 = 100 kΩ

  Serial.begin(115200); // USB CDC im Composite Device
  serviceUart.begin(115200, SERIAL_8N1, PIN_UART_RX, PIN_UART_TX);
  delay(250);

  usbMidi.begin();
  USB.begin();
  startBleMidi();
  initInputs();

  const bool fuelOk = initMax17048();
  const bool imuOk = initIim42652();
  Serial.printf("START fuel=%s imu=%s PGOOD_N=%d\n",
                fuelOk ? "OK" : "FAIL", imuOk ? "OK" : "FAIL",
                digitalRead(PIN_CHG_PGOOD_N));
  serviceUart.printf("START fuel=%s imu=%s\r\n",
                     fuelOk ? "OK" : "FAIL", imuOk ? "OK" : "FAIL");
}

void loop() {
  serviceInputs();
  serviceFuelAlert();

  if (imuDataReady) {
    imuDataReady = false;
    const ImuSample s = readIim42652();

    // Starter-Mapping: X-Beschleunigung (±16 g) auf MIDI CC 16.
    // ±16 g entsprechen nominal ±32768 Counts; auf 0..127 begrenzen.
    const int32_t shifted = static_cast<int32_t>(s.ax) + 32768;
    const uint8_t cc = static_cast<uint8_t>(constrain(shifted >> 9, 0L, 127L));
    static uint8_t lastCc = 255;
    if (cc != lastCc) {
      lastCc = cc;
      sendControlChange(16, cc);
    }
  }

  static uint32_t lastFuelMs = 0;
  if (millis() - lastFuelMs >= 1000) {
    lastFuelMs = millis();
    float voltage = 0.0f, soc = 0.0f;
    if (readFuel(voltage, soc)) {
      Serial.printf("BAT %.3f V, SOC %.1f %%, PGOOD_N=%d, BLE=%d\n",
                    voltage, soc, digitalRead(PIN_CHG_PGOOD_N), bleConnected ? 1 : 0);
      serviceUart.printf("BAT %.3f V SOC %.1f%% PGOOD_N=%d BLE=%d\r\n",
                         voltage, soc, digitalRead(PIN_CHG_PGOOD_N), bleConnected ? 1 : 0);
    }
  }

  // D2: dauerhaft an bei BLE-Verbindung, sonst 2-Hz-Blinken.
  const bool ledOn = bleConnected || ((millis() / 250) & 1u);
  digitalWrite(PIN_STATUS_LED_N, ledOn ? LOW : HIGH);
  delay(1);
}
Build, Flash und Monitor
Vom Verzeichnis firmware/ aus:

Bash


pio run
pio run -t upload
pio device monitor -b 115200
Wenn das Board nicht automatisch in den Download-Modus wechselt:
  1. SW9 BOOT gedrückt halten (MCU_BOOT/GPIO0 = Low).
  2. SW8 RESET kurz drücken und loslassen (MCU_EN = Low → High).
  3. SW9 loslassen.
  4. pio run -t upload erneut starten.
Alternativ steht der ROM-Downloader auch über UART0 zur Verfügung: TP8 ist TX GPIO43, TP9 ist RX GPIO44 und TP5 ist GND. Nur einen 3,3-V-USB-UART-Adapter verwenden; Adapter-TX an TP9, Adapter-RX an TP8.
Boot- und Reset-Verhalten
  • R12 = 10 kΩ zieht MCU_EN nach 3V3; C10 = 1 µF erzeugt den empfohlenen Einschalt-Reset. SW8 zieht EN nach GND. TP10 ist der EN-Messpunkt.
  • R13 = 10 kΩ zieht GPIO0/MCU_BOOT nach 3V3; SW9 zieht GPIO0 nach GND. TP11 ist der BOOT-Messpunkt.
  • Normaler SPI-Flash-Boot: GPIO0 beim Loslassen von Reset High.
  • Joint Download Boot: GPIO0 beim Loslassen von Reset Low; GPIO46 ist im Schaltplan unbeschaltet und folgt seinem internen Pulldown.
  • Strapping-Pins GPIO3, GPIO45 und GPIO46 sind absichtlich nicht anderweitig verwendet.
  • SW1 ist kein Reset-Schalter: Er steuert REG_EN von U4. In OFF bleibt die Lade-/Fuel-Gauge-Domäne an BAT/VBUS aktiv, während 3V3 und U1 abgeschaltet werden.
Erwartete Startausgaben

Text


PASS U5 MAX17048 VERSION=0x001x
PASS U2 IIM-42652 WHO_AM_I=0x6F, 200 Hz LN
START fuel=OK imu=OK PGOOD_N=0
BAT 3.xxx V, SOC xx.x %, PGOOD_N=0, BLE=0
PGOOD_N=0 ist bei gültigem USB-VBUS korrekt; ohne USB ist PGOOD_N=1 zu erwarten. Der erste gültige MAX17048-SOC-Wert benötigt nach POR ungefähr eine Sekunde.
Funktionszuordnung im Starter
  • SW2–SW7 senden MIDI-Noten 60–65.
  • Trigger J3/J4 senden MIDI-Noten 66/67, sobald Pin 1 nach Pin 2/GND geschlossen wird.
  • U2 X-Beschleunigung sendet MIDI CC 16, Wert 0–127.
  • Jede Aktion wird parallel über USB-MIDI und BLE-MIDI gesendet.
  • D2 blinkt ohne BLE-Verbindung und leuchtet dauerhaft bei BLE-Verbindung.
  • D1 wird ausschließlich von U3 gesteuert und benötigt keine Firmware.
Abhängigkeiten und Grenzen
  • Arduino-ESP32 stellt USB, USBMIDI, SPI, Wire und HardwareSerial bereit.
  • Externe Bibliothek: NimBLE-Arduino 2.x.
  • Keine IIM-42652-Bibliothek erforderlich; Identifikation, Reset, ODR/Full-Scale, Power-Modus, Interrupt und Datenburst sind implementiert.
  • TODO für eine Produkt-Firmware: sensorbezogene Filterauslegung, Kalibrierung, MIDI-Mapping/Presets, Energiesparzustände, BLE-Verbindungsparameter, persistent gespeicherte Konfiguration, Fehler-Recovery und Firmware-Update-Strategie.
  • Deep Sleep darf erst ergänzt werden, nachdem geprüft wurde, welche der real verwendeten GPIOs als gewünschte Wake-Quellen geeignet sind. Das ist nicht Teil dieses Starters.
  • Zweck und Hardwarebasis

  • Echtes GPIO- und Signal-Mapping

  • Wichtige reale Adressen und Register

  • MAX17048 U5

  • IIM-42652 U2

  • PlatformIO-Projekt

  • `platformio.ini`

  • `src/main.cpp`

  • Build, Flash und Monitor

  • Boot- und Reset-Verhalten

  • Erwartete Startausgaben

  • Funktionszuordnung im Starter

  • Abhängigkeiten und Grenzen