Skip to content

antonalth/esp32-loadcell

Repository files navigation

ESP32 Load-Cell Platform

Firmware + browser console for a WIKA F2802 force transducer read through a WIKA B1940 current-loop amplifier, an ADS1115 ADC, and an ESP32, with SD-card backup logging and a Web Serial HTML frontend.

F2802 load cell ── B1940 amp ── 150 Ω shunt ── ADS1115 ── ESP32 ──USB serial── browser UI
 2 kN, 2.0015mV/V   4–20 mA      I→V drop       16-bit       │
 exc 6–10 VDC       sup 18–30V                                └── SD card (FAT32 backup)

1. Hardware & wiring

Signal chain

The B1940 outputs 4–20 mA proportional to force (live zero: 4 mA = 0 N, 20 mA = full scale = 2 kN). The ADS1115 measures voltage, so the loop current is dropped across a precision shunt and the ADS reads the shunt voltage.

Shunt 4 mA 20 mA Notes
150 Ω, 0.1 %, ≤25 ppm/°C 0.60 V 3.00 V under 3.3 V, well under the amp's 400 Ω max burden

ADS1115 gain ±4.096 V (GAIN_ONE) → 125 µV/bit → ≈0.83 µA/bit → ~19 000 counts across the 16 mA span.

Connections (verify colours against your physical labels)

  • B1940 supply: 18–30 VDC (use 24 V). Its supply ground must be common with the ESP32/ADS1115 ground.
  • B1940 4–20 mA output → shunt → common GND; ADS1115 A0 taps across the shunt.
  • F2802 → B1940 (factory-matched DMS bridge): Input Red(+) / Black(−), Output Green(+) / White(−).
  • ADS1115: I²C SDA=21, SCL=22, VDD=3.3 V, addr 0x48 (ADDR→GND).
  • SD card (SPI): CS=5, SCK=18, MISO=19, MOSI=23, VCC=3.3 V.

Reading below ~3.5 mA ⇒ broken loop wire or unpowered amplifier — flagged as BROKEN and surfaced in POST and telemetry.


2. Firmware

Arduino sketch in firmware/loadcell/, split into tabs (no RTOS, no flash use):

Tab Responsibility
loadcell.ino globals, setup() (POST → sampler → calib), cooperative loop()
post.ino Power-On Self Test (ADS + loop current + SD write/read/verify) → run mode
sampler.ino ADS1115 continuous read, EMA filter, calibration math, tare/span
logger.ino SD series folders, CSV/meta, wear-aware flush, calibration file, list/get
comms.ino NDJSON command parser + telemetry/status/ack emitters
config.h all tunables — pins, shunt Ω, FNOM_N, rates, paths

Dependencies

  • Adafruit ADS1X15 ≥ 2.4
  • ArduinoJson ≥ 7
  • ESP32 Arduino core (bundled SD, Wire, SPI)

Cooperative loop

loop() is non-blocking: the ADS runs in continuous mode so getLastConversionResults() returns instantly. Each iteration samples at SAMPLE_HZ (timestamped with real millis()), services serial, flushes the SD buffer at most every FLUSH_MS, and emits decimated telemetry at TELEM_HZ.

Run modes (decided by POST)

  • NORMAL — SD mounts and passes write/read/verify → full logging to card.
  • DEGRADED — SD missing/unreadable, or pulled mid-session → no card writes, live streaming continues, every frame carries "sd":false, and the browser records as a fallback while showing a large warning banner. Calibration in this mode comes from the browser (held in RAM only).

FNOM_N is preset to 2000 N to match the labelled F2802-2KN. It only affects the uncalibrated fallback mapping; a 2-point calibration overrides it.


3. Persistence & identity (SD-only)

No ESP32 flash is used. The SD card is the single source of truth; swapping the card cleanly restarts numbering from that card's contents.

/SYS/CALIB.CSV                tare_mA,scale_N_per_mA,span_calibrated
/DATA/000123_pulltest_A/
    META.JSON                 id, label, fw, sample_hz, shunt, start_ms, host_epoch?, calib{}
    DATA.CSV                  seq,t_ms,raw,mA,force_N,flags
  • Series id = highest existing /DATA/NNNNNN_* folder + 1 (the filesystem is the counter — no flash, host-independent).
  • In-series time = t_ms since series start (millis()), always present.
  • Wall clock = optional: the UI sends host_epoch (Unix seconds) at start, stored once in META.JSON. Reconstruct offline as host_epoch + (t_ms − 0)/1000. Logging never depends on it, so a missing/odd host clock can't break a recording.

DATA.CSV example

seq,t_ms,raw,mA,force_N,flags
0,0,17234,12.0430,250.5,0
1,20,17240,12.0470,250.7,0
2,40,17198,12.0200,249.4,4

flags is a bitfield: 1=broken wire, 2=saturated, 4=uncalibrated.

SD-wear strategy

  • Writes happen only during an active series.
  • The log file stays open for the whole series; rows accumulate in the SD sector cache and are flush()ed at most once per FLUSH_MS (≈1 s) → large, sequential writes and few FAT/directory updates.
  • A flush bounds worst-case power-loss to ~1 s; the append-only CSV loses at most a partial final line, which parsers tolerate.

4. Serial protocol (NDJSON)

One JSON object per line, both directions, at 115200 baud.

Host → ESP32

Command Effect
{"cmd":"status"} request a status frame
{"cmd":"tare"} set current reading as zero
{"cmd":"calibrate","known_n":1000} set span from a known applied load
{"cmd":"start","label":"pulltest_A","host_epoch":1750000000} begin a series
{"cmd":"stop"} end the series, flush + close file
{"cmd":"stream","on":false} pause/resume telemetry
{"cmd":"list"} list series on the card
{"cmd":"get","path":"/DATA/000123_x/DATA.CSV"} stream a file back

ESP32 → Host

Frame When
{"post":{ads,loop_mA,loop_ok,sd_mount,sd_write,sd_read,sd_free_mb,mode,fw}} once at boot
{"status":{mode,sd,ads,recording,series,sample_hz,calib{...}}} on request / boot
{"telem":{t,mA,N,raw,series,rec,sd,flags}} every 1000/TELEM_HZ ms
{"ack":"start",series,path,sd} / {"ack":"stop"} / {"ack":"tare|calibrate",...} command acks
{"event":"sd_lost"} card pulled mid-session
{"err":"..."} bad json / unknown cmd / need_known_n / span_too_small
{"list_begin"} · {"series":{id,name,path,bytes}} · {"list_end"} list response
{"file_begin":{path,size}} · raw CSV bytes · {"file_end":{path}} get response

File download framing: after file_begin, the UI captures raw bytes until a line beginning {"file_end" arrives, then saves the buffer as a .csv.


5. Frontend (frontend/index.html)

Single self-contained file using the Web Serial API (Chrome/Edge over USB — no WiFi, no server, no build step). Just open it and click Connect.

Features: live force/mA/raw readout + rolling chart, fault pills (broken wire / saturated / uncalibrated), Tare, Calibrate span, labelled Start/Stop, SD series browser with per-series Download, the big NO-SD warning with a browser-side fallback recorder (+ "Download browser recording"), and a raw log.


6. Build & run

  1. Set FNOM_N in config.h if your cell isn't 2 kN (this one is).
  2. Install the two libraries (Library Manager): Adafruit ADS1X15, ArduinoJson.
  3. Open firmware/loadcell/loadcell.ino, select your ESP32 board, Upload.
  4. Open frontend/index.html in Chrome/Edge → Connect → pick the ESP32 port. (The Arduino Serial Monitor must be closed — only one app can hold the port.)

Typical session

  1. POST runs at boot; check loop_ok and mode in the log.
  2. With no load applied, Tare.
  3. Apply a known reference load, type its value (N), Calibrate span.
  4. Enter a label, Start → record → Stop.
  5. Refresh the series list and Download the CSV, or pull it later off the card.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors