@typecad/framework-zephyr 1.0.0-alpha.10

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 (150) 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/doctor.d.ts +6 -0
  32. package/dist/doctor.js +48 -0
  33. package/dist/dt-config/kconfig.d.ts +27 -0
  34. package/dist/dt-config/kconfig.js +283 -0
  35. package/dist/dt-config/overlay.d.ts +35 -0
  36. package/dist/dt-config/overlay.js +246 -0
  37. package/dist/framework.manifest.d.ts +101 -0
  38. package/dist/framework.manifest.js +536 -0
  39. package/dist/index.d.ts +6 -0
  40. package/dist/index.js +17 -0
  41. package/dist/lowering/adc.d.ts +19 -0
  42. package/dist/lowering/adc.js +76 -0
  43. package/dist/lowering/ble.d.ts +20 -0
  44. package/dist/lowering/ble.js +419 -0
  45. package/dist/lowering/board.d.ts +9 -0
  46. package/dist/lowering/board.js +33 -0
  47. package/dist/lowering/gpio.d.ts +12 -0
  48. package/dist/lowering/gpio.js +123 -0
  49. package/dist/lowering/http.d.ts +23 -0
  50. package/dist/lowering/http.js +482 -0
  51. package/dist/lowering/i2c.d.ts +15 -0
  52. package/dist/lowering/i2c.js +114 -0
  53. package/dist/lowering/index.d.ts +29 -0
  54. package/dist/lowering/index.js +93 -0
  55. package/dist/lowering/interrupts.d.ts +21 -0
  56. package/dist/lowering/interrupts.js +91 -0
  57. package/dist/lowering/mqtt.d.ts +19 -0
  58. package/dist/lowering/mqtt.js +366 -0
  59. package/dist/lowering/power.d.ts +9 -0
  60. package/dist/lowering/power.js +64 -0
  61. package/dist/lowering/preferences.d.ts +23 -0
  62. package/dist/lowering/preferences.js +345 -0
  63. package/dist/lowering/pulse.d.ts +7 -0
  64. package/dist/lowering/pulse.js +82 -0
  65. package/dist/lowering/pwm.d.ts +15 -0
  66. package/dist/lowering/pwm.js +59 -0
  67. package/dist/lowering/random.d.ts +18 -0
  68. package/dist/lowering/random.js +129 -0
  69. package/dist/lowering/spi.d.ts +17 -0
  70. package/dist/lowering/spi.js +113 -0
  71. package/dist/lowering/timing.d.ts +9 -0
  72. package/dist/lowering/timing.js +63 -0
  73. package/dist/lowering/tone.d.ts +10 -0
  74. package/dist/lowering/tone.js +50 -0
  75. package/dist/lowering/uart.d.ts +15 -0
  76. package/dist/lowering/uart.js +102 -0
  77. package/dist/lowering/util.d.ts +4 -0
  78. package/dist/lowering/util.js +12 -0
  79. package/dist/lowering/wdt.d.ts +15 -0
  80. package/dist/lowering/wdt.js +80 -0
  81. package/dist/lowering/wifi.d.ts +21 -0
  82. package/dist/lowering/wifi.js +447 -0
  83. package/dist/lowering/worker-backing.d.ts +14 -0
  84. package/dist/lowering/worker-backing.js +79 -0
  85. package/dist/lowering/worker.d.ts +6 -0
  86. package/dist/lowering/worker.js +14 -0
  87. package/dist/strategy.d.ts +168 -0
  88. package/dist/strategy.js +1094 -0
  89. package/dist/toolchain/compat.d.ts +38 -0
  90. package/dist/toolchain/compat.js +133 -0
  91. package/dist/toolchain/debug-config.d.ts +82 -0
  92. package/dist/toolchain/debug-config.js +359 -0
  93. package/dist/toolchain/index.d.ts +73 -0
  94. package/dist/toolchain/index.js +444 -0
  95. package/dist/toolchain/scaffold.d.ts +15 -0
  96. package/dist/toolchain/scaffold.js +196 -0
  97. package/dist/toolchain/west-discover.d.ts +40 -0
  98. package/dist/toolchain/west-discover.js +203 -0
  99. package/dist/toolchain/west-spawn.d.ts +39 -0
  100. package/dist/toolchain/west-spawn.js +117 -0
  101. package/package.json +82 -0
  102. package/src/async/timer-polyfill.ts +107 -0
  103. package/src/chips/controllers.ts +74 -0
  104. package/src/chips/esp32.ts +62 -0
  105. package/src/chips/esp32s3.ts +57 -0
  106. package/src/chips/index.ts +54 -0
  107. package/src/chips/resolve.ts +146 -0
  108. package/src/chips/types.ts +163 -0
  109. package/src/chips/xiao-ble.ts +64 -0
  110. package/src/debug-codegen.ts +207 -0
  111. package/src/display/gfx.ts +190 -0
  112. package/src/display/index.ts +70 -0
  113. package/src/display/profiles.ts +63 -0
  114. package/src/display/touch-adapter.ts +100 -0
  115. package/src/display/ui-adapter.ts +558 -0
  116. package/src/doctor.ts +56 -0
  117. package/src/dt-config/kconfig.ts +308 -0
  118. package/src/dt-config/overlay.ts +293 -0
  119. package/src/framework.manifest.ts +558 -0
  120. package/src/index.ts +29 -0
  121. package/src/lowering/adc.ts +102 -0
  122. package/src/lowering/ble.ts +426 -0
  123. package/src/lowering/board.ts +38 -0
  124. package/src/lowering/gpio.ts +148 -0
  125. package/src/lowering/http.ts +488 -0
  126. package/src/lowering/i2c.ts +126 -0
  127. package/src/lowering/index.ts +86 -0
  128. package/src/lowering/interrupts.ts +114 -0
  129. package/src/lowering/mqtt.ts +371 -0
  130. package/src/lowering/power.ts +70 -0
  131. package/src/lowering/preferences.ts +354 -0
  132. package/src/lowering/pulse.ts +93 -0
  133. package/src/lowering/pwm.ts +74 -0
  134. package/src/lowering/random.ts +135 -0
  135. package/src/lowering/spi.ts +124 -0
  136. package/src/lowering/timing.ts +72 -0
  137. package/src/lowering/tone.ts +60 -0
  138. package/src/lowering/uart.ts +111 -0
  139. package/src/lowering/util.ts +12 -0
  140. package/src/lowering/wdt.ts +84 -0
  141. package/src/lowering/wifi.ts +452 -0
  142. package/src/lowering/worker-backing.ts +94 -0
  143. package/src/lowering/worker.ts +17 -0
  144. package/src/strategy.ts +1265 -0
  145. package/src/toolchain/compat.ts +145 -0
  146. package/src/toolchain/debug-config.ts +399 -0
  147. package/src/toolchain/index.ts +482 -0
  148. package/src/toolchain/scaffold.ts +196 -0
  149. package/src/toolchain/west-discover.ts +233 -0
  150. package/src/toolchain/west-spawn.ts +141 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 typecad0
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,100 @@
1
+ # @typecad/framework-zephyr
2
+
3
+ TypeCAD framework package that lowers HAL operation IR to **native Zephyr RTOS
4
+ driver API calls**. Generates a real Zephyr application (`main` + `src/main.cpp`
5
+ + `CMakeLists.txt` + `prj.conf`), compiled through Zephyr's `west` / CMake /
6
+ Ninja build system.
7
+
8
+ GPIO is lowered through devicetree specs (`gpio_pin_*_dt`) so an active-low
9
+ LED's polarity is honored by the DT flags, not by the generated code.
10
+
11
+ ## Requirements
12
+
13
+ - Zephyr RTOS ≥ 3.x installed (the SDK + a west-enabled Python environment).
14
+ - The framework auto-discovers `west` without requiring you to activate the
15
+ venv first. Discovery cascade (first usable wins):
16
+ 1. `west` already on `PATH` (env already activated / global install).
17
+ 2. `$ZEPHYR_BASE` sibling venv: `${ZEPHYR_BASE}/../.venv/<python> -m west`.
18
+ 3. Well-known workspace layouts (`~/zephyrproject/.venv`,
19
+ `/opt/zephyrproject/.venv`, etc.).
20
+ 4. System pythons (`python`, `python3`, `py`) via `-m west`.
21
+
22
+ If none is found, the build errors with an actionable message pointing at the
23
+ fix (`pip install west`, set `ZEPHYR_BASE`, or activate the venv).
24
+
25
+ ## How it works
26
+
27
+ - HAL ops (`gpio.write`, `i2c.begin`, etc.) lower to native Zephyr driver calls
28
+ (`gpio_pin_set_dt`, `i2c_write`, etc.) — no Arduino API.
29
+ - The generated `src/main.cpp` defines `int main(void)` — the standard Zephyr
30
+ C entry point. `main` runs the synthesizer-emitted `setup()` once, then loops
31
+ `loop()` forever, yielding to the scheduler with `k_msleep(1)` each iteration
32
+ (cheap cooperative yield — matches the `app_main` pattern in framework-esp32).
33
+ - The framework emits a complete Zephyr application: root `CMakeLists.txt`
34
+ (`find_package(Zephyr)` + `file(GLOB src/*.cpp)`) and `prj.conf` (the Kconfig
35
+ symbols for the lowered peripherals). Both are regenerated idempotently —
36
+ only rewritten when their content changes, so Ninja's incremental build is
37
+ preserved.
38
+ - The MVP target is the **Seeed Studio XIAO nRF52840** (`xiao_ble` board).
39
+
40
+ ## Installation
41
+
42
+ ```sh
43
+ cuttlefish init # select "Zephyr RTOS" for an nRF52840-family target
44
+ ```
45
+
46
+ Or in `cuttlefish.config.ts`:
47
+
48
+ ```ts
49
+ export default {
50
+ framework: '@typecad/framework-zephyr',
51
+ frameworkData: {
52
+ buildTarget: 'xiao_ble',
53
+ },
54
+ toolchain: { type: 'west' },
55
+ };
56
+ ```
57
+
58
+ ## HAL coverage
59
+
60
+ The manifest (`src/framework.manifest.ts`) is the honest, complete record of
61
+ what lowers. Fully supported: **gpio, pwm, adc, i2c, spi, uart, wdt, ble,
62
+ shift, board constants** (Board.definition.* / Pins.definition.* folding),
63
+ **random** (xorshift32 PRNG seeded from the Zephyr entropy tap).
64
+ Partial (some ops lower, some are deferred stubs): **timing, power,
65
+ interrupts, tone, pulse**. Unsupported for this target: **wifi** (nRF52840 has
66
+ no WiFi), **http** (no networking stack lowered), **display** (deferred),
67
+ **dac** (no DAC on nRF52840).
68
+
69
+ Run `npm test` (the manifest validator) to confirm the declared coverage matches
70
+ the actual lowering behavior.
71
+
72
+ ## Hardware tests
73
+
74
+ On-device tests live in `tests/`. Run them against real hardware:
75
+
76
+ ```sh
77
+ npm run test:hw # all groups
78
+ npm run test:hw:gpio # just GPIO
79
+ npm run test:hw:timers # just timing
80
+ ```
81
+
82
+ Update `test.port` in `cuttlefish.config.ts` to your XIAO's serial device
83
+ before running. The tests transpile → `west build` → flash → read results over
84
+ the console.
85
+
86
+ ## Notes / limitations
87
+
88
+ - **Minimal C++ libc.** Zephyr's default C++ support (`lib/cpp/minimal`) has no
89
+ `<vector>`, `<string>`, `<iostream>`, `<functional>`, exceptions, or RTTI.
90
+ `prj.conf` enables `CONFIG_NEWLIB_LIBC` for `std::string`/`std::vector`; array
91
+ literals still promote to the StaticArray wrapper, not `std::vector`.
92
+ - **Timer ops unsupported.** `timing.set_interval`/`set_timeout`/`clear_*` need
93
+ a polyfill-backed async runtime this framework does not emit yet. A Zephyr
94
+ workqueue / `k_thread` backing is a follow-on.
95
+ - **Debug mode.** `cuttlefish build --debug` routes through `printk` (always
96
+ available, no `CONFIG_CONSOLE` dependency) rather than `std::cout`, since the
97
+ minimal libc has no iostream. Breakpoints halt on console input (`ENTER`
98
+ continues, `s` skips).
99
+ - **AUTOSAR compliance.** All emitted C++ uses `static_cast`/`reinterpret_cast`
100
+ (no C-style casts) so the shim bytes pass `--autosar=strict`.
@@ -0,0 +1,10 @@
1
+ import type { RuntimePolyfillIR } from '@typecad/cuttlefish/api/shared';
2
+ export interface TimerPolyfillIR extends RuntimePolyfillIR {
3
+ /** The clamped pool size the C++ was generated with. */
4
+ maxTimers: number;
5
+ }
6
+ /**
7
+ * Build the timer_methods polyfill IR. `requestedTimers` is sized from
8
+ * ctx.analysis.timerCallCount; it is clamped to [1, 16].
9
+ */
10
+ export declare function buildTimerPolyfill(requestedTimers: number): TimerPolyfillIR;
@@ -0,0 +1,95 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Timer methods polyfill — Zephyr k_timer + k_work (heap-free)
3
+ //
4
+ // setInterval / setTimeout / clearInterval / clearTimeout backed by a fixed
5
+ // pool of k_timer + k_work pairs. The timer's expiry function (ISR context)
6
+ // submits a k_work item to the system workqueue; the work handler runs the
7
+ // user callback in thread context. This split keeps the ISR cheap and lets the
8
+ // callback run on a real thread (so it can do real work — but must not block,
9
+ // since the system workqueue is shared).
10
+ //
11
+ // EMIT BOUNDARY: emitted bytes land in user firmware. Covered by the TypeCAD
12
+ // Runtime Exception (RUNTIME_EXCEPTION.md at the repo root).
13
+ // ---------------------------------------------------------------------------
14
+ const MIN_TIMERS = 1;
15
+ const MAX_TIMERS_CAP = 16;
16
+ /**
17
+ * Build the timer_methods polyfill IR. `requestedTimers` is sized from
18
+ * ctx.analysis.timerCallCount; it is clamped to [1, 16].
19
+ */
20
+ export function buildTimerPolyfill(requestedTimers) {
21
+ const maxTimers = Math.min(MAX_TIMERS_CAP, Math.max(MIN_TIMERS, requestedTimers));
22
+ const helperStructs = [`
23
+ // cuttlefish timer runtime — k_timer + k_work pool (heap-free).
24
+ struct __tc_TimerSlot {
25
+ struct k_timer timer;
26
+ struct k_work work;
27
+ void (*callback)(void);
28
+ bool active;
29
+ };
30
+
31
+ static struct __tc_TimerSlot __tc_timer_slots[${maxTimers}];
32
+
33
+ static void __tc_timer_work_handler(struct k_work* w) {
34
+ // Runs on the system workqueue thread (NOT ISR). Find the owning slot by
35
+ // address and invoke the callback. Must not block.
36
+ for (int32_t i = 0; i < ${maxTimers}; i++) {
37
+ if (&__tc_timer_slots[i].work == w) {
38
+ if (__tc_timer_slots[i].callback != nullptr) { __tc_timer_slots[i].callback(); }
39
+ return;
40
+ }
41
+ }
42
+ }
43
+
44
+ static void __tc_timer_expiry_fn(struct k_timer* t) {
45
+ // ISR context: submit the work item, do NOT run the callback here.
46
+ for (int32_t i = 0; i < ${maxTimers}; i++) {
47
+ if (&__tc_timer_slots[i].timer == t) {
48
+ (void)k_work_submit(&__tc_timer_slots[i].work);
49
+ return;
50
+ }
51
+ }
52
+ }
53
+
54
+ static int32_t __tc_timer_add(void (*cb)(void), int32_t ms, bool repeat) {
55
+ for (int32_t i = 0; i < ${maxTimers}; i++) {
56
+ if (!__tc_timer_slots[i].active) {
57
+ __tc_timer_slots[i].callback = cb;
58
+ __tc_timer_slots[i].active = true;
59
+ k_timer_init(&__tc_timer_slots[i].timer, __tc_timer_expiry_fn, nullptr);
60
+ k_work_init(&__tc_timer_slots[i].work, __tc_timer_work_handler);
61
+ k_timer_start(&__tc_timer_slots[i].timer, K_MSEC(ms), repeat ? K_MSEC(ms) : K_FOREVER);
62
+ return i + 1; // 1-based id (Arduino parity)
63
+ }
64
+ }
65
+ return 0; // pool full
66
+ }
67
+
68
+ static void __tc_timer_clear(int32_t id) {
69
+ if (id > 0 && id <= ${maxTimers}) {
70
+ int32_t i = id - 1;
71
+ k_timer_stop(&__tc_timer_slots[i].timer);
72
+ __tc_timer_slots[i].active = false;
73
+ __tc_timer_slots[i].callback = nullptr;
74
+ }
75
+ }
76
+ `];
77
+ const helperFunctions = [`
78
+ int32_t __tc_setInterval(void (*cb)(), int32_t ms) { return __tc_timer_add(cb, ms, true); }
79
+ int32_t __tc_setTimeout(void (*cb)(), int32_t ms) { return __tc_timer_add(cb, ms, false); }
80
+ void __tc_clearInterval(int32_t id) { __tc_timer_clear(id); }
81
+ void __tc_clearTimeout(int32_t id) { __tc_timer_clear(id); }
82
+ `];
83
+ return {
84
+ kind: 'polyfill',
85
+ id: 'timer_methods',
86
+ domain: 'embedded',
87
+ requiredIncludes: [],
88
+ forwardDeclarations: [],
89
+ helperStructs,
90
+ helperFunctions,
91
+ shimMacros: [],
92
+ dependencies: [],
93
+ maxTimers,
94
+ };
95
+ }
@@ -0,0 +1,21 @@
1
+ import type { ZephyrChipDescriptor } from './types.js';
2
+ /**
3
+ * Resolve the devicetree nodelabel of the GPIO controller that owns `pin`.
4
+ *
5
+ * For single-controller SoCs (no `gpioControllers`) this is always
6
+ * `chip.gpioController`. For multi-controller SoCs it finds the entry whose
7
+ * [minPin, maxPin] range contains `pin`, falling back to `chip.gpioController`
8
+ * for out-of-range pins (e.g. the manifest probe's synthetic pin 0).
9
+ */
10
+ export declare function controllerNodelabelForPin(chip: ZephyrChipDescriptor, pin: number): string;
11
+ /**
12
+ * Emit the C++ source for a runtime pin → GPIO-device dispatcher.
13
+ *
14
+ * Returns lines defining `static inline const struct device* __tc_gpio_dev(uint32_t pin)`.
15
+ * Each branch resolves its controller via `DEVICE_DT_GET(DT_NODELABEL(...))` at
16
+ * compile time (the macro is evaluated per branch, so it is always statically
17
+ * valid); only `pin` is runtime. For a single-controller SoC this collapses to
18
+ * a one-liner returning that controller, so the existing XIAO nRF52840 behavior
19
+ * is byte-for-byte unchanged.
20
+ */
21
+ export declare function emitGpioDevDispatcher(chip: ZephyrChipDescriptor): string[];
@@ -0,0 +1,66 @@
1
+ // ---------------------------------------------------------------------------
2
+ // GPIO controller resolution — pin → devicetree nodelabel
3
+ //
4
+ // Most SoCs expose a single GPIO controller, so a HAL pin maps to
5
+ // `chip.gpioController` unconditionally. SoCs that split GPIO across multiple
6
+ // devicetree nodes (ESP32-S3: `gpio0` 0–31, `gpio1` 32–48) list a range per
7
+ // controller in `chip.gpioControllers`. These helpers route a HAL pin to the
8
+ // owning controller.
9
+ //
10
+ // Two forms:
11
+ // - controllerNodelabelForPin(): used by the compile-time lowering paths
12
+ // (gpio/pulse/spi raw writes), where the pin number is a literal known at
13
+ // transpile time. Resolves to a single nodelabel so the emitted
14
+ // `DT_NODELABEL(<nodelabel>)` macro is statically valid.
15
+ // - emitGpioDevDispatcher(): used by paths that take a RUNTIME pin (the
16
+ // @typecad/safety __tc_gpio_read/__tc_gpio_write shims). Emits a tiny
17
+ // `__tc_gpio_dev(uint32_t pin)` that returns the owning `const struct
18
+ // device*`, so a single shim body handles any pin. Single-controller SoCs
19
+ // collapse to a one-liner.
20
+ // ---------------------------------------------------------------------------
21
+ /**
22
+ * Resolve the devicetree nodelabel of the GPIO controller that owns `pin`.
23
+ *
24
+ * For single-controller SoCs (no `gpioControllers`) this is always
25
+ * `chip.gpioController`. For multi-controller SoCs it finds the entry whose
26
+ * [minPin, maxPin] range contains `pin`, falling back to `chip.gpioController`
27
+ * for out-of-range pins (e.g. the manifest probe's synthetic pin 0).
28
+ */
29
+ export function controllerNodelabelForPin(chip, pin) {
30
+ const ranges = chip.gpioControllers;
31
+ if (ranges && ranges.length > 0) {
32
+ const hit = ranges.find((r) => pin >= r.minPin && pin <= r.maxPin);
33
+ if (hit)
34
+ return hit.nodelabel;
35
+ }
36
+ return chip.gpioController;
37
+ }
38
+ /**
39
+ * Emit the C++ source for a runtime pin → GPIO-device dispatcher.
40
+ *
41
+ * Returns lines defining `static inline const struct device* __tc_gpio_dev(uint32_t pin)`.
42
+ * Each branch resolves its controller via `DEVICE_DT_GET(DT_NODELABEL(...))` at
43
+ * compile time (the macro is evaluated per branch, so it is always statically
44
+ * valid); only `pin` is runtime. For a single-controller SoC this collapses to
45
+ * a one-liner returning that controller, so the existing XIAO nRF52840 behavior
46
+ * is byte-for-byte unchanged.
47
+ */
48
+ export function emitGpioDevDispatcher(chip) {
49
+ const ranges = chip.gpioControllers;
50
+ if (!ranges || ranges.length === 0) {
51
+ return [
52
+ 'static inline const struct device* __tc_gpio_dev(uint32_t pin) {',
53
+ ` (void)pin;`,
54
+ ` return DEVICE_DT_GET(DT_NODELABEL(${chip.gpioController}));`,
55
+ '}',
56
+ ];
57
+ }
58
+ const lines = [
59
+ 'static inline const struct device* __tc_gpio_dev(uint32_t pin) {',
60
+ ];
61
+ for (const r of ranges) {
62
+ lines.push(` if (pin >= ${r.minPin} && pin <= ${r.maxPin}) { return DEVICE_DT_GET(DT_NODELABEL(${r.nodelabel})); }`);
63
+ }
64
+ lines.push(` return DEVICE_DT_GET(DT_NODELABEL(${chip.gpioController}));`, '}');
65
+ return lines;
66
+ }
@@ -0,0 +1,2 @@
1
+ import type { ZephyrChipDescriptor } from './types.js';
2
+ export declare const ESP32_DEVKITC: ZephyrChipDescriptor;
@@ -0,0 +1,59 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Espressif ESP32 (esp32_devkitc) — Zephyr board descriptor
3
+ //
4
+ // Board target: `esp32_devkitc` (mainline Zephyr,
5
+ // boards/espressif/esp32_devkitc). Programmed over USB via the esptool runner
6
+ // (see toolchain/index.ts), the same flash path as the ESP32-S3.
7
+ //
8
+ // GPIO is split across TWO devicetree controllers — `gpio0` (pins 0–31) and
9
+ // `gpio1` (pins 32–39) — so this descriptor lists both in `gpioControllers`.
10
+ // The lowering routes each HAL pin to its owning controller at runtime; see
11
+ // chips/controllers.ts. (Same shape as the S3, but the ESP32's highest GPIO is
12
+ // 39, not 48 — `gpio1` has ngpios=8, not 17.)
13
+ //
14
+ // Minimal-by-design: the only DT facts carried here are the ones a compile-time
15
+ // DT macro cannot reach — the runtime pin→controller split, plus the `sw0`
16
+ // alias for the BOOT button (used by the devicetree-spec GPIO path). Every other
17
+ // DT fact (UART/I2C/SPI/`wdt` nodelabels) is resolved by Zephyr's own
18
+ // devicetree via emitted DT_NODELABEL macros, not hand-copied here.
19
+ //
20
+ // Verified against the Zephyr board DTS:
21
+ // boards/espressif/esp32_devkitc/esp32_devkitc_procpu.dts
22
+ // aliases { sw0 = &button0; } → button_0: pin 0 on gpio0, active-low + pull-up
23
+ // &gpio0/&gpio1 { status = "okay" } (esp32_devkitc_procpu.dts:64-70)
24
+ // &wifi { status = "okay" } (esp32_devkitc_procpu.dts:143-145)
25
+ // GPIO controller coverage:
26
+ // dts/xtensa/espressif/esp32/esp32_common.dtsi:314-337
27
+ // gpio0: ngpios = <32> (pins 0–31)
28
+ // gpio1: ngpios = <8> (pins 32–39)
29
+ //
30
+ // Note: GPIO 34–39 are input-only pads on ESP32 silicon (not modeled here —
31
+ // the DT does not encode output restrictions per pin; an output config on those
32
+ // pins fails at runtime against the raw controller, which is the expected
33
+ // silicon-accurate behavior).
34
+ // ---------------------------------------------------------------------------
35
+ export const ESP32_DEVKITC = {
36
+ id: 'esp32_devkitc',
37
+ soc: 'esp32',
38
+ gpioController: 'gpio0',
39
+ gpioControllers: [
40
+ { nodelabel: 'gpio0', minPin: 0, maxPin: 31 },
41
+ { nodelabel: 'gpio1', minPin: 32, maxPin: 39 },
42
+ ],
43
+ gpio: {
44
+ // The BOOT button (GPIO0) is the board's only DT-aliased GPIO. Listed so a
45
+ // program reading/interrupting pin 0 goes through the polarity-correct
46
+ // devicetree-spec path (GPIO_ACTIVE_LOW honored by the DT flags).
47
+ dtSpecs: [
48
+ { pin: 0, dtSpec: 'sw0' }, // BOOT button (GPIO0)
49
+ ],
50
+ interruptPins: [
51
+ { pin: 0, dtSpec: 'sw0' }, // BOOT button (GPIO0)
52
+ ],
53
+ },
54
+ // WiFi: the ESP32 has a 2.4GHz radio; conn_mgr + the esp32 wifi driver
55
+ // (CONFIG_WIFI_ESP32) provide connectivity. WIFI_ESP32 depends on !SMP, and
56
+ // the ESP32 is AMP (dual-image procpu/appcpu), not SMP, by default — so the
57
+ // dependency is satisfied. Omitted on radioless targets.
58
+ wifi: { supported: true },
59
+ };
@@ -0,0 +1,2 @@
1
+ import type { ZephyrChipDescriptor } from './types.js';
2
+ export declare const ESP32S3_DEVKITC: ZephyrChipDescriptor;
@@ -0,0 +1,54 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Espressif ESP32-S3 (esp32s3_devkitc) — Zephyr board descriptor
3
+ //
4
+ // Board target: `esp32s3_devkitc` (mainline Zephyr,
5
+ // boards/espressif/esp32s3_devkitc). Programmed over USB via the esptool
6
+ // runner (see toolchain/index.ts), unlike the J-Link/nrfjprog path used for
7
+ // the XIAO nRF52840.
8
+ //
9
+ // GPIO is split across TWO devicetree controllers — `gpio0` (pins 0–31) and
10
+ // `gpio1` (pins 32–48) — so this descriptor lists both in `gpioControllers`.
11
+ // The lowering routes each HAL pin to its owning controller at runtime; see
12
+ // chips/controllers.ts.
13
+ //
14
+ // Minimal-by-design: the only DT facts carried here are the ones a
15
+ // compile-time DT macro cannot reach — the runtime pin→controller split, plus
16
+ // the `sw0` alias for the BOOT button (used by the devicetree-spec GPIO path).
17
+ // Every other DT fact (UART/I2C/SPI/`wdt` nodelabels) is resolved by Zephyr's
18
+ // own devicetree via emitted DT_NODELABEL macros, not hand-copied here.
19
+ //
20
+ // Verified against the Zephyr board DTS:
21
+ // boards/espressif/esp32s3_devkitc/esp32s3_devkitc_procpu.dts
22
+ // aliases { sw0 = &button0; } → button_0: pin 0 on gpio0, active-low + pull-up
23
+ // (the DevKitC board DTS defines no led0 alias — the onboard RGB is a WS2812
24
+ // on GPIO38, not a plain GPIO LED, so it is intentionally NOT listed here.)
25
+ // ---------------------------------------------------------------------------
26
+ export const ESP32S3_DEVKITC = {
27
+ id: 'esp32s3_devkitc',
28
+ soc: 'esp32s3',
29
+ gpioController: 'gpio0',
30
+ gpioControllers: [
31
+ { nodelabel: 'gpio0', minPin: 0, maxPin: 31 },
32
+ { nodelabel: 'gpio1', minPin: 32, maxPin: 48 },
33
+ ],
34
+ gpio: {
35
+ // The BOOT button (GPIO0) is the board's only DT-aliased GPIO. Listed so a
36
+ // program reading/interrupting pin 0 goes through the polarity-correct
37
+ // devicetree-spec path (GPIO_ACTIVE_LOW honored by the DT flags). Every
38
+ // other GPIO pin uses the raw-controller path against its owning controller.
39
+ dtSpecs: [
40
+ { pin: 0, dtSpec: 'sw0' }, // BOOT button (GPIO0)
41
+ ],
42
+ interruptPins: [
43
+ { pin: 0, dtSpec: 'sw0' }, // BOOT button (GPIO0)
44
+ ],
45
+ },
46
+ // UART/I2C/SPI/`wdt` are intentionally omitted: their devicetree nodelabels
47
+ // (uart0/uart1/uart2, i2c0/i2c1, spi2/spi3, wdt0) are resolved by Zephyr's
48
+ // devicetree at compile time and don't need to be carried as data here. ADC is
49
+ // omitted as well — added when a demo needs analog reads, with the verified
50
+ // ESP32-S3 ADC1/ADC2 pin→channel map.
51
+ // WiFi: the ESP32-S3 has a 2.4GHz radio; conn_mgr + the esp32 wifi driver
52
+ // (CONFIG_WIFI_ESP32) provide connectivity. Omitted on radioless targets.
53
+ wifi: { supported: true },
54
+ };
@@ -0,0 +1,15 @@
1
+ import type { ZephyrChipDescriptor } from './types.js';
2
+ import { XIAO_BLE } from './xiao-ble.js';
3
+ import { ESP32S3_DEVKITC } from './esp32s3.js';
4
+ import { ESP32_DEVKITC } from './esp32.js';
5
+ export { XIAO_BLE, ESP32S3_DEVKITC, ESP32_DEVKITC };
6
+ export type { ZephyrChipDescriptor, ZephyrGpioDtSpec } from './types.js';
7
+ export declare function setActiveChip(d: ZephyrChipDescriptor): void;
8
+ export declare function getActiveChip(): ZephyrChipDescriptor;
9
+ /**
10
+ * Resolve a chip descriptor from the Zephyr board target string
11
+ * (frameworkData.buildTarget / frameworkData.target). Accepts the bare board
12
+ * id ('xiao_ble', 'esp32s3_devkitc', 'esp32_devkitc') or a board/qualifier
13
+ * path ('esp32s3_devkitc/esp32s3/procpu', 'esp32_devkitc/esp32/procpu').
14
+ */
15
+ export declare function chipForTarget(target?: string): ZephyrChipDescriptor;
@@ -0,0 +1,45 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Chip descriptor registry — maps Zephyr board target → ZephyrChipDescriptor
3
+ //
4
+ // Mirrors framework-esp32/src/chips/index.ts: a module-level activeChip,
5
+ // setActiveChip/getActiveChip, and chipForTarget resolving from
6
+ // frameworkData.buildTarget. The strategy calls setActiveChip during profile
7
+ // resolution so the lowering reads a single cached descriptor.
8
+ // ---------------------------------------------------------------------------
9
+ import { XIAO_BLE } from './xiao-ble.js';
10
+ import { ESP32S3_DEVKITC } from './esp32s3.js';
11
+ import { ESP32_DEVKITC } from './esp32.js';
12
+ export { XIAO_BLE, ESP32S3_DEVKITC, ESP32_DEVKITC };
13
+ /**
14
+ * Default chip used when no buildTarget is supplied. The XIAO nRF52840 is the
15
+ * canonical MVP target; subsequent board additions extend the switch below.
16
+ */
17
+ const DEFAULT_CHIP = XIAO_BLE;
18
+ let activeChip = DEFAULT_CHIP;
19
+ export function setActiveChip(d) {
20
+ activeChip = d;
21
+ }
22
+ export function getActiveChip() {
23
+ return activeChip;
24
+ }
25
+ /**
26
+ * Resolve a chip descriptor from the Zephyr board target string
27
+ * (frameworkData.buildTarget / frameworkData.target). Accepts the bare board
28
+ * id ('xiao_ble', 'esp32s3_devkitc', 'esp32_devkitc') or a board/qualifier
29
+ * path ('esp32s3_devkitc/esp32s3/procpu', 'esp32_devkitc/esp32/procpu').
30
+ */
31
+ export function chipForTarget(target) {
32
+ const t = (target ?? '').trim().toLowerCase();
33
+ const boardId = t.split('/')[0];
34
+ switch (boardId) {
35
+ case 'xiao_ble':
36
+ return XIAO_BLE;
37
+ case 'esp32s3_devkitc':
38
+ return ESP32S3_DEVKITC;
39
+ case 'esp32_devkitc':
40
+ return ESP32_DEVKITC;
41
+ case '':
42
+ default:
43
+ return DEFAULT_CHIP;
44
+ }
45
+ }
@@ -0,0 +1,9 @@
1
+ import type { BoardConstants } from '@typecad/cuttlefish/api/shared';
2
+ import type { ZephyrChipDescriptor } from './types.js';
3
+ /**
4
+ * Try to derive a ZephyrChipDescriptor from board/MCU package constants.
5
+ *
6
+ * Returns null when no zephyr info is available in the board constants
7
+ * (the caller should fall back to the hardcoded chipForTarget registry).
8
+ */
9
+ export declare function resolveChipFromBoard(bc: BoardConstants | undefined): ZephyrChipDescriptor | null;
@@ -0,0 +1,121 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Derive ZephyrChipDescriptor from board/MCU package constants
3
+ //
4
+ // The board constants resolver extracts flat dot-path scalars from the board
5
+ // and MCU definition files. This utility reconstructs the structured
6
+ // ZephyrChipDescriptor from those flat keys, merging SoC-level defaults
7
+ // (from the MCU package's zephyr field) with board-level overrides (from the
8
+ // board package's zephyr field).
9
+ //
10
+ // When a board package carries Zephyr config, this path replaces the
11
+ // hardcoded chip descriptor registry. When it doesn't (legacy), chipForTarget
12
+ // still works as the fallback.
13
+ // ---------------------------------------------------------------------------
14
+ /** Collect an indexed array of objects reconstructed from flat dot-path keys. */
15
+ function collectIndexed(bc, prefix, build) {
16
+ const result = [];
17
+ for (let i = 0; i < 256; i++) {
18
+ const checkKey = `${prefix}.${i}`;
19
+ let hasAny = false;
20
+ for (const [k] of bc) {
21
+ if (k.startsWith(checkKey)) {
22
+ hasAny = true;
23
+ break;
24
+ }
25
+ }
26
+ if (!hasAny)
27
+ break;
28
+ const item = build(bc, i);
29
+ if (item)
30
+ result.push(item);
31
+ }
32
+ return result;
33
+ }
34
+ function collectBusControllers(bc, prefix) {
35
+ return collectIndexed(bc, prefix, (m, i) => {
36
+ const nodeLabel = m.get(`${prefix}.${i}.nodeLabel`);
37
+ return nodeLabel ? { nodeLabel } : null;
38
+ });
39
+ }
40
+ /**
41
+ * Try to derive a ZephyrChipDescriptor from board/MCU package constants.
42
+ *
43
+ * Returns null when no zephyr info is available in the board constants
44
+ * (the caller should fall back to the hardcoded chipForTarget registry).
45
+ */
46
+ export function resolveChipFromBoard(bc) {
47
+ if (!bc)
48
+ return null;
49
+ const boardTarget = bc.get('build.frameworks.zephyr');
50
+ if (!boardTarget)
51
+ return null;
52
+ const zGpioController = bc.get('zephyr.gpioController');
53
+ const soc = bc.get('mcu.id') ?? '';
54
+ // ── Build mutable sub-objects, then construct the final descriptor ──────
55
+ const gc = collectIndexed(bc, 'zephyr.gpioControllers', (m, i) => {
56
+ const nodelabel = m.get(`zephyr.gpioControllers.${i}.nodelabel`);
57
+ const minPin = m.get(`zephyr.gpioControllers.${i}.minPin`);
58
+ const maxPin = m.get(`zephyr.gpioControllers.${i}.maxPin`);
59
+ if (nodelabel != null && minPin != null && maxPin != null) {
60
+ return { nodelabel, minPin, maxPin };
61
+ }
62
+ return null;
63
+ });
64
+ const dtSpecs = collectIndexed(bc, 'zephyr.gpio.dtSpecs', (m, i) => {
65
+ const pin = m.get(`zephyr.gpio.dtSpecs.${i}.pin`);
66
+ const dtSpec = m.get(`zephyr.gpio.dtSpecs.${i}.dtSpec`);
67
+ if (pin != null && dtSpec)
68
+ return { pin, dtSpec };
69
+ return null;
70
+ });
71
+ const intPins = collectIndexed(bc, 'zephyr.gpio.interruptPins', (m, i) => {
72
+ const pin = m.get(`zephyr.gpio.interruptPins.${i}.pin`);
73
+ const dtSpec = m.get(`zephyr.gpio.interruptPins.${i}.dtSpec`);
74
+ if (pin != null && dtSpec)
75
+ return { pin, dtSpec };
76
+ return null;
77
+ });
78
+ const i2cControllers = collectBusControllers(bc, 'zephyr.i2c.controllers');
79
+ const spiControllers = collectBusControllers(bc, 'zephyr.spi.controllers');
80
+ const uartControllers = collectBusControllers(bc, 'zephyr.uart.controllers');
81
+ const pwmSpecs = collectIndexed(bc, 'zephyr.pwm.specs', (m, i) => {
82
+ const pin = m.get(`zephyr.pwm.specs.${i}.pin`);
83
+ const dtSpec = m.get(`zephyr.pwm.specs.${i}.dtSpec`);
84
+ if (pin != null && dtSpec)
85
+ return { pin, dtSpec };
86
+ return null;
87
+ });
88
+ const adcNodeLabel = bc.get('zephyr.adc.nodeLabel');
89
+ const adcResolution = bc.get('zephyr.adc.resolution');
90
+ const adcVref = bc.get('zephyr.adc.vrefMv');
91
+ const adcChannels = collectIndexed(bc, 'zephyr.adc.channels', (m, i) => {
92
+ const pin = m.get(`zephyr.adc.channels.${i}.pin`);
93
+ const channel = m.get(`zephyr.adc.channels.${i}.channel`);
94
+ if (pin != null && channel != null)
95
+ return { pin, channel };
96
+ return null;
97
+ });
98
+ const wdtNodeLabel = bc.get('zephyr.wdt.nodeLabel');
99
+ const wifiSupported = bc.get('zephyr.wifi.supported');
100
+ // ── Construct the final readonly descriptor ─────────────────────────────
101
+ const gpio = {
102
+ dtSpecs,
103
+ ...(intPins.length > 0 ? { interruptPins: intPins } : {}),
104
+ };
105
+ return {
106
+ id: boardTarget,
107
+ soc,
108
+ gpioController: zGpioController ?? 'gpio0',
109
+ ...(gc.length > 0 ? { gpioControllers: gc } : {}),
110
+ gpio,
111
+ ...(i2cControllers.length > 0 ? { i2c: { controllers: i2cControllers } } : {}),
112
+ ...(spiControllers.length > 0 ? { spi: { controllers: spiControllers } } : {}),
113
+ ...(uartControllers.length > 0 ? { uart: { controllers: uartControllers } } : {}),
114
+ ...(pwmSpecs.length > 0 ? { pwm: { specs: pwmSpecs } } : {}),
115
+ ...(adcNodeLabel || adcResolution != null || adcVref != null || adcChannels.length > 0
116
+ ? { adc: { nodeLabel: adcNodeLabel ?? 'adc', resolution: adcResolution ?? 12, vrefMv: adcVref ?? 3000, channels: adcChannels } }
117
+ : {}),
118
+ ...(wdtNodeLabel ? { wdt: { nodeLabel: wdtNodeLabel } } : {}),
119
+ ...(wifiSupported ? { wifi: { supported: true } } : {}),
120
+ };
121
+ }