@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/package.json ADDED
@@ -0,0 +1,82 @@
1
+ {
2
+ "name": "@typecad/framework-zephyr",
3
+ "version": "1.0.0-alpha.10",
4
+ "description": "TypeCAD framework package for the Zephyr RTOS — west/CMake build, devicetree-driven GPIO",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ },
13
+ "./display": {
14
+ "types": "./dist/display/index.d.ts",
15
+ "default": "./dist/display/index.js"
16
+ },
17
+ "./framework.manifest": {
18
+ "types": "./dist/framework.manifest.d.ts",
19
+ "default": "./dist/framework.manifest.js"
20
+ }
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "src"
25
+ ],
26
+ "scripts": {
27
+ "build": "tsc",
28
+ "prepublishOnly": "npm run build",
29
+ "test": "vitest run --root ../.. tests/packages/framework-zephyr/manifest.test.ts",
30
+ "test:coverage": "npm test && tsx ../../scripts/render-framework-coverage.ts",
31
+ "test:hw": "npm exec -- cuttlefish-test",
32
+ "test:hw:basics": "npm exec -- cuttlefish-test tests/01-basics.test.ts",
33
+ "test:hw:gpio": "npm exec -- cuttlefish-test tests/40-gpio.test.ts",
34
+ "test:hw:timers": "npm exec -- cuttlefish-test tests/42-timers.test.ts",
35
+ "test:http": "tsx ../../tests/hardware/start-server.ts",
36
+ "test:hw:http": "cd ../../tests/hardware && npm exec -- cuttlefish-test http-client.test.ts",
37
+ "test:mqtt": "tsx ../../tests/hardware/start-server.ts",
38
+ "test:hw:mqtt": "cd ../../tests/hardware && npm exec -- cuttlefish-test mqtt-client.test.ts"
39
+ },
40
+ "dependencies": {
41
+ "@typecad/cuttlefish": "1.0.0-alpha.10"
42
+ },
43
+ "devDependencies": {
44
+ "@typecad/expect": "1.0.0-alpha.10",
45
+ "@typecad/board-xiao-nrf52840": "1.0.0-alpha.10",
46
+ "typescript": "^5.7.3"
47
+ },
48
+ "license": "MIT",
49
+ "publishConfig": {
50
+ "access": "public"
51
+ },
52
+ "repository": {
53
+ "type": "git",
54
+ "url": "git+https://github.com/justind000/typecode.git",
55
+ "directory": "packages/framework-zephyr"
56
+ },
57
+ "homepage": "https://github.com/justind000/typecode/tree/main/packages/framework-zephyr",
58
+ "bugs": {
59
+ "url": "https://github.com/justind000/typecode/issues"
60
+ },
61
+ "keywords": [
62
+ "ble",
63
+ "cpp",
64
+ "cuttlefish",
65
+ "embedded",
66
+ "firmware",
67
+ "framework",
68
+ "gpio",
69
+ "microcontroller",
70
+ "nordic",
71
+ "nrf52840",
72
+ "rtos",
73
+ "typecad",
74
+ "typescript",
75
+ "zephyr"
76
+ ],
77
+ "engines": {
78
+ "node": ">=18"
79
+ },
80
+ "author": "typecad0",
81
+ "sideEffects": false
82
+ }
@@ -0,0 +1,107 @@
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
+
15
+ import type { RuntimePolyfillIR } from '@typecad/cuttlefish/api/shared';
16
+
17
+ const MIN_TIMERS = 1;
18
+ const MAX_TIMERS_CAP = 16;
19
+
20
+ export interface TimerPolyfillIR extends RuntimePolyfillIR {
21
+ /** The clamped pool size the C++ was generated with. */
22
+ maxTimers: number;
23
+ }
24
+
25
+ /**
26
+ * Build the timer_methods polyfill IR. `requestedTimers` is sized from
27
+ * ctx.analysis.timerCallCount; it is clamped to [1, 16].
28
+ */
29
+ export function buildTimerPolyfill(requestedTimers: number): TimerPolyfillIR {
30
+ const maxTimers = Math.min(MAX_TIMERS_CAP, Math.max(MIN_TIMERS, requestedTimers));
31
+
32
+ const helperStructs = [`
33
+ // cuttlefish timer runtime — k_timer + k_work pool (heap-free).
34
+ struct __tc_TimerSlot {
35
+ struct k_timer timer;
36
+ struct k_work work;
37
+ void (*callback)(void);
38
+ bool active;
39
+ };
40
+
41
+ static struct __tc_TimerSlot __tc_timer_slots[${maxTimers}];
42
+
43
+ static void __tc_timer_work_handler(struct k_work* w) {
44
+ // Runs on the system workqueue thread (NOT ISR). Find the owning slot by
45
+ // address and invoke the callback. Must not block.
46
+ for (int32_t i = 0; i < ${maxTimers}; i++) {
47
+ if (&__tc_timer_slots[i].work == w) {
48
+ if (__tc_timer_slots[i].callback != nullptr) { __tc_timer_slots[i].callback(); }
49
+ return;
50
+ }
51
+ }
52
+ }
53
+
54
+ static void __tc_timer_expiry_fn(struct k_timer* t) {
55
+ // ISR context: submit the work item, do NOT run the callback here.
56
+ for (int32_t i = 0; i < ${maxTimers}; i++) {
57
+ if (&__tc_timer_slots[i].timer == t) {
58
+ (void)k_work_submit(&__tc_timer_slots[i].work);
59
+ return;
60
+ }
61
+ }
62
+ }
63
+
64
+ static int32_t __tc_timer_add(void (*cb)(void), int32_t ms, bool repeat) {
65
+ for (int32_t i = 0; i < ${maxTimers}; i++) {
66
+ if (!__tc_timer_slots[i].active) {
67
+ __tc_timer_slots[i].callback = cb;
68
+ __tc_timer_slots[i].active = true;
69
+ k_timer_init(&__tc_timer_slots[i].timer, __tc_timer_expiry_fn, nullptr);
70
+ k_work_init(&__tc_timer_slots[i].work, __tc_timer_work_handler);
71
+ k_timer_start(&__tc_timer_slots[i].timer, K_MSEC(ms), repeat ? K_MSEC(ms) : K_FOREVER);
72
+ return i + 1; // 1-based id (Arduino parity)
73
+ }
74
+ }
75
+ return 0; // pool full
76
+ }
77
+
78
+ static void __tc_timer_clear(int32_t id) {
79
+ if (id > 0 && id <= ${maxTimers}) {
80
+ int32_t i = id - 1;
81
+ k_timer_stop(&__tc_timer_slots[i].timer);
82
+ __tc_timer_slots[i].active = false;
83
+ __tc_timer_slots[i].callback = nullptr;
84
+ }
85
+ }
86
+ `];
87
+
88
+ const helperFunctions = [`
89
+ int32_t __tc_setInterval(void (*cb)(), int32_t ms) { return __tc_timer_add(cb, ms, true); }
90
+ int32_t __tc_setTimeout(void (*cb)(), int32_t ms) { return __tc_timer_add(cb, ms, false); }
91
+ void __tc_clearInterval(int32_t id) { __tc_timer_clear(id); }
92
+ void __tc_clearTimeout(int32_t id) { __tc_timer_clear(id); }
93
+ `];
94
+
95
+ return {
96
+ kind: 'polyfill',
97
+ id: 'timer_methods',
98
+ domain: 'embedded',
99
+ requiredIncludes: [],
100
+ forwardDeclarations: [],
101
+ helperStructs,
102
+ helperFunctions,
103
+ shimMacros: [],
104
+ dependencies: [],
105
+ maxTimers,
106
+ };
107
+ }
@@ -0,0 +1,74 @@
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
+ import type { ZephyrChipDescriptor } from './types.js';
23
+
24
+ /**
25
+ * Resolve the devicetree nodelabel of the GPIO controller that owns `pin`.
26
+ *
27
+ * For single-controller SoCs (no `gpioControllers`) this is always
28
+ * `chip.gpioController`. For multi-controller SoCs it finds the entry whose
29
+ * [minPin, maxPin] range contains `pin`, falling back to `chip.gpioController`
30
+ * for out-of-range pins (e.g. the manifest probe's synthetic pin 0).
31
+ */
32
+ export function controllerNodelabelForPin(chip: ZephyrChipDescriptor, pin: number): string {
33
+ const ranges = chip.gpioControllers;
34
+ if (ranges && ranges.length > 0) {
35
+ const hit = ranges.find((r) => pin >= r.minPin && pin <= r.maxPin);
36
+ if (hit) return hit.nodelabel;
37
+ }
38
+ return chip.gpioController;
39
+ }
40
+
41
+ /**
42
+ * Emit the C++ source for a runtime pin → GPIO-device dispatcher.
43
+ *
44
+ * Returns lines defining `static inline const struct device* __tc_gpio_dev(uint32_t pin)`.
45
+ * Each branch resolves its controller via `DEVICE_DT_GET(DT_NODELABEL(...))` at
46
+ * compile time (the macro is evaluated per branch, so it is always statically
47
+ * valid); only `pin` is runtime. For a single-controller SoC this collapses to
48
+ * a one-liner returning that controller, so the existing XIAO nRF52840 behavior
49
+ * is byte-for-byte unchanged.
50
+ */
51
+ export function emitGpioDevDispatcher(chip: ZephyrChipDescriptor): string[] {
52
+ const ranges = chip.gpioControllers;
53
+ if (!ranges || ranges.length === 0) {
54
+ return [
55
+ 'static inline const struct device* __tc_gpio_dev(uint32_t pin) {',
56
+ ` (void)pin;`,
57
+ ` return DEVICE_DT_GET(DT_NODELABEL(${chip.gpioController}));`,
58
+ '}',
59
+ ];
60
+ }
61
+ const lines: string[] = [
62
+ 'static inline const struct device* __tc_gpio_dev(uint32_t pin) {',
63
+ ];
64
+ for (const r of ranges) {
65
+ lines.push(
66
+ ` if (pin >= ${r.minPin} && pin <= ${r.maxPin}) { return DEVICE_DT_GET(DT_NODELABEL(${r.nodelabel})); }`,
67
+ );
68
+ }
69
+ lines.push(
70
+ ` return DEVICE_DT_GET(DT_NODELABEL(${chip.gpioController}));`,
71
+ '}',
72
+ );
73
+ return lines;
74
+ }
@@ -0,0 +1,62 @@
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
+
36
+ import type { ZephyrChipDescriptor } from './types.js';
37
+
38
+ export const ESP32_DEVKITC: ZephyrChipDescriptor = {
39
+ id: 'esp32_devkitc',
40
+ soc: 'esp32',
41
+ gpioController: 'gpio0',
42
+ gpioControllers: [
43
+ { nodelabel: 'gpio0', minPin: 0, maxPin: 31 },
44
+ { nodelabel: 'gpio1', minPin: 32, maxPin: 39 },
45
+ ],
46
+ gpio: {
47
+ // The BOOT button (GPIO0) is the board's only DT-aliased GPIO. Listed so a
48
+ // program reading/interrupting pin 0 goes through the polarity-correct
49
+ // devicetree-spec path (GPIO_ACTIVE_LOW honored by the DT flags).
50
+ dtSpecs: [
51
+ { pin: 0, dtSpec: 'sw0' }, // BOOT button (GPIO0)
52
+ ],
53
+ interruptPins: [
54
+ { pin: 0, dtSpec: 'sw0' }, // BOOT button (GPIO0)
55
+ ],
56
+ },
57
+ // WiFi: the ESP32 has a 2.4GHz radio; conn_mgr + the esp32 wifi driver
58
+ // (CONFIG_WIFI_ESP32) provide connectivity. WIFI_ESP32 depends on !SMP, and
59
+ // the ESP32 is AMP (dual-image procpu/appcpu), not SMP, by default — so the
60
+ // dependency is satisfied. Omitted on radioless targets.
61
+ wifi: { supported: true },
62
+ };
@@ -0,0 +1,57 @@
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
+
27
+ import type { ZephyrChipDescriptor } from './types.js';
28
+
29
+ export const ESP32S3_DEVKITC: ZephyrChipDescriptor = {
30
+ id: 'esp32s3_devkitc',
31
+ soc: 'esp32s3',
32
+ gpioController: 'gpio0',
33
+ gpioControllers: [
34
+ { nodelabel: 'gpio0', minPin: 0, maxPin: 31 },
35
+ { nodelabel: 'gpio1', minPin: 32, maxPin: 48 },
36
+ ],
37
+ gpio: {
38
+ // The BOOT button (GPIO0) is the board's only DT-aliased GPIO. Listed so a
39
+ // program reading/interrupting pin 0 goes through the polarity-correct
40
+ // devicetree-spec path (GPIO_ACTIVE_LOW honored by the DT flags). Every
41
+ // other GPIO pin uses the raw-controller path against its owning controller.
42
+ dtSpecs: [
43
+ { pin: 0, dtSpec: 'sw0' }, // BOOT button (GPIO0)
44
+ ],
45
+ interruptPins: [
46
+ { pin: 0, dtSpec: 'sw0' }, // BOOT button (GPIO0)
47
+ ],
48
+ },
49
+ // UART/I2C/SPI/`wdt` are intentionally omitted: their devicetree nodelabels
50
+ // (uart0/uart1/uart2, i2c0/i2c1, spi2/spi3, wdt0) are resolved by Zephyr's
51
+ // devicetree at compile time and don't need to be carried as data here. ADC is
52
+ // omitted as well — added when a demo needs analog reads, with the verified
53
+ // ESP32-S3 ADC1/ADC2 pin→channel map.
54
+ // WiFi: the ESP32-S3 has a 2.4GHz radio; conn_mgr + the esp32 wifi driver
55
+ // (CONFIG_WIFI_ESP32) provide connectivity. Omitted on radioless targets.
56
+ wifi: { supported: true },
57
+ };
@@ -0,0 +1,54 @@
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
+
10
+ import type { ZephyrChipDescriptor } from './types.js';
11
+ import { XIAO_BLE } from './xiao-ble.js';
12
+ import { ESP32S3_DEVKITC } from './esp32s3.js';
13
+ import { ESP32_DEVKITC } from './esp32.js';
14
+
15
+ export { XIAO_BLE, ESP32S3_DEVKITC, ESP32_DEVKITC };
16
+ export type { ZephyrChipDescriptor, ZephyrGpioDtSpec } from './types.js';
17
+
18
+ /**
19
+ * Default chip used when no buildTarget is supplied. The XIAO nRF52840 is the
20
+ * canonical MVP target; subsequent board additions extend the switch below.
21
+ */
22
+ const DEFAULT_CHIP: ZephyrChipDescriptor = XIAO_BLE;
23
+
24
+ let activeChip: ZephyrChipDescriptor = DEFAULT_CHIP;
25
+
26
+ export function setActiveChip(d: ZephyrChipDescriptor): void {
27
+ activeChip = d;
28
+ }
29
+
30
+ export function getActiveChip(): ZephyrChipDescriptor {
31
+ return activeChip;
32
+ }
33
+
34
+ /**
35
+ * Resolve a chip descriptor from the Zephyr board target string
36
+ * (frameworkData.buildTarget / frameworkData.target). Accepts the bare board
37
+ * id ('xiao_ble', 'esp32s3_devkitc', 'esp32_devkitc') or a board/qualifier
38
+ * path ('esp32s3_devkitc/esp32s3/procpu', 'esp32_devkitc/esp32/procpu').
39
+ */
40
+ export function chipForTarget(target?: string): ZephyrChipDescriptor {
41
+ const t = (target ?? '').trim().toLowerCase();
42
+ const boardId = t.split('/')[0];
43
+ switch (boardId) {
44
+ case 'xiao_ble':
45
+ return XIAO_BLE;
46
+ case 'esp32s3_devkitc':
47
+ return ESP32S3_DEVKITC;
48
+ case 'esp32_devkitc':
49
+ return ESP32_DEVKITC;
50
+ case '':
51
+ default:
52
+ return DEFAULT_CHIP;
53
+ }
54
+ }
@@ -0,0 +1,146 @@
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
+
15
+ import type { BoardConstants } from '@typecad/cuttlefish/api/shared';
16
+ import type {
17
+ ZephyrChipDescriptor,
18
+ ZephyrGpioDtSpec,
19
+ ZephyrGpioController,
20
+ ZephyrBusController,
21
+ ZephyrPwmSpec,
22
+ ZephyrInterruptPin,
23
+ ZephyrAdcChannel,
24
+ } from './types.js';
25
+
26
+ /** Collect an indexed array of objects reconstructed from flat dot-path keys. */
27
+ function collectIndexed<T>(
28
+ bc: BoardConstants,
29
+ prefix: string,
30
+ build: (bc: BoardConstants, index: number) => T | null,
31
+ ): T[] {
32
+ const result: T[] = [];
33
+ for (let i = 0; i < 256; i++) {
34
+ const checkKey = `${prefix}.${i}`;
35
+ let hasAny = false;
36
+ for (const [k] of bc) {
37
+ if (k.startsWith(checkKey)) { hasAny = true; break; }
38
+ }
39
+ if (!hasAny) break;
40
+ const item = build(bc, i);
41
+ if (item) result.push(item);
42
+ }
43
+ return result;
44
+ }
45
+
46
+ function collectBusControllers(
47
+ bc: BoardConstants,
48
+ prefix: string,
49
+ ): ZephyrBusController[] {
50
+ return collectIndexed<ZephyrBusController>(bc, prefix, (m, i) => {
51
+ const nodeLabel = m.get(`${prefix}.${i}.nodeLabel`) as string;
52
+ return nodeLabel ? { nodeLabel } : null;
53
+ });
54
+ }
55
+
56
+ /**
57
+ * Try to derive a ZephyrChipDescriptor from board/MCU package constants.
58
+ *
59
+ * Returns null when no zephyr info is available in the board constants
60
+ * (the caller should fall back to the hardcoded chipForTarget registry).
61
+ */
62
+ export function resolveChipFromBoard(
63
+ bc: BoardConstants | undefined,
64
+ ): ZephyrChipDescriptor | null {
65
+ if (!bc) return null;
66
+
67
+ const boardTarget = bc.get('build.frameworks.zephyr') as string | undefined;
68
+ if (!boardTarget) return null;
69
+
70
+ const zGpioController = bc.get('zephyr.gpioController') as string | undefined;
71
+ const soc = (bc.get('mcu.id') as string) ?? '';
72
+
73
+ // ── Build mutable sub-objects, then construct the final descriptor ──────
74
+
75
+ const gc = collectIndexed<ZephyrGpioController>(bc, 'zephyr.gpioControllers', (m, i) => {
76
+ const nodelabel = m.get(`zephyr.gpioControllers.${i}.nodelabel`) as string;
77
+ const minPin = m.get(`zephyr.gpioControllers.${i}.minPin`) as number;
78
+ const maxPin = m.get(`zephyr.gpioControllers.${i}.maxPin`) as number;
79
+ if (nodelabel != null && minPin != null && maxPin != null) {
80
+ return { nodelabel, minPin, maxPin };
81
+ }
82
+ return null;
83
+ });
84
+
85
+ const dtSpecs = collectIndexed<ZephyrGpioDtSpec>(bc, 'zephyr.gpio.dtSpecs', (m, i) => {
86
+ const pin = m.get(`zephyr.gpio.dtSpecs.${i}.pin`) as number;
87
+ const dtSpec = m.get(`zephyr.gpio.dtSpecs.${i}.dtSpec`) as string;
88
+ if (pin != null && dtSpec) return { pin, dtSpec };
89
+ return null;
90
+ });
91
+
92
+ const intPins = collectIndexed<ZephyrInterruptPin>(bc, 'zephyr.gpio.interruptPins', (m, i) => {
93
+ const pin = m.get(`zephyr.gpio.interruptPins.${i}.pin`) as number;
94
+ const dtSpec = m.get(`zephyr.gpio.interruptPins.${i}.dtSpec`) as string;
95
+ if (pin != null && dtSpec) return { pin, dtSpec };
96
+ return null;
97
+ });
98
+
99
+ const i2cControllers = collectBusControllers(bc, 'zephyr.i2c.controllers');
100
+ const spiControllers = collectBusControllers(bc, 'zephyr.spi.controllers');
101
+ const uartControllers = collectBusControllers(bc, 'zephyr.uart.controllers');
102
+
103
+ const pwmSpecs = collectIndexed<ZephyrPwmSpec>(bc, 'zephyr.pwm.specs', (m, i) => {
104
+ const pin = m.get(`zephyr.pwm.specs.${i}.pin`) as number;
105
+ const dtSpec = m.get(`zephyr.pwm.specs.${i}.dtSpec`) as string;
106
+ if (pin != null && dtSpec) return { pin, dtSpec };
107
+ return null;
108
+ });
109
+
110
+ const adcNodeLabel = bc.get('zephyr.adc.nodeLabel') as string | undefined;
111
+ const adcResolution = bc.get('zephyr.adc.resolution') as number | undefined;
112
+ const adcVref = bc.get('zephyr.adc.vrefMv') as number | undefined;
113
+ const adcChannels = collectIndexed<ZephyrAdcChannel>(bc, 'zephyr.adc.channels', (m, i) => {
114
+ const pin = m.get(`zephyr.adc.channels.${i}.pin`) as number;
115
+ const channel = m.get(`zephyr.adc.channels.${i}.channel`) as number;
116
+ if (pin != null && channel != null) return { pin, channel };
117
+ return null;
118
+ });
119
+
120
+ const wdtNodeLabel = bc.get('zephyr.wdt.nodeLabel') as string | undefined;
121
+ const wifiSupported = bc.get('zephyr.wifi.supported') as boolean | undefined;
122
+
123
+ // ── Construct the final readonly descriptor ─────────────────────────────
124
+
125
+ const gpio: ZephyrChipDescriptor['gpio'] = {
126
+ dtSpecs,
127
+ ...(intPins.length > 0 ? { interruptPins: intPins } : {}),
128
+ };
129
+
130
+ return {
131
+ id: boardTarget,
132
+ soc,
133
+ gpioController: zGpioController ?? 'gpio0',
134
+ ...(gc.length > 0 ? { gpioControllers: gc } : {}),
135
+ gpio,
136
+ ...(i2cControllers.length > 0 ? { i2c: { controllers: i2cControllers } } : {}),
137
+ ...(spiControllers.length > 0 ? { spi: { controllers: spiControllers } } : {}),
138
+ ...(uartControllers.length > 0 ? { uart: { controllers: uartControllers } } : {}),
139
+ ...(pwmSpecs.length > 0 ? { pwm: { specs: pwmSpecs } } : {}),
140
+ ...(adcNodeLabel || adcResolution != null || adcVref != null || adcChannels.length > 0
141
+ ? { adc: { nodeLabel: adcNodeLabel ?? 'adc', resolution: adcResolution ?? 12, vrefMv: adcVref ?? 3000, channels: adcChannels } }
142
+ : {}),
143
+ ...(wdtNodeLabel ? { wdt: { nodeLabel: wdtNodeLabel } } : {}),
144
+ ...(wifiSupported ? { wifi: { supported: true as const } } : {}),
145
+ };
146
+ }