USB-C Sensor Node


Created 4 project docs

Firmware Starter — ESP32-S3 Sensor Motor Carrier

View Document

MCU/module: ESP32-S3-WROOM-1 Framework: Arduino on PlatformIO

Board Bring-Up Plan — ESP32-S3 Sensor Motor Carrier

View Document

Current-limited USB-C 5 V supply or USB power meter. Multimeter, oscilloscope if available, USB cable, PC with PlatformIO/esptool.

Technical Construction File Notes — ESP32-S3 Sensor Motor Carrier

View Document

Draft design-stage notes. FCC Part 15 / CE RED / ISED consideration due to ESP32-S3 Wi-Fi/BLE radio module.

Preliminary FMEA — ESP32-S3 Sensor Motor Carrier

View Document

Design-stage risk review for a USB-C powered ESP32-S3 carrier board with IMU, temperature sensor, GPS header, and a single brushed DC motor ...
I created the ESP32-S3 carrier schematic with USB-C 5 V input, protected 5 V/3.3 V rails, ESP32-S3-WROOM-1, BNO085, SHT40 temperature/humidity sensor, GPS UART header, motor-driver carrier header, boot/reset buttons, status LED, and supporting passives; ERC/passive checks were clean, while layout/routing and generic passive MPN selection remain unfinished because no ACUs are currently available.
Open items:
  • PCB placement/routing not started; DRC airwires are expected.
  • Exact GPS module, motor driver carrier, motor voltage, and stall current still need confirmation.
  • Generic resistor/capacitor MPNs need to be assigned before manufacturing.
  • Motor current-sense protection/scaling should be finalized after choosing the driver carrier.
Project files created:
Suggested next steps once ACUs are available:
  1. Confirm the exact GPS module and brushed DC motor driver carrier.
  2. Finalize VMOTOR voltage/current limits and current-sense scaling.
  3. Assign production MPNs for generic passives.
  4. Proceed to PCB placement with ESP32 antenna keepout and motor-current separation.
Set module choices
Fill passive MPNs
Run ERC/DRC
Set up layout
Document requirements

Firmware Starter — ESP32-S3 Sensor Motor Carrier
Platform & Toolchain
  • MCU/module: ESP32-S3-WROOM-1
  • Framework: Arduino on PlatformIO
  • USB: native USB serial/JTAG via USB-C D+/D-
  • I2C sensors: BNO085 and SHT40 on shared 3.3 V I2C bus
  • GPS: UART GPS header
  • Motor driver: PWM/DIR/EN/FAULT/current-sense carrier header
Pin Mapping

Table


FunctionESP32-S3 GPIONetConnected ToDirectionNotes
USB D-GPIO19USB_DNJ1/D1bidirectionalNative USB
USB D+GPIO20USB_DPJ1/D1bidirectionalNative USB
I2C SDAGPIO8I2C_SDAU2/U3bidirectional4.7k pull-up
I2C SCLGPIO9I2C_SCLU2/U3output4.7k pull-up
BNO085 INTGPIO10BNO_INTU2 H_INTNinputActive low
BNO085 RESETGPIO11BNO_RSTU2 NRSToutputActive low reset
GPS TX from ESPGPIO17GPS_RX_FROM_ESP_TXJ2 pin 5outputTo GPS RX
GPS RX to ESPGPIO18GPS_TX_TO_ESP_RXJ2 pin 4inputFrom GPS TX
GPS PPSGPIO4GPS_PPSJ2 pin 6inputInterrupt-capable
GPS enable/resetGPIO5GPS_ENJ2 pin 7outputDepends on GPS module
Motor PWMGPIO6MOTOR_PWMJ3 pin 7output100k pull-down
Motor DIRGPIO7MOTOR_DIRJ3 pin 8outputDirection control
Motor enable/sleepGPIO15MOTOR_EN_SLEEPJ3 pin 9output100k pull-down; default off
Motor faultGPIO16MOTOR_FAULTJ3 pin 10inputUse pull-up if driver needs it
Motor current senseGPIO1MOTOR_CSENSE_ADCJ3 pin 11analog inputMust be 0–3.3 V max
Motor aux / IN2GPIO2MOTOR_IN2_AUXJ3 pin 12outputOptional second input
Motor aux 1GPIO38MOTOR_AUX1J3 pin 13GPIOSpare
Motor aux 2GPIO39MOTOR_AUX2J3 pin 14GPIOSpare
Motor aux 3GPIO40MOTOR_AUX3J3 pin 15GPIOSpare
Status LEDGPIO21STATUS_LEDR11/D2outputActive high
BOOTGPIO0ESP_BOOTSW2/R6inputPull low to enter bootloader
Reset/enableENESP_ENSW1/R5/C5inputActive low reset
GPS Header J2 Pinout

Table


PinNetFunction
1GNDGround
23V3GPS 3.3 V power
35V_USBOptional 5 V for GPS breakout boards
4GPS_TX_TO_ESP_RXGPS TX into ESP32 GPIO18
5GPS_RX_FROM_ESP_TXESP32 GPIO17 TX into GPS RX
6GPS_PPSGPS pulse-per-second
7GPS_ENGPS enable/reset control
8GPS_SPAREintentionally unused spare
Motor Carrier Header J3 Pinout

Table


PinNetFunction
1,2,20VMOTORExternal motor supply from J4
3,4,16,17,18,19GNDMotor/logic ground return
53V3Logic power
65V_USBOptional 5 V accessory power; not for motor current
7MOTOR_PWMPWM command, pulled down
8MOTOR_DIRDirection command
9MOTOR_EN_SLEEPEnable/sleep command, pulled down
10MOTOR_FAULTFault input
11MOTOR_CSENSE_ADCCurrent sense ADC input
12MOTOR_IN2_AUXOptional second driver input
13–15MOTOR_AUX1/2/3Spare GPIOs
platformio.ini

Ini


[env:esp32-s3-devkitc-1]
platform = espressif32
board = esp32-s3-devkitc-1
framework = arduino
monitor_speed = 115200
upload_speed = 921600
build_flags =
  -DARDUINO_USB_MODE=1
  -DARDUINO_USB_CDC_ON_BOOT=1
lib_deps =
  adafruit/Adafruit BNO08x@^1.2.5
  adafruit/Adafruit SHT4x Library@^1.0.5
  mikalhart/TinyGPSPlus@^1.1.0
Complete Firmware Source — src/main.cpp

Cpp


#include <Arduino.h>
#include <Wire.h>
#include <WiFi.h>
#include <Adafruit_BNO08x.h>
#include <Adafruit_SHT4x.h>
#include <TinyGPSPlus.h>

// Pin definitions from schematic
#define PIN_I2C_SDA          8
#define PIN_I2C_SCL          9
#define PIN_BNO_INT          10
#define PIN_BNO_RST          11
#define PIN_GPS_TX           17  // ESP32 TX -> GPS RX, J2 pin 5
#define PIN_GPS_RX           18  // ESP32 RX <- GPS TX, J2 pin 4
#define PIN_GPS_PPS          4
#define PIN_GPS_EN           5
#define PIN_MOTOR_PWM        6
#define PIN_MOTOR_DIR        7
#define PIN_MOTOR_EN_SLEEP   15
#define PIN_MOTOR_FAULT      16
#define PIN_MOTOR_CSENSE_ADC 1
#define PIN_MOTOR_IN2_AUX    2
#define PIN_MOTOR_AUX1       38
#define PIN_MOTOR_AUX2       39
#define PIN_MOTOR_AUX3       40
#define PIN_STATUS_LED       21

#define GPS_BAUD             9600
#define SENSOR_INTERVAL_MS   1000
#define WIFI_RETRY_LIMIT     20

const char* WIFI_SSID = "YOUR_SSID";
const char* WIFI_PASSWORD = "YOUR_PASSWORD";

Adafruit_BNO08x bno08x(PIN_BNO_RST);
Adafruit_SHT4x sht4;
TinyGPSPlus gps;
HardwareSerial GPSSerial(1);

unsigned long lastSensorRead = 0;
volatile uint32_t gpsPpsCount = 0;

void IRAM_ATTR onGpsPps() {
  gpsPpsCount++;
}

void setMotorSafeOff() {
  ledcWrite(0, 0);
  digitalWrite(PIN_MOTOR_PWM, LOW);
  digitalWrite(PIN_MOTOR_EN_SLEEP, LOW);
  digitalWrite(PIN_MOTOR_DIR, LOW);
  digitalWrite(PIN_MOTOR_IN2_AUX, LOW);
}

void setMotorCommand(uint8_t dutyPercent, bool dir) {
  dutyPercent = min<uint8_t>(dutyPercent, 100);
  digitalWrite(PIN_MOTOR_DIR, dir ? HIGH : LOW);
  digitalWrite(PIN_MOTOR_EN_SLEEP, dutyPercent > 0 ? HIGH : LOW);
  ledcWrite(0, map(dutyPercent, 0, 100, 0, 255));
}

void initMotorPins() {
  pinMode(PIN_MOTOR_PWM, OUTPUT);
  pinMode(PIN_MOTOR_DIR, OUTPUT);
  pinMode(PIN_MOTOR_EN_SLEEP, OUTPUT);
  pinMode(PIN_MOTOR_IN2_AUX, OUTPUT);
  pinMode(PIN_MOTOR_FAULT, INPUT_PULLUP);
  pinMode(PIN_MOTOR_AUX1, INPUT);
  pinMode(PIN_MOTOR_AUX2, INPUT);
  pinMode(PIN_MOTOR_AUX3, INPUT);
  analogReadResolution(12);
  ledcSetup(0, 20000, 8);
  ledcAttachPin(PIN_MOTOR_PWM, 0);
  setMotorSafeOff();
}

void initWiFi() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  Serial.print("Connecting to WiFi");
  for (int i = 0; i < WIFI_RETRY_LIMIT && WiFi.status() != WL_CONNECTED; i++) {
    digitalWrite(PIN_STATUS_LED, !digitalRead(PIN_STATUS_LED));
    delay(500);
    Serial.print('.');
  }
  if (WiFi.status() == WL_CONNECTED) {
    Serial.printf("\nWiFi connected: %s\n", WiFi.localIP().toString().c_str());
    digitalWrite(PIN_STATUS_LED, HIGH);
  } else {
    Serial.println("\nWiFi not connected; continuing offline.");
    digitalWrite(PIN_STATUS_LED, LOW);
  }
}

void initSensors() {
  Wire.begin(PIN_I2C_SDA, PIN_I2C_SCL, 400000);

  if (!sht4.begin(&Wire)) {
    Serial.println("SHT40 not detected at 0x44.");
  } else {
    sht4.setPrecision(SHT4X_HIGH_PRECISION);
    sht4.setHeater(SHT4X_NO_HEATER);
    Serial.println("SHT40 initialized.");
  }

  if (!bno08x.begin_I2C(0x4A, &Wire, PIN_BNO_INT)) {
    Serial.println("BNO085 not detected. Check I2C address/SA0 strap.");
  } else {
    bno08x.enableReport(SH2_ROTATION_VECTOR, 100000); // 100 ms
    bno08x.enableReport(SH2_ACCELEROMETER, 100000);
    Serial.println("BNO085 initialized.");
  }
}

void initGps() {
  pinMode(PIN_GPS_EN, OUTPUT);
  digitalWrite(PIN_GPS_EN, HIGH);
  pinMode(PIN_GPS_PPS, INPUT);
  attachInterrupt(digitalPinToInterrupt(PIN_GPS_PPS), onGpsPps, RISING);
  GPSSerial.begin(GPS_BAUD, SERIAL_8N1, PIN_GPS_RX, PIN_GPS_TX);
  Serial.println("GPS UART initialized.");
}

void pollGps() {
  while (GPSSerial.available()) {
    gps.encode(GPSSerial.read());
  }
}

void readSensors() {
  sensors_event_t humidity, temp;
  if (sht4.getEvent(&humidity, &temp)) {
    Serial.printf("SHT40: %.2f C, %.2f %%RH\n", temp.temperature, humidity.relative_humidity);
  } else {
    Serial.println("SHT40 read failed.");
  }

  sh2_SensorValue_t sensorValue;
  while (bno08x.getSensorEvent(&sensorValue)) {
    if (sensorValue.sensorId == SH2_ROTATION_VECTOR) {
      Serial.printf("BNO rot: i=%.4f j=%.4f k=%.4f real=%.4f\n",
                    sensorValue.un.rotationVector.i,
                    sensorValue.un.rotationVector.j,
                    sensorValue.un.rotationVector.k,
                    sensorValue.un.rotationVector.real);
    }
  }

  int rawCurrent = analogRead(PIN_MOTOR_CSENSE_ADC);
  bool motorFault = digitalRead(PIN_MOTOR_FAULT) == LOW;
  Serial.printf("Motor: csense_raw=%d fault=%s\n", rawCurrent, motorFault ? "YES" : "no");

  if (gps.location.isValid()) {
    Serial.printf("GPS: %.6f, %.6f sats=%u pps=%lu\n",
                  gps.location.lat(), gps.location.lng(), gps.satellites.value(), gpsPpsCount);
  } else {
    Serial.printf("GPS: no fix yet, chars=%lu pps=%lu\n", gps.charsProcessed(), gpsPpsCount);
  }
}

void setup() {
  Serial.begin(115200);
  delay(200);
  Serial.println("ESP32-S3 Sensor Motor Carrier starting...");

  pinMode(PIN_STATUS_LED, OUTPUT);
  digitalWrite(PIN_STATUS_LED, LOW);

  initMotorPins();
  initGps();
  initSensors();
  initWiFi();

  Serial.println("Initialization complete. Motor remains disabled until firmware commands it on.");
}

void loop() {
  pollGps();

  unsigned long now = millis();
  if (now - lastSensorRead >= SENSOR_INTERVAL_MS) {
    lastSensorRead = now;
    readSensors();
  }

  if (WiFi.status() != WL_CONNECTED) {
    digitalWrite(PIN_STATUS_LED, (millis() / 500) % 2);
  }
}
Build & Flash
  1. Install PlatformIO.
  2. Create the project with the platformio.ini above and src/main.cpp source.
  3. Build: pio run
  4. Flash over USB-C: pio run -t upload
  5. Monitor: pio device monitor
  6. If upload fails, hold BOOT, tap RESET, release BOOT, then upload again.
Open Firmware Assumptions
  • GPS baud defaults to 9600; change if the selected module differs.
  • BNO085 address is assumed 0x4A with SA0 pulled low.
  • Motor current-sense scaling depends on the selected motor driver carrier and must be calibrated before enabling closed-loop protection.
  • Platform & Toolchain

  • Pin Mapping

  • GPS Header J2 Pinout

  • Motor Carrier Header J3 Pinout

  • `platformio.ini`

  • Complete Firmware Source — `src/main.cpp`

  • Build & Flash

  • Open Firmware Assumptions