Fixing ERC/DRC Errors and Routing
Table
| ESP32-S3 signal | GPIO | Live net | Device endpoint |
|---|---|---|---|
| Native USB D- | 19 | USB_DN | D1 USBLC6-2SC6 → USB_DN_CONN → J1 DN1/DN2 |
| Native USB D+ | 20 | USB_DP | D1 USBLC6-2SC6 → USB_DP_CONN → J1 DP1/DP2 |
| I2C SDA | 8 | I2C_SDA | U2 SDA, U4 SDA, U5 SDX; 4.7 kΩ pull-up |
| I2C SCL | 9 | I2C_SCL | U2 SCL, U4 SCL, U5 SCX; 4.7 kΩ pull-up |
| PPG interrupt | 10 | HR_INT | U2 ~INT |
| IMU interrupt | 11 | IMU_INT | U5 INT1 |
| GNSS RX at ESP | 18 | GPS_TX_TO_ESP_RX | U3 TXD |
| GNSS TX at ESP | 17 | ESP_TX_TO_GPS_RX | U3 RXD |
| I2S BCLK | 4 | MIC_BCLK | U6 BCLK |
| I2S WS/LRCLK | 5 | MIC_WS | U6 WS |
| I2S data input | 6 | MIC_DATA | U6 DATA |
| Boot strap | 0 | ESP_BOOT | SW2 to GND, 10 kΩ pull-up |
| Enable/reset | EN | ESP_EN | SW1 to GND, pull-up/filter |
3V3 for VLED+ and 1V8_U2 for VDD. U3/U4/U5/U6 use 3V3. U6 SEL is tied to GND, selecting the left I2S channel. U5 SDO is tied low, so the BMI270 I2C address is 0x68. U4 ADD0 is tied low, so TMP117 is 0x48. MAX30102 is 0x57.Text
platformio.ini src/main.cpp
platformio.iniIni
[env:esp32-s3-wroom-1-n8r2] platform = espressif32@^6.7.0 board = esp32-s3-devkitc-1 framework = arduino monitor_speed = 115200 upload_speed = 921600 board_build.flash_size = 8MB board_build.psram_type = opi board_build.arduino.memory_type = qio_opi build_flags = -DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=1 lib_deps = sparkfun/SparkFun MAX3010x Pulse and Proximity Sensor Library@^1.1.2 adafruit/Adafruit TMP117@^1.1.0 adafruit/Adafruit Unified Sensor@^1.1.14 mikalhart/TinyGPSPlus@^1.1.0
src/main.cppCpp
#include <Arduino.h> #include <Wire.h> #include <SparkFun_MAX3010x.h> #include <Adafruit_TMP117.h> #include <Adafruit_Sensor.h> #include <TinyGPSPlus.h> #include <BLEDevice.h> #include <BLEServer.h> #include <BLEUtils.h> #include <BLE2902.h> #include <driver/i2s.h> #include <math.h> namespace Pins { constexpr int SDA = 8, SCL = 9; constexpr int HR_INT = 10, IMU_INT = 11; constexpr int GPS_RX = 18, GPS_TX = 17; constexpr int MIC_BCLK = 4, MIC_WS = 5, MIC_DATA = 6; } constexpr uint32_t I2C_HZ = 400000; constexpr uint32_t GPS_BAUD = 9600; constexpr uint8_t BMI270_ADDR = 0x68; constexpr uint8_t BMI270_CHIP_ID_REG = 0x00; constexpr uint8_t BMI270_EXPECTED_ID = 0x24; MAX30105 ppg; Adafruit_TMP117 tmp117; TinyGPSPlus gps; HardwareSerial gpsUart(1); BLECharacteristic *telemetry = nullptr; bool ppgOK = false, tmpOK = false, bmiOK = false, bleConnected = false; uint32_t lastReport = 0; class ServerCallbacks : public BLEServerCallbacks { void onConnect(BLEServer*) override { bleConnected = true; } void onDisconnect(BLEServer*) override { bleConnected = false; BLEDevice::startAdvertising(); } }; bool readReg8(uint8_t addr, uint8_t reg, uint8_t &value) { Wire.beginTransmission(addr); Wire.write(reg); if (Wire.endTransmission(false) != 0) return false; if (Wire.requestFrom(addr, (uint8_t)1) != 1) return false; value = Wire.read(); return true; } void initBLE() { BLEDevice::init("PetCollar"); BLEServer *server = BLEDevice::createServer(); server->setCallbacks(new ServerCallbacks()); BLEService *service = server->createService("7c010000-6d2c-4bda-9b53-6fd94c0f1000"); telemetry = service->createCharacteristic( "7c010001-6d2c-4bda-9b53-6fd94c0f1000", BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); telemetry->addDescriptor(new BLE2902()); telemetry->setValue("boot"); service->start(); BLEAdvertising *adv = BLEDevice::getAdvertising(); adv->addServiceUUID(service->getUUID()); adv->setScanResponse(true); BLEDevice::startAdvertising(); } bool initI2S() { i2s_config_t cfg{}; cfg.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX); cfg.sample_rate = 16000; cfg.bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT; cfg.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT; cfg.communication_format = I2S_COMM_FORMAT_STAND_I2S; cfg.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1; cfg.dma_buf_count = 4; cfg.dma_buf_len = 128; cfg.use_apll = false; cfg.tx_desc_auto_clear = false; cfg.fixed_mclk = 0; i2s_pin_config_t pins{}; pins.bck_io_num = Pins::MIC_BCLK; pins.ws_io_num = Pins::MIC_WS; pins.data_out_num = I2S_PIN_NO_CHANGE; pins.data_in_num = Pins::MIC_DATA; return i2s_driver_install(I2S_NUM_0, &cfg, 0, nullptr) == ESP_OK && i2s_set_pin(I2S_NUM_0, &pins) == ESP_OK; } uint32_t readMicLevel() { int32_t samples[128]; size_t bytes = 0; if (i2s_read(I2S_NUM_0, samples, sizeof(samples), &bytes, pdMS_TO_TICKS(20)) != ESP_OK) return 0; const size_t count = bytes / sizeof(samples[0]); uint64_t sum = 0; for (size_t i = 0; i < count; ++i) { int32_t s = samples[i] >> 14; // 18-bit SPH0645 sample in 32-bit slot sum += (uint32_t)abs(s); } return count ? (uint32_t)(sum / count) : 0; } void initSensors() { Wire.begin(Pins::SDA, Pins::SCL, I2C_HZ); pinMode(Pins::HR_INT, INPUT_PULLUP); pinMode(Pins::IMU_INT, INPUT); ppgOK = ppg.begin(Wire, I2C_SPEED_FAST); if (ppgOK) { ppg.setup(0x1F, 4, 2, 100, 411, 4096); // power, avg, red+IR, rate, width, range } tmpOK = tmp117.begin(0x48, &Wire); uint8_t chipId = 0; bmiOK = readReg8(BMI270_ADDR, BMI270_CHIP_ID_REG, chipId) && chipId == BMI270_EXPECTED_ID; // Production motion firmware must load Bosch's BMI270 configuration blob and // configure accel/gyro/INT1. This starter deliberately performs a safe, // compilable chip-ID transaction rather than embedding a version-ambiguous blob. Serial.printf("MAX30102=%s TMP117=%s BMI270=%s\n", ppgOK ? "OK" : "FAIL", tmpOK ? "OK" : "FAIL", bmiOK ? "ID_OK" : "FAIL"); } void serviceGPS() { while (gpsUart.available()) gps.encode(gpsUart.read()); } void report() { float tempC = NAN; sensors_event_t event{}; if (tmpOK && tmp117.getEvent(&event)) tempC = event.temperature; uint32_t red = ppgOK ? ppg.getRed() : 0; uint32_t ir = ppgOK ? ppg.getIR() : 0; uint32_t mic = readMicLevel(); char payload[220]; if (gps.location.isValid()) { snprintf(payload, sizeof(payload), "{\"tC\":%.2f,\"red\":%lu,\"ir\":%lu,\"mic\":%lu,\"bmi\":%s,\"lat\":%.6f,\"lon\":%.6f,\"sat\":%lu}", tempC, (unsigned long)red, (unsigned long)ir, (unsigned long)mic, bmiOK ? "true" : "false", gps.location.lat(), gps.location.lng(), (unsigned long)gps.satellites.value()); } else { snprintf(payload, sizeof(payload), "{\"tC\":%.2f,\"red\":%lu,\"ir\":%lu,\"mic\":%lu,\"bmi\":%s,\"gps\":false}", tempC, (unsigned long)red, (unsigned long)ir, (unsigned long)mic, bmiOK ? "true" : "false"); } Serial.println(payload); telemetry->setValue((uint8_t*)payload, strlen(payload)); if (bleConnected) telemetry->notify(); } void setup() { Serial.begin(115200); // native USB CDC because ARDUINO_USB_CDC_ON_BOOT=1 const uint32_t deadline = millis() + 3000; while (!Serial && millis() < deadline) delay(10); Serial.println("Smart Pet Collar boot"); initSensors(); gpsUart.begin(GPS_BAUD, SERIAL_8N1, Pins::GPS_RX, Pins::GPS_TX); Serial.printf("I2S=%s\n", initI2S() ? "OK" : "FAIL"); initBLE(); } void loop() { serviceGPS(); if (millis() - lastReport >= 1000) { lastReport = millis(); report(); } delay(2); }
Bash
pio run pio run -t upload pio device monitor
PetCollar and receives one JSON notification per second.pio run log with the released firmware binary/hash.Scope and source of truth
Live pin/net map
Toolchain
`platformio.ini`
`src/main.cpp`
Build, flash, and smoke test
Production firmware follow-ups