@typecad/framework-zephyr 1.0.0-alpha.13 → 1.0.0-alpha.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/README.md +22 -35
  2. package/dist/as-built.d.ts +33 -0
  3. package/dist/as-built.js +188 -0
  4. package/dist/async/timer-polyfill.js +1 -1
  5. package/dist/boardgen.d.ts +128 -0
  6. package/dist/boardgen.js +1366 -0
  7. package/dist/chips/board-overrides.d.ts +7 -0
  8. package/dist/chips/board-overrides.js +11 -0
  9. package/dist/chips/controllers.d.ts +28 -8
  10. package/dist/chips/controllers.js +49 -12
  11. package/dist/chips/esp32s3.js +49 -0
  12. package/dist/chips/index.d.ts +6 -12
  13. package/dist/chips/index.js +18 -34
  14. package/dist/chips/resolve.d.ts +16 -2
  15. package/dist/chips/resolve.js +298 -15
  16. package/dist/chips/soc/.d.ts +2 -0
  17. package/dist/chips/soc/.js +129 -0
  18. package/dist/chips/soc/esp32.d.ts +2 -0
  19. package/dist/chips/soc/esp32.js +120 -0
  20. package/dist/chips/soc/esp32c3.d.ts +2 -0
  21. package/dist/chips/soc/esp32c3.js +90 -0
  22. package/dist/chips/soc/esp32c6.d.ts +2 -0
  23. package/dist/chips/soc/esp32c6.js +109 -0
  24. package/dist/chips/soc/esp32s3.d.ts +2 -0
  25. package/dist/chips/soc/esp32s3.js +189 -0
  26. package/dist/chips/soc/index.d.ts +2 -0
  27. package/dist/chips/soc/index.js +23 -0
  28. package/dist/chips/soc/nrf52840.d.ts +2 -0
  29. package/dist/chips/soc/nrf52840.js +130 -0
  30. package/dist/chips/soc/rp2040.d.ts +2 -0
  31. package/dist/chips/soc/rp2040.js +141 -0
  32. package/dist/chips/soc/rp2350a.d.ts +2 -0
  33. package/dist/chips/soc/rp2350a.js +145 -0
  34. package/dist/chips/soc/samd21g18a.d.ts +2 -0
  35. package/dist/chips/soc/samd21g18a.js +143 -0
  36. package/dist/chips/soc/stm32f411xe.d.ts +2 -0
  37. package/dist/chips/soc/stm32f411xe.js +251 -0
  38. package/dist/chips/types.d.ts +382 -12
  39. package/dist/chips/xiao-ble.js +33 -0
  40. package/dist/display/profiles.js +1 -1
  41. package/dist/doctor.js +38 -4
  42. package/dist/dt-config/custom-board.d.ts +23 -0
  43. package/dist/dt-config/custom-board.js +227 -0
  44. package/dist/dt-config/kconfig.d.ts +83 -1
  45. package/dist/dt-config/kconfig.js +116 -27
  46. package/dist/dt-config/overlay.js +623 -18
  47. package/dist/framework.manifest.d.ts +20 -30
  48. package/dist/framework.manifest.js +124 -178
  49. package/dist/index.d.ts +4 -1
  50. package/dist/index.js +8 -2
  51. package/dist/lowering/adc.d.ts +26 -5
  52. package/dist/lowering/adc.js +137 -31
  53. package/dist/lowering/ble.js +31 -28
  54. package/dist/lowering/dac.js +9 -9
  55. package/dist/lowering/fs.js +18 -10
  56. package/dist/lowering/gpio.d.ts +2 -0
  57. package/dist/lowering/gpio.js +90 -10
  58. package/dist/lowering/http.js +39 -14
  59. package/dist/lowering/hwtimer.d.ts +4 -0
  60. package/dist/lowering/hwtimer.js +35 -10
  61. package/dist/lowering/i2c.js +46 -54
  62. package/dist/lowering/index.d.ts +5 -5
  63. package/dist/lowering/index.js +15 -16
  64. package/dist/lowering/interrupts.d.ts +22 -5
  65. package/dist/lowering/interrupts.js +104 -22
  66. package/dist/lowering/mqtt.js +12 -6
  67. package/dist/lowering/power.js +0 -4
  68. package/dist/lowering/preferences.js +49 -77
  69. package/dist/lowering/pulse.js +5 -36
  70. package/dist/lowering/pwm.d.ts +32 -3
  71. package/dist/lowering/pwm.js +105 -17
  72. package/dist/lowering/random.js +3 -3
  73. package/dist/lowering/sensor.d.ts +37 -0
  74. package/dist/lowering/sensor.js +119 -0
  75. package/dist/lowering/spi.d.ts +12 -0
  76. package/dist/lowering/spi.js +65 -40
  77. package/dist/lowering/thread.d.ts +14 -0
  78. package/dist/lowering/thread.js +55 -0
  79. package/dist/lowering/timing.js +20 -46
  80. package/dist/lowering/tone.js +18 -5
  81. package/dist/lowering/uart.d.ts +13 -0
  82. package/dist/lowering/uart.js +72 -51
  83. package/dist/lowering/usb.d.ts +34 -0
  84. package/dist/lowering/usb.js +206 -0
  85. package/dist/lowering/wdt.d.ts +1 -1
  86. package/dist/lowering/wdt.js +17 -7
  87. package/dist/lowering/wifi.d.ts +3 -2
  88. package/dist/lowering/wifi.js +120 -111
  89. package/dist/sdk/board-catalog-sync.d.ts +16 -0
  90. package/dist/sdk/board-catalog-sync.js +41 -0
  91. package/dist/sdk/board-data.generated.d.ts +2 -0
  92. package/dist/sdk/board-data.generated.js +4 -0
  93. package/dist/sdk/catalog-walker.d.ts +90 -0
  94. package/dist/sdk/catalog-walker.js +682 -0
  95. package/dist/sdk/dts-reader.d.ts +83 -0
  96. package/dist/sdk/dts-reader.js +596 -0
  97. package/dist/strategy.d.ts +136 -17
  98. package/dist/strategy.js +881 -212
  99. package/dist/toolchain/bossac-touch.d.ts +18 -0
  100. package/dist/toolchain/bossac-touch.js +88 -0
  101. package/dist/toolchain/compat.d.ts +2 -2
  102. package/dist/toolchain/compat.js +8 -3
  103. package/dist/toolchain/debug-config.d.ts +21 -26
  104. package/dist/toolchain/debug-config.js +212 -72
  105. package/dist/toolchain/env-check.d.ts +4 -4
  106. package/dist/toolchain/env-check.js +5 -5
  107. package/dist/toolchain/index.d.ts +66 -1
  108. package/dist/toolchain/index.js +713 -58
  109. package/dist/toolchain/scaffold.d.ts +9 -0
  110. package/dist/toolchain/scaffold.js +84 -7
  111. package/dist/toolchain/west-discover.d.ts +8 -1
  112. package/dist/toolchain/west-discover.js +19 -6
  113. package/dist/toolchain/west-spawn.js +17 -5
  114. package/installer/CHANGELOG.md +473 -0
  115. package/installer/README.md +328 -0
  116. package/installer/environment.yml +46 -0
  117. package/installer/etc/conda/activate.d/zephyr.bat +7 -0
  118. package/installer/etc/conda/activate.d/zephyr.ps1 +12 -0
  119. package/installer/etc/conda/activate.d/zephyr.sh +20 -0
  120. package/installer/etc/conda/deactivate.d/zephyr.bat +6 -0
  121. package/installer/etc/conda/deactivate.d/zephyr.ps1 +6 -0
  122. package/installer/etc/conda/deactivate.d/zephyr.sh +4 -0
  123. package/installer/install.mjs +580 -0
  124. package/installer/install.ps1 +619 -0
  125. package/installer/install.sh +310 -0
  126. package/installer/lib/detect-platform.sh +62 -0
  127. package/installer/lib/fetch-sdk.sh +304 -0
  128. package/installer/lib/init-workspace.sh +153 -0
  129. package/installer/lib/write-activation.sh +76 -0
  130. package/installer/patches/tfm-c-include-separators.patch +19 -0
  131. package/installer/patches/zephyr-tfm-nordic-zephyr-include.patch +24 -0
  132. package/installer/templates/project/.typecad/activate-zephyr.ps1 +31 -0
  133. package/installer/templates/project/.typecad/activate-zephyr.sh +31 -0
  134. package/installer/templates/project/.vscode/settings.json +30 -0
  135. package/installer/templates/project/README.md +58 -0
  136. package/installer/versions.env +110 -0
  137. package/package.json +16 -16
  138. package/src/as-built.ts +206 -0
  139. package/src/boardgen.ts +1428 -0
  140. package/src/chips/controllers.ts +61 -12
  141. package/src/chips/index.ts +35 -54
  142. package/src/chips/resolve.ts +316 -14
  143. package/src/chips/types.ts +388 -13
  144. package/src/display/profiles.ts +1 -1
  145. package/src/doctor.ts +110 -77
  146. package/src/dt-config/custom-board.ts +325 -0
  147. package/src/dt-config/kconfig.ts +177 -19
  148. package/src/dt-config/overlay.ts +1058 -450
  149. package/src/framework.manifest.ts +125 -178
  150. package/src/index.ts +57 -41
  151. package/src/lowering/adc.ts +222 -102
  152. package/src/lowering/ble.ts +32 -28
  153. package/src/lowering/dac.ts +82 -82
  154. package/src/lowering/fs.ts +18 -10
  155. package/src/lowering/gpio.ts +242 -152
  156. package/src/lowering/http.ts +512 -488
  157. package/src/lowering/hwtimer.ts +139 -101
  158. package/src/lowering/i2c.ts +116 -126
  159. package/src/lowering/index.ts +91 -93
  160. package/src/lowering/interrupts.ts +113 -21
  161. package/src/lowering/mqtt.ts +12 -6
  162. package/src/lowering/preferences.ts +327 -354
  163. package/src/lowering/pwm.ts +167 -74
  164. package/src/lowering/random.ts +3 -3
  165. package/src/lowering/sensor.ts +155 -0
  166. package/src/lowering/spi.ts +164 -124
  167. package/src/lowering/thread.ts +63 -0
  168. package/src/lowering/timing.ts +46 -72
  169. package/src/lowering/uart.ts +73 -51
  170. package/src/lowering/usb.ts +221 -0
  171. package/src/lowering/wdt.ts +95 -84
  172. package/src/lowering/wifi.ts +120 -111
  173. package/src/sdk/board-catalog-sync.ts +62 -0
  174. package/src/strategy.ts +888 -229
  175. package/src/toolchain/bossac-touch.ts +112 -0
  176. package/src/toolchain/compat.ts +8 -3
  177. package/src/toolchain/debug-config.ts +666 -522
  178. package/src/toolchain/env-check.ts +9 -9
  179. package/src/toolchain/index.ts +765 -44
  180. package/src/toolchain/scaffold.ts +81 -7
  181. package/src/toolchain/west-discover.ts +334 -316
  182. package/src/toolchain/west-spawn.ts +17 -5
  183. package/src/async/timer-polyfill.ts +0 -107
  184. package/src/chips/esp32.ts +0 -74
  185. package/src/chips/esp32s3.ts +0 -57
  186. package/src/chips/xiao-ble.ts +0 -70
  187. package/src/lowering/power.ts +0 -70
  188. package/src/lowering/pulse.ts +0 -93
  189. package/src/lowering/tone.ts +0 -60
  190. package/src/lowering/worker-backing.ts +0 -94
  191. package/src/lowering/worker.ts +0 -17
@@ -5,11 +5,13 @@
5
5
  // `struct gpio_callback` per pin, registered with gpio_add_callback, with a
6
6
  // trampoline that calls the user's C handler. The descriptor's gpio.interruptPins
7
7
  // lists the pins with DT specs (buttons). The user's handler is a free C
8
- // function; the trampoline calls it with no args (Arduino attachInterrupt style).
8
+ // function; the trampoline calls it with no args.
9
9
  //
10
10
  // Each interrupt pin gets a static callback struct + a trampoline in shimLines.
11
11
  // attach enables + adds the callback; detach disables + removes it.
12
12
  // ---------------------------------------------------------------------------
13
+ import { controllerNodelabelForPin, controllerRawPinForPin, controllerRangeForPin } from '../chips/controllers.js';
14
+ import { ZEPHYR_GPIO_INTS } from '@typecad/hal';
13
15
  /** Look up an interrupt pin spec by HAL pin number. */
14
16
  function findIntPin(chip, pin) {
15
17
  return chip.gpio.interruptPins?.find((p) => p.pin === pin);
@@ -18,62 +20,142 @@ function findIntPin(chip, pin) {
18
20
  function dtSpecVar(dtSpec) {
19
21
  return `__tc_int_${dtSpec.replace(/-/g, '_')}`;
20
22
  }
21
- /** Map a HAL interrupt mode string to Zephyr GPIO_INT_* flags. */
22
- function modeToFlags(mode) {
23
- switch (mode.toLowerCase()) {
24
- case 'rising': return 'GPIO_INT_EDGE_RISING';
25
- case 'falling': return 'GPIO_INT_EDGE_FALLING';
26
- case 'change': return 'GPIO_INT_EDGE_BOTH';
27
- case 'high': return 'GPIO_INT_LEVEL_HIGH';
28
- case 'low': return 'GPIO_INT_LEVEL_LOW';
29
- default: return 'GPIO_INT_EDGE_BOTH';
23
+ // ── Thin GPIO interrupts (hal/gpio-pin.ts onInterrupt) INT_* tokens ─────
24
+ //
25
+ // The name set comes from the GENERATED Zephyr token table (parsed from the
26
+ // pinned tree's drivers/gpio.h).
27
+ const GPIO_INT_TOKENS = Object.fromEntries(ZEPHYR_GPIO_INTS.map((f) => [`GPIO.${f}`, `GPIO_${f}`]));
28
+ /** Map thin-GPIO INT token text to the GPIO_INT_* macro. */
29
+ export function gpioIntTokenToMacro(intFlags) {
30
+ const token = String(intFlags ?? '').trim();
31
+ const m = GPIO_INT_TOKENS[token];
32
+ if (!m) {
33
+ throw new Error(`GPIO interrupt flag '${token}' is not a known GPIO.INT_* token — valid: ${Object.keys(GPIO_INT_TOKENS).join(', ')}.`);
30
34
  }
35
+ return m;
31
36
  }
32
37
  /**
33
38
  * Emit the per-pin interrupt callback state + trampolines. Called from shimLines
34
39
  * when the program uses interrupts. The trampoline calls a user function via a
35
40
  * function-pointer static, set at attach time.
41
+ *
42
+ * Two paths:
43
+ * - descriptor `gpio.interruptPins` (buttons with DT specs): callback state
44
+ * against the DT spec, polarity-correct via gpio_*_dt.
45
+ * - `usedPins` (any other pin the program attaches to): raw-controller state —
46
+ * GPIO.onInterrupt() works on EVERY GPIO, not just DT-aliased buttons. Each
47
+ * such pin gets its own callback struct + trampoline addressed by the owning
48
+ * controller (STM32 splits gpioa/gpiob/gpioc) and port-relative bit.
36
49
  */
37
- export function interruptInitLines(chip) {
50
+ export function interruptInitLines(chip, usedPins) {
38
51
  const pins = chip.gpio.interruptPins ?? [];
39
- if (pins.length === 0)
40
- return [];
41
52
  const lines = ['// CUTTLEFISH_INT_BEGIN'];
42
53
  for (const pin of pins) {
43
54
  const v = dtSpecVar(pin.dtSpec);
44
55
  lines.push(`static const struct gpio_dt_spec ${v} = GPIO_DT_SPEC_GET(DT_ALIAS(${pin.dtSpec}), gpios);`, `static struct gpio_callback ${v}_cb;`, `static void (*${v}_handler)(void) = NULL;`, `static void ${v}_tramp(const struct device* port, struct gpio_callback* cb, gpio_port_pins_t pins_v) {`, ` (void)port; (void)cb; (void)pins_v;`, ` if (${v}_handler) { ${v}_handler(); }`, `}`);
45
56
  }
57
+ if (usedPins) {
58
+ for (const pin of usedPins) {
59
+ if (pins.some((p) => p.pin === pin))
60
+ continue;
61
+ const v = `__tc_int_raw${pin}`;
62
+ lines.push(`static const struct device* ${v}_dev = DEVICE_DT_GET(DT_NODELABEL(${controllerNodelabelForPin(chip, pin)}));`, `static struct gpio_callback ${v}_cb;`, `static void (*${v}_handler)(void) = NULL;`, `static void ${v}_tramp(const struct device* port, struct gpio_callback* cb, gpio_port_pins_t pins_v) {`, ` (void)port; (void)cb; (void)pins_v;`, ` if (${v}_handler) { ${v}_handler(); }`, `}`);
63
+ }
64
+ }
46
65
  lines.push('// CUTTLEFISH_INT_END');
47
66
  return lines;
48
67
  }
68
+ /**
69
+ * Collect the pin numbers the program attaches interrupts to (any GPIO — not
70
+ * just descriptor-listed buttons). Feeds interruptInitLines' raw-path state.
71
+ */
72
+ export function collectInterruptPins(program) {
73
+ const pins = new Set();
74
+ const visit = (node) => {
75
+ if (!node || typeof node !== 'object')
76
+ return;
77
+ const n = node;
78
+ const op = n.operation;
79
+ if (op && typeof op === 'object') {
80
+ const o = op;
81
+ if (o.operation === 'interrupt.attach_flags' && typeof o.pin === 'number') {
82
+ pins.add(o.pin);
83
+ }
84
+ }
85
+ for (const v of Object.values(n)) {
86
+ if (Array.isArray(v)) {
87
+ for (const item of v)
88
+ visit(item);
89
+ }
90
+ else if (v && typeof v === 'object')
91
+ visit(v);
92
+ }
93
+ };
94
+ visit(program);
95
+ return pins;
96
+ }
49
97
  /**
50
98
  * Resolve a HAL interrupt.* op to Zephyr C++.
51
99
  * Returns `{ code }` for statement ops, `{ expression }` for value-returning ops.
52
100
  *
53
- * Note: attach requires the pin to be in the descriptor's interruptPins. If a
54
- * program attaches to an unlisted pin, we fall back to a diagnostic comment
55
- * (the probe uses an unknown pin, so the manifest marks these 'supported' via
56
- * the comment lowering — the probe sees a non-undefined return).
101
+ * Two paths: pins listed in the descriptor's gpio.interruptPins (DT-aliased
102
+ * buttons) go through the polarity-correct gpio_*_dt chain; every other GPIO
103
+ * attaches through the raw-controller chain (any GPIO is interrupt-capable on
104
+ * the supported SoCs — the descriptor list exists for DT specs, not as a
105
+ * capability gate). The manifest probe's synthetic op keeps the comment
106
+ * fallback (no program ⇒ no shim state was emitted for the raw path).
57
107
  */
58
108
  export function lowerInterrupt(op, chip) {
59
109
  const o = op;
60
110
  const pin = findIntPin(chip, o.pin);
61
111
  if (!pin) {
112
+ // Raw-controller path — state comes from interruptInitLines' usedPins.
113
+ // Only for pins a declared controller range covers: an out-of-range number
114
+ // (e.g. pin 99 on a 34-pin SoC) keeps the diagnostic comment. Single-
115
+ // controller chips declare no ranges, so every number is taken as real and
116
+ // the driver rejects invalid bits at runtime (Arduino-like behavior).
117
+ const knownPin = typeof o.pin === 'number' && Number.isInteger(o.pin)
118
+ && (!chip.gpioControllers || chip.gpioControllers.length === 0
119
+ || controllerRangeForPin(chip, o.pin) !== undefined);
120
+ if (knownPin) {
121
+ const v = `__tc_int_raw${o.pin}`;
122
+ const raw = controllerRawPinForPin(chip, o.pin);
123
+ if (op.operation === 'interrupt.attach_flags') {
124
+ // Thin GPIO: INT_* tokens instead of mode strings. The pin's
125
+ // input/pull configuration already came from the construction flags
126
+ // (the class emits gpio.configure ahead of this op) — no INPUT
127
+ // override here, unlike the legacy raw path above.
128
+ const flags = gpioIntTokenToMacro(o.intFlags);
129
+ return {
130
+ code: [
131
+ `${v}_handler = (${o.handler});`,
132
+ `gpio_init_callback(&${v}_cb, ${v}_tramp, BIT(${raw}));`,
133
+ `gpio_pin_interrupt_configure(${v}_dev, ${raw}, ${flags});`,
134
+ `gpio_add_callback(${v}_dev, &${v}_cb);`,
135
+ ].join(' '),
136
+ };
137
+ }
138
+ if (op.operation === 'interrupt.detach') {
139
+ return {
140
+ code: `gpio_pin_interrupt_configure(${v}_dev, ${raw}, GPIO_INT_DISABLE); gpio_remove_callback(${v}_dev, &${v}_cb); ${v}_handler = NULL;`,
141
+ };
142
+ }
143
+ }
62
144
  // Fallback (also covers the manifest probe): a diagnostic comment so the
63
145
  // resolver returns non-undefined. Real attach needs a descriptor entry.
64
- if (op.operation === 'interrupt.attach') {
65
- return { code: `/* interrupt.attach(pin ${o.pin}): no DT spec — add to chip descriptor gpio.interruptPins */` };
146
+ if (op.operation === 'interrupt.attach_flags') {
147
+ return { code: `/* interrupt.attach_flags(pin ${o.pin}): no DT spec — add to chip descriptor gpio.interruptPins */` };
66
148
  }
67
149
  return { code: `/* interrupt.detach(pin ${o.pin}): no DT spec */` };
68
150
  }
69
151
  const v = dtSpecVar(pin.dtSpec);
70
152
  switch (op.operation) {
71
- case 'interrupt.attach': {
72
- const flags = modeToFlags(o.mode);
153
+ case 'interrupt.attach_flags': {
154
+ // Thin GPIO: INT_* tokens; construction flags already configured the pin.
155
+ const flags = gpioIntTokenToMacro(o.intFlags);
73
156
  return {
74
157
  code: [
75
158
  `${v}_handler = (${o.handler});`,
76
- `gpio_pin_configure_dt(&${v}, GPIO_INPUT);`,
77
159
  `gpio_init_callback(&${v}_cb, ${v}_tramp, BIT(${v}.pin));`,
78
160
  `gpio_pin_interrupt_configure_dt(&${v}, ${flags});`,
79
161
  `gpio_add_callback(${v}.port, &${v}_cb);`,
@@ -3,11 +3,11 @@
3
3
  //
4
4
  // Zephyr's MQTT library is event-driven but has no internal task: the app
5
5
  // must call mqtt_input() when the socket is readable and mqtt_live()
6
- // periodically for keepalive. The generated firmware is setup()/loop(),
7
- // and the user's loop() is not ours to edit — so the connect shim spawns a
6
+ // periodically for keepalive. The generated firmware is main()-based and
7
+ // the user's main() is not ours to edit — so the connect shim spawns a
8
8
  // dedicated k_thread that runs the poll loop (poll() → mqtt_input/mqtt_live)
9
9
  // until disconnect. This mirrors a "managed client" model (like ESP-IDF's
10
- // esp_mqtt internal task) and keeps the user's loop() untouched.
10
+ // esp_mqtt internal task) and keeps the user's main() untouched.
11
11
  //
12
12
  // mqtt_connect() does no DNS — the broker is a resolved sockaddr — so the
13
13
  // shim getaddrinfo-resolves the broker host itself (like the HTTP shim).
@@ -266,8 +266,6 @@ export function mqttInitLines() {
266
266
  ` (void)mqtt_abort(&__tc_mqtt.client);`,
267
267
  ` return;`,
268
268
  ` }`,
269
- ` printk("tc-mqtt: connecting to %s:%u\\n", __tc_mqtt.host,`,
270
- ` static_cast<unsigned int>(__tc_mqtt.port));`,
271
269
  ` // Drive input locally until CONNACK arrives (poll thread hasn't started).`,
272
270
  ` // mqtt_connect opened the socket; poll it for the connect-ack. Cap the wait`,
273
271
  ` // so a silent broker doesn't hang setup forever.`,
@@ -290,7 +288,15 @@ export function mqttInitLines() {
290
288
  ` (void)k_thread_create(&__tc_mqtt.poll_thread, __tc_mqtt_stack,`,
291
289
  ` K_THREAD_STACK_SIZEOF(__tc_mqtt_stack),`,
292
290
  ` __tc_mqtt_poll_thread, nullptr, nullptr, nullptr,`,
293
- ` 5, 0, K_NO_WAIT);`,
291
+ ` 5, 0, K_FOREVER);`,
292
+ `#ifdef CONFIG_SMP`,
293
+ ` // On SMP targets park the poll thread on the app core so the network`,
294
+ ` // stack never competes with the main/UI thread for core 0. k_thread_cpu_pin`,
295
+ ` // is SMP-only; the pin happens before k_thread_start (which is why the`,
296
+ ` // thread is created K_FOREVER). Compiles away on !SMP builds.`,
297
+ ` (void)k_thread_cpu_pin(&__tc_mqtt.poll_thread, 1);`,
298
+ `#endif`,
299
+ ` k_thread_start(&__tc_mqtt.poll_thread);`,
294
300
  `}`,
295
301
  ``,
296
302
  `static inline void __tc_mqtt_set_on_message(__tc_mqtt_msg_cb_t fn) {`,
@@ -28,10 +28,6 @@ export function lowerPower(op) {
28
28
  // Best-effort: force a shallow suspend on the next idle. pm_state_force
29
29
  // requires a pm_state_info; SUSPEND_TO_IDLE is a common shallow state.
30
30
  return { code: `pm_state_force(0, &(const struct pm_state_info){ .state = PM_STATE_SUSPEND_TO_IDLE, .substate_id = 0 });` };
31
- case 'power.set_cpu_frequency':
32
- // nRF52 has a fixed 64 MHz HFXO; no portable dynamic scaling from C++.
33
- // Documented no-op (best-effort), matching Arduino's approach.
34
- return { code: `/* power.set_cpu_frequency(${o.mhz}): nRF52 fixed 64 MHz HFXO — no portable scaling */` };
35
31
  case 'power.deep_sleep_pin': {
36
32
  // Configure the pin as a GPIO wake source (level interrupt), then k_sleep
37
33
  // until the interrupt fires. A static gpio_callback + a no-op handler (the
@@ -29,6 +29,9 @@
29
29
  // Runtime Exception (RUNTIME_EXCEPTION.md at the repo root).
30
30
  // ---------------------------------------------------------------------------
31
31
  /** Render a HAL field: pass through (already rendered by the resolver). */
32
+ function unq(v) {
33
+ return String(v).replace(/^"|"$/g, '');
34
+ }
32
35
  function s(v) {
33
36
  return String(v);
34
37
  }
@@ -73,10 +76,7 @@ export function preferencesInitLines() {
73
76
  ` bool used;`,
74
77
  `};`,
75
78
  ``,
76
- `// Active namespace prefix ("tc/<ns>/"). begin() overwrites it; end() clears.`,
77
- `// prefs_ns is deliberately sized to hold "tc/" + a 16-char ns + "/" + NUL.`,
78
79
  `static struct {`,
79
- ` char ns[24];`,
80
80
  ` bool loaded;`,
81
81
  ` __tc_prefs_slot slots[__TC_PREFS_SLOT_COUNT];`,
82
82
  `} __tc_prefs;`,
@@ -96,15 +96,18 @@ export function preferencesInitLines() {
96
96
  ` __tc_prefs.slots[i].len = static_cast<uint8_t>(got);`,
97
97
  ` // Type is encoded as the first byte of the stored value (see put_*).`,
98
98
  ` __tc_prefs.slots[i].type = __tc_prefs.slots[i].val[0];`,
99
- ` (void)snprintf(__tc_prefs.slots[i].key, __TC_PREFS_KEY_LEN, "%s", name);`,
99
+ ` // h_set hands us the name RELATIVE to the handler's "tc"`,
100
+ ` // subtree ("rig/marker") — the cache keys full names, so`,
101
+ ` // re-attach the prefix or loaded entries never match a lookup.`,
102
+ ` (void)snprintf(__tc_prefs.slots[i].key, __TC_PREFS_KEY_LEN, "tc/%s", name);`,
100
103
  ` __tc_prefs.slots[i].used = true;`,
101
104
  ` return 0;`,
102
105
  ` }`,
103
106
  ` }`,
104
107
  ` return 0; // cache full — silently drop (matches ESP32 NVS-full behavior)`,
105
108
  `}`,
106
- `static int __tc_prefs_h_get(const char* key, char* val, int val_len_max) {`,
107
- ` (void)key; (void)val; (void)val_len_max;`,
109
+ `static int __tc_prefs_h_get(const char* full, char* val, int val_len_max) {`,
110
+ ` (void)full; (void)val; (void)val_len_max;`,
108
111
  ` return -ENOENT;`,
109
112
  `}`,
110
113
  `static int __tc_prefs_h_commit(void) { return 0; }`,
@@ -122,12 +125,6 @@ export function preferencesInitLines() {
122
125
  ` }`,
123
126
  `}`,
124
127
  ``,
125
- `// Build the full settings name "tc/<ns>/<key>" into out. ns is the active`,
126
- `// prefix set by begin(); the slot's key[] stores this full path so remove()`,
127
- `// and clear() can settings_delete the exact stored name.`,
128
- `static void __tc_prefs_full(char* out, size_t outsz, const char* key) {`,
129
- ` (void)snprintf(out, outsz, "%s%s", __tc_prefs.ns, key);`,
130
- `}`,
131
128
  ``,
132
129
  `// Slot lookup by full settings name. Returns nullptr when absent (the get_*`,
133
130
  `// helpers then fall back to the caller's default value, matching the ESP32`,
@@ -159,9 +156,8 @@ export function preferencesInitLines() {
159
156
  `// as val[0] so h_set can recover it on the next boot without a separate`,
160
157
  `// name→type map.`,
161
158
  `template <typename T>`,
162
- `static inline void __tc_prefs_put(const char* key, uint8_t tag, const T& value) {`,
163
- ` char full[__TC_PREFS_KEY_LEN];`,
164
- ` __tc_prefs_full(full, sizeof(full), key);`,
159
+ `static inline void __tc_prefs_put(const char* full, uint8_t tag, const T& value) {`,
160
+ ` __tc_prefs_ensure_loaded(); // begin() is gone — mount lazily here`,
165
161
  ` uint8_t buf[1U + sizeof(T)];`,
166
162
  ` buf[0] = tag;`,
167
163
  ` (void)memcpy(&buf[1], &value, sizeof(T));`,
@@ -177,9 +173,8 @@ export function preferencesInitLines() {
177
173
  `}`,
178
174
  ``,
179
175
  `template <typename T>`,
180
- `static inline T __tc_prefs_get(const char* key, uint8_t tag, T def) {`,
181
- ` char full[__TC_PREFS_KEY_LEN];`,
182
- ` __tc_prefs_full(full, sizeof(full), key);`,
176
+ `static inline T __tc_prefs_get(const char* full, uint8_t tag, T def) {`,
177
+ ` __tc_prefs_ensure_loaded();`,
183
178
  ` __tc_prefs_slot* slot = __tc_prefs_find(full);`,
184
179
  ` if (slot == nullptr || slot->type != tag || slot->len < (1U + sizeof(T))) {`,
185
180
  ` return def;`,
@@ -192,23 +187,12 @@ export function preferencesInitLines() {
192
187
  `// ── Lifecycle (session model on top of the flat key-space) ──────────────`,
193
188
  `// begin records "tc/<ns>/" as the prefix every subsequent key is built under.`,
194
189
  `// end clears the prefix (NOT the cache). Both are cheap on Zephyr: the real`,
195
- `// work is in settings_load() at boot. They exist for portability with the`,
196
- `// ESP32 NVS session model and so a future session-needing backend has a hook.`,
197
- `static inline void __tc_prefs_begin(const char* ns, bool readOnly) {`,
198
- ` (void)readOnly;`,
199
- ` __tc_prefs_ensure_loaded();`,
200
- ` (void)snprintf(__tc_prefs.ns, sizeof(__tc_prefs.ns), "tc/%s/", ns);`,
201
- `}`,
202
- `static inline void __tc_prefs_end(void) {`,
203
- ` __tc_prefs.ns[0] = '\\0';`,
204
- `}`,
205
190
  ``,
206
191
  `// remove: drop one key from flash and the cache. Falls back to no-op if the`,
207
192
  `// slot isn't cached (settings_delete on a missing key returns -ENOENT, which`,
208
193
  `// we swallow).`,
209
- `static inline void __tc_prefs_remove(const char* key) {`,
210
- ` char full[__TC_PREFS_KEY_LEN];`,
211
- ` __tc_prefs_full(full, sizeof(full), key);`,
194
+ `static inline void __tc_prefs_remove(const char* full) {`,
195
+ ` __tc_prefs_ensure_loaded();`,
212
196
  ` (void)settings_delete(full);`,
213
197
  ` __tc_prefs_slot* slot = __tc_prefs_find(full);`,
214
198
  ` if (slot != nullptr) { slot->used = false; }`,
@@ -218,11 +202,12 @@ export function preferencesInitLines() {
218
202
  `// authoritative list of keys we have written) and settings_delete each whose`,
219
203
  `// full name starts with the active ns prefix, then mark the slot free. Only`,
220
204
  `// the active namespace is cleared — keys under other namespaces survive.`,
221
- `static inline void __tc_prefs_clear(void) {`,
222
- ` const size_t plen = strlen(__tc_prefs.ns);`,
205
+ `static inline void __tc_prefs_clear(const char* prefix) {`,
206
+ ` __tc_prefs_ensure_loaded();`,
207
+ ` const size_t plen = strlen(prefix);`,
223
208
  ` for (int i = 0; i < __TC_PREFS_SLOT_COUNT; i++) {`,
224
209
  ` if (__tc_prefs.slots[i].used &&`,
225
- ` strncmp(__tc_prefs.slots[i].key, __tc_prefs.ns, plen) == 0) {`,
210
+ ` strncmp(__tc_prefs.slots[i].key, prefix, plen) == 0) {`,
226
211
  ` (void)settings_delete(__tc_prefs.slots[i].key);`,
227
212
  ` __tc_prefs.slots[i].used = false;`,
228
213
  ` }`,
@@ -232,39 +217,38 @@ export function preferencesInitLines() {
232
217
  `// ── Typed accessors ─────────────────────────────────────────────────────`,
233
218
  `// Each put_* stamps its tag byte and persists; each get_* reads the cache and`,
234
219
  `// returns the default on miss/type-mismatch (mirrors ESP32 Preferences).`,
235
- `static inline void __tc_prefs_put_int(const char* key, int32_t v) {`,
236
- ` __tc_prefs_put<int32_t>(key, ${TAG.INT}, v);`,
220
+ `static inline void __tc_prefs_put_int(const char* full, int32_t v) {`,
221
+ ` __tc_prefs_put<int32_t>(full, ${TAG.INT}, v);`,
237
222
  `}`,
238
- `static inline int32_t __tc_prefs_get_int(const char* key, int32_t def) {`,
239
- ` return __tc_prefs_get<int32_t>(key, ${TAG.INT}, def);`,
223
+ `static inline int32_t __tc_prefs_get_int(const char* full, int32_t def) {`,
224
+ ` return __tc_prefs_get<int32_t>(full, ${TAG.INT}, def);`,
240
225
  `}`,
241
- `static inline void __tc_prefs_put_uint(const char* key, uint32_t v) {`,
242
- ` __tc_prefs_put<uint32_t>(key, ${TAG.UINT}, v);`,
226
+ `static inline void __tc_prefs_put_uint(const char* full, uint32_t v) {`,
227
+ ` __tc_prefs_put<uint32_t>(full, ${TAG.UINT}, v);`,
243
228
  `}`,
244
- `static inline uint32_t __tc_prefs_get_uint(const char* key, uint32_t def) {`,
245
- ` return __tc_prefs_get<uint32_t>(key, ${TAG.UINT}, def);`,
229
+ `static inline uint32_t __tc_prefs_get_uint(const char* full, uint32_t def) {`,
230
+ ` return __tc_prefs_get<uint32_t>(full, ${TAG.UINT}, def);`,
246
231
  `}`,
247
- `static inline void __tc_prefs_put_bool(const char* key, bool v) {`,
232
+ `static inline void __tc_prefs_put_bool(const char* full, bool v) {`,
248
233
  ` uint8_t b = v ? 1U : 0U;`,
249
- ` __tc_prefs_put<uint8_t>(key, ${TAG.BOOL}, b);`,
234
+ ` __tc_prefs_put<uint8_t>(full, ${TAG.BOOL}, b);`,
250
235
  `}`,
251
- `static inline bool __tc_prefs_get_bool(const char* key, bool def) {`,
252
- ` uint8_t b = __tc_prefs_get<uint8_t>(key, ${TAG.BOOL}, def ? 1U : 0U);`,
236
+ `static inline bool __tc_prefs_get_bool(const char* full, bool def) {`,
237
+ ` uint8_t b = __tc_prefs_get<uint8_t>(full, ${TAG.BOOL}, def ? 1U : 0U);`,
253
238
  ` return b != 0U;`,
254
239
  `}`,
255
- `static inline void __tc_prefs_put_float(const char* key, float v) {`,
256
- ` __tc_prefs_put<float>(key, ${TAG.FLOAT}, v);`,
240
+ `static inline void __tc_prefs_put_float(const char* full, float v) {`,
241
+ ` __tc_prefs_put<float>(full, ${TAG.FLOAT}, v);`,
257
242
  `}`,
258
- `static inline float __tc_prefs_get_float(const char* key, float def) {`,
259
- ` return __tc_prefs_get<float>(key, ${TAG.FLOAT}, def);`,
243
+ `static inline float __tc_prefs_get_float(const char* full, float def) {`,
244
+ ` return __tc_prefs_get<float>(full, ${TAG.FLOAT}, def);`,
260
245
  `}`,
261
246
  ``,
262
247
  `// Strings are length-prefixed into the value buffer (capped at STR_LEN so the`,
263
248
  `// whole slot still fits in __TC_PREFS_VAL_LEN). get copies out with a NUL and`,
264
249
  `// returns def when the slot is absent or the wrong type.`,
265
- `static inline void __tc_prefs_put_string(const char* key, const char* v) {`,
266
- ` char full[__TC_PREFS_KEY_LEN];`,
267
- ` __tc_prefs_full(full, sizeof(full), key);`,
250
+ `static inline void __tc_prefs_put_string(const char* full, const char* v) {`,
251
+ ` __tc_prefs_ensure_loaded();`,
268
252
  ` uint8_t buf[1U + __TC_PREFS_STR_LEN];`,
269
253
  ` buf[0] = ${TAG.STRING};`,
270
254
  ` size_t n = strlen(v);`,
@@ -280,10 +264,9 @@ export function preferencesInitLines() {
280
264
  ` slot->used = true;`,
281
265
  ` }`,
282
266
  `}`,
283
- `static inline const char* __tc_prefs_get_string(const char* key, const char* def) {`,
267
+ `static inline const char* __tc_prefs_get_string(const char* full, const char* def) {`,
268
+ ` __tc_prefs_ensure_loaded();`,
284
269
  ` static char out[__TC_PREFS_STR_LEN + 1U];`,
285
- ` char full[__TC_PREFS_KEY_LEN];`,
286
- ` __tc_prefs_full(full, sizeof(full), key);`,
287
270
  ` __tc_prefs_slot* slot = __tc_prefs_find(full);`,
288
271
  ` if (slot == nullptr || slot->type != ${TAG.STRING} || slot->len < 1U) {`,
289
272
  ` return def;`,
@@ -307,37 +290,26 @@ export function preferencesInitLines() {
307
290
  export function lowerPreferences(op) {
308
291
  const o = op;
309
292
  switch (op.operation) {
310
- case 'preferences.begin':
311
- // readOnly is honored as a no-op: Zephyr settings has no read-only mount,
312
- // and silently ignoring it matches the ESP32 Preferences behavior when
313
- // an RO handle is written (the put returns an error the app doesn't see).
314
- return { code: `__tc_prefs_begin(${s(o.namespace)}, ${o.readOnly ? 'true' : 'false'});` };
315
- case 'preferences.end':
316
- return { code: `__tc_prefs_end();` };
317
293
  case 'preferences.clear':
318
- return { code: `__tc_prefs_clear();` };
294
+ return { code: `__tc_prefs_clear("tc/${unq(o.ns)}/");` };
319
295
  case 'preferences.remove':
320
- return { code: `__tc_prefs_remove(${s(o.key)});` };
296
+ return { code: `__tc_prefs_remove("tc/${unq(o.ns)}/${unq(o.key)}");` };
321
297
  case 'preferences.put_int':
322
- return { code: `__tc_prefs_put_int(${s(o.key)}, ${s(o.value)});` };
298
+ return { code: `__tc_prefs_put_int("tc/${unq(o.ns)}/${unq(o.key)}", ${s(o.value)});` };
323
299
  case 'preferences.get_int':
324
- return { expression: `__tc_prefs_get_int(${s(o.key)}, ${s(o.defaultValue)})` };
325
- case 'preferences.put_uint':
326
- return { code: `__tc_prefs_put_uint(${s(o.key)}, ${s(o.value)});` };
327
- case 'preferences.get_uint':
328
- return { expression: `__tc_prefs_get_uint(${s(o.key)}, ${s(o.defaultValue)})` };
300
+ return { expression: `__tc_prefs_get_int("tc/${unq(o.ns)}/${unq(o.key)}", ${s(o.defaultValue)})` };
329
301
  case 'preferences.put_bool':
330
- return { code: `__tc_prefs_put_bool(${s(o.key)}, ${o.value ? 'true' : 'false'});` };
302
+ return { code: `__tc_prefs_put_bool("tc/${unq(o.ns)}/${unq(o.key)}", ${o.value ? 'true' : 'false'});` };
331
303
  case 'preferences.get_bool':
332
- return { expression: `__tc_prefs_get_bool(${s(o.key)}, ${o.defaultValue ? 'true' : 'false'})` };
304
+ return { expression: `__tc_prefs_get_bool("tc/${unq(o.ns)}/${unq(o.key)}", ${o.defaultValue ? 'true' : 'false'})` };
333
305
  case 'preferences.put_float':
334
- return { code: `__tc_prefs_put_float(${s(o.key)}, ${s(o.value)});` };
306
+ return { code: `__tc_prefs_put_float("tc/${unq(o.ns)}/${unq(o.key)}", ${s(o.value)});` };
335
307
  case 'preferences.get_float':
336
- return { expression: `__tc_prefs_get_float(${s(o.key)}, ${s(o.defaultValue)})` };
308
+ return { expression: `__tc_prefs_get_float("tc/${unq(o.ns)}/${unq(o.key)}", ${s(o.defaultValue)})` };
337
309
  case 'preferences.put_string':
338
- return { code: `__tc_prefs_put_string(${s(o.key)}, ${s(o.value)});` };
310
+ return { code: `__tc_prefs_put_string("tc/${unq(o.ns)}/${unq(o.key)}", ${s(o.value)});` };
339
311
  case 'preferences.get_string':
340
- return { expression: `__tc_prefs_get_string(${s(o.key)}, ${s(o.defaultValue)})` };
312
+ return { expression: `__tc_prefs_get_string("tc/${unq(o.ns)}/${unq(o.key)}", ${s(o.defaultValue)})` };
341
313
  default:
342
314
  throw new Error(`framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
343
315
  `Open an issue or use rawCpp() to emit it manually.`);
@@ -7,7 +7,7 @@
7
7
  // implementations (no hardware pulse capture on nRF for the HAL surface); they
8
8
  // are correct but not high-precision.
9
9
  // ---------------------------------------------------------------------------
10
- import { controllerNodelabelForPin } from '../chips/controllers.js';
10
+ import { controllerNodelabelForPin, controllerRawPinForPin } from '../chips/controllers.js';
11
11
  /** `DEVICE_DT_GET(DT_NODELABEL(<owning-controller>))` for a HAL pin. */
12
12
  function devForPin(chip, pin) {
13
13
  return `DEVICE_DT_GET(DT_NODELABEL(${controllerNodelabelForPin(chip, pin)}))`;
@@ -23,9 +23,9 @@ export function lowerPulseOrShift(op, chip) {
23
23
  // for the start edge is bounded by the timeout; the measurement of the
24
24
  // pulse itself is intentionally unbounded (that IS the pulse length).
25
25
  // Returns -1 (0) if the start edge never arrives within the timeout.
26
- const pin = o.pin;
26
+ const pin = controllerRawPinForPin(chip, o.pin);
27
27
  const want = o.value;
28
- const dev = devForPin(chip, pin);
28
+ const dev = devForPin(chip, o.pin);
29
29
  const timeout = o.timeout ?? 1_000_000; // default 1s in us
30
30
  return {
31
31
  expression: `({ int64_t __max = static_cast<int64_t>(${timeout} / 1000); int64_t __t0 = k_uptime_get(); bool __ok = true; while (gpio_pin_get_raw(${dev}, ${pin}) != ${want}) { if ((k_uptime_get() - __t0) > __max) { __ok = false; break; } } int32_t __ret = 0; if (__ok) { int64_t __start = k_uptime_get(); while (gpio_pin_get_raw(${dev}, ${pin}) == ${want}) { } __ret = static_cast<int32_t>((k_uptime_get() - __start) * 1000); } __ret; })`,
@@ -36,45 +36,14 @@ export function lowerPulseOrShift(op, chip) {
36
36
  // with NO timeout (the comment claimed "no overflow concern" but the
37
37
  // real risk was hanging the thread on a stuck pin). Apply the same
38
38
  // timeout-bounded start-edge wait as pulse.in (bug Q5).
39
- const pin = o.pin;
39
+ const pin = controllerRawPinForPin(chip, o.pin);
40
40
  const want = o.value;
41
- const dev = devForPin(chip, pin);
41
+ const dev = devForPin(chip, o.pin);
42
42
  const timeout = o.timeout ?? 3_000_000; // default 3s in us (long pulses)
43
43
  return {
44
44
  expression: `({ int64_t __max = static_cast<int64_t>(${timeout} / 1000); int64_t __t0 = k_uptime_get(); bool __ok = true; while (gpio_pin_get_raw(${dev}, ${pin}) != ${want}) { if ((k_uptime_get() - __t0) > __max) { __ok = false; break; } } int32_t __ret = 0; if (__ok) { int64_t __start = k_uptime_get(); while (gpio_pin_get_raw(${dev}, ${pin}) == ${want}) { } __ret = static_cast<int32_t>((k_uptime_get() - __start) * 1000); } __ret; })`,
45
45
  };
46
46
  }
47
- case 'shift.out': {
48
- // Bit-bang `value` (MSB or LSB first) via dataPin + clockPin. The two
49
- // pins may sit on different GPIO controllers on multi-controller SoCs
50
- // (ESP32-S3), so each is resolved to its own owning controller.
51
- const dataPin = o.dataPin;
52
- const clockPin = o.clockPin;
53
- const dataDev = devForPin(chip, dataPin);
54
- const clockDev = devForPin(chip, clockPin);
55
- const msbFirst = o.bitOrder === 'msb';
56
- const init = msbFirst ? '7' : '0';
57
- const test = msbFirst ? '(__i >= 0)' : '(__i < 8)';
58
- const step = msbFirst ? '__i--' : '__i++';
59
- return {
60
- code: `for (int __i = ${init}; ${test}; ${step}) { gpio_pin_set_raw(${dataDev}, ${dataPin}, (${o.value} >> __i) & 1); gpio_pin_set_raw(${clockDev}, ${clockPin}, 1); k_busy_wait(1); gpio_pin_set_raw(${clockDev}, ${clockPin}, 0); }`,
61
- };
62
- }
63
- case 'shift.in': {
64
- // Read 8 bits (MSB or LSB first) from dataPin, clocking on clockPin.
65
- const dataPin = o.dataPin;
66
- const clockPin = o.clockPin;
67
- const dataDev = devForPin(chip, dataPin);
68
- const clockDev = devForPin(chip, clockPin);
69
- const msbFirst = o.bitOrder === 'msb';
70
- const init = msbFirst ? '7' : '0';
71
- const test = msbFirst ? '(__i >= 0)' : '(__i < 8)';
72
- const step = msbFirst ? '__i--' : '__i++';
73
- const accum = msbFirst ? '__v = (__v << 1)' : '__v |= (bit << __i)';
74
- return {
75
- expression: `({ uint8_t __v = 0; for (int __i = ${init}; ${test}; ${step}) { gpio_pin_set_raw(${clockDev}, ${clockPin}, 1); k_busy_wait(1); int bit = gpio_pin_get_raw(${dataDev}, ${dataPin}); gpio_pin_set_raw(${clockDev}, ${clockPin}, 0); ${accum}; } __v; })`,
76
- };
77
- }
78
47
  default:
79
48
  throw new Error(`framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
80
49
  `Open an issue or use rawCpp() to emit it manually.`);
@@ -1,10 +1,39 @@
1
1
  import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
2
- import type { ZephyrChipDescriptor } from '../chips/types.js';
2
+ import type { ZephyrChipDescriptor, ZephyrPwmSpec } from '../chips/types.js';
3
+ /**
4
+ * The DT alias a PWM spec is addressed by. Board-shipped specs carry their
5
+ * alias in `dtSpec`; synthesized specs (controller + channel) get a
6
+ * `tc-pwm<pin>` alias that the overlay generator creates in
7
+ * <board>.overlay — both sides derive the name from the pin so they agree.
8
+ */
9
+ export declare function pwmDtAlias(spec: ZephyrPwmSpec): string;
10
+ /**
11
+ * The C macro token for a spec's alias. Zephyr's devicetree macros replace
12
+ * dashes in alias names with underscores (`pwm-led0` in DTS is
13
+ * DT_ALIAS(pwm_led0) in C) — the dashed spelling is a subtraction
14
+ * expression and fails to compile (caught by the blackpill E2E west build).
15
+ */
16
+ export declare function pwmDtAliasToken(spec: ZephyrPwmSpec): string;
17
+ /**
18
+ * Look up a PWM spec by HAL pin number. Matrix pins (ESP32 LEDC) synthesize
19
+ * a spec on the fly: the channel is inert here — the emitted C++ addresses
20
+ * the pin only via its `tc-pwm<pin>` alias (the DT pwms cell carries the
21
+ * real channel, assigned by the overlay generator over the driven pins).
22
+ */
23
+ export declare function findPwmSpec(chip: ZephyrChipDescriptor, pin: number): ZephyrPwmSpec | undefined;
3
24
  /**
4
25
  * Emit the per-channel PWM spec declarations. One per spec in the chip
5
- * descriptor. Called from shimLines when the program uses PWM.
26
+ * descriptor that the PROGRAM ACTUALLY DRIVES (`usedPins`) a spec for an
27
+ * untouched pin is unused code in the emitted TU (and would need a dead DT
28
+ * alias in the overlay). When `usedPins` is omitted (probe paths with no
29
+ * program), every spec is emitted. Called from shimLines when the program
30
+ * uses PWM.
6
31
  */
7
- export declare function pwmInitLines(chip: ZephyrChipDescriptor): string[];
32
+ export declare function pwmInitLines(chip: ZephyrChipDescriptor, usedPins?: ReadonlySet<number>, userSpecs?: readonly {
33
+ pin: number;
34
+ controller: string;
35
+ channel: number;
36
+ }[]): string[];
8
37
  /**
9
38
  * Resolve a HAL pwm.* op to Zephyr C++.
10
39
  * Returns `{ code }` for statement ops, `{ expression }` for value-returning ops.