2026-03-30 11:39:04 -05:00
2025-12-29 15:49:45 -06:00
2025-12-29 15:49:45 -06:00
2025-12-29 15:49:45 -06:00
2026-03-12 08:53:01 -05:00
2026-03-12 19:12:42 -05:00
2026-03-12 08:53:01 -05:00
2026-01-06 17:01:50 -06:00
2026-03-12 19:12:42 -05:00
2026-03-11 21:46:52 -05:00

SC-F001 Firmware

Solar-powered automated crop harvesting robot built on the ESP32. Drives a carriage horizontally via a drive motor, lifts/lowers a cutting head via a jack motor, with an auxiliary "fluffer" motor always running during operation. The firmware handles motor sequencing, safety interlocks, remote control, data logging, and a WiFi web interface.

Primary operational cycle: Idle → Move Start Delay → Jack Up → Drive → Jack Down → Idle


Hardware Platform

MCU: ESP32 (Xtensa dual-core), ESP-IDF framework

GPIO Map:

GPIO Function
13 Button interrupt (active low, pull-up)
14 Jack position sensor / encoder
16 Drive encoder
19 Aux sensor 2 (reserved)
21/22 I2C SDA/SCL (400kHz) → TCA9555 I/O expander
25 433MHz RF receiver (RMT input)
26 Solar charger bulk enable (RTC GPIO)
27 Safety sensor (active low)
32/33 External 32.768 kHz RTC crystal (on PCB, not used — see RTC section)
36 (VP) ADC: drive current sense
39 (VN) ADC: battery voltage
34 ADC: jack current sense
35 ADC: aux current sense

TCA9555 (I2C at 0x21):

  • Port 0 (input): 2 physical buttons + 2 additional inputs
  • Port 1 (output): 3× H-bridge relay pairs (DRIVE, JACK, AUX) + LEDs

Motor / Bridge Specs:

  • BRIDGE_DRIVE — 100A max, ACS37220 sense chip (13.2 mV/A, inverted polarity)
  • BRIDGE_JACK — 30A max, ACS37042 sense chip (44 mV/A)
  • BRIDGE_AUX — 30A max, ACS37042 sense chip (44 mV/A)

Software Architecture

app_main()
├── rtc_xtal_init()         Button GPIO setup
├── i2c_init()              TCA9555 init (relays off, LEDs off)
├── adc_init()              ADC1 calibration (12dB attenuation, line-fit)
├── storage_init()          Flash params
├── log_init()              Circular log buffer
├── solar_run_fsm()         (called in main loop too)
├── uart_init()             Serial JSON API task
├── sensors_init()          GPIO ISR setup for sensors/encoders
├── fsm_init()              Control FSM task (priority 10, 20ms tick)
├── rf_433_init()           433MHz RMT receiver task
├── bt_hid_init()           BLE HID host scanner task
└── webserver_init()        WiFi softAP + HTTP + mDNS + DNS

Main loop (50ms):
    i2c_poll_buttons()
    fsm_request() based on button events
    solar_run_fsm()
    drive_leds() status animation
    rtc_check_shutdown_timer() → soft idle on inactivity (180s)

FreeRTOS Tasks:

Task Created by Priority Tick Purpose
app_main (main loop) system 1 (default) 50ms Button polling, LED animation, solar FSM, shutdown timer
control_task fsm_init() 10 20ms FSM state machine, relay control, ADC current monitoring, e-fuse
UART task uart_init() default event-driven Serial JSON command processing
RF 433 task rf_433_init() default event-driven RMT receive + keycode matching
BT HID task bt_hid_init() default event-driven BLE HID host scanning + button mapping
httpd workers webserver_init() default event-driven HTTP request handling (multiple workers spawned by esp_http_server)

Key Files

File Purpose
main.c Entry point, 50ms main loop, factory reset, LED animation
control_fsm.c/h State machine, relay control, current monitoring, calibration
power_mgmt.c/h ADC reading, e-fuse thermal algorithm, battery voltage
sensors.c/h GPIO ISR-based sensor debouncing, encoder counters
i2c.c/h TCA9555 relay/LED/button control
storage.c/h 48-param NVM table + circular binary log buffer
comms.c/h Unified GET/POST JSON API (shared by HTTP and UART)
webserver.c/h WiFi softAP, HTTP server, embedded gzip webpage
uart_comms.c/h Serial JSON interface (115200 8N1)
rf_433.c/h 433MHz OOK receiver, keycode learn/match
bt_hid.c/h BLE HID host, media remote button mapping
rtc.c/h Unix time, harvest alarms, soft idle, inactivity timer
solar.c/h Simple FLOAT/BULK solar charge state machine
sc_err.h Error code definitions
log_test.c/h Flash log unit tests
hard_ui.c Legacy LCD code (unused/obsolete)

Control FSM States

STATE_IDLE
STATE_MOVE_START_DELAY      (1s)
STATE_JACK_UP_START         (detect current spike → jack engaged)
STATE_JACK_UP               (continue until timer/e-fuse)
STATE_DRIVE_START_DELAY     (1s)
STATE_DRIVE                 (encoder-based distance control)
STATE_DRIVE_END_DELAY       (1s)
STATE_JACK_DOWN             (reverse until e-fuse/sensor)
→ back to STATE_IDLE

STATE_UNDO_JACK_START       (emergency: reverse jack, run until e-fuse/sensor)
→ back to STATE_IDLE

CAL_JACK_DELAY / CAL_JACK_MOVE          (jack calibration sequence)
CAL_DRIVE_DELAY / CAL_DRIVE_MOVE        (drive calibration sequence)

Guards before START:

  • Remaining distance > 0 (leash protection)
  • Battery V ≥ LOW_PROTECTION_V (default 10V)
  • Safety sensor active (debounced stable)
  • All e-fuses not tripped

FSM Loop (20ms tick in control_task()):

  1. process_bridge_current() — ADC → EMA → auto-zero → e-fuse
  2. process_battery_voltage() — ADC → EMA
  3. sensors_check() — drain ISR queue, update counters/debounce
  4. State machine transitions (timer + sensor + efuse checks)
  5. drive_relays() — write relay output from current state
  6. send_fsm_log() — 39-byte timestamped entry to flash

E-Fuse Algorithm (power_mgmt.c)

Per bridge, each 20ms tick:

  1. Raw ADC → EMA filter (α = ADC_ALPHA_ISENS)
  2. Auto-zero: learn zero offset when motor is off + grace period expired
  3. Grace period: 250ms after relay closes (ignores startup inrush)
  4. Instant trip: I ≥ EFUSE_KINST × I_nom (default 2×)
  5. Thermal trip: heat accumulates as I²·Δt; dissipates at τ_cool rate
  6. Auto-reset: after EFUSE_TCOOL seconds of cooling (default 5s)

Safety Sensor Debouncing (Asymmetric)

LOW (safe):   1000ms make time  → slow to declare safe (SAFETY_MAKE_US)
HIGH (break): 300ms break time  → fast to kill operation (SAFETY_BREAK_US)

Safety break → immediate STATE_UNDO_JACK_START.


Communication Interfaces

WiFi (softAP)

  • SSID/password/channel configurable via params (WIFI_SSID, WIFI_PASS, WIFI_CHANNEL)
  • mDNS hostname: sc.local
  • Captive portal DNS: all queries → 192.168.4.1
  • HTTP port 80

HTTP API (port 80)

Endpoint Method Description
/ GET Embedded gzip HTML webpage
/get GET JSON system status
/post POST JSON commands + parameter updates
/log GET Binary log download (4B JSON len + JSON + 8B offsets + log data)
/ota POST Firmware update upload

UART (115200 8N1)

  • GET → same as HTTP GET /get
  • POST: {json} → same as HTTP POST /post
  • RTCDEBUG → dump RTC timekeeping state (time, backup, sleep entry, clock source)
  • HELP → command reference
  • Shares comms_handle_get() / comms_handle_post() with HTTP

433MHz RF (GPIO25, RMT)

  • 24-bit OOK codes (P_HIGH≈1040µs, P_LOW≈340µs, margin 70µs)
  • 8 stored keycodes → FSM_OVERRIDE_* commands
  • Learn mode: capture next RX → temp buffer → user commits via web

Bluetooth HID Host

  • Scans for BLE HID devices (service UUID 0x1812)
  • Tries saved BDA first, then scans for best RSSI
  • Button mapping:
    • VOL_UP → Jack Up (override pulse)
    • VOL_DOWN → Jack Down
    • PREV → Drive Reverse
    • NEXT → Drive Forward

Storage Layout

Flash partition "storage":

0x0000  0x0FFF   Parameters (4 sectors, CRC32-protected, 48 params)
0x1000  end      Circular log buffer (head/tail tracked)

Log entry format (39 bytes typical):

[0:8]   Timestamp ms (u64 BE)
[8:12]  Battery voltage (f32)
[12:16] Drive current (f32)
[16:20] Jack current (f32)
[20:24] Aux current (f32)
[24:26] Drive encoder count (i16)
[26]    Sensor states (packed)
[27:31] Drive heat (f32)
[31:35] Jack heat (f32)
[35:39] Aux heat (f32)

Key Parameters:

  • Motion: DRIVE_DIST, JACK_DIST, DRIVE_KT, JACK_KT, DRIVE_KE
  • E-fuse: EFUSE_INOM_1/2/3, EFUSE_HEAT_THRESH, EFUSE_KINST, EFUSE_TCOOL
  • Safety: SAFETY_BREAK_US, SAFETY_MAKE_US, LOW_PROTECTION_V
  • RF: KEYCODE_0KEYCODE_7
  • WiFi: WIFI_SSID, WIFI_PASS, WIFI_CHANNEL
  • Schedule: NUM_MOVES, MOVE_START, MOVE_END (seconds-since-midnight)

RTC & Timekeeping

Time source: esp_timer (40 MHz APB crystal, ~20 ppm accuracy). The external 32.768 kHz crystal on GPIO32/33 is present on the PCB but not used — deep sleep is disabled (soft idle instead), so RTC slow clock accuracy is irrelevant. The RTC slow clock uses the default internal RC oscillator.

rtc_xtal_init() in rtc.c: Configures the button GPIO (GPIO13); no crystal bootstrap or sleep wakeup sources.

Time persistence across resets: sync_unix_us and sync_rtc_us are RTC_DATA_ATTR (survive software resets — panics, WDT). On restart, rtc_restore_time() recovers time via the RTC hardware counter (which runs in the RTC domain and survives resets). RC oscillator drift (~±5%) is negligible over a <30s crash restart (~1.5s worst case).

Diagnosing time issues: Run RTCDEBUG over UART. Reports current time, sync time, elapsed since sync, next alarm, uptime, and soft idle state.


Power Management

  • Battery voltage: GPIO39, divider → V = raw × V_SENS_K + V_SENS_OFFSET (defaults: K=0.00766̄, offset=0.4)
  • Solar charger: GPIO26 (RTC hold) — FLOAT/BULK FSM, bulk for 20s when V < 5V for 5s
  • Inactivity shutdown: 180s → soft idle (WiFi/BT off, LEDs off — not deep sleep). Button press exits soft idle.
  • RTC_DATA_ATTR: Sync timestamps, alarm times, charge state — survive software resets (panics, WDT)

Error Codes (sc_err.h)

SC_ERR_EFUSE_TRIP_1  = 0x201  // Drive overcurrent/overheat
SC_ERR_EFUSE_TRIP_2  = 0x202  // Jack
SC_ERR_EFUSE_TRIP_3  = 0x203  // Aux
SC_ERR_SAFETY_TRIP   = 0x210  // Safety sensor break
SC_ERR_LEASH_HIT     = 0x211  // Distance limit reached
SC_ERR_RTC_NOT_SET   = 0x220  // Clock not synchronized
SC_ERR_LOW_BATTERY   = 0x230  // Voltage below threshold

Build System

  • Framework: ESP-IDF (>=5.0)
  • Component deps (main/idf_component.yml): espressif/mdns ~1.9.1
  • IDF requires: driver, esp_http_server, esp_netif, lwip, json, esp_timer, esp_adc, app_update, esp_wifi, nvs_flash, mdns, bt, esp_hid
  • Webpage: webpage.htmlwebpage_compile.pywebpage_gzip.h (embedded gzip binary). Must re-run webpage_compile.py after any HTML edit before building.
  • Version: version.h.in filled by CMake from git tags → FIRMWARE_VERSION, BUILD_DATE
  • Factory reset: Hold GPIO13 button on cold boot → full parameter + log erase
Description
No description provided
Readme 9.4 MiB
Languages
C 80.2%
Python 10.3%
HTML 8.5%
CMake 0.6%
Makefile 0.3%