|
rcbench
Servo and ESC test bench: panel, IOmcu and shared C
|
The output configuration, kept in the IOmcu's own flash. More...
Go to the source code of this file.
Macros | |
| #define | OUT_STORE_SETTLE_MS 400u |
| How long after the last write the save is taken. | |
| #define | OUT_STORE_QUIET_MS 5u |
| How long the bus is quiet before a window is opened in it. | |
| #define | OUT_STORE_SECTORS 2u |
| How many sectors the store spans, so a caller reclaiming at boot can bound its loop by the store's shape rather than by the flash answering. | |
| #define | OUT_STORE_GAP_WAIT_MS 1000u |
| How long a settled save waits for a gap before taking one anyway. | |
| #define | OUT_STORE_LOCKOUT_MS 10u |
| How long a window waits for core 1 to stop, and to start again after. | |
| #define | OUT_STORE_REFUSED_WAIT_MS 1000u |
| After a window core 1 did not stop for, how long before the next is tried. | |
Enumerations | |
| enum | out_store_step_t { OUT_STORE_IDLE = 0 , OUT_STORE_ERASED , OUT_STORE_WROTE , OUT_STORE_REFUSED } |
| What a pass of out_store_tick() did. More... | |
Functions | |
| bool | out_store_load (out_store_t *out) |
| Read what was saved. | |
| void | out_store_save (const out_store_t *cfg, uint32_t now_ms) |
Ask for cfg to be written. | |
| void | out_store_core1_parkable (void) |
| Core 1 registered for the flash lock-out: every window from now on through flash_safe_execute(), which parks it in RAM. | |
| void | out_store_off (void) |
| Switch the store's writing off for this boot: core 1 was launched and did not register for the flash lock-out. | |
| bool | out_store_is_off (void) |
| Whether out_store_off() has been called. | |
| out_store_step_t | out_store_tick (bool driving, uint32_t quiet_ms, uint32_t now_ms) |
| Take a deferred save if it is safe to. | |
| bool | out_store_reclaim (bool driving, uint32_t quiet_ms) |
| Erase a sector that holds nothing still wanted. | |
| bool | out_store_pending (void) |
| Whether a save has been asked for and not yet reached flash. | |
| uint32_t | out_store_last_erase_us (void) |
| How long the last sector erase held interrupts off, in microseconds. | |
| uint32_t | out_store_last_program_us (void) |
| How long the last page program held interrupts off, in microseconds. | |
| uint8_t | out_store_last_record (void) |
| Which of the 16 record slots the store last used (of the 32 one-page slots before version 8 while the boot read a record of that layout). | |
| bool | out_store_window_open (void) |
| Whether a window may open now: the store is not off, no save is waiting, and core 1 did not refuse the last one. | |
| bool | out_store_flash_erase (uint32_t off) |
Erase the 4KiB sector at flash offset off, below the store. | |
| bool | out_store_flash_program (uint32_t off, const uint8_t *data, uint32_t len) |
Program len bytes, whole 256-byte pages up to one sector, at flash offset off, below the store. | |
| bool | out_store_flash_call (void(*fn)(void *), void *arg) |
Run fn inside a window: the boot ROM's BUY, which rewrites flash. | |
The output configuration, kept in the IOmcu's own flash.
It lives here rather than on the panel because a binding describes wiring, and this is the board the wires are in. A panel that remembered one would reapply it to whatever is on the bench now; an IOmcu that remembers one reapplies it to the leads still plugged into it.
Restoring at boot configures the slots. It does not drive them: every driver is gated by outputs_driving(), which is the bank's armed flag, and main.c sets that flag only while the ARM register is set, the link is out of failsafe and the heartbeat is trusted, so a restored binding claims its pins and holds them at idle until somebody arms.
What a write costs. The flash cannot be read while it is written and this core executes from it, so a write runs with interrupts off and this end answers nothing for its length. On the bring-up module an erase and program together measured 19,178us: a CAN (Controller Area Network) frame at 1Mbit/s is about 130us and the XL2515 holds two of them, so a request arriving in that window is lost with nothing wrong on the wire. That measurement is the two operations inside one window; neither half of it was timed on its own. It is 1 sample on the bring-up module, with the flash part not yet identified; the window is to be argued again against the part's datasheet maximum once the part is known. A lost request costs the panel LINK_HOST_TIMEOUT_MS (1000ms) of waiting, and 1000ms of silence latches this end's LINK_DEV_SILENCE_MS (200ms) failsafe. One lost frame is enough for that, which is what makes the length of these windows a safety number rather than a performance one.
What this store does about it:
The sector is not erased per save. Two sectors hold eight record slots each, a slot two 256-byte flash pages (record version 8; one page a slot and sixteen a sector before it); a save programs the next erased slot, and a sector is erased only once its records are all superseded. Seven saves in eight therefore cost one program of two pages rather than an erase and a program.
The erase is taken before the save that needs it, by out_store_reclaim(): at boot before the CAN controller is started, and otherwise on the first pass after the save that left a sector behind. That pass is milliseconds after the record, not a lull chosen later; what it buys is that an erase and the record it makes room for are never one window, and that the save which finds the sector ready costs a page program alone.
Both wait for a gap in the traffic; see OUT_STORE_QUIET_MS.
The window is not eliminated. A page program is not measured on this part: the erase covers 4,096 bytes and the program 512, and on serial NOR (not-or) flash a page program takes one to two orders of magnitude less than an erase, which puts the two pages somewhere between 400 and 4,000us against the 260us of frames the controller holds. out_store_last_program_us() is what will settle it. Removing the loss rather than shrinking it needs the receive buffers emptied during the window, which means reading the controller over SPI (Serial Peripheral Interface) from a routine held in RAM while the flash is busy; that is not written.
SPDX-License-Identifier: MIT
| #define OUT_STORE_GAP_WAIT_MS 1000u |
How long a settled save waits for a gap before taking one anyway.
A save that waits for ever is a binding an operator set that the next boot does not have. One second is twenty of the panel's poll periods.
| #define OUT_STORE_LOCKOUT_MS 10u |
How long a window waits for core 1 to stop, and to start again after.
Every erase and program runs through flash_safe_execute(): interrupts off on this core, and core 1 – the sensor bus – parked in RAM by the multicore lock-out, because a core executing from flash while it is written faults. Core 1 answers the lock-out between two I2C transactions, each held to 1ms (sense_i2c.h), so it stops within about 1.2ms; 10ms is that with room. The wait for it is spent before the window opens and is not counted in it.
| #define OUT_STORE_QUIET_MS 5u |
How long the bus is quiet before a window is opened in it.
The panel polls every 50ms and each poll is a run of back-to-back transactions, so silence longer than any gap inside a run means the run is over. Five milliseconds is that.
It is a minimum and nothing more. The test says the last frame was 5ms ago; it does not say how much of the gap is left, so a window can open in front of the next run as well as behind the last one. A run that has just ended leaves up to 45ms before the next, which is more than the 19,178us an erase and a program together measured; a 5ms lull inside a run leaves whatever the run has left. Nothing enforces the pattern either – a write from a screen arrives when a finger moves. This narrows the odds rather than removing them.
| enum out_store_step_t |
What a pass of out_store_tick() did.
| bool out_store_flash_call | ( | void(* | fn )(void *), |
| void * | arg ) |
Run fn inside a window: the boot ROM's BUY, which rewrites flash.
True when it ran.
| fn | The function to run inside the window. NULL is refused. |
| arg | Passed to fn. |
fn ran. False when no window may open. | bool out_store_flash_erase | ( | uint32_t | off | ) |
Erase the 4KiB sector at flash offset off, below the store.
True when the erase ran.
| off | Flash offset of the sector, in bytes. An offset that is not a sector boundary, or that is at or past the store, is refused. |
| bool out_store_flash_program | ( | uint32_t | off, |
| const uint8_t * | data, | ||
| uint32_t | len ) |
Program len bytes, whole 256-byte pages up to one sector, at flash offset off, below the store.
True when the program ran.
| off | Flash offset, in bytes. A null data, a len that is not a whole number of pages, or a range past the store is refused. |
| data | The bytes to program. |
| len | Number of bytes, at most one sector. |
| bool out_store_is_off | ( | void | ) |
Whether out_store_off() has been called.
| uint32_t out_store_last_erase_us | ( | void | ) |
How long the last sector erase held interrupts off, in microseconds.
Zero until one has run. The erase on its own is not measured: what the bring-up module printed was 19,174 to 19,186us over eight saves, and each of those windows held an erase and a page program together. This is what separates the two.
| uint32_t out_store_last_program_us | ( | void | ) |
How long the last page program held interrupts off, in microseconds.
Zero until one has run. It is not measured. It is the window every save pays, so it is the number that says whether a save can still cost a frame.
| uint8_t out_store_last_record | ( | void | ) |
Which of the 16 record slots the store last used (of the 32 one-page slots before version 8 while the boot read a record of that layout).
The slot the last save was programmed into. out_store_load() sets it as well, so before the first save of a run it is the slot the boot read the binding from, and it is zero when neither has happened. The console line prints it: it is what shows the store advancing towards its next erase.
| bool out_store_load | ( | out_store_t * | out | ) |
Read what was saved.
Takes the valid record with the highest sequence number across both sectors. A record of an earlier version (out_store_rec.h) is read too, with what it does not carry – the supply's wiring before version 4, the current monitors' set-up before version 5 – disabled. Returns false when no slot holds one – a store never written, one written by a build with a record this one does not read, or one whose records are all torn – and then out is untouched, so the caller keeps the defaults it already had.
| out | Receives the configuration. Untouched when the function returns false. |
| void out_store_off | ( | void | ) |
Switch the store's writing off for this boot: core 1 was launched and did not register for the flash lock-out.
A direct window would fault core 1 executing from flash, and flash_safe_execute() would refuse, or in a build with PICO_FLASH_ASSERT_ON_UNSAFE and asserts on, stop the core. Afterwards no erase or program runs and flash_safe_execute() is never called: out_store_save() takes nothing, so nothing waits for a save that cannot come, out_store_tick() is idle and out_store_reclaim() false. What was loaded stays readable. Nothing switches it on again.
| bool out_store_pending | ( | void | ) |
Whether a save has been asked for and not yet reached flash.
| bool out_store_reclaim | ( | bool | driving, |
| uint32_t | quiet_ms ) |
Erase a sector that holds nothing still wanted.
The window is the same length wherever it is taken; the point is that the caller picks the moment, and that the save which later finds the sector ready costs a page program alone. Returns true on the pass that erased.
A window core 1 did not stop for erases nothing and returns false.
One call erases at most one sector, and the call after it looks again. Nothing to do is the ordinary answer: one save in eight leaves a sector behind. A store whose sectors all hold records this build cannot read – an earlier record version – has one to reclaim per sector, so a caller that wants them all takes them in as many calls.
| driving | True while the bank is driving. |
| quiet_ms | Milliseconds since the last frame on the bus. |
| void out_store_save | ( | const out_store_t * | cfg, |
| uint32_t | now_ms ) |
Ask for cfg to be written.
Returns immediately. Nothing reaches flash until out_store_tick() finds the bank idle, the request settled and the bus quiet, and a request that matches what is already saved is dropped: a record written with the same content would spend a slot to change nothing.
now_ms restarts the settle window. The two pages that describe the outputs are written in two transactions – CHAN_CFG, then OUTPUTS – and saving between them would record a new channel configuration against the slots from before it, which is what would come back at the next boot. Waiting for the writes to stop is what keeps the pair together.
| cfg | The configuration to save. |
| now_ms | The pass's clock in milliseconds. It restarts the settle window. |
| out_store_step_t out_store_tick | ( | bool | driving, |
| uint32_t | quiet_ms, | ||
| uint32_t | now_ms ) |
Take a deferred save if it is safe to.
Call every pass with whether the bank is driving, how long the bus has been quiet, and the clock of the pass. A save that needs an erase first erases on one pass and writes on a later one, so the caller services the CAN controller between the two windows rather than holding both off at once.
| driving | True while the bank is driving. |
| quiet_ms | Milliseconds since the last frame on the bus. |
| now_ms | The pass's clock in milliseconds. |
| bool out_store_window_open | ( | void | ) |
Whether a window may open now: the store is not off, no save is waiting, and core 1 did not refuse the last one.