Smart Espresso Scale

Created 3 project docs
Firmware Starter — ESP32-S3 Smart Espresso Scale
MCU: ESP32-S3-WROOM-1-N8R8 Framework: Arduino via PlatformIO
View doc
Board Bring-Up Plan — Smart Espresso Scale
Multimeter, USB current meter, oscilloscope, 500 mA current-limited USB supply if available. Firmware from the Firmware Starter project file...
View doc
Regulatory FCC/CE Notes — Smart Espresso Scale
Draft design-stage notes for a wireless USB-powered consumer device. | Module | Technology | Notes |
View doc
Implemented the smart espresso scale schematic: USB-C power/data, 1.5 A 3.3 V buck rail, ESP32-S3 BLE/USB MCU, NAU7802 load-cell ADC, I2C OLED screen, microSD local logging, buttons, and status LED. I also created/updated the design files: Project Specification, Power Budget, Firmware Starter, Board Bring-Up Plan, and Regulatory FCC/CE Notes. ERC validation for the checked schematic categories is clean.
Open items:
  • L1 is still a generic 3.3 µH inductor requirement; it should be replaced with a real shielded inductor MPN before manufacturing.
  • Confirm the selected OLED module accepts 3.3 V on its VCC/“5V” pin.
  • Layout is not done yet; antenna keepout, buck SW-node placement, and quiet load-cell routing are critical.
Suggested next steps:
  1. Replace L1 with a real inductor MPN.
  2. Start PCB layout with ESP32 antenna at the board edge.
  3. Add test points for 3V3, 3V3_AFE, NAU_DRDY, I2C, SPI, and load-cell signals.
  4. Route and run DRC/SI checks.
Finish layout prep
Replace L1
Verify OLED power
Add test points
Start PCB layout
Run schematic review
J1
U1
R2
Resistance
5.1kΩ
R17
Resistance
10kΩ
R15
Resistance
10kΩ
R6
Resistance
4.7kΩ
R8
Resistance
100 Ω
R9
Resistance
100 Ω
R14
Resistance
10kΩ
R10
Resistance
4.7kΩ
R5
Resistance
4.7kΩ
R18
Resistance
10kΩ
R1
Resistance
5.1kΩ
R7
Resistance
330 Ω
R16
Resistance
10kΩ
R3
Resistance
10kΩ
R4
Resistance
10kΩ
J2
U5
C1
Capacitance
4.7µF
C5
Capacitance
10uF
C6
Capacitance
1uF
L1
Inductance
3.3uH
SW4
C2
Capacitance
4.7µF
U2
C8
Capacitance
.1uF
C10
Capacitance
10uF
C9
Capacitance
1uF
SW1
C11
Capacitance
10uF
C12
Capacitance
1uF
C4
Capacitance
1uF
SW3
C7
Capacitance
1uF
SW2
U4
End of Life
J3
C3
Capacitance
22uF
DISP1
FB1
F1
U3
D1

Refine this doc
Ask about this doc
Firmware Starter — ESP32-S3 Smart Espresso Scale
Platform & Toolchain
  • MCU: ESP32-S3-WROOM-1-N8R8
  • Framework: Arduino via PlatformIO
  • Main peripherals: NAU7802 over I2C, SSD1306 OLED over I2C, microSD over SPI, BLE GATT, buttons, status LED.
Pin Mapping

Table


FunctionESP32-S3 GPIONet
I2C SDA8I2C_SDA
I2C SCL9I2C_SCL
NAU7802 DRDY7NAU_DRDY
SD CS10SD_CS
SD MOSI11SD_MOSI
SD SCK12SD_SCK
SD MISO13SD_MISO
SD power enable21SD_PWR_EN
Tare button4BTN_TARE
Start/stop button5BTN_START_STOP
Status LED6LED_STATUS_DRV
Native USB D-19USB_D_N
Native USB D+20USB_D_P
BOOT button0ESP_BOOT
platformio.ini

Ini


[env:esp32-s3-devkitc-1]
platform = espressif32
board = esp32-s3-devkitc-1
framework = arduino
monitor_speed = 115200
build_flags =
  -DARDUINO_USB_MODE=1
  -DARDUINO_USB_CDC_ON_BOOT=1
lib_deps =
  sparkfun/SparkFun Qwiic Scale NAU7802 Arduino Library@^1.0.5
  adafruit/Adafruit SSD1306@^2.5.10
  adafruit/Adafruit GFX Library@^1.11.9
  h2zero/NimBLE-Arduino@^1.4.2
Complete Starter Source

Cpp


#include <Arduino.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <SparkFun_Qwiic_Scale_NAU7802_Arduino_Library.h>
#include <NimBLEDevice.h>

#define I2C_SDA_PIN 8
#define I2C_SCL_PIN 9
#define NAU_DRDY_PIN 7
#define SD_CS_PIN 10
#define SD_MOSI_PIN 11
#define SD_SCK_PIN 12
#define SD_MISO_PIN 13
#define SD_PWR_EN_PIN 21
#define BTN_TARE_PIN 4
#define BTN_START_STOP_PIN 5
#define STATUS_LED_PIN 6

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
NAU7802 scale;

NimBLEServer *bleServer = nullptr;
NimBLECharacteristic *weightCharacteristic = nullptr;
static const char *SERVICE_UUID = "b7a8f7e0-41a3-42e5-8c6e-espresso0001";
static const char *WEIGHT_UUID  = "b7a8f7e0-41a3-42e5-8c6e-espresso0002";

float calibrationFactor = 1.0f;
long zeroOffset = 0;
bool timerRunning = false;
uint32_t shotStartMs = 0;
uint32_t lastSampleMs = 0;
uint32_t lastLogMs = 0;
File logFile;

bool pressed(uint8_t pin) { return digitalRead(pin) == LOW; }

void initBLE() {
  NimBLEDevice::init("EspressoScale");
  bleServer = NimBLEDevice::createServer();
  NimBLEService *service = bleServer->createService(SERVICE_UUID);
  weightCharacteristic = service->createCharacteristic(
      WEIGHT_UUID, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY);
  weightCharacteristic->setValue("0.00,0");
  service->start();
  NimBLEAdvertising *adv = NimBLEDevice::getAdvertising();
  adv->addServiceUUID(SERVICE_UUID);
  adv->start();
}

void initDisplay() {
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println("OLED not found at 0x3C");
    return;
  }
  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);
  display.setTextSize(1);
  display.setCursor(0, 0);
  display.println("Smart Espresso Scale");
  display.display();
}

void initScale() {
  if (!scale.begin()) {
    Serial.println("NAU7802 not detected");
    return;
  }
  scale.setGain(NAU7802_GAIN_128);
  scale.setSampleRate(NAU7802_SPS_80);
  scale.calibrateAFE();
  delay(100);
  zeroOffset = scale.getAverage(16);
}

void initSD() {
  digitalWrite(SD_PWR_EN_PIN, HIGH);
  delay(20);
  SPI.begin(SD_SCK_PIN, SD_MISO_PIN, SD_MOSI_PIN, SD_CS_PIN);
  if (!SD.begin(SD_CS_PIN, SPI)) {
    Serial.println("microSD init failed; continuing without local log");
    return;
  }
  logFile = SD.open("/shots.csv", FILE_APPEND);
  if (logFile && logFile.size() == 0) {
    logFile.println("ms,grams,timer_ms");
    logFile.flush();
  }
}

float readGrams() {
  if (!scale.available()) return NAN;
  long raw = scale.getReading();
  return (raw - zeroOffset) / calibrationFactor;
}

void updateDisplay(float grams, uint32_t timerMs) {
  display.clearDisplay();
  display.setTextSize(2);
  display.setCursor(0, 0);
  display.printf("%6.1fg", grams);
  display.setTextSize(2);
  display.setCursor(0, 28);
  display.printf("%5.1fs", timerMs / 1000.0f);
  display.setTextSize(1);
  display.setCursor(0, 56);
  display.print(NimBLEDevice::getAdvertising()->isAdvertising() ? "BLE adv" : "BLE on");
  display.print(logFile ? " SD" : " noSD");
  display.display();
}

void logSample(float grams, uint32_t timerMs) {
  if (!logFile) return;
  logFile.printf("%lu,%.2f,%lu\n", millis(), grams, timerMs);
  if (millis() - lastLogMs > 1000) {
    logFile.flush();
    lastLogMs = millis();
  }
}

void handleButtons() {
  static bool lastTare = false, lastStart = false;
  bool tareNow = pressed(BTN_TARE_PIN);
  bool startNow = pressed(BTN_START_STOP_PIN);
  if (tareNow && !lastTare) zeroOffset = scale.getAverage(16);
  if (startNow && !lastStart) {
    timerRunning = !timerRunning;
    if (timerRunning) shotStartMs = millis();
  }
  lastTare = tareNow;
  lastStart = startNow;
}

void setup() {
  pinMode(STATUS_LED_PIN, OUTPUT);
  pinMode(SD_PWR_EN_PIN, OUTPUT);
  pinMode(BTN_TARE_PIN, INPUT_PULLUP);
  pinMode(BTN_START_STOP_PIN, INPUT_PULLUP);
  pinMode(NAU_DRDY_PIN, INPUT);
  digitalWrite(STATUS_LED_PIN, LOW);
  digitalWrite(SD_PWR_EN_PIN, LOW);

  Serial.begin(115200);
  delay(200);
  Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN, 400000);
  initDisplay();
  initScale();
  initSD();
  initBLE();
}

void loop() {
  handleButtons();
  uint32_t now = millis();
  if (now - lastSampleMs >= 50) {
    lastSampleMs = now;
    float grams = readGrams();
    if (isnan(grams)) return;
    uint32_t timerMs = timerRunning ? now - shotStartMs : 0;
    updateDisplay(grams, timerMs);
    logSample(grams, timerMs);
    char payload[32];
    snprintf(payload, sizeof(payload), "%.2f,%lu", grams, timerMs);
    weightCharacteristic->setValue(payload);
    weightCharacteristic->notify();
    digitalWrite(STATUS_LED_PIN, !digitalRead(STATUS_LED_PIN));
  }
}
Build & Flash
  • Build: pio run
  • Flash over native USB: hold BOOT, tap RESET, then pio run -t upload
  • Monitor: pio device monitor
Calibration TODO
Set calibrationFactor from a known mass: tare the scale, place a calibration weight, record raw delta, then calibrationFactor = raw_delta / grams.
  • Platform & Toolchain

  • Pin Mapping

  • platformio.ini

  • Complete Starter Source

  • Build & Flash

  • Calibration TODO

Documents

  • Project Specification — Smart Espresso Scale

  • Block Diagram — Smart Espresso Scale

  • Reference Recreation Ledger — Smart Espresso Scale

  • Power Budget — Smart Espresso Scale

  • Firmware Starter — ESP32-S3 Smart Espresso Scale

  • Board Bring-Up Plan — Smart Espresso Scale

  • Regulatory FCC/CE Notes — Smart Espresso Scale

Assets

No assets added.

Smart Espresso Scale

Smart Espresso Scale thumbnail
USB-C powered smart espresso scale with ESP32 phone connectivity, load-cell measurement, local data storage, and an integrated display for live weight/timer readout.

Properties

USB-C

Consumer electronics

ESP32

Display

Bluetooth

Pricing & Availability

Distributor

Qty 1

Arrow

$2.44–$2.93

Digi-Key

$2.14

HQonline

$1.63–$1.73

LCSC

$12.20–$12.68

Mouser

$16.38

TME

$4.05

Verical

$2.06–$3.73

Controls