@typecad/framework-zephyr 1.0.0-alpha.8

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 (144) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +100 -0
  3. package/dist/async/timer-polyfill.d.ts +10 -0
  4. package/dist/async/timer-polyfill.js +95 -0
  5. package/dist/chips/controllers.d.ts +21 -0
  6. package/dist/chips/controllers.js +66 -0
  7. package/dist/chips/esp32.d.ts +2 -0
  8. package/dist/chips/esp32.js +59 -0
  9. package/dist/chips/esp32s3.d.ts +2 -0
  10. package/dist/chips/esp32s3.js +54 -0
  11. package/dist/chips/index.d.ts +15 -0
  12. package/dist/chips/index.js +45 -0
  13. package/dist/chips/resolve.d.ts +9 -0
  14. package/dist/chips/resolve.js +121 -0
  15. package/dist/chips/types.d.ts +154 -0
  16. package/dist/chips/types.js +14 -0
  17. package/dist/chips/xiao-ble.d.ts +2 -0
  18. package/dist/chips/xiao-ble.js +61 -0
  19. package/dist/debug-codegen.d.ts +33 -0
  20. package/dist/debug-codegen.js +162 -0
  21. package/dist/display/gfx.d.ts +17 -0
  22. package/dist/display/gfx.js +171 -0
  23. package/dist/display/index.d.ts +20 -0
  24. package/dist/display/index.js +53 -0
  25. package/dist/display/profiles.d.ts +25 -0
  26. package/dist/display/profiles.js +41 -0
  27. package/dist/display/touch-adapter.d.ts +9 -0
  28. package/dist/display/touch-adapter.js +95 -0
  29. package/dist/display/ui-adapter.d.ts +21 -0
  30. package/dist/display/ui-adapter.js +534 -0
  31. package/dist/dt-config/kconfig.d.ts +27 -0
  32. package/dist/dt-config/kconfig.js +283 -0
  33. package/dist/dt-config/overlay.d.ts +35 -0
  34. package/dist/dt-config/overlay.js +246 -0
  35. package/dist/framework.manifest.d.ts +98 -0
  36. package/dist/framework.manifest.js +525 -0
  37. package/dist/index.d.ts +5 -0
  38. package/dist/index.js +12 -0
  39. package/dist/lowering/adc.d.ts +19 -0
  40. package/dist/lowering/adc.js +76 -0
  41. package/dist/lowering/ble.d.ts +20 -0
  42. package/dist/lowering/ble.js +419 -0
  43. package/dist/lowering/board.d.ts +9 -0
  44. package/dist/lowering/board.js +33 -0
  45. package/dist/lowering/gpio.d.ts +12 -0
  46. package/dist/lowering/gpio.js +123 -0
  47. package/dist/lowering/http.d.ts +23 -0
  48. package/dist/lowering/http.js +482 -0
  49. package/dist/lowering/i2c.d.ts +15 -0
  50. package/dist/lowering/i2c.js +114 -0
  51. package/dist/lowering/index.d.ts +29 -0
  52. package/dist/lowering/index.js +93 -0
  53. package/dist/lowering/interrupts.d.ts +21 -0
  54. package/dist/lowering/interrupts.js +91 -0
  55. package/dist/lowering/mqtt.d.ts +19 -0
  56. package/dist/lowering/mqtt.js +366 -0
  57. package/dist/lowering/power.d.ts +9 -0
  58. package/dist/lowering/power.js +64 -0
  59. package/dist/lowering/preferences.d.ts +23 -0
  60. package/dist/lowering/preferences.js +345 -0
  61. package/dist/lowering/pulse.d.ts +7 -0
  62. package/dist/lowering/pulse.js +82 -0
  63. package/dist/lowering/pwm.d.ts +15 -0
  64. package/dist/lowering/pwm.js +59 -0
  65. package/dist/lowering/random.d.ts +18 -0
  66. package/dist/lowering/random.js +129 -0
  67. package/dist/lowering/spi.d.ts +17 -0
  68. package/dist/lowering/spi.js +113 -0
  69. package/dist/lowering/timing.d.ts +9 -0
  70. package/dist/lowering/timing.js +63 -0
  71. package/dist/lowering/tone.d.ts +10 -0
  72. package/dist/lowering/tone.js +50 -0
  73. package/dist/lowering/uart.d.ts +15 -0
  74. package/dist/lowering/uart.js +102 -0
  75. package/dist/lowering/util.d.ts +4 -0
  76. package/dist/lowering/util.js +12 -0
  77. package/dist/lowering/wdt.d.ts +15 -0
  78. package/dist/lowering/wdt.js +80 -0
  79. package/dist/lowering/wifi.d.ts +21 -0
  80. package/dist/lowering/wifi.js +447 -0
  81. package/dist/lowering/worker-backing.d.ts +14 -0
  82. package/dist/lowering/worker-backing.js +79 -0
  83. package/dist/lowering/worker.d.ts +6 -0
  84. package/dist/lowering/worker.js +14 -0
  85. package/dist/strategy.d.ts +168 -0
  86. package/dist/strategy.js +1094 -0
  87. package/dist/toolchain/debug-config.d.ts +82 -0
  88. package/dist/toolchain/debug-config.js +359 -0
  89. package/dist/toolchain/index.d.ts +73 -0
  90. package/dist/toolchain/index.js +427 -0
  91. package/dist/toolchain/scaffold.d.ts +15 -0
  92. package/dist/toolchain/scaffold.js +196 -0
  93. package/dist/toolchain/west-discover.d.ts +40 -0
  94. package/dist/toolchain/west-discover.js +203 -0
  95. package/dist/toolchain/west-spawn.d.ts +39 -0
  96. package/dist/toolchain/west-spawn.js +117 -0
  97. package/package.json +82 -0
  98. package/src/async/timer-polyfill.ts +107 -0
  99. package/src/chips/controllers.ts +74 -0
  100. package/src/chips/esp32.ts +62 -0
  101. package/src/chips/esp32s3.ts +57 -0
  102. package/src/chips/index.ts +54 -0
  103. package/src/chips/resolve.ts +146 -0
  104. package/src/chips/types.ts +163 -0
  105. package/src/chips/xiao-ble.ts +64 -0
  106. package/src/debug-codegen.ts +207 -0
  107. package/src/display/gfx.ts +190 -0
  108. package/src/display/index.ts +70 -0
  109. package/src/display/profiles.ts +63 -0
  110. package/src/display/touch-adapter.ts +100 -0
  111. package/src/display/ui-adapter.ts +558 -0
  112. package/src/dt-config/kconfig.ts +308 -0
  113. package/src/dt-config/overlay.ts +293 -0
  114. package/src/framework.manifest.ts +545 -0
  115. package/src/index.ts +23 -0
  116. package/src/lowering/adc.ts +102 -0
  117. package/src/lowering/ble.ts +426 -0
  118. package/src/lowering/board.ts +38 -0
  119. package/src/lowering/gpio.ts +148 -0
  120. package/src/lowering/http.ts +488 -0
  121. package/src/lowering/i2c.ts +126 -0
  122. package/src/lowering/index.ts +86 -0
  123. package/src/lowering/interrupts.ts +114 -0
  124. package/src/lowering/mqtt.ts +371 -0
  125. package/src/lowering/power.ts +70 -0
  126. package/src/lowering/preferences.ts +354 -0
  127. package/src/lowering/pulse.ts +93 -0
  128. package/src/lowering/pwm.ts +74 -0
  129. package/src/lowering/random.ts +135 -0
  130. package/src/lowering/spi.ts +124 -0
  131. package/src/lowering/timing.ts +72 -0
  132. package/src/lowering/tone.ts +60 -0
  133. package/src/lowering/uart.ts +111 -0
  134. package/src/lowering/util.ts +12 -0
  135. package/src/lowering/wdt.ts +84 -0
  136. package/src/lowering/wifi.ts +452 -0
  137. package/src/lowering/worker-backing.ts +94 -0
  138. package/src/lowering/worker.ts +17 -0
  139. package/src/strategy.ts +1265 -0
  140. package/src/toolchain/debug-config.ts +399 -0
  141. package/src/toolchain/index.ts +459 -0
  142. package/src/toolchain/scaffold.ts +196 -0
  143. package/src/toolchain/west-discover.ts +233 -0
  144. package/src/toolchain/west-spawn.ts +141 -0
@@ -0,0 +1,124 @@
1
+ // ---------------------------------------------------------------------------
2
+ // SPI lowering — spi_transceive_dt against a spi_dt_spec; CS as GPIO
3
+ //
4
+ // Zephyr's `spi_transceive_dt(&spec, tx, rx)` is stateless per-call, so there
5
+ // is no pending-tx-buffer (unlike I2C). The CS pin is driven as a plain GPIO
6
+ // (like ESP32), since the DT spec's CS handling is configured at compile time
7
+ // but the HAL's explicit cs_low/cs_high ops want manual control.
8
+ //
9
+ // The board's SPI node (spi2 on the XIAO) has no pre-declared client device
10
+ // node, so the lowering cannot use SPI_DT_SPEC_GET (which needs a client node).
11
+ // Instead we build a `struct spi_config` at runtime against
12
+ // DEVICE_DT_GET(DT_NODELABEL(spi2)) and call spi_transceive directly.
13
+ // ---------------------------------------------------------------------------
14
+
15
+ import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
16
+ import type { ZephyrChipDescriptor } from '../chips/types.js';
17
+ import { parseControllerIndex } from './util.js';
18
+ import { controllerNodelabelForPin } from '../chips/controllers.js';
19
+
20
+ /** The C variable prefix for a controller's state. */
21
+ function prefix(idx: number): string {
22
+ return `__tc_spi${idx}`;
23
+ }
24
+
25
+ /**
26
+ * Emit the per-controller SPI state. The bus device resolves at compile time;
27
+ * a static spi_config holds the base operation flags, and mutable runtime fields
28
+ * hold the mode (CPOL/CPHA bits) + bit order (lsb) so set_mode/set_bit_order can
29
+ * rebuild operation at init time (Zephyr's spi_config.operation is the only knob).
30
+ */
31
+ export function spiInitLines(chip: ZephyrChipDescriptor, controllerIndex: number): string[] {
32
+ const ctrl = chip.spi?.controllers[controllerIndex];
33
+ if (!ctrl) return [];
34
+ const p = prefix(controllerIndex);
35
+ return [
36
+ '// CUTTLEFISH_SPI_BEGIN',
37
+ `static const struct device* ${p}_dev = DEVICE_DT_GET(DT_NODELABEL(${ctrl.nodeLabel}));`,
38
+ `static bool ${p}_ready = false;`,
39
+ `static uint8_t ${p}_mode = 0; // bit0=CPOL, bit1=CPHA`,
40
+ `static bool ${p}_lsb = false; // false=MSB (default), true=LSB`,
41
+ `static struct spi_config ${p}_cfg = {`,
42
+ ` .frequency = 1000000,`,
43
+ `};`,
44
+ `static void ${p}_init(void) {`,
45
+ ` if (!${p}_ready) {`,
46
+ ` ${p}_cfg.bus = ${p}_dev;`,
47
+ ` ${p}_cfg.operation = SPI_OP_MODE_MASTER | SPI_WORD_SET(8)`,
48
+ ` | (${p}_lsb ? SPI_TRANSFER_LSB : SPI_TRANSFER_MSB)`,
49
+ ` | ((${p}_mode & 0x1) ? SPI_MODE_CPOL : 0)`,
50
+ ` | ((${p}_mode & 0x2) ? SPI_MODE_CPHA : 0);`,
51
+ ` ${p}_ready = true;`,
52
+ ` }`,
53
+ `}`,
54
+ '// CUTTLEFISH_SPI_END',
55
+ ];
56
+ }
57
+
58
+ /**
59
+ * Resolve a HAL spi.* op to Zephyr C++.
60
+ * Returns `{ code }` for statement ops, `{ expression }` for value-returning ops.
61
+ */
62
+ export function lowerSpi(
63
+ op: HALOpIR,
64
+ chip: ZephyrChipDescriptor,
65
+ ): { code?: string; expression?: string } {
66
+ const o = op as any;
67
+ const idx = parseControllerIndex(o.bus);
68
+ const p = prefix(idx);
69
+
70
+ switch (op.operation) {
71
+ case 'spi.begin':
72
+ return { code: `${p}_init();` };
73
+ case 'spi.end':
74
+ return { code: `spi_release(${p}_dev, &${p}_cfg);` };
75
+ case 'spi.begin_transaction':
76
+ // Config is static; transaction begin is a no-op (frequency/mode baked in).
77
+ return { code: `${p}_init();` };
78
+ case 'spi.end_transaction':
79
+ return { code: `(void)0;` };
80
+ case 'spi.set_mode':
81
+ // Apply CPOL/CPHA: store the mode byte then re-init so the next transfer
82
+ // picks up the rebuilt operation flags. The ready flag is cleared so
83
+ // _init() rebuilds rather than early-returning.
84
+ return { code: `{ ${p}_mode = static_cast<uint8_t>(${o.mode}); ${p}_ready = false; ${p}_init(); }` };
85
+ case 'spi.set_bit_order': {
86
+ // The HAL payload `order` is a string ("lsb" | "msb"), per SpiSetBitOrderOp.
87
+ // Normalize here in TS so we emit a boolean literal, not the raw string
88
+ // (which would be an undeclared C++ identifier).
89
+ const key = String(o.order).replace(/^["']|["']$/g, '').toLowerCase();
90
+ const lsb = (key === 'lsb' || key === 'lsbfirst') ? 'true' : 'false';
91
+ return { code: `{ ${p}_lsb = ${lsb}; ${p}_ready = false; ${p}_init(); }` };
92
+ }
93
+ case 'spi.transfer': {
94
+ // Single-byte full-duplex, returns the received byte (GCC stmt-expr).
95
+ return {
96
+ 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; })`,
97
+ };
98
+ }
99
+ case 'spi.read_buffer': {
100
+ // Read count bytes by sending 0xFF dummy bytes (full-duplex read).
101
+ const count = o.count;
102
+ return {
103
+ 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); }`,
104
+ };
105
+ }
106
+ case 'spi.cs_low':
107
+ case 'spi.cs_high': {
108
+ // CS driven as a plain GPIO via the owning controller (the CS pin comes
109
+ // from the op's `pin` field; Zephyr uses gpio_pin_set_raw). Resolve the
110
+ // controller by pin so a CS on a high-numbered pin (ESP32-S3 gpio1) lands
111
+ // on the right node.
112
+ const val = op.operation === 'spi.cs_low' ? 0 : 1;
113
+ const gpioController = controllerNodelabelForPin(chip, o.pin);
114
+ return {
115
+ code: `gpio_pin_set_raw(DEVICE_DT_GET(DT_NODELABEL(${gpioController})), ${o.pin}, ${val});`,
116
+ };
117
+ }
118
+ default:
119
+ throw new Error(
120
+ `framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
121
+ `Open an issue or use rawCpp() to emit it manually.`,
122
+ );
123
+ }
124
+ }
@@ -0,0 +1,72 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Timing lowering — Zephyr kernel timing
3
+ //
4
+ // delay/millis/delay_microseconds/micros/free_heap lower directly. The four
5
+ // timer ops (set_interval/set_timeout/clear_interval/clear_timeout) call the
6
+ // timer_methods polyfill helpers (k_timer + k_work pool), declared as 'polyfill'
7
+ // status in the manifest — the validator skips the resolver probe for these
8
+ // (they legitimately return polyfill-helper calls, not direct lowering).
9
+ // free_heap has no portable Zephyr query without CONFIG_SYS_HEAP_RUNTIME_STATS;
10
+ // it returns 0 with a comment (honest limitation).
11
+ // ---------------------------------------------------------------------------
12
+
13
+ import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
14
+
15
+ /**
16
+ * Resolve a HAL timing.* op to Zephyr C++.
17
+ * Returns `{ code }` for statement ops, `{ expression }` for value-returning ops.
18
+ */
19
+ export function lowerTiming(
20
+ op: HALOpIR,
21
+ ): { code?: string; expression?: string } {
22
+ const o = op as any;
23
+
24
+ switch (op.operation) {
25
+ case 'timing.delay': {
26
+ // Milliseconds — k_msleep is the blocking Zephyr call. Because
27
+ // isRtosTarget() returns true, the transpiler does not warn about
28
+ // blocking delays inside loop().
29
+ const ms = o.ms;
30
+ return { code: `k_msleep(${ms});` };
31
+ }
32
+ case 'timing.delay_microseconds': {
33
+ // k_busy_wait spins (does not yield); for cooperative μs delays.
34
+ return { code: `k_busy_wait(${o.us});` };
35
+ }
36
+ case 'timing.millis':
37
+ // k_uptime_get_32() returns int64_t milliseconds since boot. Cast to the
38
+ // Arduino-millis() return type (uint32_t) so overflow wraps identically.
39
+ return { expression: 'static_cast<uint32_t>(k_uptime_get_32())' };
40
+ case 'timing.micros': {
41
+ // Convert hardware cycles to microseconds. k_cycle_get_32 + the cycles/sec
42
+ // macro (note: sys_clock_hw_cycles_per_sec is a MACRO — needs parens).
43
+ return {
44
+ expression: '(uint32_t)(((uint64_t)k_cycle_get_32() * 1000000ULL) / sys_clock_hw_cycles_per_sec())',
45
+ };
46
+ }
47
+ case 'timing.free_heap':
48
+ // No portable free-heap query without CONFIG_SYS_HEAP_RUNTIME_STATS.
49
+ // Return 0 with a comment so callers don't get a link error.
50
+ return { expression: '(0 /* free_heap: enable CONFIG_SYS_HEAP_RUNTIME_STATS for real value */)' };
51
+ case 'timing.set_interval':
52
+ // Backed by the timer_methods polyfill (k_timer + k_work). The handler is
53
+ // the resolved C++ callback name; timeout is the repeat period (ms).
54
+ // NOTE: these ops are declared 'polyfill' in the manifest, so setInterval
55
+ // is rewritten to __tc_setInterval before reaching the lowering — these
56
+ // cases are a fallback/defense and use the real op fields (handler/timeout).
57
+ return { expression: `__tc_setInterval(${o.handler}, ${o.timeout})` };
58
+ case 'timing.set_timeout':
59
+ // One-shot: k_timer with K_FOREVER period.
60
+ return { expression: `__tc_setTimeout(${o.handler}, ${o.timeout})` };
61
+ case 'timing.clear_interval':
62
+ return { code: `__tc_clearInterval(${o.id});` };
63
+ case 'timing.clear_timeout':
64
+ return { code: `__tc_clearTimeout(${o.id});` };
65
+ default:
66
+ throw new Error(
67
+ `framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
68
+ `Open an issue or use rawCpp() to emit it manually.`,
69
+ );
70
+ }
71
+ }
72
+
@@ -0,0 +1,60 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Tone lowering — PWM-based square-wave generation
3
+ //
4
+ // tone is a convenience wrapper around PWM: a square wave at `frequency` Hz with
5
+ // a 50% duty cycle. The lowering maps frequency → period (ns) and sets the PWM
6
+ // channel to a 50% pulse. stop turns the output off (0% duty). The PWM spec is
7
+ // resolved from the chip descriptor (pwm-led0 on the XIAO).
8
+ //
9
+ // An optional duration is honored by scheduling a stop via k_sleep in a detached
10
+ // fashion — but the simple lowering is blocking (k_msleep(duration) then stop),
11
+ // matching the synchronous Arduino tone() semantics. For non-blocking tone,
12
+ // a workqueue would be needed (deferred).
13
+ // ---------------------------------------------------------------------------
14
+
15
+ import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
16
+ import type { ZephyrChipDescriptor } from '../chips/types.js';
17
+ import { lowerPwm } from './pwm.js';
18
+
19
+ /**
20
+ * Resolve a HAL tone.* op to Zephyr C++ via PWM.
21
+ * Returns `{ code }` for statement ops, `{ expression }` for value-returning ops.
22
+ */
23
+ export function lowerTone(
24
+ op: HALOpIR,
25
+ chip: ZephyrChipDescriptor,
26
+ ): { code?: string; expression?: string } {
27
+ const o = op as any;
28
+
29
+ switch (op.operation) {
30
+ case 'tone.play': {
31
+ // period_ns = 1e9 / freq; pulse_ns = period / 2 (50% duty).
32
+ // Build a fake pwm.write would need the spec var directly; emit the
33
+ // pwm_set_dt call against the same spec var the PWM lowering uses.
34
+ const spec = chip.pwm?.specs[0];
35
+ if (!spec) {
36
+ return { code: `/* tone.play(${o.frequency}): no PWM spec in chip descriptor */` };
37
+ }
38
+ const v = `__tc_pwm_${spec.dtSpec.replace(/-/g, '_')}`;
39
+ const freq = o.frequency;
40
+ const duration = o.duration;
41
+ const setTone = `uint32_t __period = (${freq} > 0) ? (1000000000ULL / static_cast<uint64_t>(${freq})) : 0; pwm_set_dt(&${v}, __period, __period / 2);`;
42
+ if (duration !== undefined) {
43
+ // Blocking tone for the requested duration, then stop.
44
+ return { code: `${setTone} k_msleep(${duration}); pwm_set_pulse_dt(&${v}, 0);` };
45
+ }
46
+ return { code: setTone };
47
+ }
48
+ case 'tone.stop': {
49
+ const spec = chip.pwm?.specs[0];
50
+ if (!spec) return { code: `/* tone.stop: no PWM spec */` };
51
+ const v = `__tc_pwm_${spec.dtSpec.replace(/-/g, '_')}`;
52
+ return { code: `pwm_set_pulse_dt(&${v}, 0);` };
53
+ }
54
+ default:
55
+ throw new Error(
56
+ `framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
57
+ `Open an issue or use rawCpp() to emit it manually.`,
58
+ );
59
+ }
60
+ }
@@ -0,0 +1,111 @@
1
+ // ---------------------------------------------------------------------------
2
+ // UART lowering — uart_poll_out / uart_poll_in per-byte
3
+ //
4
+ // Zephyr's UART API is byte-oriented (uart_poll_out / uart_poll_in). The HAL's
5
+ // uart.print/println/write lower to per-byte poll_out loops. For console output
6
+ // the strategy's transformConsoleCall routes to printk; these uart.* ops are
7
+ // for a specific UART port (the XIAO exposes uart0 on D6/D7).
8
+ //
9
+ // The device resolves at compile time via DEVICE_DT_GET(DT_NODELABEL(uart0)).
10
+ // uart.begin configures the baud via uart_configure.
11
+ // ---------------------------------------------------------------------------
12
+
13
+ import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
14
+ import type { ZephyrChipDescriptor } from '../chips/types.js';
15
+ import { parseControllerIndex } from './util.js';
16
+
17
+ /** The C variable prefix for a controller's state. */
18
+ function prefix(idx: number): string {
19
+ return `__tc_uart${idx}`;
20
+ }
21
+
22
+ /**
23
+ * Emit the per-controller UART device + an init helper. Called from shimLines
24
+ * when the program uses UART.
25
+ */
26
+ export function uartInitLines(chip: ZephyrChipDescriptor, controllerIndex: number): string[] {
27
+ const ctrl = chip.uart?.controllers[controllerIndex];
28
+ if (!ctrl) return [];
29
+ const p = prefix(controllerIndex);
30
+ return [
31
+ '// CUTTLEFISH_UART_BEGIN',
32
+ `static const struct device* ${p}_dev = DEVICE_DT_GET(DT_NODELABEL(${ctrl.nodeLabel}));`,
33
+ `static void ${p}_init(uint32_t baud) {`,
34
+ ` const struct uart_config cfg = { .baudrate = (baud ? baud : 115200), .parity = UART_CFG_PARITY_NONE, .stop_bits = UART_CFG_STOP_BITS_1, .data_bits = UART_CFG_DATA_BITS_8, .flow_ctrl = UART_CFG_FLOW_CTRL_NONE };`,
35
+ ` uart_configure(${p}_dev, &cfg);`,
36
+ `}`,
37
+ '// CUTTLEFISH_UART_END',
38
+ ];
39
+ }
40
+
41
+ /** Render a string-literal or expression to a per-byte poll_out loop. */
42
+ function renderWrite(dev: string, value: string, newline: boolean): string {
43
+ // String literal → emit a char-array loop (known length). Otherwise fall back
44
+ // to a const char* cast with a strlen-guarded loop.
45
+ if (/^".*"$/.test(value)) {
46
+ const body = `for (size_t __i = 0; __i < sizeof(${value}) - 1; __i++) { uart_poll_out(${dev}, (${value})[__i]); }`;
47
+ return newline ? `${body} uart_poll_out(${dev}, '\\n');` : body;
48
+ }
49
+ const body = `for (const char* __s = (const char*)(${value}); *__s; __s++) { uart_poll_out(${dev}, *__s); }`;
50
+ return newline ? `${body} uart_poll_out(${dev}, '\\n');` : body;
51
+ }
52
+
53
+ /**
54
+ * Resolve a HAL uart.* op to Zephyr C++.
55
+ * Returns `{ code }` for statement ops, `{ expression }` for value-returning ops.
56
+ */
57
+ export function lowerUart(op: HALOpIR): { code?: string; expression?: string } {
58
+ const o = op as any;
59
+ const idx = parseControllerIndex(o.port);
60
+ const p = prefix(idx);
61
+ const dev = `${p}_dev`;
62
+
63
+ switch (op.operation) {
64
+ case 'uart.begin':
65
+ return { code: `${p}_init(static_cast<uint32_t>(${o.baud}));` };
66
+ case 'uart.end':
67
+ return { code: `(void)${dev};` };
68
+ case 'uart.print':
69
+ return { code: renderWrite(dev, o.value, false) };
70
+ case 'uart.println':
71
+ return { code: renderWrite(dev, o.value, true) };
72
+ case 'uart.write':
73
+ return { code: renderWrite(dev, o.data, false) };
74
+ case 'uart.printf': {
75
+ // snprintf into a buffer, then poll_out each byte.
76
+ const fmt = o.format;
77
+ const args = (o.args ?? []).join(', ');
78
+ const argList = args ? `, ${args}` : '';
79
+ return {
80
+ code: `char __buf[128]; int __n = snprintk(__buf, sizeof(__buf), ${fmt}${argList}); for (int __i = 0; __i < __n; __i++) { uart_poll_out(${dev}, __buf[__i]); }`,
81
+ };
82
+ }
83
+ case 'uart.read':
84
+ // Non-blocking poll; returns the byte or -1 if none available.
85
+ return { expression: `({ unsigned char __b = 0; (uart_poll_in(${dev}, &__b) == 0) ? (int)__b : -1; })` };
86
+ case 'uart.peek':
87
+ // The poll API has no buffered-byte store, so there is no true peek.
88
+ // Return -1 (the Arduino "no data" sentinel) rather than blocking. This
89
+ // is an honest limitation of the byte-level poll driver; an interrupt- or
90
+ // DMA-backed UART driver would be needed for real peek semantics.
91
+ return { expression: '(-1)' };
92
+ case 'uart.available':
93
+ // The poll API reports only "at least one byte ready" via uart_poll_in's
94
+ // return code — it has no buffered-byte count, and probing with poll_in
95
+ // would DRAIN the very byte the caller next wants to read. So we cannot
96
+ // honestly report availability. Return 0 (Arduino's "no data" value)
97
+ // rather than the old truthy -1, so `if (uart.available())` loops don't
98
+ // spin forever on a false-positive. Callers should instead just call
99
+ // uart.read() directly (it returns -1 when no byte is ready). For true
100
+ // buffered availability, use an interrupt/DMA-backed UART driver.
101
+ return { expression: '(0)' };
102
+ case 'uart.flush':
103
+ // poll_out is synchronous (blocking until sent); flush is a no-op.
104
+ return { code: `(void)${dev};` };
105
+ default:
106
+ throw new Error(
107
+ `framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
108
+ `Open an issue or use rawCpp() to emit it manually.`,
109
+ );
110
+ }
111
+ }
@@ -0,0 +1,12 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Shared lowering utilities
3
+ // ---------------------------------------------------------------------------
4
+
5
+ /** Parse a peripheral bus/port string like "UART0" / "I2C1" / "SPI2" → numeric
6
+ * index. The HAL op stream carries the bus as a TS-side name (I2C0, SPI0);
7
+ * the index selects which controller in the chip descriptor is used. */
8
+ export function parseControllerIndex(busOrPort: string | undefined): number {
9
+ if (!busOrPort) return 0;
10
+ const m = busOrPort.match(/(\d+)$/);
11
+ return m ? parseInt(m[1], 10) : 0;
12
+ }
@@ -0,0 +1,84 @@
1
+ // ---------------------------------------------------------------------------
2
+ // WDT lowering — nRF watchdog via wdt0
3
+ //
4
+ // Zephyr's WDT API: install a timeout (before setup), then setup, then feed
5
+ // periodically. The lowering caches the channel id returned by
6
+ // wdt_install_timeout in a static var so wdt.reset can feed it.
7
+ //
8
+ // enable(timeout): wdt_install_timeout + wdt_setup. Zephyr expects the timeout
9
+ // in milliseconds (wdt_window.max). The HAL passes a "250ms" string or WDTO_*
10
+ // constant or a number; we parse to ms in the lowering.
11
+ // ---------------------------------------------------------------------------
12
+
13
+ import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
14
+ import type { ZephyrChipDescriptor } from '../chips/types.js';
15
+
16
+ /** Parse a HAL wdt timeout ("250ms", WDTO_2S, or a bare number) to ms.
17
+ * Tolerates undefined (the manifest validator's probe sends a minimal op). */
18
+ function timeoutToMs(timeout: string | number | undefined): number {
19
+ if (typeof timeout === 'number') return timeout;
20
+ if (!timeout) return 1000; // default 1s when absent (e.g. the validator probe)
21
+ // Arduino WDTO_* constants.
22
+ const wdto: Record<string, number> = {
23
+ WDTO_15MS: 15, WDTO_30MS: 30, WDTO_60MS: 60, WDTO_120MS: 120,
24
+ WDTO_250MS: 250, WDTO_500MS: 500, WDTO_1S: 1000, WDTO_2S: 2000,
25
+ WDTO_4S: 4000, WDTO_8S: 8000,
26
+ };
27
+ if (wdto[timeout]) return wdto[timeout];
28
+ const m = timeout.match(/^(\d+)\s*ms$/i);
29
+ if (m) return parseInt(m[1], 10);
30
+ const s = timeout.match(/^(\d+)\s*s$/i);
31
+ if (s) return parseInt(s[1], 10) * 1000;
32
+ const n = parseInt(timeout, 10);
33
+ return isNaN(n) ? 1000 : n;
34
+ }
35
+
36
+ /**
37
+ * Emit the WDT device + channel state. Called from shimLines when the program
38
+ * uses the watchdog.
39
+ */
40
+ export function wdtInitLines(chip: ZephyrChipDescriptor): string[] {
41
+ const nodeLabel = chip.wdt?.nodeLabel ?? 'wdt0';
42
+ return [
43
+ '// CUTTLEFISH_WDT_BEGIN',
44
+ `static const struct device* __tc_wdt_dev = DEVICE_DT_GET(DT_NODELABEL(${nodeLabel}));`,
45
+ 'static int __tc_wdt_channel = -1;',
46
+ 'static bool __tc_wdt_setup_done = false;',
47
+ '// CUTTLEFISH_WDT_END',
48
+ ];
49
+ }
50
+
51
+ /**
52
+ * Resolve a HAL wdt.* op to Zephyr C++.
53
+ * Returns `{ code }` for statement ops, `{ expression }` for value-returning ops.
54
+ */
55
+ export function lowerWdt(op: HALOpIR): { code?: string; expression?: string } {
56
+ const o = op as any;
57
+
58
+ switch (op.operation) {
59
+ case 'wdt.enable': {
60
+ const ms = timeoutToMs(o.timeout);
61
+ // Install a timeout then set up the watchdog. Both are idempotent via
62
+ // the static flags. WDT_FLAG_RESET_CPU_CORE = full reset on timeout.
63
+ return {
64
+ code: [
65
+ `if (!__tc_wdt_setup_done) {`,
66
+ ` const struct wdt_timeout_cfg __cfg = { .window = { .min = 0, .max = ${ms} }, .callback = NULL, .flags = WDT_FLAG_RESET_CPU_CORE };`,
67
+ ` __tc_wdt_channel = wdt_install_timeout(__tc_wdt_dev, &__cfg);`,
68
+ ` wdt_setup(__tc_wdt_dev, WDT_OPT_PAUSE_HALTED_BY_DBG);`,
69
+ ` __tc_wdt_setup_done = true;`,
70
+ `}`,
71
+ ].join(' '),
72
+ };
73
+ }
74
+ case 'wdt.reset':
75
+ return { code: `if (__tc_wdt_channel >= 0) { wdt_feed(__tc_wdt_dev, __tc_wdt_channel); }` };
76
+ case 'wdt.disable':
77
+ return { code: `wdt_disable(__tc_wdt_dev); __tc_wdt_setup_done = false;` };
78
+ default:
79
+ throw new Error(
80
+ `framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
81
+ `Open an issue or use rawCpp() to emit it manually.`,
82
+ );
83
+ }
84
+ }