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

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors