Fixing ERC/DRC Errors and Routing


Replacement/placement completed, but validation found two remaining blocker types: stale traces from the old footprints and U7 still has duplicate exposed-pad footprint geometry. I’m correcting U7 with a different clean same-function BQ24074 package entry, then deleting stale traces before rechecking.
Pre-routing check is now clean, so I’m spending the routing ACUs now. I also removed the last stale trace first to avoid wasting a routing pass.
Blocked: the project ran out of ACUs during the auto-route job, so I can’t continue tool work in this run.
Completed so far:
  • Replaced the problematic footprint components:
    • U1 ESP32-S3 module
    • U3 MAX-M10S GNSS
    • U7 BQ24074 charger/power path
    • U8 TPS63070 buck-boost regulator
  • Restored their original functional connections: USB, GPS UART, I2C, I2S mic, battery charger, SYS/3V3 power, boot/reset.
  • Re-placed the replacements on the PCB.
  • Removed stale/dangling traces from the old footprints.
  • Fixed the SYS rail reconnection for C12/R15.
  • Reduced small-pin routing width for U8 BB_L1 and BB_VAUX.
  • Got the board to pass the pre-routing feasibility check before routing was attempted.
Remaining:
  • Add ACUs, then re-run routing.
  • The paused route reported USB differential pair routing stalled, so after ACUs are available the next step is to adjust/route USB_DP/USB_DN specifically, then run final ERC/DRC/airwire checks.
  • U3:VIO_SEL may still appear as a false ERC issue; per MAX-M10S datasheet it should be left open for 3.3 V V_IO operation.
Route USB first
Verify VIO_SEL
Run final checks
Inspect footprints

Firmware Starter — ESP32-S3 Smart Pet Collar Sensor Hub
Scope and source of truth
This PlatformIO/Arduino starter is matched to the live schematic. It provides native USB CDC serial, the shared I2C bus, MAX30102/TMP117/BMI270 presence and starter reads, MAX-M10S UART parsing, SPH0645LM4H-1 I2S capture, and BLE GATT reporting. It does not replace production sensor algorithms, calibration, secure provisioning, power management, or regulatory firmware validation.
Live pin/net map

Table


ESP32-S3 signalGPIOLive netDevice endpoint
Native USB D-19USB_DND1 USBLC6-2SC6 → USB_DN_CONN → J1 DN1/DN2
Native USB D+20USB_DPD1 USBLC6-2SC6 → USB_DP_CONN → J1 DP1/DP2
I2C SDA8I2C_SDAU2 SDA, U4 SDA, U5 SDX; 4.7 kΩ pull-up
I2C SCL9I2C_SCLU2 SCL, U4 SCL, U5 SCX; 4.7 kΩ pull-up
PPG interrupt10HR_INTU2 ~INT
IMU interrupt11IMU_INTU5 INT1
GNSS RX at ESP18GPS_TX_TO_ESP_RXU3 TXD
GNSS TX at ESP17ESP_TX_TO_GPS_RXU3 RXD
I2S BCLK4MIC_BCLKU6 BCLK
I2S WS/LRCLK5MIC_WSU6 WS
I2S data input6MIC_DATAU6 DATA
Boot strap0ESP_BOOTSW2 to GND, 10 kΩ pull-up
Enable/resetENESP_ENSW1 to GND, pull-up/filter
U2 MAX30102 uses 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.
Toolchain
Create this file tree:

Text


platformio.ini
src/main.cpp
platformio.ini

Ini


[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.cpp

Cpp


#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);
}
Build, flash, and smoke test

Bash


pio run
pio run -t upload
pio device monitor
Normal flashing uses J1 native USB. If automatic download does not start: hold SW2 BOOT, tap SW1 RESET, release RESET, then release BOOT and retry upload. Flashing production firmware is a mandatory post-assembly operation; an unflashed board is not a functional unit.
Pass smoke test: USB CDC enumerates; boot log reports MAX30102/TMP117/BMI270 presence; GNSS UART produces a valid fix outdoors with J3 antenna; microphone level changes with sound; phone sees PetCollar and receives one JSON notification per second.
Production firmware follow-ups
  • Integrate Bosch BMI270 Sensor API/configuration blob, configure ranges/ODR and map INT1 to GPIO11.
  • Add PPG signal-quality, animal-contact detection, validated heart-rate algorithm, calibration and fault limits.
  • Add BLE security/bonding, privacy, authenticated updates, unique device identity and production-test commands.
  • Add low-power states, watchdog, brownout/reset logging, battery/charger telemetry strategy, and data retention policy.
  • Freeze dependency versions and archive a clean 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