@typecad/framework-zephyr 1.0.0-alpha.16 → 1.0.0-alpha.19

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 (210) hide show
  1. package/LICENSE +202 -21
  2. package/README.md +49 -87
  3. package/dist/as-built.d.ts +2 -2
  4. package/dist/as-built.js +2 -2
  5. package/dist/audit.d.ts +111 -0
  6. package/dist/audit.js +416 -0
  7. package/dist/boardgen.d.ts +1 -9
  8. package/dist/boardgen.js +288 -47
  9. package/dist/chips/resolve.js +20 -0
  10. package/dist/chips/types.d.ts +22 -1
  11. package/dist/debug-codegen.js +1 -1
  12. package/dist/display/bindings.d.ts +55 -0
  13. package/dist/display/bindings.js +316 -0
  14. package/dist/display/gfx.d.ts +2 -3
  15. package/dist/display/gfx.js +166 -154
  16. package/dist/display/index.js +20 -1
  17. package/dist/display/mipi-dbi-host.d.ts +9 -0
  18. package/dist/display/mipi-dbi-host.js +174 -0
  19. package/dist/display/profiles.d.ts +109 -4
  20. package/dist/display/profiles.js +270 -7
  21. package/dist/display/touch-adapter.js +119 -49
  22. package/dist/display/ui-adapter-eink.d.ts +2 -0
  23. package/dist/display/ui-adapter-eink.js +4 -0
  24. package/dist/display/ui-adapter-gray.d.ts +8 -0
  25. package/dist/display/ui-adapter-gray.js +170 -0
  26. package/dist/display/ui-adapter-mono.d.ts +13 -0
  27. package/dist/display/ui-adapter-mono.js +230 -0
  28. package/dist/display/ui-adapter-native.d.ts +10 -0
  29. package/dist/display/ui-adapter-native.js +295 -0
  30. package/dist/display/ui-adapter-shared.d.ts +11 -0
  31. package/dist/display/ui-adapter-shared.js +122 -0
  32. package/dist/display/ui-adapter.js +510 -558
  33. package/dist/doctor.js +4 -4
  34. package/dist/dt-config/custom-board.js +2 -2
  35. package/dist/dt-config/kconfig.d.ts +62 -1
  36. package/dist/dt-config/kconfig.js +141 -38
  37. package/dist/dt-config/overlay.d.ts +15 -2
  38. package/dist/dt-config/overlay.js +433 -18
  39. package/dist/framework.manifest.d.ts +10 -4
  40. package/dist/framework.manifest.js +133 -17
  41. package/dist/index.d.ts +2 -0
  42. package/dist/index.js +14 -6
  43. package/dist/licenses.d.ts +2 -2
  44. package/dist/licenses.js +13 -92
  45. package/dist/lowering/can.d.ts +25 -0
  46. package/dist/lowering/can.js +97 -0
  47. package/dist/lowering/clock.d.ts +17 -0
  48. package/dist/lowering/clock.js +58 -0
  49. package/dist/lowering/fs.js +1 -1
  50. package/dist/lowering/gpio.js +0 -32
  51. package/dist/lowering/hid.d.ts +27 -0
  52. package/dist/lowering/hid.js +244 -0
  53. package/dist/lowering/http.js +264 -32
  54. package/dist/lowering/i2c.d.ts +8 -0
  55. package/dist/lowering/i2c.js +137 -5
  56. package/dist/lowering/i2s.d.ts +27 -0
  57. package/dist/lowering/i2s.js +98 -0
  58. package/dist/lowering/index.d.ts +9 -1
  59. package/dist/lowering/index.js +25 -1
  60. package/dist/lowering/interrupts.js +6 -0
  61. package/dist/lowering/matrix.d.ts +15 -0
  62. package/dist/lowering/matrix.js +63 -0
  63. package/dist/lowering/mqtt.js +110 -8
  64. package/dist/lowering/power.d.ts +3 -2
  65. package/dist/lowering/power.js +20 -45
  66. package/dist/lowering/pwm.js +25 -0
  67. package/dist/lowering/sensor.d.ts +2 -2
  68. package/dist/lowering/sensor.js +8 -4
  69. package/dist/lowering/strip.d.ts +16 -0
  70. package/dist/lowering/strip.js +70 -0
  71. package/dist/lowering/thread.js +5 -1
  72. package/dist/lowering/trace.d.ts +44 -0
  73. package/dist/lowering/trace.js +239 -0
  74. package/dist/lowering/uart.js +6 -1
  75. package/dist/lowering/usb.d.ts +3 -1
  76. package/dist/lowering/usb.js +16 -13
  77. package/dist/lowering/wdt.js +2 -29
  78. package/dist/sbom.d.ts +181 -0
  79. package/dist/sbom.js +901 -0
  80. package/dist/sdk/board-catalog-sync.d.ts +1 -3
  81. package/dist/sdk/board-catalog-sync.js +4 -10
  82. package/dist/strategy.d.ts +82 -46
  83. package/dist/strategy.js +639 -182
  84. package/dist/tmp-probe.d.ts +2 -0
  85. package/dist/tmp-probe.js +9 -0
  86. package/dist/toolchain/debug-config.d.ts +50 -90
  87. package/dist/toolchain/debug-config.js +239 -510
  88. package/dist/toolchain/env-check.d.ts +1 -3
  89. package/dist/toolchain/env-check.js +2 -7
  90. package/dist/toolchain/index.d.ts +28 -3
  91. package/dist/toolchain/index.js +498 -62
  92. package/dist/toolchain/runners.d.ts +16 -0
  93. package/dist/toolchain/runners.js +75 -0
  94. package/dist/toolchain/scaffold.d.ts +5 -2
  95. package/dist/toolchain/scaffold.js +79 -15
  96. package/dist/toolchain/west-discover.d.ts +6 -0
  97. package/dist/toolchain/west-discover.js +36 -13
  98. package/dist/toolchain/west-spawn.js +8 -2
  99. package/dist/west-inventory.d.ts +25 -0
  100. package/dist/west-inventory.js +97 -0
  101. package/installer/README.md +328 -328
  102. package/installer/install.sh +2 -2
  103. package/installer/templates/project/.typecad/activate-zephyr.ps1 +1 -1
  104. package/installer/templates/project/.typecad/activate-zephyr.sh +1 -1
  105. package/installer/templates/project/.vscode/settings.json +1 -1
  106. package/installer/templates/project/README.md +2 -2
  107. package/package.json +5 -5
  108. package/src/as-built.ts +206 -206
  109. package/src/audit.ts +529 -0
  110. package/src/boardgen.ts +265 -50
  111. package/src/chips/resolve.ts +21 -0
  112. package/src/chips/types.ts +10 -1
  113. package/src/display/bindings.ts +347 -0
  114. package/src/display/gfx.ts +318 -306
  115. package/src/display/index.ts +87 -70
  116. package/src/display/mipi-dbi-host.ts +183 -0
  117. package/src/display/profiles.ts +458 -139
  118. package/src/display/touch-adapter.ts +119 -49
  119. package/src/display/ui-adapter-eink.ts +13 -0
  120. package/src/display/ui-adapter-gray.ts +178 -0
  121. package/src/display/ui-adapter-mono.ts +238 -0
  122. package/src/display/ui-adapter-native.ts +304 -0
  123. package/src/display/ui-adapter-shared.ts +125 -0
  124. package/src/display/ui-adapter.ts +732 -781
  125. package/src/doctor.ts +4 -4
  126. package/src/dt-config/custom-board.ts +2 -2
  127. package/src/dt-config/kconfig.ts +647 -505
  128. package/src/dt-config/overlay.ts +1475 -1058
  129. package/src/framework.manifest.ts +659 -535
  130. package/src/index.ts +16 -6
  131. package/src/licenses.ts +346 -425
  132. package/src/lowering/can.ts +140 -0
  133. package/src/lowering/clock.ts +91 -0
  134. package/src/lowering/fs.ts +135 -135
  135. package/src/lowering/gpio.ts +0 -33
  136. package/src/lowering/hid.ts +261 -0
  137. package/src/lowering/http.ts +264 -32
  138. package/src/lowering/i2c.ts +142 -5
  139. package/src/lowering/i2s.ts +143 -0
  140. package/src/lowering/index.ts +18 -1
  141. package/src/lowering/interrupts.ts +6 -0
  142. package/src/lowering/matrix.ts +70 -0
  143. package/src/lowering/mqtt.ts +109 -8
  144. package/src/lowering/power.ts +41 -0
  145. package/src/lowering/pwm.ts +192 -167
  146. package/src/lowering/sensor.ts +159 -155
  147. package/src/lowering/strip.ts +81 -0
  148. package/src/lowering/thread.ts +5 -1
  149. package/src/lowering/trace.ts +270 -0
  150. package/src/lowering/uart.ts +6 -1
  151. package/src/lowering/usb.ts +16 -13
  152. package/src/lowering/wdt.ts +2 -25
  153. package/src/sbom.ts +1117 -0
  154. package/src/sdk/board-catalog-sync.ts +4 -25
  155. package/src/strategy.ts +2680 -2309
  156. package/src/toolchain/debug-config.ts +262 -522
  157. package/src/toolchain/env-check.ts +279 -285
  158. package/src/toolchain/index.ts +1792 -1359
  159. package/src/toolchain/runners.ts +80 -0
  160. package/src/toolchain/scaffold.ts +355 -296
  161. package/src/toolchain/west-discover.ts +35 -13
  162. package/src/toolchain/west-spawn.ts +174 -168
  163. package/src/west-inventory.ts +102 -0
  164. package/dist/async/timer-polyfill.d.ts +0 -10
  165. package/dist/async/timer-polyfill.js +0 -95
  166. package/dist/chips/board-overrides.d.ts +0 -7
  167. package/dist/chips/board-overrides.js +0 -11
  168. package/dist/chips/esp32.d.ts +0 -2
  169. package/dist/chips/esp32.js +0 -71
  170. package/dist/chips/esp32s3.d.ts +0 -2
  171. package/dist/chips/esp32s3.js +0 -103
  172. package/dist/chips/soc/.d.ts +0 -2
  173. package/dist/chips/soc/.js +0 -129
  174. package/dist/chips/soc/esp32.d.ts +0 -2
  175. package/dist/chips/soc/esp32.js +0 -120
  176. package/dist/chips/soc/esp32c3.d.ts +0 -2
  177. package/dist/chips/soc/esp32c3.js +0 -90
  178. package/dist/chips/soc/esp32c6.d.ts +0 -2
  179. package/dist/chips/soc/esp32c6.js +0 -109
  180. package/dist/chips/soc/esp32s3.d.ts +0 -2
  181. package/dist/chips/soc/esp32s3.js +0 -189
  182. package/dist/chips/soc/index.d.ts +0 -2
  183. package/dist/chips/soc/index.js +0 -23
  184. package/dist/chips/soc/nrf52840.d.ts +0 -2
  185. package/dist/chips/soc/nrf52840.js +0 -130
  186. package/dist/chips/soc/rp2040.d.ts +0 -2
  187. package/dist/chips/soc/rp2040.js +0 -141
  188. package/dist/chips/soc/rp2350a.d.ts +0 -2
  189. package/dist/chips/soc/rp2350a.js +0 -145
  190. package/dist/chips/soc/samd21g18a.d.ts +0 -2
  191. package/dist/chips/soc/samd21g18a.js +0 -143
  192. package/dist/chips/soc/stm32f411xe.d.ts +0 -2
  193. package/dist/chips/soc/stm32f411xe.js +0 -251
  194. package/dist/chips/xiao-ble.d.ts +0 -2
  195. package/dist/chips/xiao-ble.js +0 -100
  196. package/dist/lowering/pulse.d.ts +0 -7
  197. package/dist/lowering/pulse.js +0 -51
  198. package/dist/lowering/tone.d.ts +0 -10
  199. package/dist/lowering/tone.js +0 -63
  200. package/dist/lowering/worker-backing.d.ts +0 -14
  201. package/dist/lowering/worker-backing.js +0 -79
  202. package/dist/lowering/worker.d.ts +0 -6
  203. package/dist/lowering/worker.js +0 -14
  204. package/dist/sdk/board-data.generated.d.ts +0 -2
  205. package/dist/sdk/board-data.generated.js +0 -4
  206. package/dist/sdk/catalog-walker.d.ts +0 -90
  207. package/dist/sdk/catalog-walker.js +0 -682
  208. package/dist/sdk/dts-reader.d.ts +0 -83
  209. package/dist/sdk/dts-reader.js +0 -596
  210. package/src/debug-codegen.ts +0 -207
@@ -1,167 +1,192 @@
1
- // ---------------------------------------------------------------------------
2
- // PWM lowering — pwm_dt_spec via pwm-led0 alias
3
- //
4
- // The XIAO nRF52840 exposes PWM via the `pwm-led0` devicetree alias (PWM_OUT0
5
- // on P0.17, inverted). The lowering emits a `pwm_dt_spec` per channel and uses
6
- // `pwm_set_pulse_dt` / `pwm_set_dt`. Duty is scaled from the Arduino-style
7
- // 0–255 (or 0–1023) range to nanoseconds against the spec's period.
8
- // ---------------------------------------------------------------------------
9
-
10
- import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
11
- import type { ZephyrChipDescriptor, ZephyrPwmSpec } from '../chips/types.js';
12
-
13
- /**
14
- * The DT alias a PWM spec is addressed by. Board-shipped specs carry their
15
- * alias in `dtSpec`; synthesized specs (controller + channel) get a
16
- * `tc-pwm<pin>` alias that the overlay generator creates in
17
- * <board>.overlay — both sides derive the name from the pin so they agree.
18
- */
19
- export function pwmDtAlias(spec: ZephyrPwmSpec): string {
20
- return spec.dtSpec ?? `tc-pwm${spec.pin}`;
21
- }
22
-
23
- /**
24
- * The C macro token for a spec's alias. Zephyr's devicetree macros replace
25
- * dashes in alias names with underscores (`pwm-led0` in DTS is
26
- * DT_ALIAS(pwm_led0) in C) — the dashed spelling is a subtraction
27
- * expression and fails to compile (caught by the blackpill E2E west build).
28
- */
29
- export function pwmDtAliasToken(spec: ZephyrPwmSpec): string {
30
- return pwmDtAlias(spec).replace(/-/g, '_');
31
- }
32
-
33
- /**
34
- * Look up a PWM spec by HAL pin number. Matrix pins (ESP32 LEDC) synthesize
35
- * a spec on the fly: the channel is inert here — the emitted C++ addresses
36
- * the pin only via its `tc-pwm<pin>` alias (the DT pwms cell carries the
37
- * real channel, assigned by the overlay generator over the driven pins).
38
- */
39
- export function findPwmSpec(chip: ZephyrChipDescriptor, pin: number): ZephyrPwmSpec | undefined {
40
- const spec = chip.pwm?.specs.find((s) => s.pin === pin);
41
- if (spec) return spec;
42
- const m = chip.pwm?.matrix;
43
- if (m && m.pins.includes(pin)) return { pin, controller: m.controller, channel: 0 };
44
- return undefined;
45
- }
46
-
47
- /** The C variable name emitted for a PWM channel's spec. */
48
- function pwmVarName(spec: ZephyrPwmSpec): string {
49
- return `__tc_pwm_${pwmDtAliasToken(spec)}`;
50
- }
51
-
52
- /**
53
- * Emit the per-channel PWM spec declarations. One per spec in the chip
54
- * descriptor that the PROGRAM ACTUALLY DRIVES (`usedPins`) — a spec for an
55
- * untouched pin is unused code in the emitted TU (and would need a dead DT
56
- * alias in the overlay). When `usedPins` is omitted (probe paths with no
57
- * program), every spec is emitted. Called from shimLines when the program
58
- * uses PWM.
59
- */
60
- export function pwmInitLines(
61
- chip: ZephyrChipDescriptor,
62
- usedPins?: ReadonlySet<number>,
63
- userSpecs?: readonly { pin: number; controller: string; channel: number }[],
64
- ): string[] {
65
- const lines: string[] = ['// CUTTLEFISH_PWM_BEGIN'];
66
- for (const spec of chip.pwm?.specs ?? []) {
67
- if (usedPins && !usedPins.has(spec.pin)) continue;
68
- lines.push(
69
- `static const struct pwm_dt_spec ${pwmVarName(spec)} = PWM_DT_SPEC_GET(DT_ALIAS(${pwmDtAliasToken(spec)}));`,
70
- );
71
- }
72
- // Inline-override pins (the escape hatch): the construction opts vouch for
73
- // controller+channel on a pin the manifest does not map — the alias var
74
- // the lowered calls reference (the DT node itself comes from the overlay
75
- // regen's marker merge).
76
- for (const spec of userSpecs ?? []) {
77
- if (chip.pwm?.specs.some((s) => s.pin === spec.pin)) continue;
78
- if (usedPins && !usedPins.has(spec.pin)) continue;
79
- lines.push(
80
- `static const struct pwm_dt_spec ${pwmVarName(spec)} = PWM_DT_SPEC_GET(DT_ALIAS(${pwmDtAliasToken(spec)}));`,
81
- );
82
- }
83
- // Matrix pins (ESP32 LEDC): one alias per driven pin, ascending — the same
84
- // order the overlay generator assigns channels in, though the C++ never
85
- // needs the channel (the DT pwms cell carries it). An omitted usage set
86
- // (probe paths) emits every matrix pin, mirroring the static behavior.
87
- const m = chip.pwm?.matrix;
88
- if (m) {
89
- const pins = (usedPins ? [...usedPins].filter((p) => m.pins.includes(p)) : [...m.pins])
90
- .sort((a, b) => a - b);
91
- for (const pin of pins) {
92
- const spec: ZephyrPwmSpec = { pin, controller: m.controller, channel: 0 };
93
- lines.push(
94
- `static const struct pwm_dt_spec ${pwmVarName(spec)} = PWM_DT_SPEC_GET(DT_ALIAS(${pwmDtAliasToken(spec)}));`,
95
- );
96
- }
97
- }
98
- lines.push('// CUTTLEFISH_PWM_END');
99
- return lines;
100
- }
101
-
102
- /**
103
- * Resolve a HAL pwm.* op to Zephyr C++.
104
- * Returns `{ code }` for statement ops, `{ expression }` for value-returning ops.
105
- */
106
- export function lowerPwm(
107
- op: HALOpIR,
108
- chip: ZephyrChipDescriptor,
109
- ): { code?: string; expression?: string } {
110
- const o = op as any;
111
- // Construction-time controller/channel overrides (hal/pwm-pin.ts opts):
112
- // the user vouches for the routing. The spec synthesizes from the
113
- // override (addressing rides the tc-pwm<pin> alias like a matrix pin) and
114
- // a marker comment carries controller+channel to the overlay regen, which
115
- // synthesizes the DT node — the transpiler cannot.
116
- const hasOverride = (typeof o.controllerOverride === 'string' && o.controllerOverride !== '')
117
- || (typeof o.channelOverride === 'number' && o.channelOverride >= 0);
118
- const spec = hasOverride
119
- ? {
120
- pin: o.pin as number,
121
- controller: (o.controllerOverride as string | undefined) ?? 'pwm0',
122
- channel: (o.channelOverride as number | undefined) ?? 0,
123
- }
124
- : findPwmSpec(chip, o.pin);
125
- if (!spec) {
126
- // Probe / unlisted pin: return a comment so the resolver reports non-
127
- // undefined (the manifest validator's probe sends pin:0 with no spec).
128
- // A real program pins the descriptor's pwm.specs entry.
129
- return { code: `/* pwm on pin ${o.pin}: no PWM spec in chip descriptor */` };
130
- }
131
- const v = pwmVarName(spec);
132
- // The overlay regen (toolchain) parses this into a synthesized pwm-leds
133
- // spec: pin → controller/channel. Comment placement inside the block
134
- // braces is legal C.
135
- const marker = hasOverride
136
- ? `/* cuttlefish-user-facts: pwm pin=${spec.pin} controller=${spec.controller} channel=${spec.channel} */ `
137
- : '';
138
-
139
- switch (op.operation) {
140
- // ── Thin PWM (hal/pwm-pin.ts) — ns-true verbs ──────────────────────────
141
- // Zephyr 4.4 has pwm_set_dt (period + pulse) and pwm_set_pulse_dt (pulse
142
- // only) — no period-only setter. The construction period is established
143
- // once via pwm_set_dt(period, pulse 0 = line idle), then every set is one
144
- // pwm_set_pulse_dt. No 0–255 scaling anywhere.
145
- case 'pwm.set_pulse': {
146
- return {
147
- code: `{ ${marker}static bool __tc_pwm_p${o.pin}_prd = false; if (!__tc_pwm_p${o.pin}_prd) { (void)pwm_set_dt(&${v}, ${o.periodNs}, 0); __tc_pwm_p${o.pin}_prd = true; } (void)pwm_set_pulse_dt(&${v}, ${o.pulseNs}); }`,
148
- };
149
- }
150
- case 'pwm.set_duty': {
151
- // duty is 0.0–1.0; pulse = duty × the construction period.
152
- return {
153
- code: `{ ${marker}static bool __tc_pwm_p${o.pin}_prd = false; if (!__tc_pwm_p${o.pin}_prd) { (void)pwm_set_dt(&${v}, ${o.periodNs}, 0); __tc_pwm_p${o.pin}_prd = true; } (void)pwm_set_pulse_dt(&${v}, static_cast<uint32_t>(static_cast<double>(${o.duty}) * static_cast<double>(${o.periodNs}))); }`,
154
- };
155
- }
156
- case 'pwm.set_period': {
157
- // No period-only API: pwm_set_dt applies the new period and resets the
158
- // pulse to idle — follow with setPulse/setDuty to drive the line.
159
- return { code: `${marker}(void)pwm_set_dt(&${v}, ${o.periodNs}, 0);` };
160
- }
161
- default:
162
- throw new Error(
163
- `framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
164
- `Open an issue or use rawCpp() to emit it manually.`,
165
- );
166
- }
167
- }
1
+ // ---------------------------------------------------------------------------
2
+ // PWM lowering — pwm_dt_spec via pwm-led0 alias
3
+ //
4
+ // The XIAO nRF52840 exposes PWM via the `pwm-led0` devicetree alias (PWM_OUT0
5
+ // on P0.17, inverted). The lowering emits a `pwm_dt_spec` per channel and uses
6
+ // `pwm_set_pulse_dt` / `pwm_set_dt`. Duty is scaled from the Arduino-style
7
+ // 0–255 (or 0–1023) range to nanoseconds against the spec's period.
8
+ // ---------------------------------------------------------------------------
9
+
10
+ import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
11
+ import type { ZephyrChipDescriptor, ZephyrPwmSpec } from '../chips/types.js';
12
+
13
+ /**
14
+ * The DT alias a PWM spec is addressed by. Board-shipped specs carry their
15
+ * alias in `dtSpec`; synthesized specs (controller + channel) get a
16
+ * `tc-pwm<pin>` alias that the overlay generator creates in
17
+ * <board>.overlay — both sides derive the name from the pin so they agree.
18
+ */
19
+ export function pwmDtAlias(spec: ZephyrPwmSpec): string {
20
+ return spec.dtSpec ?? `tc-pwm${spec.pin}`;
21
+ }
22
+
23
+ /**
24
+ * The C macro token for a spec's alias. Zephyr's devicetree macros replace
25
+ * dashes in alias names with underscores (`pwm-led0` in DTS is
26
+ * DT_ALIAS(pwm_led0) in C) — the dashed spelling is a subtraction
27
+ * expression and fails to compile (caught by the blackpill E2E west build).
28
+ */
29
+ export function pwmDtAliasToken(spec: ZephyrPwmSpec): string {
30
+ return pwmDtAlias(spec).replace(/-/g, '_');
31
+ }
32
+
33
+ /**
34
+ * Look up a PWM spec by HAL pin number. Matrix pins (ESP32 LEDC) synthesize
35
+ * a spec on the fly: the channel is inert here — the emitted C++ addresses
36
+ * the pin only via its `tc-pwm<pin>` alias (the DT pwms cell carries the
37
+ * real channel, assigned by the overlay generator over the driven pins).
38
+ */
39
+ export function findPwmSpec(chip: ZephyrChipDescriptor, pin: number): ZephyrPwmSpec | undefined {
40
+ const spec = chip.pwm?.specs.find((s) => s.pin === pin);
41
+ if (spec) return spec;
42
+ const m = chip.pwm?.matrix;
43
+ if (m && m.pins.includes(pin)) return { pin, controller: m.controller, channel: 0 };
44
+ return undefined;
45
+ }
46
+
47
+ /** The C variable name emitted for a PWM channel's spec. */
48
+ function pwmVarName(spec: ZephyrPwmSpec): string {
49
+ return `__tc_pwm_${pwmDtAliasToken(spec)}`;
50
+ }
51
+
52
+ /**
53
+ * Emit the per-channel PWM spec declarations. One per spec in the chip
54
+ * descriptor that the PROGRAM ACTUALLY DRIVES (`usedPins`) — a spec for an
55
+ * untouched pin is unused code in the emitted TU (and would need a dead DT
56
+ * alias in the overlay). When `usedPins` is omitted (probe paths with no
57
+ * program), every spec is emitted. Called from shimLines when the program
58
+ * uses PWM.
59
+ */
60
+ export function pwmInitLines(
61
+ chip: ZephyrChipDescriptor,
62
+ usedPins?: ReadonlySet<number>,
63
+ userSpecs?: readonly { pin: number; controller: string; channel: number }[],
64
+ ): string[] {
65
+ const lines: string[] = ['// CUTTLEFISH_PWM_BEGIN'];
66
+ for (const spec of chip.pwm?.specs ?? []) {
67
+ if (usedPins && !usedPins.has(spec.pin)) continue;
68
+ lines.push(
69
+ `static const struct pwm_dt_spec ${pwmVarName(spec)} = PWM_DT_SPEC_GET(DT_ALIAS(${pwmDtAliasToken(spec)}));`,
70
+ );
71
+ }
72
+ // Inline-override pins (the escape hatch): the construction opts vouch for
73
+ // controller+channel on a pin the manifest does not map — the alias var
74
+ // the lowered calls reference (the DT node itself comes from the overlay
75
+ // regen's marker merge).
76
+ for (const spec of userSpecs ?? []) {
77
+ if (chip.pwm?.specs.some((s) => s.pin === spec.pin)) continue;
78
+ if (usedPins && !usedPins.has(spec.pin)) continue;
79
+ lines.push(
80
+ `static const struct pwm_dt_spec ${pwmVarName(spec)} = PWM_DT_SPEC_GET(DT_ALIAS(${pwmDtAliasToken(spec)}));`,
81
+ );
82
+ }
83
+ // Matrix pins (ESP32 LEDC): one alias per driven pin, ascending — the same
84
+ // order the overlay generator assigns channels in, though the C++ never
85
+ // needs the channel (the DT pwms cell carries it). An omitted usage set
86
+ // (probe paths) emits every matrix pin, mirroring the static behavior.
87
+ const m = chip.pwm?.matrix;
88
+ if (m) {
89
+ const pins = (usedPins ? [...usedPins].filter((p) => m.pins.includes(p)) : [...m.pins])
90
+ .sort((a, b) => a - b);
91
+ for (const pin of pins) {
92
+ const spec: ZephyrPwmSpec = { pin, controller: m.controller, channel: 0 };
93
+ lines.push(
94
+ `static const struct pwm_dt_spec ${pwmVarName(spec)} = PWM_DT_SPEC_GET(DT_ALIAS(${pwmDtAliasToken(spec)}));`,
95
+ );
96
+ }
97
+ }
98
+ lines.push('// CUTTLEFISH_PWM_END');
99
+ return lines;
100
+ }
101
+
102
+ /**
103
+ * Resolve a HAL pwm.* op to Zephyr C++.
104
+ * Returns `{ code }` for statement ops, `{ expression }` for value-returning ops.
105
+ */
106
+ export function lowerPwm(
107
+ op: HALOpIR,
108
+ chip: ZephyrChipDescriptor,
109
+ ): { code?: string; expression?: string } {
110
+ const o = op as any;
111
+ // Construction-time controller/channel overrides (hal/pwm-pin.ts opts):
112
+ // the user vouches for the routing. The spec synthesizes from the
113
+ // override (addressing rides the tc-pwm<pin> alias like a matrix pin) and
114
+ // a marker comment carries controller+channel to the overlay regen, which
115
+ // synthesizes the DT node — the transpiler cannot.
116
+ const hasOverride = (typeof o.controllerOverride === 'string' && o.controllerOverride !== '')
117
+ || (typeof o.channelOverride === 'number' && o.channelOverride >= 0);
118
+ const spec = hasOverride
119
+ ? {
120
+ pin: o.pin as number,
121
+ controller: (o.controllerOverride as string | undefined) ?? 'pwm0',
122
+ channel: (o.channelOverride as number | undefined) ?? 0,
123
+ }
124
+ : findPwmSpec(chip, o.pin);
125
+ if (!spec) {
126
+ // Probe / unlisted pin: return a comment so the resolver reports non-
127
+ // undefined (the manifest validator's probe sends pin:0 with no spec).
128
+ // A real program pins the descriptor's pwm.specs entry.
129
+ return { code: `/* pwm on pin ${o.pin}: no PWM spec in chip descriptor */` };
130
+ }
131
+ const v = pwmVarName(spec);
132
+ // The overlay regen (toolchain) parses this into a synthesized pwm-leds
133
+ // spec: pin → controller/channel. Comment placement inside the block
134
+ // braces is legal C.
135
+ const marker = hasOverride
136
+ ? `/* cuttlefish-user-facts: pwm pin=${spec.pin} controller=${spec.controller} channel=${spec.channel} */ `
137
+ : '';
138
+
139
+ switch (op.operation) {
140
+ // ── Thin PWM (hal/pwm-pin.ts) — ns-true verbs ──────────────────────────
141
+ // Zephyr 4.4 has pwm_set_dt (period + pulse) and pwm_set_pulse_dt (pulse
142
+ // only) — no period-only setter. The construction period is established
143
+ // once via pwm_set_dt(period, pulse 0 = line idle), then every set is one
144
+ // pwm_set_pulse_dt. No 0–255 scaling anywhere.
145
+ case 'pwm.set_pulse': {
146
+ return {
147
+ code: `{ ${marker}static bool __tc_pwm_p${o.pin}_prd = false; if (!__tc_pwm_p${o.pin}_prd) { (void)pwm_set_dt(&${v}, ${o.periodNs}, 0); __tc_pwm_p${o.pin}_prd = true; } (void)pwm_set_pulse_dt(&${v}, ${o.pulseNs}); }`,
148
+ };
149
+ }
150
+ case 'pwm.set_duty': {
151
+ // duty is 0.0–1.0; pulse = duty × the construction period.
152
+ return {
153
+ code: `{ ${marker}static bool __tc_pwm_p${o.pin}_prd = false; if (!__tc_pwm_p${o.pin}_prd) { (void)pwm_set_dt(&${v}, ${o.periodNs}, 0); __tc_pwm_p${o.pin}_prd = true; } (void)pwm_set_pulse_dt(&${v}, static_cast<uint32_t>(static_cast<double>(${o.duty}) * static_cast<double>(${o.periodNs}))); }`,
154
+ };
155
+ }
156
+ case 'pwm.set_period': {
157
+ // No period-only API: pwm_set_dt applies the new period and resets the
158
+ // pulse to idle — follow with setPulse/setDuty to drive the line.
159
+ return { code: `${marker}(void)pwm_set_dt(&${v}, ${o.periodNs}, 0);` };
160
+ }
161
+ // ── Servo (hal/servo.ts) — calibrated 50 Hz sugar on the same channel ──
162
+ // The commanded value is runtime text; the calibrated range and travel
163
+ // are construction literals carried on the op. Clamping and the angle→
164
+ // pulse mapping run in the emitted C++ — they cannot fold at transpile
165
+ // time. Pulse widths convert µs→ns here (×1000).
166
+ case 'pwm.servo_us': {
167
+ const minNs = Math.round(Number(o.minUs ?? 1000) * 1000);
168
+ const maxNs = Math.round(Number(o.maxUs ?? 2000) * 1000);
169
+ return {
170
+ code: `{ ${marker}static bool __tc_pwm_p${o.pin}_prd = false; if (!__tc_pwm_p${o.pin}_prd) { (void)pwm_set_dt(&${v}, ${o.periodNs}, 0); __tc_pwm_p${o.pin}_prd = true; } uint32_t __tc_sv_p${o.pin} = static_cast<uint32_t>(${o.us}) * 1000U; if (__tc_sv_p${o.pin} < ${minNs}U) { __tc_sv_p${o.pin} = ${minNs}U; } if (__tc_sv_p${o.pin} > ${maxNs}U) { __tc_sv_p${o.pin} = ${maxNs}U; } (void)pwm_set_pulse_dt(&${v}, __tc_sv_p${o.pin}); }`,
171
+ };
172
+ }
173
+ case 'pwm.servo_angle': {
174
+ const minNs = Math.round(Number(o.minUs ?? 1000) * 1000);
175
+ const maxNs = Math.round(Number(o.maxUs ?? 2000) * 1000);
176
+ const maxAngle = Number(o.maxAngle ?? 180) || 180;
177
+ return {
178
+ code: `{ ${marker}static bool __tc_pwm_p${o.pin}_prd = false; if (!__tc_pwm_p${o.pin}_prd) { (void)pwm_set_dt(&${v}, ${o.periodNs}, 0); __tc_pwm_p${o.pin}_prd = true; } double __tc_sv_a${o.pin} = static_cast<double>(${o.angle}); if (__tc_sv_a${o.pin} < 0.0) { __tc_sv_a${o.pin} = 0.0; } if (__tc_sv_a${o.pin} > ${maxAngle}.0) { __tc_sv_a${o.pin} = ${maxAngle}.0; } (void)pwm_set_pulse_dt(&${v}, static_cast<uint32_t>(static_cast<double>(${minNs}) + (__tc_sv_a${o.pin} * ((static_cast<double>(${maxNs}) - static_cast<double>(${minNs})) / static_cast<double>(${maxAngle}.0))))); }`,
179
+ };
180
+ }
181
+ case 'pwm.servo_idle': {
182
+ return {
183
+ code: `{ ${marker}static bool __tc_pwm_p${o.pin}_prd = false; if (!__tc_pwm_p${o.pin}_prd) { (void)pwm_set_dt(&${v}, ${o.periodNs}, 0); __tc_pwm_p${o.pin}_prd = true; } (void)pwm_set_pulse_dt(&${v}, 0U); }`,
184
+ };
185
+ }
186
+ default:
187
+ throw new Error(
188
+ `framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
189
+ `Open an issue or use rawCpp() to emit it manually.`,
190
+ );
191
+ }
192
+ }