Files
hpsg5-controller_v2-stm32g4/Core/Advance_Control/pwm.h

437 lines
24 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @file pwm.h (families/t06211/compact_src)
* @brief Compact single-header API for the t06211 PWM controller.
*
* Mirrors the public shape of the default family's `compact_src/pwm.h`
* but tailored to t06211's 5-stage pipeline (no standalone max_cl_error
* lookup, single-submap setpoint, factor+offset target compute).
*
* Public API is just two functions:
* pwm_init() — one-time setup
* pwm_service() — per-cycle update (pulls inputs via getters,
* runs the 5-stage pipeline, writes rt outputs)
*
* External inputs arrive through a getter vtable; each callback returns
* one signal in native PWM units.
*
* Every arithmetic choice mirrors the MCS-96 assembly; see
* families/t06211/src/ for per-stage commentary.
*/
#ifndef PWM_H
#define PWM_H
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
/* ── MCS-96 arithmetic primitives ───────────────────────────────────── */
#define MUL_S16(a, b) ((int32_t)(int16_t)(a) * (int32_t)(int16_t)(b))
#define MULU_U16(a, b) ((uint32_t)(uint16_t)(a) * (uint32_t)(uint16_t)(b))
#define CLAMP(v, lo, hi) ((v) < (lo) ? (lo) : (v) > (hi) ? (hi) : (v))
static inline int16_t shra16(int16_t v, unsigned n) {
if (!n) return v;
uint16_t u = (uint16_t)v, sign = (u >> 15) & 1u;
uint16_t m = sign ? (uint16_t)(((uint16_t)~0u) << (16u - n)) : 0u;
return (int16_t)((u >> n) | m);
}
static inline int32_t shra32(int32_t v, unsigned n) {
if (!n) return v;
uint32_t u = (uint32_t)v, sign = (u >> 31) & 1u;
uint32_t m = sign ? (~(uint32_t)0u << (32u - n)) : 0u;
return (int32_t)((u >> n) | m);
}
/* ══════════════════════════════════════════════════════════════════════
* 1D INTERPOLATION SLOT
* Layout matches family-1's pwm_interp_slot_t and the family-1 ROM
* convention [count*2, range, offset, seg_bytes] — t06211's FUN_6fb8
* fingerprints identically, so the same layout applies.
* ══════════════════════════════════════════════════════════════════════ */
typedef struct pwm_interp_slot {
int16_t row_stride; /* count * 2 (byte stride for Y-table rows) */
int16_t x_interval; /* x[k-1] - x[k] */
int16_t x_offset; /* input - x[k] */
int16_t y_byte_off; /* k * 2 */
} pwm_interp_slot_t;
/* ══════════════════════════════════════════════════════════════════════
* EXTERNAL INPUTS
* Populated each cycle by pwm_service via the getter vtable.
* t06211-specific set: no temperature, no angle_dec_cmd in the setpoint
* (the family-2 setpoint is single-submap RPM-indexed).
* ══════════════════════════════════════════════════════════════════════ */
typedef struct pwm_inputs {
int16_t ckp_in; /* 0x02f8 — sensed position */
uint16_t rpm; /* 0x0040 — engine RPM */
int16_t angle_dec_cmd; /* 0x0042 — accepted for family-1 API parity; unused here */
int16_t inj_qty_demand; /* 0x0044 — CAN: inj quantity demand (PI large-neg gate) */
int16_t b_fb_kw; /* CAN: plunger feedback baseline. Gets copied into
can_raw_b_fb_kw and decoded by s_setpoint_can_decode
(port of FUN_64c3) to produce target. */
int16_t cl_gate_input; /* 0x0130 — CAN: open/closed-loop discriminant */
uint16_t supply_voltage; /* 0x0142 — shape_eval submap input */
int16_t temperature; /* 0x0146 — accepted for family-1 API parity; unused here */
} pwm_inputs_t;
/** Getter vtable. 8-callback layout matches family-1's pwm_input_getters_t
* so a single FBKW.c can drive either family's pwm.c without changes.
* t06211 does not consume angle_dec_cmd or temperature; those getters are
* called and stored into rt->inputs but the pipeline ignores them. */
typedef struct pwm_input_getters {
int16_t (*ckp_in) (void *ctx);
uint16_t (*rpm) (void *ctx);
int16_t (*angle_dec_cmd) (void *ctx); /* accepted; unused by t06211 */
int16_t (*inj_qty_demand)(void *ctx);
int16_t (*b_fb_kw) (void *ctx);
int16_t (*cl_gate_input) (void *ctx);
uint16_t (*supply_voltage)(void *ctx);
int16_t (*temperature) (void *ctx); /* accepted; unused by t06211 */
void *ctx;
} pwm_input_getters_t;
typedef struct pwm_calibration pwm_calibration_t;
typedef struct pwm_submap_descr pwm_submap_descr_t;
/* ══════════════════════════════════════════════════════════════════════
* RUNTIME STATE
* RAM-address comments reference the t06211 MCS-96 map (see
* families/t06211/docs/variable-glossary.md).
* ══════════════════════════════════════════════════════════════════════ */
typedef struct pwm_runtime {
/* External inputs — refreshed each cycle from getters */
pwm_inputs_t inputs;
/* Async flags.
* reset_flag — set on reset edge; supervisor clears.
* system_flags_110 — bit 5 forces open-loop; bit 0 is fast-recovery latch.
*/
uint8_t reset_flag; /* 0x002e */
uint8_t system_flags_110; /* 0x0110 */
/* ── Dual-target setpoint architecture ── */
/* target (RW5E = RAM[0x005e]): PRIMARY CAN-driven setpoint
* computed by FUN_64c3 (called from CAN parser FUN_6192). Drives
* the PI controller in both open-loop (FUN_67c4:67e5) and
* closed-loop (FUN_67c4:680a) paths, and supervisor error compute
* (FUN_7beb:7c20). Formula: raw/2 + setpoint_offset + rw42_state,
* clamped >= cal->target_5e_min_clamp. */
int16_t target_5e; /* RW5E — primary CAN-decoded setpoint */
/* pi_high_clamp_ceiling (DAT_0336): SECONDARY RPM-derived ceiling
* computed by FUN_7168(setpoint_descr) + FUN_77b3:77c7. Used ONLY as
* upper-clamp in FUN_672b PI compensation (0x67a9/0x67b0) and
* FUN_7beb supervisor (0x7c30/0x7c37). Does not drive control
* directly. (Was misnamed `target_336` across earlier ports — the
* address-suffixed style hid that this is a PI clamp, not a target.) */
int16_t pi_high_clamp_ceiling;
/* CAN-staging buffers — written externally before pwm_service so
* s_setpoint_can_decode can transform them into target. */
int16_t can_raw_b_fb_kw; /* 0x012c — raw b_fb_kw from CAN */
int16_t can_aux_12e; /* 0x012e — secondary CAN field (drives rw42_state) */
int16_t can_half_12a; /* 0x012a — cached raw>>1 (debug visibility) */
/* setpoint_offset (RAM[0x0150]) is copied at runtime_init from
* cal.setpoint_offset (mirrors family-1's pattern at
* compact_src/pwm.c:66). The REAL ROM derives it at boot via
* FUN_6b7b @ 0x6bb5: RAM[0x150] = cal+0x4c - RW1E. Because RW1E's
* source at the call site (0x6c46) is in a Ghidra-unrecognised code
* region, we expose the field directly through cal so the user can
* set it to the match-fitted value without needing to simulate the
* full FUN_6b7b boot path. */
int16_t setpoint_offset; /* runtime copy of cal->setpoint_offset (DAT_0150 in ROM) */
int16_t rw42_state; /* RW42 register — = can_aux_12e when valid */
/* DAT_033a — supervisor reset stash: snapshots pi_integ_hi at the
* reset edge (FUN_7beb:7c05-7c0a). Dead store (no readers anywhere
* in the ROM); kept here for parity with the disasm. NOT a baseline
* of b_fb_kw — that name was inherited from the family-1 idiom and
* is incorrect for this variant. */
int16_t pi_integ_hi_snapshot;
/* PI P-term — produced by FUN_672b @ 0x6775 as `(p_gain_normal * err) >> 8`
* and consumed by the supervisor at FUN_7beb:0x7c26 as the additive
* shaping term on the error. (DAT_02b8 in ROM; was named
* `compensation_angle` in earlier ports.) */
int16_t pi_p_term;
int16_t angle_error_raw; /* DAT_033e — supervisor output (raw + ceiling-clamped) */
uint16_t cl_enable_counter; /* DAT_033c */
/* ── CL correction ── */
int16_t cl_correction_raw; /* DAT_0176 */
int16_t angle_offset; /* DAT_017c */
int16_t supervisor_state; /* RW17E — CL accumulator */
int16_t pos_error_normalizer; /* runtime copy of cal->init_pos_error_normalizer (DAT_0332 in ROM) */
int16_t neg_error_normalizer; /* runtime copy of cal->init_neg_error_normalizer (DAT_0334 in ROM) */
/* ── Publish + PI ── */
int16_t estimated_angle; /* DAT_02cc */
int16_t angle_error_pi; /* DAT_02be — target_5e estimated_angle */
int16_t active_request; /* RW46 — published PI output / PWM feed-forward */
uint8_t pi_open_loop_flag; /* DAT_02c4 */
uint8_t pi_shape_flag; /* DAT_02c5 */
uint8_t pi_flag_c6; /* DAT_02c6 — previous-cycle pi_shape_flag latch */
uint8_t pi_flag_338; /* DAT_0338 — Block-4 cal-pinned latch */
/* P-shape segment bounds (boot-init from cal+0x10A/0x10C). PI Block-4
* error-window bounds (independent int16s, NOT a 32-bit integrator).
* Trim bytes at RAM[0x0414]/[0x0416] have no writers in the ROM, so
* bounds resolve to the cal bases verbatim. See open-questions §2. */
int16_t p_shape_bound_pos; /* DAT_0450 */
int16_t p_shape_bound_neg; /* DAT_0452 */
int16_t pi_state_118; /* DAT_0118 — recovery counter */
int16_t pi_state_c2; /* DAT_02c2 — cooldown counter */
/* PI integrator pair {hi:lo} at {DAT_02b4 : DAT_02b6}. Disasm
* `disasm_nav rw 0x02b4` shows only internal writers (FUN_672b,
* FUN_76aa, FUN_7c85), confirming this is a PI integrator state,
* not an external/CAN input. (Was misnamed `b_fb_kw` in the original
* glossary; renamed via the address-suffixed `pi_b4_state`/`pi_b6_state`
* intermediate; final semantic name aligns with the cross-family pair.) */
int16_t pi_integ_hi; /* DAT_02b4 — integrator high word */
int16_t pi_integ_lo; /* DAT_02b6 — integrator low word */
/* PI gains — boot-set by FUN_76aa from cal scalars (with optional
* trim bytes at RAM[0x0410]/[0x0412] which have no writers anywhere
* in the ROM, so the cal bases pass through unchanged). */
int16_t p_gain_normal; /* DAT_0454 — boot from cal+0x118; multiplier in pi_p_term = (gain*err)>>8 */
int16_t integ_step_normal; /* DAT_0456 — boot from cal+0x11A; multiplier in FUN_7c85 integrator step */
int16_t open_loop_p_gain; /* DAT_0330 — boot from cal+0x11C byte (clamped ≤15); used in `pi_integ_hi = (gain*err)>>4 + ckp` reset branch */
/* Anti-windup flag set by FUN_672b clamps; consulted by FUN_7c85
* to gate integration direction. Values: 0 (in-range), 1 (clamped
* low), 2 (clamped high). */
uint8_t pi_flag_c7; /* DAT_02c7 */
/* ── PWM output ── */
uint16_t pwm_duty; /* 0x02d2 */
uint16_t pwm_on_time; /* 0x02ce */
uint16_t pwm_off_time; /* 0x02d0 */
/* pwm_period — total period in Timer2 ticks = on_time + off_time.
* Mirrored from t06211 RAM[0x02e4] (computed each cycle by the PWM
* output stage). Exposed for family-1 FBKW.c API parity
* (UpdatePWM(&htim4, ch, pwm_on_time, pwm_period)). */
uint16_t pwm_period;
uint8_t pwm_duty_range_flag; /* 0x00d1 */
pwm_interp_slot_t pwm_slot_a; /* 0x02d4 */
pwm_interp_slot_t pwm_slot_b; /* 0x02dc */
/* pwm_shape_state[6] mirrors RAM[0x02e4..0x02ee]:
* [0] pwm_period working value (RAM 0x02e4)
* [1] (unused — was slew_increment, broken out as field below)
* [2] (RAM 0x02e8 — ROM band-array ptr, unused in C)
* [3] (RAM 0x02ea — ROM halfwidth ptr, unused in C)
* [4] (RAM 0x02ec — slew step magnitude, unused; read from cal)
* [5] shape_height (RAM 0x02ee, E2 output) */
int16_t pwm_shape_state[6]; /* 0x02e4-0x02ee */
/* Persistent slew_increment (RAM[0x02e6]). Carried across cycles so
* the hysteresis-margin HOLD path can preserve previous direction.
* Subtracted from pwm_period each cycle. See open-questions §5. */
int16_t pwm_slew_increment; /* 0x02e6 */
/* ── Bindings (set once by pwm_init) ── */
const pwm_calibration_t *bound_cal;
const pwm_input_getters_t *bound_getters;
} pwm_runtime_t;
/* ── Calibration (decoded ROM values) ───────────────────────────────── */
struct pwm_calibration {
/* Scalars (from families/t06211/cal_offsets.py FLASH_OFFSETS) */
int16_t large_pos_error_thresh; /* cal+0x10E */
int16_t large_neg_error_thresh; /* cal+0x110 */
int16_t pi_low_clamp; /* cal+0x120 = -512 — Block-4 low clamp + open-loop lower bound */
int16_t pi_high_clamp; /* cal+0x124 = +1707 — Block-4 high clamp */
/* CAN-decoded setpoint (FUN_64c3) cal constants */
int16_t b_fb_kw_upper_bound; /* cal+0x004 = +7680 — raw b_fb_kw upper sanity bound */
int16_t b_fb_kw_lower_bound; /* cal+0x006 = -768 — raw b_fb_kw lower sanity bound */
/* setpoint_offset — static bias added after halving raw b_fb_kw.
* Family-1 analog: CAL+0x0052 - CAL+0x0054 (compact_src/pwm.h:210).
* For t06211, FUN_6b7b computes the equivalent as cal+0x4c - cal+0x4e
* (= 3499 - 4156 = -657) at boot and stores at RAM[0x150].
* Copied into runtime.setpoint_offset by runtime_reset. */
int16_t setpoint_offset; /* = cal+0x4c - cal+0x4e */
int16_t target_5e_min_clamp; /* cal+0x122 = -512 — RW5E lower clamp */
int16_t can_aux_12e_max; /* cal+0x002 — upper bound for RAM[0x12e] (FUN_649e) */
/* CKP-zero acquisition (FUN_6b67 chain — caller @ 0x65c7).
* Slot offsets verified at FUN_6b67:0x6b71/0x6b81/0x6b93 + Stage-1
* caller @ 0x654f/0x656d. */
int16_t ckp_zero_anchor; /* cal+0x04E — FUN_6b67 additive anchor */
int16_t can_dckp_offset_bias; /* cal+0x050 — Stage-1/Stage-2 bias */
int16_t ckp_modulus; /* cal+0x0A0 — FUN_6b67 modulo wrap (also
* reused by FUN_6d4a process-tooth
* below; high byte = ckp_modulus>>8 = 30). */
/* CKP process-tooth derivation (FUN_6d4a @ 0x6d4a — analog of T06215
* FUN_7293 / default FUN_87ea). Same body shape; cal slot for the
* per-tick advance is at cal+0x124 (NOT cal+0x12C as in T06215),
* verified as the sole `TABLE[RWA4]` reader at FUN_6d4a:0x6d6a.
* Consumed by get_ckp_process_tooth() in ckp_acquisition.c.
*
* Note: cal+0x124 is **aliased** with `pi_high_clamp` (FUN_67c4
* reads it via the indirect cal-address idiom at 0x68c3/0x68d9).
* The value 1707 happens to serve both roles in this ROM. */
int16_t ckp_advance_per_tick; /* cal+0x124 — angular advance per tick
* (t06211: 1707 = same value as
* pi_high_clamp; alias by coincidence). */
int16_t ckp_seg_wrap_threshold; /* cal+0x09D — tooth-counter test
* (t06211: 29; if tooth > threshold
* → tooth -= 30). */
int16_t ckp_teeth_per_seg; /* cal+0x09E — per-segment clamp
* (t06211: 26 teeth / 90° segment;
* if tooth > teeth_per_seg → 0). */
int16_t error_thresh_114; /* cal+0x114 */
int16_t pi_thresh_116; /* cal+0x116 */
/* pi_state_118 saturation threshold — when the recovery counter
* reaches this, FUN_66a8 latches bit0 of system_flags_110, zeroes
* the counter, and reloads pi_state_c2 from error_thresh_114.
* Boot-cached at RAM[0x02c0] by FUN_6b7b:0x6bd4. ROM literal 0x0320 = 800. */
int16_t pi_sat_count_threshold; /* cal+0x112 */
int16_t rpm_threshold_11E; /* cal+0x11E — RPM gate inside FUN_66a8 */
/* Closed-loop entry RPM floor (FUN_67c4:0x67d9). Sourced from
* RAM[0x605c] (flash mirror; ROM literal 0x01A4 = 420). */
int16_t pi_cl_rpm_floor;
int16_t pwm_detail_x0; /* cal+0x0EE */
int16_t pwm_detail_x1; /* cal+0x0F0 */
int16_t pwm_cached_ptr_0F2; /* cal+0x0F2 — first RPM-window breakpoint (legacy scalar) */
int16_t pwm_cached_ptr_102; /* cal+0x102 — window halfwidth (legacy scalar) */
int16_t pwm_const_104; /* cal+0x104 */
/* RPM-window matching for pwm_period slew (ROM 0x538b-0x5575).
* Eight breakpoints at cal+0xF2 define four bands (lo,hi); the
* cal+0x102 halfwidth adds hysteresis. RPM INSIDE any band drives
* pwm_period toward pwm_period_min (→ non-zero shape contribution
* adds ~49 duty ticks). RPM OUTSIDE all bands drives pwm_period
* toward pwm_period_max (→ zero contribution; Y-table floor). */
int16_t pwm_rpm_windows[8]; /* cal+0xF2 — 4 (lo,hi) pairs */
int16_t pwm_window_halfwidth; /* cal+0x102 — hysteresis halfwidth */
/* Per-cycle slew step magnitude for pwm_period. Sourced from
* cal+0x104 (= 354); cached to RAM[0x02ec] at FUN_5314:0x53b9 and
* read at the Phase 1 and Phase 3 sites. Aliases pwm_const_104 —
* same offset, semantic name. */
int16_t pwm_slew_step; /* cal+0x104 — slew magnitude */
const int16_t *pwm_y_table; /* cal+0x154 (indirect) */
const int16_t *shape_y_table; /* cal+0x15E (indirect) */
int16_t closed_loop_gain_const; /* cached at ROM 0x6056 = 0x000A */
/* PI runtime-reset values — boot-derived in ROM (FUN_76aa @ 0x76aa);
* cal-resident here so each variant carries its own values without
* code edits. Trim bytes at RAM[0x0410-0x0416] have no writers in the
* ROM (EEPROM trim slots, default 0), so each cal base is used
* verbatim — see open-questions §2 closeout. Field shape mirrors
* T06215's pwm_calibration_t for cross-family consistency. */
int16_t init_p_shape_bound_pos; /* cal+0x10A — copied to rt->p_shape_bound_pos */
int16_t init_p_shape_bound_neg; /* cal+0x10C — copied to rt->p_shape_bound_neg */
int16_t init_p_gain_normal; /* cal+0x118 — copied to rt->p_gain_normal */
int16_t init_integ_step_normal; /* cal+0x11A — copied to rt->integ_step_normal */
int16_t init_open_loop_p_gain; /* cal+0x11C byte clamped to 15 — copied to rt->open_loop_p_gain */
int16_t init_pos_error_normalizer;/* cal+0x108 — copied to rt->pos_error_normalizer */
int16_t init_neg_error_normalizer;/* cal+0x106 — copied to rt->neg_error_normalizer */
uint16_t pwm_period_min; /* hypothesised; see cal_tables_rom.c */
uint16_t pwm_period_max; /* hypothesised */
/* PWM duty clamp bounds — RAM[0x6058]/RAM[0x605a] flash mirrors.
* Defaults 0x00CD/0x0F32 match the default family's pwm_min/pwm_max. */
uint16_t pwm_min; /* RAM[0x6058] cache */
uint16_t pwm_max; /* RAM[0x605a] cache */
};
/* ── Submap descriptor ──────────────────────────────────────────────── */
struct pwm_submap_descr {
uint16_t flags; /* +0 */
const int16_t *input_ptr; /* +2 (bound at runtime) */
uint16_t count; /* +4 */
const int16_t *x; /* +6 */
uint16_t input_addr;
};
/** Bind descriptor input_ptr fields to rt inputs. */
static inline void pwm_bind_submap_inputs(
pwm_runtime_t *rt,
pwm_submap_descr_t *descrs,
uint16_t n)
{
for (uint16_t i = 0; i < n; i++) {
switch (descrs[i].input_addr) {
case 0x0040: descrs[i].input_ptr = (const int16_t *)&rt->inputs.rpm; break;
case 0x0044: descrs[i].input_ptr = &rt->inputs.inj_qty_demand; break;
case 0x0046: descrs[i].input_ptr = &rt->active_request; break;
case 0x0142: descrs[i].input_ptr = (const int16_t *)&rt->inputs.supply_voltage; break;
default: descrs[i].input_ptr = NULL; break;
}
}
}
/* ══════════════════════════════════════════════════════════════════════
* PUBLIC API
* ══════════════════════════════════════════════════════════════════════ */
/** pwm_flash_t — placeholder for family-1 API parity. Family-1 has Y-tables
* in a separate `pwm_flash_t` struct; t06211 keeps them inside
* pwm_calibration_t so this struct is empty. Kept so FBKW.c / callers
* can pass &pwm_flash_rom without the call failing. */
typedef struct pwm_flash { char _unused; } pwm_flash_t;
/** 4-argument init matching family-1's pwm_init signature. The `flash`
* pointer is accepted (must be non-NULL — pass &pwm_flash_rom) and
* ignored by the t06211 pipeline. */
void pwm_init(pwm_runtime_t *rt,
const pwm_calibration_t *cal,
const pwm_flash_t *flash,
const pwm_input_getters_t *getters);
void pwm_service(pwm_runtime_t *rt);
/** @brief CKP-edge reset hook — call from the CKP interrupt handler.
*
* Sets rt->reset_flag = 1 so the next pwm_service() call zeroes the
* closed-loop accumulator (supervisor_state) and the enable counter
* (cl_enable_counter). Byte store is atomic on all mainstream targets;
* no locking required as long as it is not called concurrently with
* pwm_service().
*
* Pattern parity with the default family. See
* docs/re-guide-ckp-reset-pattern.md for the full rationale.
*
* **Variant note for t06211:** the reset_flag producer is **absent from
* the ROM** (no STB #1 site writing to 0x002e). The supervisor at 0x7beb
* still consumes reset_flag == 1 and clears it, but nothing inside the
* ROM ever sets it. Consequence: the accumulator walk-off hazard is
* structurally identical to family-1, and the application layer must
* drive this hook itself at engine-rev rate (typically 4 pulses per
* revolution on VP44, ~1215 scheduler cycles apart at 1200 rpm with a
* 1 kHz scheduler). Without it, supervisor_state integrates forever.
*/
static inline void pwm_ckp_isr(pwm_runtime_t *rt) { rt->reset_flag = 1; }
/** Utility: 1D descending-X piecewise-linear lookup. */
int16_t pwm_interp_lookup(const int16_t *x, const int16_t *y,
uint16_t n, int16_t in);
/** Utility: bypass-PI bilinear LUT — eval(pwm_A, rpm), eval(pwm_B, fbkw),
* combine over Y-table, then apply [205, 3890] clamp. Use this to query
* the ROM Y-table directly as a (rpm, fbkw) lookup, skipping the PI
* controller entirely. Returns the clamped duty. */
uint16_t pwm_lut_duty(const pwm_calibration_t *cal,
uint16_t rpm, int16_t fbkw);
/* ── ROM-decoded cal + flash placeholder (defined in cal_tables_rom.c) ── */
extern const pwm_calibration_t pwm_cal_rom;
extern const pwm_flash_t pwm_flash_rom;
/** Descriptor array indices. */
enum pwm_submap_id {
PWM_SUBMAP_SETPOINT_INTERP = 0,
PWM_SUBMAP_PWM_A = 1,
PWM_SUBMAP_PWM_B = 2,
PWM_SUBMAP_SHAPE_EVAL = 3,
PWM_SUBMAP_COUNT = 4
};
extern pwm_submap_descr_t pwm_submap_descrs[PWM_SUBMAP_COUNT];
extern const int16_t *pwm_submap_y_of(uint16_t idx);
#endif /* PWM_H */