@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
@@ -1,102 +1,222 @@
1
- // ---------------------------------------------------------------------------
2
- // ADC lowering — nRF SAADC via raw adc_read
3
- //
4
- // The XIAO nRF52840 has no pre-declared ADC channel nodes in devicetree, so we
5
- // cannot use the `adc_dt_spec` convenience. Instead the lowering configures the
6
- // channel up-front against DEVICE_DT_GET(DT_NODELABEL(adc)) and reads it with
7
- // `adc_read`. The channel index comes from the chip descriptor's pin→channel
8
- // map (XIAO D0–D3 = AIN0–AIN3).
9
- //
10
- // Channel setup is emitted in adcInitLines() (one per channel used); the ops
11
- // then just read. A static `adc_channel_cfg` + a per-channel "configured" flag
12
- // guards the setup, so repeated reads don't reconfigure.
13
- // ---------------------------------------------------------------------------
14
-
15
- import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
16
- import type { ZephyrChipDescriptor } from '../chips/types.js';
17
-
18
- /** Resolve the SAADC channel index for a HAL pin number; -1 if unmapped. */
19
- export function adcChannelForPin(chip: ZephyrChipDescriptor, pin: number): number {
20
- const ch = chip.adc?.channels.find((c) => c.pin === pin);
21
- return ch ? ch.channel : -1;
22
- }
23
-
24
- /**
25
- * Emit the per-channel ADC setup state. One block per channel in the chip
26
- * descriptor, each guarded by a `static bool __tc_adc<N>_ready` so the first
27
- * read configures it and subsequent reads skip. Called from shimLines when
28
- * the program uses ADC.
29
- */
30
- export function adcInitLines(chip: ZephyrChipDescriptor): string[] {
31
- const dev = `DEVICE_DT_GET(DT_NODELABEL(${chip.adc?.nodeLabel ?? 'adc'}))`;
32
- const res = chip.adc?.resolution ?? 12;
33
- const vref = chip.adc?.vrefMv ?? 3000;
34
- const lines: string[] = ['// CUTTLEFISH_ADC_BEGIN'];
35
- lines.push(`static const struct device* __tc_adc_dev = ${dev};`);
36
- for (const c of chip.adc?.channels ?? []) {
37
- const n = c.channel;
38
- lines.push(
39
- `static bool __tc_adc${n}_ready = false;`,
40
- `static void __tc_adc${n}_setup(void) {`,
41
- ` if (__tc_adc${n}_ready) return;`,
42
- ` const struct adc_channel_cfg cfg = {`,
43
- ` .gain = ADC_GAIN_1_4,`,
44
- ` .reference = ADC_REF_INTERNAL,`,
45
- ` .acquisition_time = ADC_ACQ_TIME_DEFAULT,`,
46
- ` .channel_id = ${n},`,
47
- ` .differential = 0,`,
48
- ` };`,
49
- ` adc_channel_setup(__tc_adc_dev, &cfg);`,
50
- ` __tc_adc${n}_ready = true;`,
51
- `}`,
52
- );
53
- }
54
- lines.push(`#define __TC_ADC_VREF_MV ${vref}`);
55
- lines.push(`#define __TC_ADC_RESOLUTION ${res}`);
56
- lines.push('// CUTTLEFISH_ADC_END');
57
- return lines;
58
- }
59
-
60
- /**
61
- * Resolve a HAL adc.* op to Zephyr C++.
62
- * Returns `{ code }` for statement ops, `{ expression }` for value-returning ops.
63
- */
64
- export function lowerAdc(
65
- op: HALOpIR,
66
- chip: ZephyrChipDescriptor,
67
- ): { code?: string; expression?: string } {
68
- const o = op as any;
69
- const res = chip.adc?.resolution ?? 12;
70
- const vref = chip.adc?.vrefMv ?? 3000;
71
-
72
- switch (op.operation) {
73
- case 'adc.read': {
74
- const ch = adcChannelForPin(chip, o.pin);
75
- // GCC statement-expression: configures-on-first-call, reads, returns raw.
76
- return {
77
- expression: `({ __tc_adc${ch}_setup(); int16_t __b = 0; struct adc_sequence __s = { .channels = BIT(${ch}), .buffer = &__b, .buffer_size = sizeof(__b), .resolution = ${res} }; adc_read(__tc_adc_dev, &__s); __b; })`,
78
- };
79
- }
80
- case 'adc.read_voltage': {
81
- const ch = adcChannelForPin(chip, o.pin);
82
- // Read raw, convert to millivolts via adc_raw_to_millivolts (gain 1/4,
83
- // internal ref). Returns mV as int.
84
- return {
85
- expression: `({ __tc_adc${ch}_setup(); int16_t __b = 0; struct adc_sequence __s = { .channels = BIT(${ch}), .buffer = &__b, .buffer_size = sizeof(__b), .resolution = ${res} }; adc_read(__tc_adc_dev, &__s); int32_t __v = __b; adc_raw_to_millivolts(${vref}, ADC_GAIN_1_4, ${res}, &__v); __v; })`,
86
- };
87
- }
88
- case 'adc.get_resolution':
89
- return { expression: String(res) };
90
- case 'adc.set_reference':
91
- // Zephyr configures the reference at channel-setup time; runtime switching
92
- // would require re-setup. Record the intent as a no-op statement.
93
- return { code: `/* adc.set_reference(${o.reference}): configured at channel setup (ADC_REF_INTERNAL) */` };
94
- case 'adc.get_reference':
95
- return { expression: `0 /* DEFAULT (ADC_REF_INTERNAL) */` };
96
- default:
97
- throw new Error(
98
- `framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
99
- `Open an issue or use rawCpp() to emit it manually.`,
100
- );
101
- }
102
- }
1
+ // ---------------------------------------------------------------------------
2
+ // ADC lowering — nRF SAADC via raw adc_read
3
+ //
4
+ // The XIAO nRF52840 has no pre-declared ADC channel nodes in devicetree, so we
5
+ // cannot use the `adc_dt_spec` convenience. Instead the lowering configures the
6
+ // channel up-front against DEVICE_DT_GET(DT_NODELABEL(adc)) and reads it with
7
+ // `adc_read`. The channel index comes from the chip descriptor's pin→channel
8
+ // map (XIAO D0–D3 = AIN0–AIN3).
9
+ //
10
+ // Channel setup is emitted in adcInitLines() (one per channel used); the ops
11
+ // then just read. A static `adc_channel_cfg` + a per-channel "configured" flag
12
+ // guards the setup, so repeated reads don't reconfigure.
13
+ // ---------------------------------------------------------------------------
14
+
15
+ import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
16
+ import type { ZephyrChipDescriptor } from '../chips/types.js';
17
+ import { ZEPHYR_ADC_GAINS, ZEPHYR_ADC_REFERENCES } from '@typecad/hal';
18
+
19
+ /**
20
+ * Resolve the SAADC channel index for an adc.read argument; -1 if unmapped.
21
+ *
22
+ * The numeric argument serves two HAL forms: `InputPin.readAnalog()` passes
23
+ * a GPIO number, while the Arduino-compat `ADC.read(channel)` passes an ADC
24
+ * channel number. Pin-first resolution keeps readAnalog behavior unchanged;
25
+ * the channel fallback makes `ADC.read(n)` resolve on targets where channel
26
+ * n does not alias GPIO n (e.g. the XIAO's AIN2 = P0.28). Where a number
27
+ * matches both a mapped pin and another channel's index, the pin wins.
28
+ */
29
+ export function adcChannelForPin(chip: ZephyrChipDescriptor, pin: number): number {
30
+ const channels = chip.adc?.channels ?? [];
31
+ const byPin = channels.find((c) => c.pin === pin);
32
+ if (byPin) return byPin.channel;
33
+ const byChannel = channels.find((c) => c.channel === pin);
34
+ return byChannel ? byChannel.channel : -1;
35
+ }
36
+
37
+ /**
38
+ * Resolve the owning ADC controller's node label for an adc.read argument.
39
+ * Defaults to the descriptor's primary (`chip.adc.nodeLabel`) channels on
40
+ * additional controllers (STM32 adc3-only routes, ESP32 adc2 pads) carry
41
+ * their controller explicitly, and their device handle is
42
+ * `__tc_adc_<label>_dev` (the primary keeps the unsuffixed `__tc_adc_dev`
43
+ * so single-controller output is unchanged).
44
+ */
45
+ export function adcControllerForPin(chip: ZephyrChipDescriptor, pin: number): string {
46
+ const channels = chip.adc?.channels ?? [];
47
+ const byPin = channels.find((c) => c.pin === pin);
48
+ if (byPin) return byPin.controller ?? chip.adc?.nodeLabel ?? 'adc';
49
+ const byChannel = channels.find((c) => c.channel === pin);
50
+ return byChannel?.controller ?? chip.adc?.nodeLabel ?? 'adc';
51
+ }
52
+
53
+ /** The C++ device-handle symbol for an ADC controller nodelabel. The primary
54
+ * controller maps to the historical unsuffixed `__tc_adc_dev`. */
55
+ function adcDevSymbol(controller: string, primary: string): string {
56
+ return controller === primary ? '__tc_adc_dev' : `__tc_adc_${controller}_dev`;
57
+ }
58
+
59
+ // ── Thin ADC (hal/adc-pin.ts) — gain/reference tokens ──────────────────────
60
+ //
61
+ // "ADC.GAIN_1_4" / "ADC.REF_INTERNAL" token text maps name-for-
62
+ // name onto the enum adc_gain / adc_reference macros. The name sets come from
63
+ // the GENERATED Zephyr token tables (scripts/gen-zephyr-hal-tokens.mjs —
64
+ // parsed from the pinned tree's headers, so the set cannot drift from
65
+ // upstream); empty/absent tokens mean "use the chip descriptor's pair".
66
+
67
+ const ADC_GAIN_TOKENS: Record<string, string> = Object.fromEntries(
68
+ ZEPHYR_ADC_GAINS.map((g) => [`ADC.GAIN_${g}`, `ADC_GAIN_${g}`]),
69
+ );
70
+ const ADC_REF_TOKENS: Record<string, string> = Object.fromEntries(
71
+ ZEPHYR_ADC_REFERENCES.map((r) => [`ADC.REF_${r}`, `ADC_REF_${r}`]),
72
+ );
73
+
74
+ /** Resolve one thin-ADC token to its macro; undefined when absent/empty.
75
+ * Unknown tokens are build errors naming the valid spellings. */
76
+ function adcThinToken(token: string | undefined, map: Record<string, string>, kind: string): string | undefined {
77
+ const t = String(token ?? '').trim();
78
+ if (!t || t === '0' || t === "''") return undefined;
79
+ const m = map[t];
80
+ if (!m) {
81
+ throw new Error(
82
+ `ADC ${kind} token '${t}' is not a known ADC.* token — valid ${kind}s: ${Object.keys(map).join(', ')}.`,
83
+ );
84
+ }
85
+ return m;
86
+ }
87
+
88
+ /**
89
+ * Emit the per-channel ADC setup state. One block per channel in the chip
90
+ * descriptor that the PROGRAM ACTUALLY READS (`usedPins`) — an unread
91
+ * channel's `static` setup function would trip -Wunused-function in the
92
+ * single generated TU. When `usedPins` is omitted (probe paths with no
93
+ * program), every descriptor channel is emitted. Each block is guarded by a
94
+ * `static bool __tc_adc<N>_ready` so the first read configures it and
95
+ * subsequent reads skip. Called from shimLines when the program uses ADC.
96
+ */
97
+ export function adcInitLines(
98
+ chip: ZephyrChipDescriptor,
99
+ usedPins?: ReadonlySet<number>,
100
+ overrideDevices?: ReadonlySet<string>,
101
+ ): string[] {
102
+ const primary = chip.adc?.nodeLabel ?? 'adc';
103
+ const res = chip.adc?.resolution ?? 12;
104
+ const vref = chip.adc?.vrefMv ?? 3000;
105
+ // Gain/reference are SoC-specific: the nRF SAADC scheme (gain 1/4 against
106
+ // the 0.6V internal ref, vref-mv 3000 = VDD) is the default; the STM32
107
+ // driver requires exactly ADC_GAIN_1 + ADC_REF_INTERNAL (Zephyr maps
108
+ // "internal" to the VREF+ pad) with vref-mv = VDDA. The descriptor carries
109
+ // the SoC's pair so the emitted channel setup validates in the driver.
110
+ const gain = chip.adc?.gain ?? 'ADC_GAIN_1_4';
111
+ const reference = chip.adc?.reference ?? 'ADC_REF_INTERNAL';
112
+ const owns = (c: { controller?: string }, label: string) => (c.controller ?? primary) === label;
113
+ const channels = (chip.adc?.channels ?? []).filter(
114
+ (c) =>
115
+ !usedPins ||
116
+ // Pin-form usage (the ADC/readAnalog path): the exact channel.
117
+ [...usedPins].some((n) => n === c.pin) ||
118
+ // Channel-number fallback (the Arduino-compat ADC.read(n) form) — must
119
+ // match the CONTROLLER too, or colliding indices across controllers
120
+ // (both ESP32 SARADC units expose channel 0) over-include.
121
+ [...usedPins].some((n) => adcChannelForPin(chip, n) === c.channel && owns(c, adcControllerForPin(chip, n))),
122
+ );
123
+ const lines: string[] = ['// CUTTLEFISH_ADC_BEGIN'];
124
+ lines.push(`static const struct device* __tc_adc_dev = DEVICE_DT_GET(DT_NODELABEL(${primary}));`);
125
+ // Additional controllers referenced by used channels get their own device
126
+ // handle (sorted for deterministic output). Channel indices collide across
127
+ // controllers, so non-primary setup symbols carry the controller label.
128
+ const extraControllers = [...new Set(channels.map((c) => c.controller).filter((x): x is string => !!x && x !== primary))].sort();
129
+ // Inline-override devices (escape hatch): handles for the DT labels the
130
+ // construction opts name — they may not appear in the manifest at all.
131
+ for (const label of (overrideDevices ?? [])) {
132
+ if (label !== primary && !extraControllers.includes(label)) extraControllers.push(label);
133
+ }
134
+ for (const label of extraControllers) {
135
+ lines.push(`static const struct device* __tc_adc_${label}_dev = DEVICE_DT_GET(DT_NODELABEL(${label}));`);
136
+ }
137
+ for (const c of channels) {
138
+ const n = c.channel;
139
+ const dev = adcDevSymbol(c.controller ?? primary, primary);
140
+ const sym = c.controller && c.controller !== primary ? `__tc_adc_${c.controller}_${n}` : `__tc_adc${n}`;
141
+ lines.push(
142
+ `static bool ${sym}_ready = false;`,
143
+ `static void ${sym}_setup(void) {`,
144
+ ` if (${sym}_ready) return;`,
145
+ ` const struct adc_channel_cfg cfg = {`,
146
+ ` .gain = ${gain},`,
147
+ ` .reference = ${reference},`,
148
+ ` .acquisition_time = ADC_ACQ_TIME_DEFAULT,`,
149
+ ` .channel_id = ${n},`,
150
+ ` .differential = 0,`,
151
+ ` };`,
152
+ ` adc_channel_setup(${dev}, &cfg);`,
153
+ ` ${sym}_ready = true;`,
154
+ `}`,
155
+ );
156
+ }
157
+ lines.push(`#define __TC_ADC_VREF_MV ${vref}`);
158
+ lines.push(`#define __TC_ADC_RESOLUTION ${res}`);
159
+ lines.push('// CUTTLEFISH_ADC_END');
160
+ return lines;
161
+ }
162
+
163
+ /**
164
+ * Resolve a HAL adc.* op to Zephyr C++.
165
+ * Returns `{ code }` for statement ops, `{ expression }` for value-returning ops.
166
+ */
167
+ export function lowerAdc(
168
+ op: HALOpIR,
169
+ chip: ZephyrChipDescriptor,
170
+ ): { code?: string; expression?: string } {
171
+ const o = op as any;
172
+ const res = chip.adc?.resolution ?? 12;
173
+ const vref = chip.adc?.vrefMv ?? 3000;
174
+ const gain = chip.adc?.gain ?? 'ADC_GAIN_1_4';
175
+ const reference = chip.adc?.reference ?? 'ADC_REF_INTERNAL';
176
+
177
+ switch (op.operation) {
178
+ case 'adc.read_raw':
179
+ case 'adc.read_mv': {
180
+ // Construction-time overrides (hal/adc-pin.ts opts: channel/device/
181
+ // pinctrl): the user vouches for the routing on a pin the facts layer
182
+ // does not cover. The channel and device symbol come from the op; the
183
+ // marker comment carries device/pinctrl to the overlay regen (the
184
+ // transpiler cannot synthesize DT nodes) and suppresses the
185
+ // unavailable-pin diagnostic.
186
+ const hasOverride = (typeof o.channelOverride === 'number' && o.channelOverride >= 0)
187
+ || (typeof o.deviceOverride === 'string' && o.deviceOverride !== '')
188
+ || (typeof o.pinctrlOverride === 'string' && o.pinctrlOverride !== '');
189
+ const ch = hasOverride && typeof o.channelOverride === 'number' && o.channelOverride >= 0
190
+ ? (o.channelOverride as number)
191
+ : adcChannelForPin(chip, o.pin);
192
+ const primary = chip.adc?.nodeLabel ?? 'adc';
193
+ const devCtrl = (typeof o.deviceOverride === 'string' && o.deviceOverride !== '')
194
+ ? (o.deviceOverride as string)
195
+ : adcControllerForPin(chip, o.pin);
196
+ const dev = adcDevSymbol(devCtrl, primary);
197
+ const gainMacro = adcThinToken(o.gain, ADC_GAIN_TOKENS, 'gain');
198
+ const refMacro = adcThinToken(o.reference, ADC_REF_TOKENS, 'reference');
199
+ const g = gainMacro ?? gain;
200
+ const r = refMacro ?? reference;
201
+ const marker = hasOverride
202
+ ? `/* cuttlefish-user-facts: adc pin=${o.pin}`
203
+ + `${typeof o.deviceOverride === 'string' && o.deviceOverride !== '' ? ` device=${o.deviceOverride}` : ''}`
204
+ + `${typeof o.pinctrlOverride === 'string' && o.pinctrlOverride !== '' ? ` pinctrl=${o.pinctrlOverride}` : ''}`
205
+ + ` channel=${ch} */ `
206
+ : '';
207
+ const setup = `static bool __tc_adct${o.pin}_done = false; if (!__tc_adct${o.pin}_done) { const struct adc_channel_cfg __tc_adct${o.pin}_cfg = { .gain = ${g}, .reference = ${r}, .acquisition_time = ADC_ACQ_TIME_DEFAULT, .channel_id = ${ch}, .differential = 0 }; adc_channel_setup(${dev}, &__tc_adct${o.pin}_cfg); __tc_adct${o.pin}_done = true; }`;
208
+ const read = `int16_t __b = 0; struct adc_sequence __s = { .channels = BIT(${ch}), .buffer = &__b, .buffer_size = sizeof(__b), .resolution = ${res} }; adc_read(${dev}, &__s);`;
209
+ if (op.operation === 'adc.read_raw') {
210
+ return { expression: `({ ${marker}${setup} ${read} __b; })` };
211
+ }
212
+ return {
213
+ expression: `({ ${marker}${setup} ${read} int32_t __v = __b; adc_raw_to_millivolts(${vref}, ${g}, ${res}, &__v); __v; })`,
214
+ };
215
+ }
216
+ default:
217
+ throw new Error(
218
+ `framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
219
+ `Open an issue or use rawCpp() to emit it manually.`,
220
+ );
221
+ }
222
+ }
@@ -91,6 +91,9 @@ export function bleInitLines(): string[] {
91
91
  `// bt_gatt_chrc metadata for each characteristic declaration attr.`,
92
92
  `static struct bt_gatt_chrc __tc_ble_chrc_meta[__TC_BLE_MAX_CHARS];`,
93
93
  `// The flat attribute array + its wrapping service. Plus per-char CCC cfg.`,
94
+ `static struct bt_gatt_ccc_managed_user_data __tc_ble_ccc[__TC_BLE_MAX_CHARS];`,
95
+ `// write_ccc dereferences cfg_changed — every slot needs the no-op.`,
96
+ `static void __tc_ble_ccc_changed(const struct bt_gatt_attr* a, uint16_t v) { (void)a; (void)v; }`,
94
97
  `static struct bt_gatt_attr __tc_ble_attrs[__TC_BLE_MAX_ATTRS];`,
95
98
  `static size_t __tc_ble_attr_count = 0;`,
96
99
  `static struct bt_gatt_service __tc_ble_svc;`,
@@ -145,8 +148,10 @@ export function bleInitLines(): string[] {
145
148
  ` if (idx < 0 || idx >= __TC_BLE_MAX_CHARS || !__tc_ble.on_read[idx]) return 0;`,
146
149
  ` const char* t = __tc_ble_char_defs[idx].type;`,
147
150
  ` if (t && strcmp(t, "utf8") == 0) {`,
148
- ` const char* s_c = reinterpret_cast<const char*(*)(void)>(__tc_ble.on_read[idx])();`,
149
- ` return bt_gatt_attr_read(conn, attr, buf, len, offset, s_c, strlen(s_c));`,
151
+ ` // Hoisted string callbacks return std::string BY VALUE — call through`,
152
+ ` // the true signature and borrow c_str() for the copy below.`,
153
+ ` std::string s_v = reinterpret_cast<std::string(*)(void)>(__tc_ble.on_read[idx])();`,
154
+ ` return bt_gatt_attr_read(conn, attr, buf, len, offset, s_v.c_str(), s_v.size());`,
150
155
  ` } else if (t && strcmp(t, "float32") == 0) {`,
151
156
  ` double d = reinterpret_cast<double(*)(void)>(__tc_ble.on_read[idx])();`,
152
157
  ` float f = static_cast<float>(d);`,
@@ -266,6 +271,7 @@ export function bleInitLines(): string[] {
266
271
  `// (declaration + value); the value attr carries the char index in user_data.`,
267
272
  `static void __tc_ble_build_svc_table(void) {`,
268
273
  ` size_t a = 0;`,
274
+ ` for (int c = 0; c < __TC_BLE_MAX_CHARS; c++) { __tc_ble_ccc[c].cfg_changed = __tc_ble_ccc_changed; }`,
269
275
  ` for (int s = 0; s < static_cast<int>(__tc_ble.svc_count); s++) {`,
270
276
  ` // Primary service declaration.`,
271
277
  ` __tc_ble_attrs[a].uuid = BT_UUID_GATT_PRIMARY;`,
@@ -301,6 +307,17 @@ export function bleInitLines(): string[] {
301
307
  ` __tc_ble_attrs[a].perm = BT_GATT_PERM_READ | BT_GATT_PERM_WRITE;`,
302
308
  ` __tc_ble.val_attr_idx[c] = static_cast<int>(a);`,
303
309
  ` a++;`,
310
+ ` if (__tc_ble_char_defs[c].perms & 4) {`,
311
+ ` // CCC descriptor — without it a central cannot subscribe`,
312
+ ` // and notifications never reach anyone.`,
313
+ ` __tc_ble_attrs[a].uuid = BT_UUID_GATT_CCC;`,
314
+ ` __tc_ble_attrs[a].read = bt_gatt_attr_read_ccc;`,
315
+ ` __tc_ble_attrs[a].write = bt_gatt_attr_write_ccc;`,
316
+ ` __tc_ble_attrs[a].user_data = &__tc_ble_ccc[c];`,
317
+ ` __tc_ble_attrs[a].handle = 0;`,
318
+ ` __tc_ble_attrs[a].perm = BT_GATT_PERM_READ | BT_GATT_PERM_WRITE;`,
319
+ ` a++;`,
320
+ ` }`,
304
321
  ` }`,
305
322
  ` }`,
306
323
  ` __tc_ble_attr_count = a;`,
@@ -323,24 +340,28 @@ export function bleInitLines(): string[] {
323
340
  ` bt_gatt_service_register(&__tc_ble_svc);`,
324
341
  ` __tc_ble.status = 1; // Initializing`,
325
342
  ` bt_enable(NULL); // synchronous`,
343
+ ` bt_enable(NULL); // synchronous`,
326
344
  ` __tc_ble.inited = true;`,
327
345
  ` __tc_ble_advertise_start();`,
328
346
  `}`,
329
347
  ``,
330
348
  `// ── Notify ──`,
349
+ `// Explicit-conn: the NULL (broadcast) form hit an ESP32 controller assert`,
350
+ `// (lld_con.c) on the first push after subscription on the S3.`,
331
351
  `static void __tc_ble_notify(int idx, int16_t value) {`,
332
352
  ` if (idx < 0 || idx >= __TC_BLE_MAX_CHARS) return;`,
353
+ ` if (__tc_ble.conn == NULL) return; // no link: a NULL-conn notify asserted the controller`,
333
354
  ` int ai = __tc_ble.val_attr_idx[idx];`,
334
355
  ` if (ai == 0) return;`,
335
356
  ` const char* t = __tc_ble_char_defs[idx].type;`,
336
357
  ` if (t && strcmp(t, "uint32") == 0) {`,
337
- ` uint32_t u = static_cast<uint32_t>(value); bt_gatt_notify(NULL, &__tc_ble_attrs[ai], &u, sizeof(u));`,
358
+ ` uint32_t u = static_cast<uint32_t>(value); bt_gatt_notify(__tc_ble.conn, &__tc_ble_attrs[ai], &u, sizeof(u));`,
338
359
  ` } else if (t && strcmp(t, "float32") == 0) {`,
339
- ` float f = static_cast<float>(value); bt_gatt_notify(NULL, &__tc_ble_attrs[ai], &f, sizeof(f));`,
360
+ ` float f = static_cast<float>(value); bt_gatt_notify(__tc_ble.conn, &__tc_ble_attrs[ai], &f, sizeof(f));`,
340
361
  ` } else if (t && strcmp(t, "boolean") == 0) {`,
341
- ` uint8_t b = value ? 1 : 0; bt_gatt_notify(NULL, &__tc_ble_attrs[ai], &b, sizeof(b));`,
362
+ ` uint8_t b = value ? 1 : 0; bt_gatt_notify(__tc_ble.conn, &__tc_ble_attrs[ai], &b, sizeof(b));`,
342
363
  ` } else {`,
343
- ` int16_t s16 = value; bt_gatt_notify(NULL, &__tc_ble_attrs[ai], &s16, sizeof(s16));`,
364
+ ` int16_t s16 = value; bt_gatt_notify(__tc_ble.conn, &__tc_ble_attrs[ai], &s16, sizeof(s16));`,
344
365
  ` }`,
345
366
  `}`,
346
367
  ``,
@@ -348,18 +369,7 @@ export function bleInitLines(): string[] {
348
369
  ` return __tc_ble.status == 3 && __tc_ble.connected_clients > 0;`,
349
370
  `}`,
350
371
  `static inline int __tc_ble_client_count(void) { return __tc_ble.connected_clients; }`,
351
- `static inline void __tc_ble_set_name(const char* name) { strncpy(__tc_ble.name, name, sizeof(__tc_ble.name) - 1); __tc_ble.name[sizeof(__tc_ble.name) - 1] = 0; }`,
352
- `static inline void __tc_ble_set_tx_power(int dbm) { (void)dbm; /* nRF TX power via bt_le_set_tx_power — deferred */ }`,
353
372
  ``,
354
- `static inline bool __tc_ble_until_connected(uint32_t timeout_ms) {`,
355
- ` int64_t deadline = static_cast<int64_t>(k_uptime_get()) + static_cast<int64_t>(timeout_ms);`,
356
- ` while (!__tc_ble_is_connected()) {`,
357
- ` if (timeout_ms > 0 && k_uptime_get() >= deadline) return false;`,
358
- ` k_msleep(50);`,
359
- ` }`,
360
- ` return true;`,
361
- `}`,
362
- `static inline void __tc_ble_until_connected_start(void) { /* __tc_ble_server_begin already started advertising */ }`,
363
373
  `// CUTTLEFISH_BLE_END`,
364
374
  ``,
365
375
  ];
@@ -404,21 +414,14 @@ export function lowerBle(op: HALOpIR): { code?: string; expression?: string } {
404
414
  case 'ble.on_disconnect':
405
415
  return { code: `__tc_ble.on_disconnect = (${s(o.handler)});` };
406
416
  case 'ble.notify':
407
- return { expression: `__tc_ble_notify(__tc_ble.current_char, ${s(o.value)})` };
417
+ // Index-based: val_attr_idx[idx] resolves the registered attr (the
418
+ // declaration-order slot). current_char would be whatever add_char ran
419
+ // last — wrong for a standalone notify after later declarations.
420
+ return { expression: `__tc_ble_notify(${s(o.index ?? 0)}, ${s(o.value)})` };
408
421
  case 'ble.is_connected':
409
422
  return { expression: `__tc_ble_is_connected()` };
410
423
  case 'ble.client_count':
411
424
  return { expression: `__tc_ble_client_count()` };
412
- case 'ble.status':
413
- return { expression: `__tc_ble.status` };
414
- case 'ble.set_name':
415
- return { code: `__tc_ble_set_name(${s(o.name)});` };
416
- case 'ble.set_tx_power':
417
- return { code: `__tc_ble_set_tx_power(${s(o.dbm)});` };
418
- case 'ble.until_connected':
419
- return { expression: `__tc_ble_until_connected(${s(o.timeoutMs)})` };
420
- case 'ble.until_connected_start':
421
- return { code: `__tc_ble_until_connected_start();` };
422
425
  default:
423
426
  throw new Error(
424
427
  `framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
@@ -426,3 +429,4 @@ export function lowerBle(op: HALOpIR): { code?: string; expression?: string } {
426
429
  );
427
430
  }
428
431
  }
432
+
@@ -1,82 +1,82 @@
1
- // ---------------------------------------------------------------------------
2
- // DAC lowering — Zephyr DAC driver via dac_channel_setup / dac_write_value
3
- //
4
- // Targets with a DAC (ESP32: 2× 8-bit channels on GPIO25/26) declare a `dac`
5
- // entry in the chip descriptor: the DT device node label + the pin→channel map.
6
- // The lowering emits a `dac_channel_setup` (lazy, on first write) + a
7
- // `dac_write_value` against DEVICE_DT_GET(DT_NODELABEL(<device>)).
8
- //
9
- // Targets without a DAC (nRF52840, ESP32-S3) omit `dac`; usage there lowers to a
10
- // comment and profileDiagnostics flags it (mirror of the ADC pin-validity gate).
11
- // ---------------------------------------------------------------------------
12
-
13
- import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
14
- import type { ZephyrChipDescriptor, ZephyrDacChannel } from '../chips/types.js';
15
-
16
- /** Look up a DAC channel by HAL pin number. */
17
- function findDacChannel(chip: ZephyrChipDescriptor, pin: number): ZephyrDacChannel | undefined {
18
- return chip.dac?.channels.find((c) => c.pin === pin);
19
- }
20
-
21
- /**
22
- * Emit the DAC device handle. Called from shimLines when the program uses the
23
- * DAC (the chip must declare a `dac` entry, or nothing is emitted).
24
- */
25
- export function dacInitLines(chip: ZephyrChipDescriptor): string[] {
26
- if (!chip.dac) return [];
27
- return [
28
- '// CUTTLEFISH_DAC_BEGIN',
29
- `static const struct device* __tc_dac_dev = DEVICE_DT_GET(DT_NODELABEL(${chip.dac.device}));`,
30
- '// CUTTLEFISH_DAC_END',
31
- ];
32
- }
33
-
34
- /**
35
- * Resolve a HAL dac.* op to Zephyr C++.
36
- * Returns `{ code }` for statement ops.
37
- */
38
- export function lowerDac(
39
- op: HALOpIR,
40
- chip: ZephyrChipDescriptor,
41
- ): { code?: string; expression?: string } {
42
- const o = op as any;
43
-
44
- // No DAC on this target (e.g. nRF52840). Return a comment so the resolver
45
- // reports non-undefined (the manifest validator's probe sends pin:25 on the
46
- // default chip, which has no DAC). profileDiagnostics flags real misuse.
47
- if (!chip.dac) {
48
- return { code: `/* dac on pin ${o.pin}: no DAC on ${chip.id} */` };
49
- }
50
-
51
- const ch = findDacChannel(chip, o.pin);
52
- if (!ch) {
53
- const valid = [...chip.dac.channels].map((c) => c.pin).sort((a, b) => a - b).join(', ');
54
- return {
55
- code: `/* dac on pin ${o.pin}: not a DAC pin on ${chip.id} (valid: ${valid}) */`,
56
- };
57
- }
58
-
59
- switch (op.operation) {
60
- case 'dac.write': {
61
- // Lazy one-time channel setup on first write (the HAL DAC surface has no
62
- // begin()), then output the value. Arduino analogWrite is 0–255 against
63
- // the channel's resolution; dac_write_value takes the raw code.
64
- return {
65
- code: [
66
- `{ static bool __tc_dac_done = false;`,
67
- ` if (!__tc_dac_done) {`,
68
- ` static const struct dac_channel_cfg __tc_dac_cfg = { .channel_id = ${ch.channel}, .resolution = ${ch.resolution} };`,
69
- ` (void)dac_channel_setup(__tc_dac_dev, &__tc_dac_cfg);`,
70
- ` __tc_dac_done = true;`,
71
- ` }`,
72
- ` (void)dac_write_value(__tc_dac_dev, ${ch.channel}, ${o.value}); }`,
73
- ].join(' '),
74
- };
75
- }
76
- default:
77
- throw new Error(
78
- `framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
79
- `Open an issue or use rawCpp() to emit it manually.`,
80
- );
81
- }
82
- }
1
+ // ---------------------------------------------------------------------------
2
+ // DAC lowering — Zephyr DAC driver via dac_channel_setup / dac_write_value
3
+ //
4
+ // Targets with a DAC (ESP32: 2× 8-bit channels on GPIO25/26) declare a `dac`
5
+ // entry in the chip descriptor: the DT device node label + the pin→channel map.
6
+ // The lowering emits a `dac_channel_setup` (lazy, on first write) + a
7
+ // `dac_write_value` against DEVICE_DT_GET(DT_NODELABEL(<device>)).
8
+ //
9
+ // Targets without a DAC (nRF52840, ESP32-S3) omit `dac`; usage there lowers to a
10
+ // comment and profileDiagnostics flags it (mirror of the ADC pin-validity gate).
11
+ // ---------------------------------------------------------------------------
12
+
13
+ import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
14
+ import type { ZephyrChipDescriptor, ZephyrDacChannel } from '../chips/types.js';
15
+
16
+ /** Look up a DAC channel by HAL pin number. */
17
+ function findDacChannel(chip: ZephyrChipDescriptor, pin: number): ZephyrDacChannel | undefined {
18
+ return chip.dac?.channels.find((c) => c.pin === pin);
19
+ }
20
+
21
+ /**
22
+ * Emit the DAC device handle. Called from shimLines when the program uses the
23
+ * DAC (the chip must declare a `dac` entry, or nothing is emitted).
24
+ */
25
+ export function dacInitLines(chip: ZephyrChipDescriptor): string[] {
26
+ if (!chip.dac) return [];
27
+ return [
28
+ '// CUTTLEFISH_DAC_BEGIN',
29
+ `static const struct device* __tc_dac_dev = DEVICE_DT_GET(DT_NODELABEL(${chip.dac.device}));`,
30
+ '// CUTTLEFISH_DAC_END',
31
+ ];
32
+ }
33
+
34
+ /**
35
+ * Resolve a HAL dac.* op to Zephyr C++.
36
+ * Returns `{ code }` for statement ops.
37
+ */
38
+ export function lowerDac(
39
+ op: HALOpIR,
40
+ chip: ZephyrChipDescriptor,
41
+ ): { code?: string; expression?: string } {
42
+ const o = op as any;
43
+
44
+ // No DAC on this target (e.g. nRF52840). Return a comment so the resolver
45
+ // reports non-undefined (the manifest validator's probe sends pin:25 on the
46
+ // default chip, which has no DAC). profileDiagnostics flags real misuse.
47
+ if (!chip.dac) {
48
+ return { code: `/* dac on pin ${o.pin}: no DAC on ${chip.id} */` };
49
+ }
50
+
51
+ const ch = findDacChannel(chip, o.pin);
52
+ if (!ch) {
53
+ const valid = [...chip.dac.channels].map((c) => c.pin).sort((a, b) => a - b).join(', ');
54
+ return {
55
+ code: `/* dac on pin ${o.pin}: not a DAC pin on ${chip.id} (valid: ${valid}) */`,
56
+ };
57
+ }
58
+
59
+ switch (op.operation) {
60
+ case 'dac.write_value': {
61
+ // Thin DAC: raw code with the construction resolution (0 = the
62
+ // descriptor channel's), lazy setup per pin.
63
+ const res = Number(o.resolution) > 0 ? Number(o.resolution) : ch.resolution;
64
+ return {
65
+ code: [
66
+ `{ static bool __tc_dact${o.pin}_done = false;`,
67
+ ` if (!__tc_dact${o.pin}_done) {`,
68
+ ` static const struct dac_channel_cfg __tc_dact${o.pin}_cfg = { .channel_id = ${ch.channel}, .resolution = ${res} };`,
69
+ ` (void)dac_channel_setup(__tc_dac_dev, &__tc_dact${o.pin}_cfg);`,
70
+ ` __tc_dact${o.pin}_done = true;`,
71
+ ` }`,
72
+ ` (void)dac_write_value(__tc_dac_dev, ${ch.channel}, ${o.value}); }`,
73
+ ].join(' '),
74
+ };
75
+ }
76
+ default:
77
+ throw new Error(
78
+ `framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
79
+ `Open an issue or use rawCpp() to emit it manually.`,
80
+ );
81
+ }
82
+ }