Smart Espresso Scale


Routing completed successfully. I’m running final DRC/manufacturing/BOM checks now and will call out any remaining items that need manual sourcing or review.
The small post-route placement adjustment invalidated several existing traces, so I’m clearing the stale routing and running one clean re-route from the final placement.
Created a project doc

Layout Verification Notes — Smart Espresso Scale

View Document

Board set to compact 50 x 80 mm rounded rectangle. Stackup set to Standard 4 Layer.
Completed the PCB layout pass: the board is 50 × 80 mm, 4-layer, placed with the ESP32 antenna intentionally overhanging the top edge, and fully routed with no airwires/overlap/dangling/invalid-layer errors remaining.
Open items:
  • U1 still reports a body-over-edge warning; this is intentional for the ESP32 antenna keepout.
  • BOM still needs final MPNs for generic resistors/capacitors, L3 inductor, and J4 header; TP1–TP4 were excluded from BOM/PnP.
  • Routing used 0.1 mm traces to enter fine-pitch parts, so power paths should be widened/poured before manufacturing.
I documented the verification details in Layout Verification Notes.
Suggested next steps:
  1. Select final passive/inductor/header MPNs.
  2. Widen/pour VBUS, BAT, SYS, REG_IN, and 3V3 power paths.
  3. Verify antenna keepout visually/mechanically.
  4. Export Gerbers/BOM/PnP once BOM MPNs are finalized.
Finalize BOM and power
Select final MPNs
Widen power paths
Verify RF keepout
Run manufacture export

J1
U4
U3
J5
U1
C16
Capacitance
100nF
C10
Capacitance
1uF
C7
Capacitance
10uF
C1
Capacitance
10uF
C2
Capacitance
10uF
C8
Capacitance
10uF
C4
Capacitance
10uF
C3
Capacitance
22uF
C9
Capacitance
100nF
C12
Capacitance
10uF
C13
Capacitance
100nF
C6
Capacitance
10uF
C14
Capacitance
10nF
C11
Capacitance
10uF
C15
Capacitance
100nF
C5
Capacitance
100nF
R5
Resistance
2.94kΩ
TP4
R10
Resistance
10kΩ
R15
Resistance
10kΩ
R16
Resistance
10kΩ
R21
Resistance
330kΩ
J4
R1
Resistance
5.1kΩ
R8
Resistance
1.5kΩ
R7
Resistance
46.4kΩ
C17
Capacitance
10uF
TP3
R20
Resistance
1MΩ
R14
Resistance
4.7kΩ
R2
Resistance
5.1kΩ
TP1
TP2
R9
Resistance
10kΩ
R6
Resistance
10kΩ
R19
Resistance
100Ω
R17
Resistance
10Ω
R4
Resistance
3.57kΩ
R13
Resistance
4.7kΩ
R12
Resistance
10kΩ
R11
Resistance
10kΩ
R18
Resistance
100Ω
R3
Resistance
3.09kΩ
J3
F1
J2
SW1
D2
SW2
SW5
SW4
L3
Inductance
1.5uH
SW3
U2
D3
D1
Firmware Starter — Smart Espresso Scale
Platform & Toolchain
  • MCU: ESP32-C3-WROOM-02-N4
  • Framework: Arduino for ESP32 using PlatformIO
  • Board target: esp32-c3-devkitm-1 as a practical starting target for ESP32-C3 modules
Pin Mapping

Table


FunctionESP32-C3 GPIOSchematic NetDirectionNotes
HX711 dataGPIO0HX711_DOUTInputData ready + serial data from HX711.
HX711 clockGPIO1HX711_SCKOutputKeep low when idle.
Battery ADCGPIO3BAT_SENSEAnalog input1M/330k divider.
Tare buttonGPIO4BTN_TAREInput pull-upActive-low.
OLED SDAGPIO5I2C_SDAI2C4.7k pull-up.
OLED SCLGPIO6I2C_SCLI2C4.7k pull-up.
Mode buttonGPIO7BTN_MODEInput pull-upActive-low.
BootGPIO9ESP_BOOT_GPIO9Strap/inputPull-up + boot button.
UART RXGPIO20/RXDUART_RXDInputJ5 programming header.
UART TXGPIO21/TXDUART_TXDOutputJ5 programming header.
Dependencies & Project Setup
Create platformio.ini:

Ini


[env:espresso_scale]
platform = espressif32
board = esp32-c3-devkitm-1
framework = arduino
monitor_speed = 115200
build_flags =
  -D ARDUINO_USB_MODE=1
  -D ARDUINO_USB_CDC_ON_BOOT=1
lib_deps =
  bogde/HX711@^0.7.5
  olikraus/U8g2@^2.35.30
Complete Firmware Source
Create src/main.cpp:

Cpp


#include <Arduino.h>
#include <Wire.h>
#include <WiFi.h>
#include <HX711.h>
#include <U8g2lib.h>

#define HX711_DOUT_PIN   0
#define HX711_SCK_PIN    1
#define BAT_SENSE_PIN    3
#define BTN_TARE_PIN     4
#define I2C_SDA_PIN      5
#define I2C_SCL_PIN      6
#define BTN_MODE_PIN     7

#define VBAT_TOP_OHMS    1000000.0f
#define VBAT_BOTTOM_OHMS 330000.0f
#define ADC_MAX_COUNTS   4095.0f
#define ADC_REF_VOLTS    3.3f

#define SCALE_READ_INTERVAL_MS 100
#define DISPLAY_INTERVAL_MS    250
#define LONG_PRESS_MS          800

HX711 scale;
U8G2_SSD1306_128X64_NONAME_F_HW_I2C display(U8G2_R0, U8X8_PIN_NONE);

static float calibrationFactor = 1000.0f;  // TODO: calibrate with known mass
static unsigned long lastScaleRead = 0;
static unsigned long lastDisplay = 0;
static float weightGrams = 0.0f;
static bool timerRunning = false;
static unsigned long timerStartMs = 0;

float readBatteryVoltage() {
  uint16_t raw = analogRead(BAT_SENSE_PIN);
  float adcVoltage = (raw / ADC_MAX_COUNTS) * ADC_REF_VOLTS;
  return adcVoltage * ((VBAT_TOP_OHMS + VBAT_BOTTOM_OHMS) / VBAT_BOTTOM_OHMS);
}

bool pressed(uint8_t pin) {
  static unsigned long lastDebounce[10] = {0};
  static bool lastState[10] = {false};
  bool nowPressed = digitalRead(pin) == LOW;
  uint8_t idx = pin < 10 ? pin : 0;
  if (nowPressed != lastState[idx] && millis() - lastDebounce[idx] > 40) {
    lastDebounce[idx] = millis();
    lastState[idx] = nowPressed;
    return nowPressed;
  }
  return false;
}

void initScale() {
  scale.begin(HX711_DOUT_PIN, HX711_SCK_PIN);
  scale.set_scale(calibrationFactor);
  scale.tare(20);
}

void initDisplay() {
  Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
  display.begin();
  display.setFont(u8g2_font_6x12_tf);
}

void updateDisplay() {
  float vbat = readBatteryVoltage();
  unsigned long elapsed = timerRunning ? (millis() - timerStartMs) / 1000UL : 0;

  display.clearBuffer();
  display.setCursor(0, 12);
  display.print("Smart Espresso Scale");
  display.setCursor(0, 30);
  display.print("Weight: ");
  display.print(weightGrams, 1);
  display.print(" g");
  display.setCursor(0, 46);
  display.print("Timer: ");
  display.print(elapsed);
  display.print(" s");
  display.setCursor(0, 62);
  display.print("VBAT: ");
  display.print(vbat, 2);
  display.print(" V");
  display.sendBuffer();
}

void setup() {
  Serial.begin(115200);
  delay(300);
  Serial.println("Smart Espresso Scale booting");

  WiFi.mode(WIFI_OFF); // keep RF quiet during precision measurements by default

  pinMode(BTN_TARE_PIN, INPUT_PULLUP);
  pinMode(BTN_MODE_PIN, INPUT_PULLUP);
  analogReadResolution(12);
  analogSetPinAttenuation(BAT_SENSE_PIN, ADC_11db);

  initDisplay();
  initScale();
  updateDisplay();
}

void loop() {
  unsigned long now = millis();

  if (pressed(BTN_TARE_PIN)) {
    Serial.println("Tare requested");
    scale.tare(20);
  }

  if (pressed(BTN_MODE_PIN)) {
    timerRunning = !timerRunning;
    if (timerRunning) timerStartMs = now;
    Serial.printf("Timer %s\n", timerRunning ? "started" : "stopped");
  }

  if (now - lastScaleRead >= SCALE_READ_INTERVAL_MS) {
    lastScaleRead = now;
    if (scale.is_ready()) {
      weightGrams = scale.get_units(3);
      Serial.printf("Weight: %.2f g, VBAT: %.2f V\n", weightGrams, readBatteryVoltage());
    } else {
      Serial.println("HX711 not ready");
    }
  }

  if (now - lastDisplay >= DISPLAY_INTERVAL_MS) {
    lastDisplay = now;
    updateDisplay();
  }
}
Build & Flash Instructions

Bash


pio run
pio run -t upload
pio device monitor
To enter bootloader manually: hold BOOT, tap RESET/EN, then release BOOT before flashing. Calibrate calibrationFactor with a known weight before using the scale.
  • Platform & Toolchain

  • Pin Mapping

  • Dependencies & Project Setup

  • Complete Firmware Source

  • Build & Flash Instructions