Smart Espresso Scale

Created 5 project docs
Firmware Starter — ESP32-S3 Smart Espresso Scale
MCU: ESP32-S3-WROOM-1-N8R8 (U4) Framework: Arduino on PlatformIO
View doc
Board Bring-Up Plan — Smart Espresso Scale
Equipment: current-limited USB supply, multimeter, oscilloscope, USB-C cable, known calibration weights, Li-ion cell simulator or protected ...
View doc
Technical Construction File — Smart Espresso Scale
Draft FCC Part 15 intentional radiator / modular transmitter integration review
View doc
Safety Analysis — Smart Espresso Scale
Draft Low-voltage, USB-C powered, single-cell Li-ion battery product. No mains voltage is present on the PCB.
View doc
Preliminary FMEA — Smart Espresso Scale
Design-stage FMEA for a USB-C rechargeable BLE espresso scale with Li-ion battery, load-cell ADC, display, and microSD logging. | Subsystem ...
View doc
Designed the smart espresso scale schematic and renamed the project Smart Espresso Scale. The schematic now has 55 components / 44 nets, including USB-C power/ESD, BQ24295 Li-ion charger/power-path, 3.3 V regulation, ESP32-S3 BLE MCU, NAU7802 load-cell AFE, microSD logging, I2C OLED display, buttons, and status LEDs; verification found no open ERC/component/footprint errors in the checked categories.
Project files created:
Open items:
  • Select the final Li-ion cell capacity and set BQ24295 charge current accordingly; firmware currently notes a conservative ≤512 mA target.
  • Validate TLV1117 dropout/thermal margin during BLE + display + SD write bursts.
  • Verify the exact OLED/display module pinout and 3.3 V compatibility before production.
  • PCB layout still needs antenna keepout, charger/regulator thermal layout, and load-cell analog routing care.
Suggested next steps:
  1. Proceed to PCB board size/constraints and component placement.
  2. Choose the final battery capacity and enclosure/display mechanical constraints.
  3. Run PCB layout with ESP32 antenna keepout and load-cell analog separation.
  4. After placement, route and run full DRC/PI/SI checks.
Layout and fix risks
Proceed to layout
Fix open risks
Set design constraints
Run schematic review

Refine this doc
Ask about this doc
Firmware Starter — ESP32-S3 Smart Espresso Scale
Platform & Toolchain
  • MCU: ESP32-S3-WROOM-1-N8R8 (U4)
  • Framework: Arduino on PlatformIO
  • Interfaces: BLE, I2C, SPI microSD, native USB serial
Pin Mapping

Table


FunctionESP32-S3 GPIONetConnected ToNotes
I2C SDAIO9I2C_SDANAU7802 U5, BQ24295 U2, OLED DS13.3 V pull-up present
I2C SCLIO10I2C_SCLNAU7802 U5, BQ24295 U2, OLED DS13.3 V pull-up present
Load cell DRDYIO8LOAD_DRDYNAU7802 U5Interrupt input
microSD CSIO14SPI_SD_CSmicroSD J5Pull-up present
microSD SCKIO13SPI_SD_SCKmicroSD J5SPI clock
microSD MOSIIO11SPI_SD_MOSImicroSD J5SPI MOSI
microSD MISOIO12SPI_SD_MISOmicroSD J5SPI MISO
microSD detectIO15SD_CARD_DETECTmicroSD J5Card detect input
microSD power enableIO39SD_PWR_ENTPS22919 U6Drives SD switched 3.3 V
USB D-IO19USB_D-USB-C J1 via ESDNative USB
USB D+IO20USB_D+USB-C J1 via ESDNative USB
Tare buttonIO4BTN_TARESW1Active-low button
Start/stop buttonIO5BTN_START_STOPSW2Active-low button
Status LEDIO6STATUS_LEDLED pathActive-high assumed
Charger interruptIO7CHG_INTBQ24295 U2Active-low interrupt
Dependencies & Project Setup
platformio.ini:

Ini


[env:esp32-s3-devkitc-1]
platform = espressif32
board = esp32-s3-devkitc-1
framework = arduino
monitor_speed = 115200
board_build.filesystem = littlefs
lib_deps =
  bogde/HX711@^0.7.5
  adafruit/Adafruit SSD1306@^2.5.13
  adafruit/Adafruit GFX Library@^1.11.11
  h2zero/NimBLE-Arduino@^1.4.3
Note: The bogde/HX711 dependency is not used by the NAU7802 code below; remove it if unused. It is listed only as a common scale-calibration utility dependency. The NAU7802 is accessed directly over I2C for a minimal starter.
Complete Firmware Source
src/main.cpp:

Cpp


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

// Pin definitions from schematic
constexpr int PIN_I2C_SDA = 9;
constexpr int PIN_I2C_SCL = 10;
constexpr int PIN_LOAD_DRDY = 8;
constexpr int PIN_SD_CS = 14;
constexpr int PIN_SD_SCK = 13;
constexpr int PIN_SD_MOSI = 11;
constexpr int PIN_SD_MISO = 12;
constexpr int PIN_SD_DETECT = 15;
constexpr int PIN_SD_PWR_EN = 39;
constexpr int PIN_BTN_TARE = 4;
constexpr int PIN_BTN_START_STOP = 5;
constexpr int PIN_STATUS_LED = 6;
constexpr int PIN_CHG_INT = 7;

// I2C addresses
constexpr uint8_t NAU7802_ADDR = 0x2A;
constexpr uint8_t BQ24295_ADDR = 0x6B;
constexpr uint8_t OLED_ADDR = 0x3C;

// NAU7802 registers used by this starter
constexpr uint8_t NAU_PU_CTRL = 0x00;
constexpr uint8_t NAU_CTRL1 = 0x01;
constexpr uint8_t NAU_CTRL2 = 0x02;
constexpr uint8_t NAU_ADCO_B2 = 0x12;
constexpr uint8_t NAU_ADCO_B1 = 0x13;
constexpr uint8_t NAU_ADCO_B0 = 0x14;

Adafruit_SSD1306 display(128, 64, &Wire, -1);
SPIClass sdSPI(FSPI);
NimBLECharacteristic *weightCharacteristic = nullptr;

long tareOffset = 0;
float scaleCountsPerGram = 1000.0f; // Calibrate with known weight.
bool loggingEnabled = true;
unsigned long lastSampleMs = 0;

bool i2cWrite8(uint8_t addr, uint8_t reg, uint8_t value) {
  Wire.beginTransmission(addr);
  Wire.write(reg);
  Wire.write(value);
  return Wire.endTransmission() == 0;
}

bool i2cRead8(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;
}

bool initNAU7802() {
  uint8_t v = 0;
  // Power up digital and analog sections, then wait for power-ready.
  if (!i2cWrite8(NAU7802_ADDR, NAU_PU_CTRL, 0x06)) return false;
  delay(10);
  if (!i2cRead8(NAU7802_ADDR, NAU_PU_CTRL, v)) return false;
  i2cWrite8(NAU7802_ADDR, NAU_PU_CTRL, v | 0x20); // Start conversions.
  // Gain/rate defaults are usable for first bring-up; tune during calibration.
  i2cWrite8(NAU7802_ADDR, NAU_CTRL1, 0x27); // PGA gain and LDO defaults; verify for final sensor.
  i2cWrite8(NAU7802_ADDR, NAU_CTRL2, 0x30); // Conversion-rate default pattern.
  return true;
}

bool readNAU7802Raw(long &raw) {
  if (digitalRead(PIN_LOAD_DRDY) == HIGH) return false;
  Wire.beginTransmission(NAU7802_ADDR);
  Wire.write(NAU_ADCO_B2);
  if (Wire.endTransmission(false) != 0) return false;
  if (Wire.requestFrom(NAU7802_ADDR, (uint8_t)3) != 3) return false;
  uint32_t value = ((uint32_t)Wire.read() << 16) | ((uint32_t)Wire.read() << 8) | Wire.read();
  if (value & 0x800000UL) value |= 0xFF000000UL; // Sign extend 24-bit value.
  raw = (int32_t)value;
  return true;
}

void configureBQ24295SafeDefaults() {
  // The charger reset default can be too high for an unknown small Li-ion cell.
  // Set input/charge behavior conservatively in firmware after I2C starts.
  // Exact register bitfields should be reviewed against the TI BQ24295 datasheet
  // before production calibration; this starter intentionally avoids aggressive charging.
  i2cWrite8(BQ24295_ADDR, 0x00, 0x10); // conservative input current limit pattern
  i2cWrite8(BQ24295_ADDR, 0x02, 0x20); // conservative charge-current pattern
}

bool initSD() {
  digitalWrite(PIN_SD_PWR_EN, HIGH);
  delay(20);
  sdSPI.begin(PIN_SD_SCK, PIN_SD_MISO, PIN_SD_MOSI, PIN_SD_CS);
  return SD.begin(PIN_SD_CS, sdSPI, 10000000);
}

void appendLog(float grams, long raw) {
  if (!loggingEnabled) return;
  File f = SD.open("/shots.csv", FILE_APPEND);
  if (!f) return;
  f.printf("%lu,%.2f,%ld\n", millis(), grams, raw);
  f.close();
}

void initBLE() {
  NimBLEDevice::init("Smart Espresso Scale");
  NimBLEServer *server = NimBLEDevice::createServer();
  NimBLEService *service = server->createService("181D"); // Weight Scale service UUID
  weightCharacteristic = service->createCharacteristic("2A9D", NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY);
  weightCharacteristic->setValue("0.00 g");
  service->start();
  NimBLEAdvertising *adv = NimBLEDevice::getAdvertising();
  adv->addServiceUUID("181D");
  adv->start();
}

void updateDisplay(float grams, bool sdOK) {
  display.clearDisplay();
  display.setTextSize(2);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 0);
  display.printf("%6.1f g", grams);
  display.setTextSize(1);
  display.setCursor(0, 32);
  display.printf("SD:%s BLE:ON", sdOK ? "OK" : "NO");
  display.display();
}

void setup() {
  Serial.begin(115200);
  pinMode(PIN_LOAD_DRDY, INPUT);
  pinMode(PIN_SD_DETECT, INPUT_PULLUP);
  pinMode(PIN_SD_PWR_EN, OUTPUT);
  pinMode(PIN_BTN_TARE, INPUT_PULLUP);
  pinMode(PIN_BTN_START_STOP, INPUT_PULLUP);
  pinMode(PIN_STATUS_LED, OUTPUT);
  pinMode(PIN_CHG_INT, INPUT_PULLUP);
  digitalWrite(PIN_STATUS_LED, HIGH);

  Wire.begin(PIN_I2C_SDA, PIN_I2C_SCL, 400000);

  bool oledOK = display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR);
  if (oledOK) {
    display.clearDisplay();
    display.setTextSize(1);
    display.setTextColor(SSD1306_WHITE);
    display.setCursor(0, 0);
    display.println("Scale booting...");
    display.display();
  }

  bool nauOK = initNAU7802();
  configureBQ24295SafeDefaults();
  bool sdOK = initSD();
  initBLE();

  Serial.printf("NAU7802:%s SD:%s\n", nauOK ? "OK" : "FAIL", sdOK ? "OK" : "FAIL");
  if (sdOK) {
    File f = SD.open("/shots.csv", FILE_APPEND);
    if (f && f.size() == 0) f.println("ms,grams,raw");
    if (f) f.close();
  }
}

void loop() {
  static bool sdOK = SD.cardType() != CARD_NONE;

  if (digitalRead(PIN_BTN_TARE) == LOW) {
    long raw;
    if (readNAU7802Raw(raw)) tareOffset = raw;
    delay(250);
  }
  if (digitalRead(PIN_BTN_START_STOP) == LOW) {
    loggingEnabled = !loggingEnabled;
    delay(250);
  }

  if (millis() - lastSampleMs >= 100) {
    lastSampleMs = millis();
    long raw;
    if (readNAU7802Raw(raw)) {
      float grams = (raw - tareOffset) / scaleCountsPerGram;
      Serial.printf("raw=%ld grams=%.2f\n", raw, grams);
      appendLog(grams, raw);
      if (weightCharacteristic) {
        char buf[24];
        snprintf(buf, sizeof(buf), "%.2f g", grams);
        weightCharacteristic->setValue(buf);
        weightCharacteristic->notify();
      }
      updateDisplay(grams, sdOK);
    }
  }
}
Build & Flash
  • Build: pio run
  • Flash: pio run -t upload
  • Monitor: pio device monitor -b 115200
  • Native USB uses IO19/IO20. If flashing fails, hold BOOT while resetting, then release BOOT after upload starts.
Calibration Notes
  1. Power the board from current-limited USB-C.
  2. Run the firmware and press TARE with no load.
  3. Place a known mass on the scale and compute scaleCountsPerGram = (raw - tareOffset) / known_grams.
  4. Store calibration constants in NVS/LittleFS in a production firmware revision.
  • Platform & Toolchain

  • Pin Mapping

  • Dependencies & Project Setup

  • Complete Firmware Source

  • Build & Flash

  • Calibration Notes

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

  • Technical Construction File — Smart Espresso Scale

  • Safety Analysis — Smart Espresso Scale

  • Preliminary FMEA — Smart Espresso Scale

Assets

No assets added.

Smart Espresso Scale

Smart Espresso Scale thumbnail
BLE smart espresso scale with USB-C power, single-cell Li-ion battery charging, load-cell measurement, local data logging, and an integrated segment/display interface.

Properties

Properties describe core aspects of the project.

Pricing & Availability

Distributor

Qty 1

Arrow

$3.59–$4.32

Digi-Key

$1.90

HQonline

$1.48–$1.72

LCSC

$13.57–$14.21

Mouser

$15.81

TME

$7.01

Verical

$1.51–$2.05

Controls