@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
@@ -6,22 +6,81 @@
6
6
  // `pwm_set_pulse_dt` / `pwm_set_dt`. Duty is scaled from the Arduino-style
7
7
  // 0–255 (or 0–1023) range to nanoseconds against the spec's period.
8
8
  // ---------------------------------------------------------------------------
9
- /** Look up a PWM spec by HAL pin number. */
10
- function findPwmSpec(chip, pin) {
11
- return chip.pwm?.specs.find((s) => s.pin === pin);
9
+ /**
10
+ * The DT alias a PWM spec is addressed by. Board-shipped specs carry their
11
+ * alias in `dtSpec`; synthesized specs (controller + channel) get a
12
+ * `tc-pwm<pin>` alias that the overlay generator creates in
13
+ * <board>.overlay — both sides derive the name from the pin so they agree.
14
+ */
15
+ export function pwmDtAlias(spec) {
16
+ return spec.dtSpec ?? `tc-pwm${spec.pin}`;
17
+ }
18
+ /**
19
+ * The C macro token for a spec's alias. Zephyr's devicetree macros replace
20
+ * dashes in alias names with underscores (`pwm-led0` in DTS is
21
+ * DT_ALIAS(pwm_led0) in C) — the dashed spelling is a subtraction
22
+ * expression and fails to compile (caught by the blackpill E2E west build).
23
+ */
24
+ export function pwmDtAliasToken(spec) {
25
+ return pwmDtAlias(spec).replace(/-/g, '_');
26
+ }
27
+ /**
28
+ * Look up a PWM spec by HAL pin number. Matrix pins (ESP32 LEDC) synthesize
29
+ * a spec on the fly: the channel is inert here — the emitted C++ addresses
30
+ * the pin only via its `tc-pwm<pin>` alias (the DT pwms cell carries the
31
+ * real channel, assigned by the overlay generator over the driven pins).
32
+ */
33
+ export function findPwmSpec(chip, pin) {
34
+ const spec = chip.pwm?.specs.find((s) => s.pin === pin);
35
+ if (spec)
36
+ return spec;
37
+ const m = chip.pwm?.matrix;
38
+ if (m && m.pins.includes(pin))
39
+ return { pin, controller: m.controller, channel: 0 };
40
+ return undefined;
12
41
  }
13
42
  /** The C variable name emitted for a PWM channel's spec. */
14
43
  function pwmVarName(spec) {
15
- return `__tc_pwm_${spec.dtSpec.replace(/-/g, '_')}`;
44
+ return `__tc_pwm_${pwmDtAliasToken(spec)}`;
16
45
  }
17
46
  /**
18
47
  * Emit the per-channel PWM spec declarations. One per spec in the chip
19
- * descriptor. Called from shimLines when the program uses PWM.
48
+ * descriptor that the PROGRAM ACTUALLY DRIVES (`usedPins`) a spec for an
49
+ * untouched pin is unused code in the emitted TU (and would need a dead DT
50
+ * alias in the overlay). When `usedPins` is omitted (probe paths with no
51
+ * program), every spec is emitted. Called from shimLines when the program
52
+ * uses PWM.
20
53
  */
21
- export function pwmInitLines(chip) {
54
+ export function pwmInitLines(chip, usedPins, userSpecs) {
22
55
  const lines = ['// CUTTLEFISH_PWM_BEGIN'];
23
56
  for (const spec of chip.pwm?.specs ?? []) {
24
- lines.push(`static const struct pwm_dt_spec ${pwmVarName(spec)} = PWM_DT_SPEC_GET(DT_ALIAS(${spec.dtSpec}));`);
57
+ if (usedPins && !usedPins.has(spec.pin))
58
+ continue;
59
+ lines.push(`static const struct pwm_dt_spec ${pwmVarName(spec)} = PWM_DT_SPEC_GET(DT_ALIAS(${pwmDtAliasToken(spec)}));`);
60
+ }
61
+ // Inline-override pins (the escape hatch): the construction opts vouch for
62
+ // controller+channel on a pin the manifest does not map — the alias var
63
+ // the lowered calls reference (the DT node itself comes from the overlay
64
+ // regen's marker merge).
65
+ for (const spec of userSpecs ?? []) {
66
+ if (chip.pwm?.specs.some((s) => s.pin === spec.pin))
67
+ continue;
68
+ if (usedPins && !usedPins.has(spec.pin))
69
+ continue;
70
+ lines.push(`static const struct pwm_dt_spec ${pwmVarName(spec)} = PWM_DT_SPEC_GET(DT_ALIAS(${pwmDtAliasToken(spec)}));`);
71
+ }
72
+ // Matrix pins (ESP32 LEDC): one alias per driven pin, ascending — the same
73
+ // order the overlay generator assigns channels in, though the C++ never
74
+ // needs the channel (the DT pwms cell carries it). An omitted usage set
75
+ // (probe paths) emits every matrix pin, mirroring the static behavior.
76
+ const m = chip.pwm?.matrix;
77
+ if (m) {
78
+ const pins = (usedPins ? [...usedPins].filter((p) => m.pins.includes(p)) : [...m.pins])
79
+ .sort((a, b) => a - b);
80
+ for (const pin of pins) {
81
+ const spec = { pin, controller: m.controller, channel: 0 };
82
+ lines.push(`static const struct pwm_dt_spec ${pwmVarName(spec)} = PWM_DT_SPEC_GET(DT_ALIAS(${pwmDtAliasToken(spec)}));`);
83
+ }
25
84
  }
26
85
  lines.push('// CUTTLEFISH_PWM_END');
27
86
  return lines;
@@ -32,7 +91,20 @@ export function pwmInitLines(chip) {
32
91
  */
33
92
  export function lowerPwm(op, chip) {
34
93
  const o = op;
35
- const spec = findPwmSpec(chip, o.pin);
94
+ // Construction-time controller/channel overrides (hal/pwm-pin.ts opts):
95
+ // the user vouches for the routing. The spec synthesizes from the
96
+ // override (addressing rides the tc-pwm<pin> alias like a matrix pin) and
97
+ // a marker comment carries controller+channel to the overlay regen, which
98
+ // synthesizes the DT node — the transpiler cannot.
99
+ const hasOverride = (typeof o.controllerOverride === 'string' && o.controllerOverride !== '')
100
+ || (typeof o.channelOverride === 'number' && o.channelOverride >= 0);
101
+ const spec = hasOverride
102
+ ? {
103
+ pin: o.pin,
104
+ controller: o.controllerOverride ?? 'pwm0',
105
+ channel: o.channelOverride ?? 0,
106
+ }
107
+ : findPwmSpec(chip, o.pin);
36
108
  if (!spec) {
37
109
  // Probe / unlisted pin: return a comment so the resolver reports non-
38
110
  // undefined (the manifest validator's probe sends pin:0 with no spec).
@@ -40,18 +112,34 @@ export function lowerPwm(op, chip) {
40
112
  return { code: `/* pwm on pin ${o.pin}: no PWM spec in chip descriptor */` };
41
113
  }
42
114
  const v = pwmVarName(spec);
115
+ // The overlay regen (toolchain) parses this into a synthesized pwm-leds
116
+ // spec: pin → controller/channel. Comment placement inside the block
117
+ // braces is legal C.
118
+ const marker = hasOverride
119
+ ? `/* cuttlefish-user-facts: pwm pin=${spec.pin} controller=${spec.controller} channel=${spec.channel} */ `
120
+ : '';
43
121
  switch (op.operation) {
44
- case 'pwm.write': {
45
- // Duty is 0–255 (Arduino analogWrite). Scale to ns against the period.
46
- return { code: `pwm_set_pulse_dt(&${v}, (static_cast<uint32_t>(${o.duty}) * ${v}.period) / 255);` };
122
+ // ── Thin PWM (hal/pwm-pin.ts) — ns-true verbs ──────────────────────────
123
+ // Zephyr 4.4 has pwm_set_dt (period + pulse) and pwm_set_pulse_dt (pulse
124
+ // only) no period-only setter. The construction period is established
125
+ // once via pwm_set_dt(period, pulse 0 = line idle), then every set is one
126
+ // pwm_set_pulse_dt. No 0–255 scaling anywhere.
127
+ case 'pwm.set_pulse': {
128
+ return {
129
+ 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}); }`,
130
+ };
131
+ }
132
+ case 'pwm.set_duty': {
133
+ // duty is 0.0–1.0; pulse = duty × the construction period.
134
+ return {
135
+ 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}))); }`,
136
+ };
47
137
  }
48
- case 'pwm.get_frequency': {
49
- // period is in ns; frequency = 1e9 / period (Hz).
50
- return { expression: `(${v}.period ? (1000000000ULL / ${v}.period) : 0)` };
138
+ case 'pwm.set_period': {
139
+ // No period-only API: pwm_set_dt applies the new period and resets the
140
+ // pulse to idle follow with setPulse/setDuty to drive the line.
141
+ return { code: `${marker}(void)pwm_set_dt(&${v}, ${o.periodNs}, 0);` };
51
142
  }
52
- case 'pwm.get_resolution':
53
- // Arduino-compatible 8-bit duty range.
54
- return { expression: '8' };
55
143
  default:
56
144
  throw new Error(`framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
57
145
  `Open an issue or use rawCpp() to emit it manually.`);
@@ -5,7 +5,7 @@
5
5
  // the configured entropy source (the nRF52840 hardware RNG when
6
6
  // CONFIG_HARDWARE_DEVICE_RANDOM_GENERATOR is selected, or the default test/
7
7
  // xoroshiro generator otherwise). It is seeded automatically at boot from the
8
- // entropy driver, so unlike Arduino there is no implicit seeding step.
8
+ // entropy driver, so there is no implicit seeding step.
9
9
  //
10
10
  // The HAL random surface is the Arduino-core model:
11
11
  // random.int() → a non-negative 31-bit integer [0, 2^31 - 1]
@@ -70,7 +70,7 @@ export function randomInitLines() {
70
70
  `}`,
71
71
  ``,
72
72
  `// Re-seed the PRNG. After this call the sequence is deterministic from`,
73
- `// \`seed\`, matching Arduino randomSeed. A literal zero seed is accepted and`,
73
+ `// \`seed\`, matching the platform's randomSeed. A literal zero seed is accepted and`,
74
74
  `// remapped to the same odd constant (xorshift32 cannot start from 0).`,
75
75
  `static inline void __tc_rand_seed(uint32_t seed) {`,
76
76
  ` __tc_rand_state = (seed == 0U) ? 0x9E3779B9U : seed;`,
@@ -84,7 +84,7 @@ export function randomInitLines() {
84
84
  `}`,
85
85
  ``,
86
86
  `// Half-open range [min, max-1]. Implements the HAL contract directly rather`,
87
- `// than calling Arduino random(min, max): the modulus rejection-free form`,
87
+ `// than calling the platform random(min, max): the modulus rejection-free form`,
88
88
  `// avoids the modulo bias a naive (rand % (max-min)) introduces. max > min`,
89
89
  `// is assumed (the HAL validates this at the source level).`,
90
90
  `static inline int32_t __tc_rand_range(int32_t min, int32_t max) {`,
@@ -0,0 +1,37 @@
1
+ import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
2
+ /** Strip the 'SENSOR.' property-access prefix from a part token arg. */
3
+ export declare function sensorPartKey(part: string): string;
4
+ /** Strip the 'CHAN.' property-access prefix from a channel arg. */
5
+ export declare function sensorChanName(chan: string): string;
6
+ /** All names a constructed sensor is known by, derived from the op facts. */
7
+ export interface SensorNames {
8
+ /** DT nodelabel of the child node the overlay emits
9
+ * (tc_<part>_i2c<N>_0x<addr> / tc_<part>_spi<N>_cs<pin>). */
10
+ dtLabel: string;
11
+ /** C device-handle variable (__tc_sensor_<stem>_dev). */
12
+ devVar: string;
13
+ /** C sensor_value scratch variable (__tc_sensor_<stem>_val). */
14
+ valVar: string;
15
+ /** The controller index the bus string carries. */
16
+ busIndex: number;
17
+ /** The bus port: I2C address or SPI CS pin. */
18
+ port: number;
19
+ /** 'i2c' | 'spi'. */
20
+ busKind: 'i2c' | 'spi';
21
+ }
22
+ /** Derive a sensor's DT/C++ names. The scanner regexes the emitted __tc_
23
+ * prefix form, so the part group must stay [a-z0-9_]+ and greedy. */
24
+ export declare function sensorNames(part: string, bus: string, port: number | string, busKind?: string): SensorNames;
25
+ /**
26
+ * Emit the per-sensor state block (device handle + sensor_value scratch).
27
+ * Called from shimLines for each distinct sensor the program's ops reference.
28
+ */
29
+ export declare function sensorStateLines(part: string, bus: string, port: number | string, busKind?: string, spiHz?: number | string, spiMode?: number | string, alertPin?: number | string): string[];
30
+ /**
31
+ * Resolve a HAL sensor.* op to Zephyr C++.
32
+ * Returns `{ code }` for fetch (statement), `{ expression }` for get (value).
33
+ */
34
+ export declare function lowerSensor(op: HALOpIR): {
35
+ code?: string;
36
+ expression?: string;
37
+ };
@@ -0,0 +1,119 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Sensor lowering — Zephyr's uniform sensor API over the generated catalog
3
+ //
4
+ // One code shape for every part in @typecad/hal's sensor catalog: the op's
5
+ // part token resolves to the DT compatible, the bus string to the controller
6
+ // index, and the address to the I2C reg. The names are derived identically
7
+ // here (device handles), in strategy.ts state blocks, and in the overlay
8
+ // scanner (dt-config/overlay.ts + toolchain/index.ts) — the tc-pwm<pin>
9
+ // discipline: both sides derive from the same facts, so they cannot drift.
10
+ //
11
+ // Kconfig: nothing per-part. The in-tree driver Kconfigs are `default y` on
12
+ // DT_HAS_<COMPAT>_ENABLED, so the overlay's child node IS the enable switch;
13
+ // CONFIG_SENSOR (the umbrella under `if SENSOR`) is usage-gated in
14
+ // dt-config/kconfig.ts off the lowered `sensor_` tokens.
15
+ // ---------------------------------------------------------------------------
16
+ import { SENSOR_PART_INFO } from '@typecad/hal';
17
+ import { parseControllerIndex } from './util.js';
18
+ /** Strip the 'SENSOR.' property-access prefix from a part token arg. */
19
+ export function sensorPartKey(part) {
20
+ return part.replace(/^SENSOR\./, '');
21
+ }
22
+ /** Strip the 'CHAN.' property-access prefix from a channel arg. */
23
+ export function sensorChanName(chan) {
24
+ return chan.replace(/^CHAN\./, '');
25
+ }
26
+ /** Derive a sensor's DT/C++ names. The scanner regexes the emitted __tc_
27
+ * prefix form, so the part group must stay [a-z0-9_]+ and greedy. */
28
+ export function sensorNames(part, bus, port, busKind = 'i2c') {
29
+ const partKey = sensorPartKey(String(part));
30
+ const busIndex = parseControllerIndex(typeof bus === 'string' ? bus : String(bus));
31
+ const portNum = typeof port === 'number' ? port : parseInt(String(port));
32
+ const stem = busKind === 'spi'
33
+ ? `${partKey}_spi${busIndex}_cs${portNum}`
34
+ : `${partKey}_i2c${busIndex}_0x${portNum.toString(16)}`;
35
+ return {
36
+ dtLabel: `tc_${stem}`,
37
+ devVar: `__tc_sensor_${stem}_dev`,
38
+ valVar: `__tc_sensor_${stem}_val`,
39
+ busIndex,
40
+ port: portNum,
41
+ busKind: busKind === 'spi' ? 'spi' : 'i2c',
42
+ };
43
+ }
44
+ /**
45
+ * Resolve the part token against the generated catalog, validating the bus
46
+ * kind and (when the driver scan knows them) the channel. Throws a build-time
47
+ * error with the Zephyr-facing name — the same words the user would meet in
48
+ * Zephyr docs, checked before west ever runs.
49
+ */
50
+ /** @internal */
51
+ function requirePartInfo(partKey) {
52
+ const info = SENSOR_PART_INFO[partKey];
53
+ if (!info) {
54
+ throw new Error(`Sensor part '${partKey}' is not in the Zephyr sensor catalog (SENSOR in @typecad/hal). ` +
55
+ `Check the token against the catalog — it is the compatible with , and - replaced by _.`);
56
+ }
57
+ return info;
58
+ }
59
+ /** Validate the op's bus kind against the part's bindings. */
60
+ function requireBusKind(partKey, info, busKind) {
61
+ if (!info.buses.includes(busKind)) {
62
+ throw new Error(`Sensor part '${partKey}' (${info.compatible}) does not bind on ${busKind.toUpperCase()} — ` +
63
+ `its bindings are ${info.buses.join('/').toUpperCase()}.`);
64
+ }
65
+ }
66
+ /**
67
+ * Emit the per-sensor state block (device handle + sensor_value scratch).
68
+ * Called from shimLines for each distinct sensor the program's ops reference.
69
+ */
70
+ export function sensorStateLines(part, bus, port, busKind = 'i2c', spiHz = 0, spiMode = 0, alertPin = -1) {
71
+ const partKey = sensorPartKey(String(part));
72
+ requireBusKind(partKey, requirePartInfo(partKey), busKind);
73
+ const n = sensorNames(part, bus, port, busKind);
74
+ // The config comment carries construction facts (SPI clock, mode, alert
75
+ // GPIO) to the overlay scanner — they shape the DT node, not the C++.
76
+ const cfg = `// tc-sensor-cfg: ${n.dtLabel} hz=${Number(spiHz)} mode=${Number(spiMode)} alert=${Number(alertPin)}`;
77
+ return [
78
+ '// CUTTLEFISH_SENSOR_BEGIN',
79
+ `static const struct device* ${n.devVar} = DEVICE_DT_GET(DT_NODELABEL(${n.dtLabel}));`,
80
+ `static struct sensor_value ${n.valVar};`,
81
+ cfg,
82
+ '// CUTTLEFISH_SENSOR_END',
83
+ ];
84
+ }
85
+ /**
86
+ * Resolve a HAL sensor.* op to Zephyr C++.
87
+ * Returns `{ code }` for fetch (statement), `{ expression }` for get (value).
88
+ */
89
+ export function lowerSensor(op) {
90
+ const o = op;
91
+ const partKey = sensorPartKey(String(o.part));
92
+ const info = requirePartInfo(partKey);
93
+ requireBusKind(partKey, info, String(o.busKind ?? 'i2c'));
94
+ const n = sensorNames(o.part, o.bus, o.port ?? o.address, String(o.busKind ?? 'i2c'));
95
+ switch (op.operation) {
96
+ case 'sensor.fetch':
97
+ // sensor_sample_fetch. The leading (void) keeps the value scratch
98
+ // referenced under Zephyr's -Werror when the program fetches but never
99
+ // reads a channel (the state block is emitted per referenced sensor).
100
+ return {
101
+ code: `(void)${n.valVar}; sensor_sample_fetch(${n.devVar});`,
102
+ };
103
+ case 'sensor.get': {
104
+ const chan = sensorChanName(String(o.chan));
105
+ if (info.channels.length > 0 && !info.channels.includes(chan)) {
106
+ throw new Error(`Sensor part '${partKey}' (${info.compatible}) does not serve channel '${chan}' — ` +
107
+ `its driver serves: ${info.channels.join(', ')}.`);
108
+ }
109
+ // sensor_value is val1 + val2 * 1e-6; evaluate once per read.
110
+ return {
111
+ expression: `(sensor_channel_get(${n.devVar}, SENSOR_CHAN_${chan}, &${n.valVar}), ` +
112
+ `static_cast<double>(${n.valVar}.val1) + static_cast<double>(${n.valVar}.val2) / 1000000.0)`,
113
+ };
114
+ }
115
+ default:
116
+ throw new Error(`framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
117
+ `Open an issue or use rawCpp() to emit it manually.`);
118
+ }
119
+ }
@@ -1,5 +1,17 @@
1
1
  import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
2
2
  import type { ZephyrChipDescriptor } from '../chips/types.js';
3
+ export interface SpiTargetNames {
4
+ dtLabel: string;
5
+ varName: string;
6
+ busIndex: number;
7
+ cs: number;
8
+ }
9
+ /** Derive a thin SPI target's names from the op facts. */
10
+ export declare function spiTargetNames(bus: string, cs: number | string): SpiTargetNames;
11
+ /** The per-target state block: one spi_dt_spec against the DT child node the
12
+ * overlay emits, plus the tc-spit-cfg comment the overlay scanner reads (the
13
+ * tc-sensor-cfg channel). Called from shimLines for each distinct target. */
14
+ export declare function spiTargetStateLines(bus: string, cs: number | string, hz?: number | string, mode?: number | string): string[];
3
15
  /**
4
16
  * Emit the per-controller SPI state. The bus device resolves at compile time;
5
17
  * a static spi_config holds the base operation flags, and mutable runtime fields
@@ -12,11 +12,54 @@
12
12
  // DEVICE_DT_GET(DT_NODELABEL(spi2)) and call spi_transceive directly.
13
13
  // ---------------------------------------------------------------------------
14
14
  import { parseControllerIndex } from './util.js';
15
- import { controllerNodelabelForPin } from '../chips/controllers.js';
16
15
  /** The C variable prefix for a controller's state. */
17
16
  function prefix(idx) {
18
17
  return `__tc_spi${idx}`;
19
18
  }
19
+ /** Derive a thin SPI target's names from the op facts. */
20
+ export function spiTargetNames(bus, cs) {
21
+ const busIndex = parseControllerIndex(typeof bus === 'string' ? bus : String(bus));
22
+ const csNum = typeof cs === 'number' ? cs : parseInt(String(cs), 10);
23
+ const stem = `spit_spi${busIndex}_cs${csNum}`;
24
+ return { dtLabel: `tc_${stem}`, varName: `__tc_${stem}_spec`, busIndex, cs: csNum };
25
+ }
26
+ /** The per-target state block: one spi_dt_spec against the DT child node the
27
+ * overlay emits, plus the tc-spit-cfg comment the overlay scanner reads (the
28
+ * tc-sensor-cfg channel). Called from shimLines for each distinct target. */
29
+ export function spiTargetStateLines(bus, cs, hz = 0, mode = 0) {
30
+ const n = spiTargetNames(bus, cs);
31
+ return [
32
+ '// CUTTLEFISH_SPIT_BEGIN',
33
+ // Zephyr 4.4's SPI_DT_SPEC_GET takes the base operation explicitly (mode
34
+ // bits come from the DT node's spi-cpol/spi-cpha via SPI_CONFIG_DT).
35
+ `static const struct spi_dt_spec ${n.varName} = SPI_DT_SPEC_GET(DT_NODELABEL(${n.dtLabel}), SPI_OP_MODE_MASTER | SPI_WORD_SET(8), 0);`,
36
+ `// tc-spit-cfg: ${n.dtLabel} hz=${Number(hz)} mode=${Number(mode)}`,
37
+ '// CUTTLEFISH_SPIT_END',
38
+ ];
39
+ }
40
+ /** Build the tx buffer declaration + set for a thin SPI op. A literal byte
41
+ * array becomes a local array; a single identifier is the user's own buffer
42
+ * (its storage + sizeof drive length). spi_buf.buf is void*, and the user's
43
+ * buffer may be const-qualified — cast through const void* to stay legal
44
+ * under -Werror without mutating anyone's qualifications. */
45
+ function spiTxBuffers(tx, tag) {
46
+ const bytes = (Array.isArray(tx) ? tx : []);
47
+ // Only an identifier-shaped string is a real caller buffer — the resolver
48
+ // collapses a single-byte literal array to buffer-kind with NUMERIC text
49
+ // (e.g. "159"), which must take the literal path below.
50
+ const isIdentifier = bytes.length === 1 && typeof bytes[0] === 'string' && /^[A-Za-z_][A-Za-z0-9_]*$/.test(bytes[0]);
51
+ if (isIdentifier) {
52
+ const name = bytes[0];
53
+ return {
54
+ decl: `const struct spi_buf __tb${tag} = { .buf = const_cast<void*>(static_cast<const void*>(${name})), .len = sizeof(${name}) }; const struct spi_buf_set __txs${tag} = { .buffers = &__tb${tag}, .count = 1 };`,
55
+ set: `__txs${tag}`,
56
+ };
57
+ }
58
+ return {
59
+ decl: `uint8_t __tx${tag}[] = { ${bytes.join(', ')} }; const struct spi_buf __tb${tag} = { .buf = __tx${tag}, .len = sizeof(__tx${tag}) }; const struct spi_buf_set __txs${tag} = { .buffers = &__tb${tag}, .count = 1 };`,
60
+ set: `__txs${tag}`,
61
+ };
62
+ }
20
63
  /**
21
64
  * Emit the per-controller SPI state. The bus device resolves at compile time;
22
65
  * a static spi_config holds the base operation flags, and mutable runtime fields
@@ -39,7 +82,9 @@ export function spiInitLines(chip, controllerIndex) {
39
82
  `};`,
40
83
  `static void ${p}_init(void) {`,
41
84
  ` if (!${p}_ready) {`,
42
- ` ${p}_cfg.bus = ${p}_dev;`,
85
+ // NOTE: no .bus assignment — struct spi_config lost its `bus` member in
86
+ // Zephyr 4.x (deprecated 3.5, removed 4.0); spi_transceive takes the
87
+ // device alongside the config, which the ops below already do.
43
88
  ` ${p}_cfg.operation = SPI_OP_MODE_MASTER | SPI_WORD_SET(8)`,
44
89
  ` | (${p}_lsb ? SPI_TRANSFER_LSB : SPI_TRANSFER_MSB)`,
45
90
  ` | ((${p}_mode & 0x1) ? SPI_MODE_CPOL : 0)`,
@@ -59,51 +104,31 @@ export function lowerSpi(op, chip) {
59
104
  const idx = parseControllerIndex(o.bus);
60
105
  const p = prefix(idx);
61
106
  switch (op.operation) {
62
- case 'spi.begin':
63
- return { code: `${p}_init();` };
64
- case 'spi.end':
65
- return { code: `spi_release(${p}_dev, &${p}_cfg);` };
66
- case 'spi.begin_transaction':
67
- // Config is static; transaction begin is a no-op (frequency/mode baked in).
68
- return { code: `${p}_init();` };
69
- case 'spi.end_transaction':
70
- return { code: `(void)0;` };
71
- case 'spi.set_mode':
72
- // Apply CPOL/CPHA: store the mode byte then re-init so the next transfer
73
- // picks up the rebuilt operation flags. The ready flag is cleared so
74
- // _init() rebuilds rather than early-returning.
75
- return { code: `{ ${p}_mode = static_cast<uint8_t>(${o.mode}); ${p}_ready = false; ${p}_init(); }` };
76
- case 'spi.set_bit_order': {
77
- // The HAL payload `order` is a string ("lsb" | "msb"), per SpiSetBitOrderOp.
78
- // Normalize here in TS so we emit a boolean literal, not the raw string
79
- // (which would be an undeclared C++ identifier).
80
- const key = String(o.order).replace(/^["']|["']$/g, '').toLowerCase();
81
- const lsb = (key === 'lsb' || key === 'lsbfirst') ? 'true' : 'false';
82
- return { code: `{ ${p}_lsb = ${lsb}; ${p}_ready = false; ${p}_init(); }` };
83
- }
84
- case 'spi.transfer': {
85
- // Single-byte full-duplex, returns the received byte (GCC stmt-expr).
107
+ case 'spi.transceive': {
108
+ const v = spiTargetNames(o.bus, o.cs).varName;
109
+ const tx = spiTxBuffers(o.tx, 't');
110
+ const rxName = String(o.rx ?? '');
111
+ const rx = rxName
112
+ ? `struct spi_buf __rb = { .buf = const_cast<void*>(static_cast<const void*>(${rxName})), .len = sizeof(${rxName}) }; const struct spi_buf_set __rbs = { .buffers = &__rb, .count = 1 };`
113
+ : `const struct spi_buf_set __rbs = { .buffers = NULL, .count = 0 };`;
86
114
  return {
87
- expression: `({ uint8_t __tx = static_cast<uint8_t>(${o.data}); uint8_t __rx = 0; struct spi_buf __tb = { .buf = &__tx, .len = 1 }; struct spi_buf_set __tbs = { .buffers = &__tb, .count = 1 }; struct spi_buf __rb = { .buf = &__rx, .len = 1 }; struct spi_buf_set __rbs = { .buffers = &__rb, .count = 1 }; ${p}_init(); spi_transceive(${p}_dev, &${p}_cfg, &__tbs, &__rbs); __rx; })`,
115
+ code: `{ ${tx.decl} ${rx} (void)spi_transceive_dt(&${v}, &${tx.set}, &__rbs); }`,
88
116
  };
89
117
  }
90
- case 'spi.read_buffer': {
91
- // Read count bytes by sending 0xFF dummy bytes (full-duplex read).
92
- const count = o.count;
118
+ case 'spi.dev_write': {
119
+ const v = spiTargetNames(o.bus, o.cs).varName;
120
+ const tx = spiTxBuffers(o.tx, 'w');
93
121
  return {
94
- code: `{ uint8_t __dummy[${count}] = {0}; for (int __i = 0; __i < (int)(${count}); __i++) __dummy[__i] = 0xFF; struct spi_buf __tb = { .buf = __dummy, .len = ${count} }; struct spi_buf_set __tbs = { .buffers = &__tb, .count = 1 }; struct spi_buf __rb = { .buf = reinterpret_cast<void*>(${o.buffer}), .len = ${count} }; struct spi_buf_set __rbs = { .buffers = &__rb, .count = 1 }; ${p}_init(); spi_transceive(${p}_dev, &${p}_cfg, &__tbs, &__rbs); }`,
122
+ code: `{ ${tx.decl} (void)spi_write_dt(&${v}, &${tx.set}); }`,
95
123
  };
96
124
  }
97
- case 'spi.cs_low':
98
- case 'spi.cs_high': {
99
- // CS driven as a plain GPIO via the owning controller (the CS pin comes
100
- // from the op's `pin` field; Zephyr uses gpio_pin_set_raw). Resolve the
101
- // controller by pin so a CS on a high-numbered pin (ESP32-S3 gpio1) lands
102
- // on the right node.
103
- const val = op.operation === 'spi.cs_low' ? 0 : 1;
104
- const gpioController = controllerNodelabelForPin(chip, o.pin);
125
+ case 'spi.reg_read': {
126
+ // One-byte register read against an INTERNAL buffer — no caller array
127
+ // (sidesteps the file-scope Uint8Array promotion issue; also just the
128
+ // right shape for ID/status registers).
129
+ const v = spiTargetNames(o.bus, o.cs).varName;
105
130
  return {
106
- code: `gpio_pin_set_raw(DEVICE_DT_GET(DT_NODELABEL(${gpioController})), ${o.pin}, ${val});`,
131
+ expression: `({ uint8_t __txr = static_cast<uint8_t>(${o.reg}); uint8_t __rxr = 0; struct spi_buf __tbr = { .buf = &__txr, .len = 1 }; const struct spi_buf_set __txsr = { .buffers = &__tbr, .count = 1 }; struct spi_buf __rbr = { .buf = &__rxr, .len = 1 }; const struct spi_buf_set __rxsr = { .buffers = &__rbr, .count = 1 }; (void)spi_transceive_dt(&${v}, &__txsr, &__rxsr); __rxr; })`,
107
132
  };
108
133
  }
109
134
  default:
@@ -0,0 +1,14 @@
1
+ import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
2
+ /**
3
+ * Emit the per-thread state. Called from shimLines for each distinct thread
4
+ * the program starts.
5
+ */
6
+ export declare function threadStateLines(instance: number, stackBytes: number): string[];
7
+ /**
8
+ * Resolve a HAL thread.* op to Zephyr C++.
9
+ * Returns `{ code }` for statement ops.
10
+ */
11
+ export declare function lowerThread(op: HALOpIR): {
12
+ code?: string;
13
+ expression?: string;
14
+ };
@@ -0,0 +1,55 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Thread lowering — Zephyr kernel threads (k_thread_create / k_thread_join)
3
+ //
4
+ // One state block per thread identity slot the program STARTS: a static
5
+ // K_THREAD_STACK_DEFINE (sized by the construction stackKb), the k_thread
6
+ // struct, and an entry trampoline bridging Zephyr's (void*, void*, void*)
7
+ // entry signature to the no-argument registered callback. start() creates
8
+ // and schedules (K_NO_WAIT); join() blocks (K_FOREVER).
9
+ //
10
+ // No devicetree, no Kconfig — kernel.h (always included) is the whole
11
+ // dependency. State emission is keyed on thread.start ops only, so a
12
+ // join-only program emits nothing (and its join guards on the same slot).
13
+ // ---------------------------------------------------------------------------
14
+ /** Per-instance symbol stem. */
15
+ function stem(instance) {
16
+ return `__tc_thrd${instance}`;
17
+ }
18
+ /**
19
+ * Emit the per-thread state. Called from shimLines for each distinct thread
20
+ * the program starts.
21
+ */
22
+ export function threadStateLines(instance, stackBytes) {
23
+ const p = stem(instance);
24
+ return [
25
+ '// CUTTLEFISH_THREAD_BEGIN',
26
+ `K_THREAD_STACK_DEFINE(${p}_stack, ${stackBytes});`,
27
+ `static struct k_thread ${p}_thread;`,
28
+ `static void (*${p}_fn)(void) = NULL;`,
29
+ `static void ${p}_tramp(void* a, void* b, void* c) { (void)a; (void)b; (void)c; if (${p}_fn) { ${p}_fn(); } }`,
30
+ '// CUTTLEFISH_THREAD_END',
31
+ ];
32
+ }
33
+ /**
34
+ * Resolve a HAL thread.* op to Zephyr C++.
35
+ * Returns `{ code }` for statement ops.
36
+ */
37
+ export function lowerThread(op) {
38
+ const o = op;
39
+ const instance = typeof o.instance === 'number' ? o.instance : 0;
40
+ const p = stem(instance);
41
+ switch (op.operation) {
42
+ case 'thread.start': {
43
+ // Store the entry, create + schedule immediately. The stack size comes
44
+ // from the state block (the construction fact), via K_THREAD_STACK_SIZEOF.
45
+ return {
46
+ code: `${p}_fn = (${o.handler}); (void)k_thread_create(&${p}_thread, ${p}_stack, K_THREAD_STACK_SIZEOF(${p}_stack), ${p}_tramp, NULL, NULL, NULL, ${o.priority ?? 5}, 0, K_NO_WAIT);`,
47
+ };
48
+ }
49
+ case 'thread.join':
50
+ return { code: `(void)k_thread_join(&${p}_thread, K_FOREVER);` };
51
+ default:
52
+ throw new Error(`framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
53
+ `Open an issue or use rawCpp() to emit it manually.`);
54
+ }
55
+ }