Feeder components
Power
The feeder runs from its adapter, with three D cells as a backup. This page covers what keeps a feed safe on either supply: how the firmware reads the supply, the battery floor below which it will not feed, what happens after a brownout, and why the chip does not sleep yet. What the feeder does on batteries as a feature (which radios stay up, whether schedules run, a low-battery light) is product policy; see the roadmap.
Status: Experimental. The inputs are observed on our unit. The floor, the brownout handling and the supply reporting are host-tested and simulated; none has run on the pack under load.
Inputs#
- Pack. ADC channel 5, through a divider of about 2.063 (
power::DIVIDER_PER_MILLE). Fresh cells read 2357 mV at the pin with the adapter in (4.85 V on a meter) and 2306 mV without (4.77 V). With no pack the pin reads about 16 mV. - Adapter. PA19, HIGH while the adapter is in. The feeder rides through both transitions without a reset.
- Motor current. ADC channel 4 (jams and sensor faults).
- Chip supply. Internal ADC channel 10, in millivolts with stock's scaling (bring-up probes).
The runtime's power monitor samples every 5 s. In the feeder firmware the feeding owner holds the ADC and samples on each of its 20 ms polls instead. Both publish the same sample (wellfed_hal::power).
The monitor logs PLBAT1 records, in every firmware that does not claim the ADC: start created=<0|1>, open ok=<0|1> once the startup probe has released the ADC, sample pin_mv=<n> adapter=<0|1> for the first sample and each adapter change, error adc if a conversion fails (the monitor then stops), or claimed when the firmware keeps the ADC.
The supply on the wire#
Capabilities::POWER (bit 128) carries the supply as the feeder judges it. An unknown is never passed off as a value.
-
Supply { adapter, battery, below_floor }.adapterisNonewithout a fresh sample.batteryis one of:Absent, no pack;Reading { mv };Implausible { mv }, above 5.5 V, or below 2.5 V on the pack alone, where the board could not be running;Unread, no sample: the monitor has not run, its ADC failed, or the firmware does not sample;Stale { age_ms }, older than three monitor periods.
below_flooris the feeding owner's verdict on the pack's floor,Nonebefore it has judged and in a firmware without an owner. The judging iswellfed_core::supply::classify, against the board'spower::PACK. -
Metric::Supplygoes to subscribers with the bit at each power sample (5 s), beside the version 1Powermetric, which is unchanged. -
EventBody::Supplyrecords the adapter coming or going and the pack crossing its floor, at most one a minute (feeder::supply::EVENT_MS). Changes inside the minute are recorded once it ends, as the supply then stands, so a chattering adapter or a pack hovering at its floor costs one event a minute. A boot's first reading is a baseline, not an event, and a stale sample changes nothing. -
Readiness gains
Blocked::Battery(below), refusedBusy. A session that claimsREADINESSbut notPOWERsees it asBlocked::Starting(Blocked::seen_by), the reason that also refusesBusyand clears by itself. It is not lasting, so it is never aFeedingBlockedevent; theSupplyevent records it instead. -
Statistics. The
Powersection's battery history, least and most take onlyReadingvalues, so an implausible reading never enters them.
Nothing is stored in a new place: the events go through the existing ring in its existing 16-byte records. Bank 0 runs an older feeder that cannot decode a Supply body. It skips such records as a one-event gap, as it does any newer body, so no bank-0 refresh is needed.
The floor#
The pack's floor is wellfed_hal::power::FLOOR, in the battery channel's scaled unit (pin millivolts). It is provisional: no motor has run on the pack yet, and the regulator's dropout and the chip's brownout level are unmeasured on this board. The SDK leaves the brownout thresholds at their reset values and documents them only as codes.
| Limit | Pin mV | Pack | Why |
|---|---|---|---|
floor |
1650 | 3.40 V (1.13 V a cell) | Stock's 0 % point, below which stock stops its outputs on battery (stock firmware) |
clear |
1710 | 3.53 V | Stock's 10 % step: once below, a pack must recover by a step before feeding again |
cutoff |
1550 | 3.20 V | A sag this deep leaves the 3.3 V regulator little headroom, so stop at once rather than risk a brownout mid-drive |
hold_ms |
300 | Fifteen 20 ms polls. The start-up surge lasted one poll (on the adapter); a sag that outlasts the hold is the pack failing under load |
wellfed_core::supply::Floor applies them on every acquisition the owner takes, on the adapter too, so the verdict is current when the adapter is pulled:
- a reading at or above
clearis above the floor; - between
floorandcleara pack keeps its verdict, and a first reading there counts as above; - under
floorforhold_msin a row (a failed conversion counts as under), or undercutoffat all, is below.
Where it acts:
- A running feed on the pack alone (PA19 LOW) stops when the pack is no longer judged above the floor, or its conversion fails. The controller's power fault ends it
Failed,Power, with STOP applied and the result written as any failure. It is never restarted; a client or schedule must ask again. - A new feed on the pack is refused before it reaches the owner.
Feeding::blockedanswersBatterywhileOwnerStatus::supply_lowholds, and readiness reports it. The controller's own start check stays behind it: a feed that lost the race endsFailed,Powerwithout driving, once its prepare window runs out. - On the adapter the floor never refuses or stops a feed.
- An adapter change during a feed stops it (
Failed,Power). The change unsettles power for 100 ms (stock's settle), and a feed does not ride across a change of source. The next feed runs on whichever supply is there.
A pack that sags under load and recovers above clear at rest will feed again, and may fail again. Each feed ends with an explicit result and none is retried, so this costs failed feeds, not motion without a result.
Brownout#
A brownout is a reset caused by the supply dropping too low. Three rules keep it from feeding twice, looping, or rolling back a good build:
- No feed is replayed. A feed a brownout interrupted is resolved as
Uncertainby the next boot's journal recovery, under its own id, and never resumed. - The next feed waits for the adapter. A boot whose cause is brownout holds feeding as
Blocked::Batteryuntil the adapter is seen (feeder::supply::Watch). At rest the pack recovers, so its reading alone would let the next feed start and sag it into the same brownout: a loop of resets, each leaving a feed uncertain. New cells mean a power cycle, a power-on reset, which does not hold. - Brownouts are not a reset storm. Brownouts keep the backup registers, so without a rule KM0 would count a brownout reset like any other unrequested one. A pack weak enough to brown out at idle, on Wi-Fi transmit peaks, could then reach the storm limit of five within ten minutes. A confirmed bank 1 would then give way to bank 0, an older build, for a supply fault rather than a build fault. So KM0 reads
BOOT_Reasonbefore its trial step, and on a brownout boot takes back the count the previous boot left pending (trial::decide_after,PLTRL1 brownout=1 reg=…). The brownout neither counts nor ends a burst. A trial still counts a brownout boot, so no trial is shortened or skipped (OTA design).
The cause comes from the ROM's flags: BOOT_Reason bit 18 is brownout. The statistics report it (ResetCause::Brownout), and the Boot event carries the raw flags.
The bit may be stale. The loader that copies the ROM's flags into BOOT_Reason returns early when it finds none set, so after one real brownout, a reset that sets no flag can read as a brownout too. Two guards follow:
- No two boots in a row are forgiven (
trial::FORGIVEN; bit 31 of backup register 4 marks a forgiven boot, and the register's identity tag is now 7 bits). A fault still gives way within 10 early resets, and halts within 16, however the bit reads. - A later reset with no flag can read as a brownout and hold feeds the same way.
The floor makes a motor-driven brownout unlikely. It cannot prevent an idle one on a pack below the regulator's dropout.
Sleep#
Sleep is deferred, and both cores keep their wakelocks (recovered):
- Both cores hold
PMU_OSfrommain(firstboot_km0.c,firstboot_km4.c), and the feeding owner takes its own PMU wakelock when it opens. KM4's tickless idle only stops the tick while every task sleeps; with the wakelock held it never enters a low-power mode. - KM0's DLPS path is unmediated. On entering sleep, the SDK's tickless code reaches flash users (the flash lock and its clock changes) outside the flash authority, so a sleep entry could touch flash while KM4 holds a lease. Before KM0 may sleep, its flash users must go through the authority, or a KM0 deny provider must refuse them.
- What safe sleep needs. The motor verified at STOP and the emitters off before entry. The runtime's watchdog either stopped or fed across the sleep. The 32 kHz clocks that the owner's timing and the clock rely on kept running. And a wake that resumes no feed: the journal already ensures none is resumed across a reset, but a sleep is not a reset. None of that is built.
- Where the saving is. On batteries, stock shuts Wi-Fi and MQTT 5 s after the adapter leaves and duty-cycles its beams (recovered). The stock inventory does not record whether it enters the chip's low-power modes. Dropping Wi-Fi on the pack is the larger saving, and it is battery-mode policy that needs no chip sleep.
Bench checks#
Physical acceptance needs the cells in, which is the owner's call. The order keeps motion out of the first steps.
- Readings at rest. Fit three fresh D cells, adapter in.
wellfed stats --section powerand a subscriber'sSupplysample should readReadingnear the meter's voltage, withbelow_floor: false. Pull the adapter with relay 1: you should see oneSupplyevent (adapter: false) and readinessReady. Restore it: one more event within a minute. - The floor without motion. Replace the cells with a bench supply at the pack terminals, take the adapter out, and start at 4.5 V. Step it down. Readiness turns
Batterywithin a poll of the pin reading under 1650 for 300 ms (about 3.40 V), and returns toReadyonly at about 3.53 V. Note the voltage at which the board resets, if it does, and whether the next boot's reset cause isBrownout. That is the margin the floor is provisional on. - Under load. This needs an authorization covering motion with the cells or supply in. Run a one-portion feed on fresh cells, with the analyzer on the motor and the supply's voltage logged, to measure the real surge and drive sag. Then repeat at a supply voltage just above the floor. Those figures replace the simulator's zero sags and confirm or move
FLOOR. - Brownout hold. After a brownout reset on the supply, readiness stays
Batteryuntil the adapter returns. - The brownout bit is fresh. After that brownout, with the adapter in, force a watchdog reset with a build or probe that stalls the watchdog, such as the
owner-stallvariant. (wellfed rebootis deliberate and does not count.) The next boot must not logPLTRL1 brownout=1, and itsBootevent's reset flags must not carry bit 18. If it does, the bit is stale on this board: the bounds above still hold, but record it.
Open questions#
- The floor's figures under load, and the board's brownout level.
- Whether a real brownout sets
BOOT_Reason's bit here. The storm breaker's brownout rule and the brownout hold both rest on it. - Sleep, and with it the KM0 flash users on the DLPS path.
- Battery-mode behavior, and a battery percentage on stock's curve.
Evidence#
-
Pack and adapter inputs. Observed (October 23, UART and meter, the
adcprobe, three D cells, no reboot between states):State PA19 Channel 5 mean (raw) Scaled (mV) Pack on meter Adapter, no batteries 1 240 16 — Adapter and batteries 1 2849 2357 4.85 V Batteries only 0 2737 just after the unplug, settling to about 2793 over 20 s 2256 to about 2306 4.77 V Adapter back in 1 2848 2356 — So channel 5 measures the pack, not the adapter, through a divider of about 2.06 (2.058 and 2.068 in the two states). Channel 4 stayed at 240 throughout, and the feeder neither reset nor showed anything visible at either transition. Channel 5 is noisier than the others: about 110 raw between the minimum and maximum of each 16-conversion sample, against 3 or 4 on channels 4 and 10. The meter readings were not time-stamped against particular samples.
-
Health with a pack. Observed (October 23): health read
"adapter":true,"battery_mv":4862with the adapter (meter earlier: 4.85 V), and"adapter":false,"battery_mv":4755with it pulled (meter: 4.77 V). With no pack, the monitor loggedPLBAT1 open ok=1only after the startup probe ended, thensample pin_mv=15 adapter=1. -
Surge. Observed: the start-up surge lasted one 20 ms poll on the adapter.
-
Power cut mid-drive. Observed: an interrupted feed resolves as uncertain at the next boot and is never resumed (motor and feeds).
-
Stock's floor and battery behavior. Recovered from the stock firmware.
-
The floor, supply reporting and brownout rules. Modeled (October 26):
- Core:
wellfed-core/tests/supply.rs(readings, the floor's hold, cutoff, hysteresis and wrap) andtests/sensor_inputs.rs(a surge under the floor tolerated, a long sag or one under the cutoff not, the clear level, the adapter's verdict, a failed conversion). - HAL:
wellfed-hal/tests/power.rs(unread, stale and implausible readings from the monitor's own samples) andwellfed-hal-common/tests/trial_brownout.rs(a brownout bit stuck set still gives way within 10 resets and holds, then halts, on bank 0; never two forgiven in a row; a trial still counts a brownout boot; a brownout in a held, halted or spent-trial boot, and after a settled or deliberate one). - Product:
wellfed-feeder/tests/supply.rs(events and their rate limit, the brownout hold, readiness order); the firmware'stests/manual_feed.rs(a press refused on a low pack, the adapter's loss as aSupplyevent from the board's samples);wellfed-client/tests/api.rs(the battery reason to aPOWERsession, and asStartingto an older one); wire fixtures for every new variant. - Simulation: the real owner on the fake SDK (
wellfed-sim). The plant sags the battery channel bysurge_sag_adcanddrive_sag_adcwhile it drives on the pack, both unmeasured and zero by default. The adapter pulled mid-feed fails it, and nothing drives until a new feed, which completes on the pack. A surge under the floor is tolerated, and a sustained sag or one under the cutoff fails the feed. Under the floor and in the band no feed drives; at the clear level one does. Checked-in scenarios:adapter-loss-mid-feed.json,battery-sag.json.
- Core:
-
The brownout bit on hardware. Unknown: whether a real brownout on this board sets bit 18.