|
ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
|
Per-device e-paper panel calibration (VCOM) – record, storage seam, resolution policy. More...
Go to the source code of this file.
Data Structures | |
| struct | ra8_epd_cal_limits_mv_t |
| The panel's documented VCOM window, in millivolt magnitudes. More... | |
| struct | ra8_epd_cal_record_t |
| Decoded per-device calibration record. More... | |
| struct | ra8_epd_cal_store_t |
| Injected non-volatile store for the calibration record. More... | |
| struct | ra8_epd_cal_panel_ops_t |
| Injected controller access for VCOM. More... | |
| struct | ra8_epd_cal_cfg_t |
| Everything ra8_epd_cal_resolve needs. More... | |
| struct | ra8_epd_cal_result_t |
| Outcome of ra8_epd_cal_resolve. More... | |
Typedefs | |
| typedef ra8_err_t(* | ra8_epd_cal_nv_read_fn_t) (void *ctx, uint8_t *dst, size_t len) |
| Read the serialised record out of non-volatile storage (DI seam). | |
| typedef ra8_err_t(* | ra8_epd_cal_nv_write_fn_t) (void *ctx, const uint8_t *src, size_t len) |
| Durably store the serialised record (DI seam). | |
| typedef ra8_err_t(* | ra8_epd_cal_panel_get_fn_t) (void *ctx, uint16_t *out_mv) |
| Read the controller's current VCOM, in millivolts (DI seam). | |
| typedef ra8_err_t(* | ra8_epd_cal_panel_set_fn_t) (void *ctx, uint16_t mv) |
| Programme the controller's VCOM, in millivolts (DI seam). | |
Functions | |
| bool | ra8_epd_cal_vcom_in_range (uint16_t mv, const ra8_epd_cal_limits_mv_t *limits) |
| Range-check a candidate VCOM against a panel's documented window. | |
| ra8_err_t | ra8_epd_cal_serialize (const ra8_epd_cal_record_t *rec, uint8_t *dst, size_t dst_size) |
| Serialise a record into its 32-byte on-flash form. | |
| ra8_err_t | ra8_epd_cal_deserialize (const uint8_t *src, size_t src_size, ra8_epd_cal_record_t *out_rec) |
| Parse and integrity-check a serialised record. | |
| ra8_err_t | ra8_epd_cal_resolve (const ra8_epd_cal_cfg_t *cfg, ra8_epd_cal_result_t *out_result) |
| Resolve the VCOM to drive this panel at, or fail safe. | |
| ra8_err_t | ra8_epd_cal_apply (const ra8_epd_cal_cfg_t *cfg, const ra8_epd_cal_result_t *result) |
| Programme a resolved VCOM onto the controller and confirm it took. | |
| ra8_err_t | ra8_epd_cal_provision (const ra8_epd_cal_cfg_t *cfg, uint16_t vcom_mv) |
| Write a per-device calibration record to non-volatile storage. | |
Per-device e-paper panel calibration (VCOM) – record, storage seam, resolution policy.
Every electrophoretic panel is manufactured with its own VCOM – the common-electrode bias that centres the drive waveform on that particular sheet of film. The correct value is measured at the factory and printed on that panel's flex cable (e.g. -1.53V). It is per-unit data, in exactly the same sense as a board's MAC address or a sensor's trim constants.
Driving a panel at the wrong VCOM is not a cosmetic problem:
That makes VCOM the one value in this system where a plausible-looking wrong number is worse than no number at all, and it drives every design decision below.
A hardcoded VCOM is correct for exactly the one panel the author had on the bench. Anyone who clones this firmware onto a panel calibrated differently gets a wrong value and quietly damages their hardware – and so does the original owner the day they replace a cracked panel. There is therefore no default VCOM anywhere in this tree, and no code path that invents one.
ra8_epd_cal_resolve consults sources in descending order of authority and stops at the first that yields an in-range value:
If none of those produces a trusted value, resolution fails (k_ra8_err_not_found, source == k_ra8_epd_cal_src_none) and the caller must refuse to drive the panel. A blank screen is a support call; a DC-biased panel is landfill.
Resolving a value is necessary but not sufficient: ra8_epd_cal_apply writes it and then reads it back and compares, and the e-paper driver refuses every display command until a VCOM has been echoed back unchanged. A link that accepts writes and changes nothing is the failure mode that otherwise looks exactly like success.
-DRA8_BENCH_VCOM_MV=<mv> adds a fourth, lowest-authority source so first light on a bench panel is not blocked behind provisioned storage. It is off unless typed on the compiler command line, it is consulted only after all three real sources decline (so it can never override a genuine calibration), it is range-checked like everything else, it logs loudly on every boot that uses it, and it is a compile error when RA8_PRODUCTION_BUILD is defined. That last guard is what keeps it from becoming the shipped hardcoded VCOM this module exists to prevent.
The device carries an A/B-bank DFU bootloader (libs/ra8_dfu) with Root-of-Trust-signed images (ra8_rot_verify_image). Two consequences follow, and they are the whole reason this record has its own integrity check rather than leaning on the image signature:
Because the record is unsigned, its integrity check is a corruption check, not an authenticity one: it detects a torn write or a bit flip, not an attacker with physical write access to data flash. That is the correct threat model here – an attacker who can rewrite data flash can equally attack the panel directly – but it is stated rather than assumed.
* offset size field * ------ ---- ------------------------------------------------ * 0 4 magic = 'E','V','C','M' (ASCII) * 4 1 version = ::k_ra8_epd_cal_schema_version * 5 1 reserved (zeroed) * 6 2 payload_len = bytes of payload (2 for schema 1) * 8 2 vcom_mv, little-endian magnitude in millivolts * 10 18 reserved (zeroed, room for later schema growth) * 28 4 crc32 = IEEE 802.3 polynomial over bytes 0..27 *
payload_len plus version are what make forward migration possible: a future schema appends fields into the reserved span and bumps both, and this reader rejects what it does not understand rather than misreading it.
The blob is 32 bytes so it maps exactly onto one extra-MRAM erase block.
This module includes no HAL header. It reaches the controller and the non-volatile store only through injected function-pointer seams (ra8_epd_cal_panel_ops_t, ra8_epd_cal_store_t) – NASA Power-of-10 Rule 9 deviation, documented in CLAUDE.md. Production binds ra8_epaper_get_vcom / ra8_epaper_set_vcom and an extra-MRAM store; host tests bind mocks and drive every branch.
Definition in file ra8_epd_cal.h.
| typedef ra8_err_t(* ra8_epd_cal_nv_read_fn_t) (void *ctx, uint8_t *dst, size_t len) |
Read the serialised record out of non-volatile storage (DI seam).
Production reads k_ra8_epd_cal_blob_size bytes from extra-MRAM at k_ra8_flash_extra_start + k_ra8_epd_cal_extra_mram_offset; host tests read a RAM fixture. Returning any non-k_ra8_ok code makes the resolver treat the record source as absent and fall through – it never fabricates a value.
| [in] | ctx | Implementation context (may be NULL). |
| [out] | dst | Destination buffer; non-NULL, at least len bytes. |
| [in] | len | Bytes to read; always k_ra8_epd_cal_blob_size. |
| k_ra8_ok | dst holds len bytes. |
| k_ra8_err_null_ptr | dst was NULL. |
| other | Backing-store fault; treated as "no record". |
Definition at line 334 of file ra8_epd_cal.h.
| typedef ra8_err_t(* ra8_epd_cal_nv_write_fn_t) (void *ctx, const uint8_t *src, size_t len) |
Durably store the serialised record (DI seam).
Called only from the explicit provisioning path (ra8_epd_cal_provision), never from the boot-time read path – a boot must not silently rewrite calibration.
| [in] | ctx | Implementation context (may be NULL). |
| [in] | src | Serialised record; non-NULL, len bytes. |
| [in] | len | Bytes to write; always k_ra8_epd_cal_blob_size. |
| k_ra8_ok | Record committed durably. |
| k_ra8_err_null_ptr | src was NULL. |
| other | Program fault; the caller must not assume the record persisted. |
Definition at line 358 of file ra8_epd_cal.h.
| typedef ra8_err_t(* ra8_epd_cal_panel_get_fn_t) (void *ctx, uint16_t *out_mv) |
Read the controller's current VCOM, in millivolts (DI seam).
Production binds ra8_epaper_get_vcom.
| [in] | ctx | Implementation context (may be NULL). |
| [out] | out_mv | Receives the VCOM magnitude; non-NULL. |
| k_ra8_ok | *out_mv holds the controller's VCOM. |
| k_ra8_err_null_ptr | out_mv was NULL. |
| other | Bus fault; treated as "no value". |
Definition at line 404 of file ra8_epd_cal.h.
| typedef ra8_err_t(* ra8_epd_cal_panel_set_fn_t) (void *ctx, uint16_t mv) |
Programme the controller's VCOM, in millivolts (DI seam).
Production binds ra8_epaper_set_vcom.
| [in] | ctx | Implementation context (may be NULL). |
| [in] | mv | VCOM magnitude to programme; already range-checked. |
| k_ra8_ok | Controller accepted the new VCOM. |
| other | Bus fault; the previous VCOM remains in effect. |
Definition at line 422 of file ra8_epd_cal.h.
| enum ra8_epd_cal_layout_t : uint8_t |
Byte offsets inside the serialised record.
Definition at line 169 of file ra8_epd_cal.h.
| enum ra8_epd_cal_limits_t : uint16_t |
Sizing and schema constants for the calibration record.
| Enumerator | |
|---|---|
| k_ra8_epd_cal_blob_size | Serialised record size in bytes. |
| k_ra8_epd_cal_schema_version | Current on-flash schema version. |
| k_ra8_epd_cal_payload_len | Schema-1 payload bytes (vcom_mv). |
Definition at line 159 of file ra8_epd_cal.h.
| enum ra8_epd_cal_magic_t : uint8_t |
Bytes of the record magic 'EVCM' (E-paper VCOM).
| Enumerator | |
|---|---|
| k_ra8_epd_cal_magic_b0 | 'E' |
| k_ra8_epd_cal_magic_b1 | 'V' |
| k_ra8_epd_cal_magic_b2 | 'C' |
| k_ra8_epd_cal_magic_b3 | 'M' |
Definition at line 183 of file ra8_epd_cal.h.
| enum ra8_epd_cal_source_t : uint8_t |
Which authority supplied the resolved VCOM.
Reported by ra8_epd_cal_resolve so the caller can log provenance and so a caller that requires provisioned-on-this-device calibration can reject a merely controller-reported value.
Definition at line 248 of file ra8_epd_cal.h.
| enum ra8_epd_cal_storage_t : uint32_t |
Placement of the record in non-volatile storage.
The offset is relative to k_ra8_flash_extra_start (the extra-MRAM option-setting window base, 0x02E07600 – HUM Ch 59.7.4.5 Table 59.15 p 3592). No DFU path erases extra-MRAM, so the record survives an A/B firmware update and a rollback alike.
Block 0 of the window is already claimed by the DFU anti-rollback counter and block 1 is left spare for it to grow into, which makes 0x40 look like the natural next slot. It is not available: the general per-device configuration record specified alongside this module reserves 0x40 .. 0x1BF for its two sequence-numbered 192-byte copies. Landing this blob at 0x40 would have put a VCOM record exactly on top of copy 0 of that record – two writers, one address, discovered on the first provisioned unit rather than in review. This blob therefore starts after that reservation, at 0x200.
The per-device record is the intended long-term home for VCOM: one record, one CRC, one signature, all the per-unit bytes together. When it lands, the production binding of ra8_epd_cal_store_t becomes a reader over that record and this standalone blob is retired – the resolution policy in this module, which is the part that matters, does not change, because the store is an injected seam and callers of ra8_epd_cal_resolve cannot tell which backing answered. That substitutability is the whole reason the seam exists.
| Enumerator | |
|---|---|
| k_ra8_epd_cal_extra_mram_offset | Byte offset into extra-MRAM. |
Definition at line 233 of file ra8_epd_cal.h.
|
nodiscard |
Programme a resolved VCOM onto the controller and confirm it took.
Re-checks the value against cfg->limits before writing it, writes it through cfg->panel.set, then reads it straight back through cfg->panel.get and requires an exact match. The pre-write re-check is deliberate belt-and-braces – result is caller-owned memory between resolve and here, and this is the last point before a number reaches the panel.
The readback is the load-bearing part. A dead link, a controller that ignores the command, or a bus stuck returning zeroes all accept the write and change nothing; without the compare that is indistinguishable from success, and the film would sit biased at the controller's own unknown value. The readback is compared for equality and deliberately not re-range-checked – the written value was range-checked a few lines earlier, so an equal readback is in range by construction and a second test would be unreachable code wearing a safety check's clothes. Range validation of a value the controller reports belongs where such a value is adopted rather than confirmed, which is the resolve-from- controller source, and it is applied there.
A cfg with no panel.get binding is rejected rather than write-and-hope: an unconfirmable bias is not one to leave on a panel.
| [in] | cfg | Seams and limits; non-NULL, with both panel.set and panel.get bound. |
| [in] | result | A successful ra8_epd_cal_resolve outcome; non-NULL. |
| k_ra8_ok | Controller accepted the VCOM and echoed it back unchanged. |
| k_ra8_err_null_ptr | cfg or result is NULL. |
| k_ra8_err_invalid_state | result->source is none. |
| k_ra8_err_not_supported | cfg->panel.set or cfg->panel.get is not bound. |
| k_ra8_err_range_check_failed | result->vcom_mv is out of range. |
| k_ra8_err_hw_error | The readback transaction failed. |
| k_ra8_err_validation_failed | The controller echoed a different value. Do not drive the panel. |
| other | Forwarded from cfg->panel.set. |
Definition at line 507 of file ra8_epd_cal.c.
References ra8_epd_cal_panel_ops_t::ctx, ra8_epd_cal_panel_ops_t::get, k_ra8_epd_cal_src_none, k_ra8_err_hw_error, k_ra8_err_invalid_state, k_ra8_err_not_supported, k_ra8_err_range_check_failed, k_ra8_err_validation_failed, k_ra8_ok, ra8_epd_cal_cfg_t::limits, ra8_epd_cal_cfg_t::panel, RA8_CHECK_NULL_PTR, ra8_epd_cal_vcom_in_range(), ra8_log_error, s_tag, ra8_epd_cal_panel_ops_t::set, ra8_epd_cal_result_t::source, and ra8_epd_cal_result_t::vcom_mv.
Referenced by ep_calibrate_vcom().
|
nodiscard |
Parse and integrity-check a serialised record.
Validation order is magic, then schema version, then payload length, then CRC-32. Each rejection is distinct so a caller can tell "never provisioned" (blank flash fails the magic check) from "provisioned but corrupted" (magic passes, CRC fails) – the two demand different operator responses.
A record whose version exceeds k_ra8_epd_cal_schema_version is rejected rather than best-effort parsed: a newer writer may have redefined the payload, and misreading calibration is exactly the failure this module exists to prevent.
| [in] | src | Serialised record; non-NULL. |
| [in] | src_size | Bytes available at src; must be >= k_ra8_epd_cal_blob_size. |
| [out] | out_rec | Receives the decoded record; non-NULL. |
| k_ra8_ok | Record valid; *out_rec populated. |
| k_ra8_err_null_ptr | src or out_rec is NULL. |
| k_ra8_err_invalid_size | src_size too small. |
| k_ra8_err_not_found | Magic absent – never provisioned. |
| k_ra8_err_not_supported | Schema version newer than this build. |
| k_ra8_err_validation_failed | Payload length wrong for the schema. |
| k_ra8_err_crc_mismatch | CRC trailer does not match the body. |
Definition at line 348 of file ra8_epd_cal.c.
References internal_ra8_epd_cal_crc32(), internal_ra8_epd_cal_magic_ok(), internal_ra8_epd_cal_unpack_le16(), internal_ra8_epd_cal_unpack_le32(), k_ra8_epd_cal_blob_size, k_ra8_epd_cal_off_crc32, k_ra8_epd_cal_off_payload_len, k_ra8_epd_cal_off_vcom_mv, k_ra8_epd_cal_off_version, k_ra8_epd_cal_payload_len, k_ra8_epd_cal_schema_version, k_ra8_err_crc_mismatch, k_ra8_err_invalid_size, k_ra8_err_not_found, k_ra8_err_not_supported, k_ra8_err_validation_failed, k_ra8_ok, RA8_CHECK_NULL_PTR, ra8_log_error, s_tag, ra8_epd_cal_record_t::schema_version, and ra8_epd_cal_record_t::vcom_mv.
Referenced by internal_ra8_epd_cal_read_record().
|
nodiscard |
Write a per-device calibration record to non-volatile storage.
The explicit provisioning path: range-checks vcom_mv, serialises a sealed record and commits it through cfg->store.write. Called by a provisioning app or a service menu after the operator enters the value printed on the panel's flex cable – never automatically from a boot path, so calibration cannot drift without someone asking for it.
| [in] | cfg | Seams and limits; non-NULL. |
| [in] | vcom_mv | VCOM magnitude to persist, in millivolts. |
| k_ra8_ok | Record committed durably. |
| k_ra8_err_null_ptr | cfg is NULL. |
| k_ra8_err_not_supported | cfg->store.write is not bound. |
| k_ra8_err_range_check_failed | vcom_mv outside cfg->limits. |
| other | Forwarded from cfg->store.write. |
Definition at line 554 of file ra8_epd_cal.c.
References ra8_epd_cal_store_t::ctx, k_ra8_epd_cal_blob_size, k_ra8_epd_cal_schema_version, k_ra8_err_not_supported, k_ra8_err_range_check_failed, k_ra8_ok, ra8_epd_cal_cfg_t::limits, RA8_CHECK_NULL_PTR, ra8_epd_cal_serialize(), ra8_epd_cal_vcom_in_range(), ra8_log_error, s_tag, ra8_epd_cal_cfg_t::store, and ra8_epd_cal_store_t::write.
|
nodiscard |
Resolve the VCOM to drive this panel at, or fail safe.
Walks the sources documented at the top of this file – controller, then per-device record, then operator-provisioned value – and returns the first that yields a value inside cfg->limits. Every source is optional: a NULL seam or a failing read simply moves to the next.
This function never invents a value. When no source yields an in-range number it returns k_ra8_err_not_found with out_result->source == k_ra8_epd_cal_src_none and vcom_mv == 0, and the caller must not drive the panel. That is the whole point: an unpowered panel is recoverable, a panel driven at a guessed bias is not.
| [in] | cfg | Seams, limits and any provisioning value; non-NULL. |
| [out] | out_result | Receives the resolved value and its provenance; non-NULL. |
| k_ra8_ok | A trusted VCOM was resolved. |
| k_ra8_err_null_ptr | cfg or out_result is NULL. |
| k_ra8_err_invalid_arg | cfg->limits is self-inconsistent (zero or inverted window). |
| k_ra8_err_not_found | No source produced an in-range value – refuse to drive the panel. |
Definition at line 464 of file ra8_epd_cal.c.
References ra8_epd_cal_cfg_t::has_provisioned, internal_ra8_epd_cal_try_panel(), internal_ra8_epd_cal_try_record(), k_ra8_epd_cal_src_bench, k_ra8_epd_cal_src_none, k_ra8_epd_cal_src_provisioned, k_ra8_err_invalid_arg, k_ra8_err_not_found, k_ra8_ok, ra8_epd_cal_cfg_t::limits, ra8_epd_cal_limits_mv_t::max_mv, ra8_epd_cal_limits_mv_t::min_mv, ra8_epd_cal_cfg_t::provisioned_mv, RA8_CHECK_NULL_PTR, ra8_epd_cal_vcom_in_range(), ra8_log_error, ra8_log_warn, s_tag, ra8_epd_cal_result_t::source, and ra8_epd_cal_result_t::vcom_mv.
Referenced by ep_calibrate_vcom().
|
nodiscard |
Serialise a record into its 32-byte on-flash form.
Writes magic, schema version, payload length and the VCOM field, zeroes every reserved byte, then appends the CRC-32 (IEEE 802.3) of everything preceding the trailer. Reserved bytes are zeroed rather than left undefined so the CRC is reproducible for a given record.
| [in] | rec | Record to serialise; non-NULL. |
| [out] | dst | Destination buffer; non-NULL. |
| [in] | dst_size | Capacity of dst; must be >= k_ra8_epd_cal_blob_size. |
| k_ra8_ok | dst holds a sealed record. |
| k_ra8_err_null_ptr | rec or dst is NULL. |
| k_ra8_err_invalid_size | dst_size too small. |
| k_ra8_err_invalid_arg | rec->vcom_mv is zero. |
Definition at line 318 of file ra8_epd_cal.c.
References internal_ra8_epd_cal_crc32(), internal_ra8_epd_cal_pack_le16(), internal_ra8_epd_cal_pack_le32(), k_ra8_epd_cal_blob_size, k_ra8_epd_cal_magic_b0, k_ra8_epd_cal_magic_b1, k_ra8_epd_cal_magic_b2, k_ra8_epd_cal_magic_b3, k_ra8_epd_cal_off_b0, k_ra8_epd_cal_off_b1, k_ra8_epd_cal_off_b2, k_ra8_epd_cal_off_b3, k_ra8_epd_cal_off_crc32, k_ra8_epd_cal_off_magic, k_ra8_epd_cal_off_payload_len, k_ra8_epd_cal_off_vcom_mv, k_ra8_epd_cal_off_version, k_ra8_epd_cal_payload_len, k_ra8_epd_cal_schema_version, k_ra8_err_invalid_arg, k_ra8_err_invalid_size, k_ra8_ok, RA8_CHECK_NULL_PTR, ra8_log_error, s_tag, and ra8_epd_cal_record_t::vcom_mv.
Referenced by ra8_epd_cal_provision().
|
nodiscard |
Range-check a candidate VCOM against a panel's documented window.
The single decision every source's value passes through. Rejects the two failure signatures that matter in practice – 0 from a controller that has not finished booting, and 0xFFFF from blank flash or a failed read – by requiring a non-zero min_mv in the limits.
| [in] | mv | Candidate VCOM magnitude in millivolts. |
| [in] | limits | Panel's documented window; non-NULL. |
| true | limits->min_mv <= mv <= limits->max_mv. |
| false | mv is outside the window, or limits is NULL. |
Definition at line 306 of file ra8_epd_cal.c.
References ra8_epd_cal_limits_mv_t::max_mv.
Referenced by internal_ra8_epd_cal_try_panel(), internal_ra8_epd_cal_try_record(), ra8_epd_cal_apply(), ra8_epd_cal_provision(), and ra8_epd_cal_resolve().