@typecad/framework-zephyr 1.0.0-alpha.13 → 1.0.0-alpha.14

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 (52) hide show
  1. package/README.md +13 -1
  2. package/dist/chips/controllers.d.ts +28 -8
  3. package/dist/chips/controllers.js +49 -12
  4. package/dist/chips/resolve.js +32 -6
  5. package/dist/chips/types.d.ts +63 -12
  6. package/dist/chips/xiao-ble.js +12 -0
  7. package/dist/dt-config/kconfig.d.ts +11 -0
  8. package/dist/dt-config/kconfig.js +1 -1
  9. package/dist/dt-config/overlay.js +85 -0
  10. package/dist/framework.manifest.d.ts +20 -30
  11. package/dist/framework.manifest.js +12 -3
  12. package/dist/lowering/adc.d.ts +7 -4
  13. package/dist/lowering/adc.js +25 -11
  14. package/dist/lowering/gpio.js +9 -6
  15. package/dist/lowering/mqtt.js +9 -1
  16. package/dist/lowering/pulse.js +7 -7
  17. package/dist/lowering/pwm.d.ts +21 -3
  18. package/dist/lowering/pwm.js +28 -4
  19. package/dist/lowering/spi.js +2 -2
  20. package/dist/lowering/tone.js +3 -2
  21. package/dist/lowering/wifi.js +28 -5
  22. package/dist/strategy.js +56 -4
  23. package/dist/toolchain/debug-config.js +1 -1
  24. package/dist/toolchain/index.d.ts +1 -1
  25. package/dist/toolchain/index.js +83 -8
  26. package/dist/toolchain/scaffold.d.ts +9 -0
  27. package/dist/toolchain/scaffold.js +45 -0
  28. package/dist/toolchain/west-discover.d.ts +4 -1
  29. package/dist/toolchain/west-discover.js +2 -0
  30. package/dist/toolchain/west-spawn.js +17 -5
  31. package/package.json +5 -5
  32. package/src/chips/controllers.ts +61 -12
  33. package/src/chips/resolve.ts +32 -5
  34. package/src/chips/types.ts +63 -12
  35. package/src/chips/xiao-ble.ts +82 -70
  36. package/src/dt-config/kconfig.ts +12 -1
  37. package/src/dt-config/overlay.ts +546 -450
  38. package/src/framework.manifest.ts +12 -3
  39. package/src/lowering/adc.ts +28 -12
  40. package/src/lowering/gpio.ts +9 -6
  41. package/src/lowering/mqtt.ts +9 -1
  42. package/src/lowering/pulse.ts +7 -7
  43. package/src/lowering/pwm.ts +29 -4
  44. package/src/lowering/spi.ts +2 -2
  45. package/src/lowering/tone.ts +3 -3
  46. package/src/lowering/wifi.ts +29 -5
  47. package/src/strategy.ts +52 -4
  48. package/src/toolchain/debug-config.ts +1 -1
  49. package/src/toolchain/index.ts +645 -565
  50. package/src/toolchain/scaffold.ts +43 -0
  51. package/src/toolchain/west-discover.ts +321 -316
  52. package/src/toolchain/west-spawn.ts +17 -5
package/README.md CHANGED
@@ -35,7 +35,19 @@ fix (`pip install west`, set `ZEPHYR_BASE`, or activate the venv).
35
35
  symbols for the lowered peripherals). Both are regenerated idempotently —
36
36
  only rewritten when their content changes, so Ninja's incremental build is
37
37
  preserved.
38
- - The MVP target is the **Seeed Studio XIAO nRF52840** (`xiao_ble` board).
38
+ - Supported board targets: **Seeed Studio XIAO nRF52840** (`xiao_ble`),
39
+ **ESP32 DevKit** (`esp32_devkitc`), **ESP32-S3 DevKit** (`esp32s3_devkitc`),
40
+ **ESP32-C3 DevKitM** (`esp32c3_devkitm/esp32c3`), **ESP32-C6 DevKitC**
41
+ (`esp32c6_devkitc/esp32c6/hpcore` — the qualified form is required, the
42
+ board also ships an lpcore variant), **Raspberry Pi Pico** (`rpi_pico`),
43
+ **Pico 2** (`rpi_pico2/rp2350a/m33`), and **WeAct Black Pill V2.0**
44
+ (`blackpill_f411ce/stm32f411xe` — the first STM32 target; flashed over USB
45
+ via the ROM DFU bootloader). The rpi_pico, esp32c3, esp32c6, and blackpill
46
+ boards resolve their chip data from the board packages' `zephyr` field (see
47
+ `resolveChipFromBoard`) rather than the hardcoded chip registry; the
48
+ qualified Pico 2 target selects the Cortex-M33 cpucluster (Zephyr 4.3+
49
+ rejects the bare `rpi_pico2` because the board also ships a Hazard3 variant
50
+ with no default).
39
51
 
40
52
  ## Installation
41
53
 
@@ -1,4 +1,4 @@
1
- import type { ZephyrChipDescriptor } from './types.js';
1
+ import type { ZephyrChipDescriptor, ZephyrGpioController } from './types.js';
2
2
  /**
3
3
  * Resolve the devicetree nodelabel of the GPIO controller that owns `pin`.
4
4
  *
@@ -9,13 +9,33 @@ import type { ZephyrChipDescriptor } from './types.js';
9
9
  */
10
10
  export declare function controllerNodelabelForPin(chip: ZephyrChipDescriptor, pin: number): string;
11
11
  /**
12
- * Emit the C++ source for a runtime pin GPIO-device dispatcher.
12
+ * The owning controller range for a HAL pin, if the chip declares a split.
13
+ */
14
+ export declare function controllerRangeForPin(chip: ZephyrChipDescriptor, pin: number): ZephyrGpioController | undefined;
15
+ /**
16
+ * The PORT-RELATIVE pin index for the raw gpio_pin_*_raw() API — the Zephyr
17
+ * raw calls address the index WITHIN the controller, not the global HAL pin
18
+ * number. For split SoCs each controller's `minPin` is its base (STM32:
19
+ * gpiob minPin 16, so PB12 = pin 28 → raw 12); single-controller SoCs have
20
+ * no offset. Emitting the global number against a port driver would address
21
+ * a nonexistent port bit (STM32 gpiob is 0–15) and fail at runtime.
22
+ */
23
+ export declare function controllerRawPinForPin(chip: ZephyrChipDescriptor, pin: number): number;
24
+ /**
25
+ * Emit the C++ source for runtime pin → GPIO-device / port-relative-index
26
+ * dispatchers.
27
+ *
28
+ * Returns lines defining:
29
+ * - `static inline const struct device* __tc_gpio_dev(uint32_t pin)` — the
30
+ * owning controller's device, resolved via DEVICE_DT_GET(DT_NODELABEL(...))
31
+ * at compile time (the macro is evaluated per branch, so it is always
32
+ * statically valid); only `pin` is runtime.
33
+ * - `static inline gpio_pin_t __tc_gpio_pin(uint32_t pin)` — the
34
+ * port-relative index for gpio_pin_*_raw() (see controllerRawPinForPin).
35
+ * The runtime shim paths (UI pin-watch, safety voters) take a runtime pin,
36
+ * so they cannot bake the offset in at emit time.
13
37
  *
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.
38
+ * For a single-controller SoC both collapse to one-liners, so the existing
39
+ * XIAO nRF52840 behavior is byte-for-byte unchanged.
20
40
  */
21
41
  export declare function emitGpioDevDispatcher(chip: ZephyrChipDescriptor): string[];
@@ -27,23 +27,47 @@
27
27
  * for out-of-range pins (e.g. the manifest probe's synthetic pin 0).
28
28
  */
29
29
  export function controllerNodelabelForPin(chip, pin) {
30
+ const hit = controllerRangeForPin(chip, pin);
31
+ return hit ? hit.nodelabel : chip.gpioController;
32
+ }
33
+ /**
34
+ * The owning controller range for a HAL pin, if the chip declares a split.
35
+ */
36
+ export function controllerRangeForPin(chip, pin) {
30
37
  const ranges = chip.gpioControllers;
31
38
  if (ranges && ranges.length > 0) {
32
- const hit = ranges.find((r) => pin >= r.minPin && pin <= r.maxPin);
33
- if (hit)
34
- return hit.nodelabel;
39
+ return ranges.find((r) => pin >= r.minPin && pin <= r.maxPin);
35
40
  }
36
- return chip.gpioController;
41
+ return undefined;
37
42
  }
38
43
  /**
39
- * Emit the C++ source for a runtime pin GPIO-device dispatcher.
44
+ * The PORT-RELATIVE pin index for the raw gpio_pin_*_raw() API the Zephyr
45
+ * raw calls address the index WITHIN the controller, not the global HAL pin
46
+ * number. For split SoCs each controller's `minPin` is its base (STM32:
47
+ * gpiob minPin 16, so PB12 = pin 28 → raw 12); single-controller SoCs have
48
+ * no offset. Emitting the global number against a port driver would address
49
+ * a nonexistent port bit (STM32 gpiob is 0–15) and fail at runtime.
50
+ */
51
+ export function controllerRawPinForPin(chip, pin) {
52
+ const hit = controllerRangeForPin(chip, pin);
53
+ return pin - (hit?.minPin ?? 0);
54
+ }
55
+ /**
56
+ * Emit the C++ source for runtime pin → GPIO-device / port-relative-index
57
+ * dispatchers.
58
+ *
59
+ * Returns lines defining:
60
+ * - `static inline const struct device* __tc_gpio_dev(uint32_t pin)` — the
61
+ * owning controller's device, resolved via DEVICE_DT_GET(DT_NODELABEL(...))
62
+ * at compile time (the macro is evaluated per branch, so it is always
63
+ * statically valid); only `pin` is runtime.
64
+ * - `static inline gpio_pin_t __tc_gpio_pin(uint32_t pin)` — the
65
+ * port-relative index for gpio_pin_*_raw() (see controllerRawPinForPin).
66
+ * The runtime shim paths (UI pin-watch, safety voters) take a runtime pin,
67
+ * so they cannot bake the offset in at emit time.
40
68
  *
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.
69
+ * For a single-controller SoC both collapse to one-liners, so the existing
70
+ * XIAO nRF52840 behavior is byte-for-byte unchanged.
47
71
  */
48
72
  export function emitGpioDevDispatcher(chip) {
49
73
  const ranges = chip.gpioControllers;
@@ -53,14 +77,27 @@ export function emitGpioDevDispatcher(chip) {
53
77
  ` (void)pin;`,
54
78
  ` return DEVICE_DT_GET(DT_NODELABEL(${chip.gpioController}));`,
55
79
  '}',
80
+ 'static inline gpio_pin_t __tc_gpio_pin(uint32_t pin) {',
81
+ ` return (gpio_pin_t)pin;`,
82
+ '}',
56
83
  ];
57
84
  }
58
85
  const lines = [
59
86
  'static inline const struct device* __tc_gpio_dev(uint32_t pin) {',
60
87
  ];
88
+ const pinLines = [
89
+ 'static inline gpio_pin_t __tc_gpio_pin(uint32_t pin) {',
90
+ ];
61
91
  for (const r of ranges) {
62
92
  lines.push(` if (pin >= ${r.minPin} && pin <= ${r.maxPin}) { return DEVICE_DT_GET(DT_NODELABEL(${r.nodelabel})); }`);
93
+ if (r.minPin === 0) {
94
+ pinLines.push(` if (pin >= ${r.minPin} && pin <= ${r.maxPin}) { return (gpio_pin_t)pin; }`);
95
+ }
96
+ else {
97
+ pinLines.push(` if (pin >= ${r.minPin} && pin <= ${r.maxPin}) { return (gpio_pin_t)(pin - ${r.minPin}); }`);
98
+ }
63
99
  }
64
100
  lines.push(` return DEVICE_DT_GET(DT_NODELABEL(${chip.gpioController}));`, '}');
65
- return lines;
101
+ pinLines.push(` return (gpio_pin_t)pin;`, '}');
102
+ return [...lines, ...pinLines];
66
103
  }
@@ -81,18 +81,35 @@ export function resolveChipFromBoard(bc) {
81
81
  const pwmSpecs = collectIndexed(bc, 'zephyr.pwm.specs', (m, i) => {
82
82
  const pin = m.get(`zephyr.pwm.specs.${i}.pin`);
83
83
  const dtSpec = m.get(`zephyr.pwm.specs.${i}.dtSpec`);
84
- if (pin != null && dtSpec)
85
- return { pin, dtSpec };
86
- return null;
84
+ const controller = m.get(`zephyr.pwm.specs.${i}.controller`);
85
+ const channel = m.get(`zephyr.pwm.specs.${i}.channel`);
86
+ const periodNs = m.get(`zephyr.pwm.specs.${i}.periodNs`);
87
+ const polarity = m.get(`zephyr.pwm.specs.${i}.polarity`);
88
+ // Board-shipped alias form (dtSpec) or synthesized form (controller +
89
+ // channel → overlay-generated alias) — at least one, else drop the entry.
90
+ if (pin == null || !(dtSpec || (controller && channel != null)))
91
+ return null;
92
+ return {
93
+ pin,
94
+ ...(dtSpec ? { dtSpec } : {}),
95
+ ...(controller ? { controller } : {}),
96
+ ...(channel != null ? { channel } : {}),
97
+ ...(periodNs != null ? { periodNs } : {}),
98
+ ...(polarity ? { polarity } : {}),
99
+ };
87
100
  });
88
101
  const adcNodeLabel = bc.get('zephyr.adc.nodeLabel');
89
102
  const adcResolution = bc.get('zephyr.adc.resolution');
90
103
  const adcVref = bc.get('zephyr.adc.vrefMv');
104
+ const adcGain = bc.get('zephyr.adc.gain');
105
+ const adcReference = bc.get('zephyr.adc.reference');
91
106
  const adcChannels = collectIndexed(bc, 'zephyr.adc.channels', (m, i) => {
92
107
  const pin = m.get(`zephyr.adc.channels.${i}.pin`);
93
108
  const channel = m.get(`zephyr.adc.channels.${i}.channel`);
94
- if (pin != null && channel != null)
95
- return { pin, channel };
109
+ const pinctrl = m.get(`zephyr.adc.channels.${i}.pinctrl`);
110
+ if (pin != null && channel != null) {
111
+ return { pin, channel, ...(pinctrl ? { pinctrl } : {}) };
112
+ }
96
113
  return null;
97
114
  });
98
115
  const wdtNodeLabel = bc.get('zephyr.wdt.nodeLabel');
@@ -113,7 +130,16 @@ export function resolveChipFromBoard(bc) {
113
130
  ...(uartControllers.length > 0 ? { uart: { controllers: uartControllers } } : {}),
114
131
  ...(pwmSpecs.length > 0 ? { pwm: { specs: pwmSpecs } } : {}),
115
132
  ...(adcNodeLabel || adcResolution != null || adcVref != null || adcChannels.length > 0
116
- ? { adc: { nodeLabel: adcNodeLabel ?? 'adc', resolution: adcResolution ?? 12, vrefMv: adcVref ?? 3000, channels: adcChannels } }
133
+ ? {
134
+ adc: {
135
+ nodeLabel: adcNodeLabel ?? 'adc',
136
+ resolution: adcResolution ?? 12,
137
+ vrefMv: adcVref ?? 3000,
138
+ channels: adcChannels,
139
+ ...(adcGain ? { gain: adcGain } : {}),
140
+ ...(adcReference ? { reference: adcReference } : {}),
141
+ },
142
+ }
117
143
  : {}),
118
144
  ...(wdtNodeLabel ? { wdt: { nodeLabel: wdtNodeLabel } } : {}),
119
145
  ...(wifiSupported ? { wifi: { supported: true } } : {}),
@@ -47,14 +47,37 @@ export interface ZephyrBusController {
47
47
  /**
48
48
  * A PWM channel described as a devicetree spec.
49
49
  *
50
- * Emitted as `struct pwm_dt_spec __tc_pwm<N> = PWM_DT_SPEC_GET(DT_ALIAS(<dtSpec>))`.
51
- * `pwm_set_pulse_dt(&__tc_pwm<N>, pulse_ns)` honors the spec's period/polarity.
50
+ * Two forms, mutually exclusive:
51
+ * - **Board-shipped alias:** `dtSpec` names a DT alias the board's own DTS
52
+ * already defines (e.g. the XIAO's `pwm-led0`). Emitted as
53
+ * `PWM_DT_SPEC_GET(DT_ALIAS(<dtSpec>))`.
54
+ * - **Synthesized** (controller + channel): the board DTS enables a PWM
55
+ * controller node (e.g. `pwm4`) but defines no alias for it. The overlay
56
+ * generator synthesizes a `pwm-leds` consumer node + a `tc-pwm<pin>` alias
57
+ * in `<board>.overlay`; the lowering emits
58
+ * `PWM_DT_SPEC_GET(DT_ALIAS(tc-pwm<pin>))`. Both sides derive the alias
59
+ * name from the pin, so they always agree.
60
+ *
61
+ * `pwm_set_pulse_dt(&spec, pulse_ns)` honors the spec's period/polarity.
52
62
  */
53
63
  export interface ZephyrPwmSpec {
54
64
  /** GPIO number (matches the HAL op `pin` field). */
55
65
  readonly pin: number;
56
- /** Devicetree alias, e.g. 'pwm-led0'. */
57
- readonly dtSpec: string;
66
+ /** Board-shipped DT alias, e.g. 'pwm-led0'. Omit when using the
67
+ * synthesized form (controller + channel). */
68
+ readonly dtSpec?: string;
69
+ /** Synthesized form: PWM controller DT nodelabel, e.g. 'pwm4' (the STM32
70
+ * timer's pwm child node). The overlay's pwm-leds node consumes it. */
71
+ readonly controller?: string;
72
+ /** Synthesized form: channel index within the controller (1-based timer
73
+ * channel, matching the `pwms` binding's channel cell). */
74
+ readonly channel?: number;
75
+ /** Synthesized form: period in nanoseconds, baked into the DT spec. The
76
+ * lowering scales duty against `spec.period`. Default 20 000 000 (20 ms /
77
+ * 50 Hz — the servo convention; harmless for LED dimming). */
78
+ readonly periodNs?: number;
79
+ /** Synthesized form: PWM polarity flag. Default PWM_POLARITY_NORMAL. */
80
+ readonly polarity?: string;
58
81
  }
59
82
  /**
60
83
  * A GPIO pin usable as an interrupt source.
@@ -69,7 +92,7 @@ export interface ZephyrInterruptPin {
69
92
  readonly dtSpec: string;
70
93
  }
71
94
  /**
72
- * An ADC channel: which SAADC input a given HAL pin maps to.
95
+ * An ADC channel: which ADC input a given HAL pin maps to.
73
96
  *
74
97
  * The XIAO nRF52840 has no pre-declared ADC channel nodes in devicetree, so the
75
98
  * lowering emits `adc_channel_setup` against `DEVICE_DT_GET(DT_NODELABEL(adc))`
@@ -78,8 +101,16 @@ export interface ZephyrInterruptPin {
78
101
  export interface ZephyrAdcChannel {
79
102
  /** GPIO number (matches the HAL op `pin` field). */
80
103
  readonly pin: number;
81
- /** SAADC channel index (AIN0–AIN7). */
104
+ /** ADC channel index (nRF SAADC AIN0–AIN7; STM32 ADC1_IN0–IN9). */
82
105
  readonly channel: number;
106
+ /**
107
+ * Pinctrl node label that muxes this pin to analog mode, e.g.
108
+ * 'adc1_in0_pa0' (STM32). When present, the overlay generator rewrites the
109
+ * ADC node's pinctrl-0 to the channels the program actually reads — SoCs
110
+ * like STM32 leave the pad in GPIO mode otherwise and reads float.
111
+ * Omit on SoCs whose ADC needs no pad muxing (nRF SAADC, RP2040).
112
+ */
113
+ readonly pinctrl?: string;
83
114
  }
84
115
  /**
85
116
  * A DAC channel: which DAC output a given HAL pin maps to. The lowering emits
@@ -113,11 +144,18 @@ export interface ZephyrChipDescriptor {
113
144
  readonly gpioController: string;
114
145
  /**
115
146
  * Per-range GPIO controllers for SoCs that split GPIO across multiple
116
- * devicetree nodes (ESP32-S3: `gpio0` 0–31, `gpio1` 32–48). When present, the
117
- * lowering routes a HAL pin to its owning controller at runtime via the
118
- * emitted `__tc_gpio_dev(pin)` dispatcher; `gpioController` is the fallback.
119
- * Omit on single-controller SoCs (nRF52840, RP2040, …) every pin is on the
120
- * one controller described by `gpioController`.
147
+ * devicetree nodes (ESP32-S3: `gpio0` 0–31, `gpio1` 32–48; STM32: one
148
+ * controller per port `gpioa` 0–15, `gpiob` 16–31, `gpioc` 32–47).
149
+ * When present, the lowering routes a HAL pin to its owning controller at
150
+ * runtime via the emitted `__tc_gpio_dev(pin)` dispatcher; `gpioController`
151
+ * is the fallback. Omit on single-controller SoCs (RP2040, …) — every pin
152
+ * is on the one controller described by `gpioController`.
153
+ *
154
+ * NUMBERING RULE (load-bearing): `minPin` must equal the controller's port
155
+ * base so the port-relative raw index is `pin - minPin` (STM32 PB12 = pin
156
+ * 28 → raw 12 — the Zephyr raw API addresses the index WITHIN the
157
+ * controller). Number pins by port blocks and never contiguously across
158
+ * unbonded pins.
121
159
  */
122
160
  readonly gpioControllers?: readonly ZephyrGpioController[];
123
161
  /** GPIO pins with devicetree specs (LEDs, buttons, board-defined pins). */
@@ -142,7 +180,7 @@ export interface ZephyrChipDescriptor {
142
180
  readonly pwm?: {
143
181
  readonly specs: readonly ZephyrPwmSpec[];
144
182
  };
145
- /** ADC: the SAADC node label + the pin→channel map. */
183
+ /** ADC: the ADC device node label + the pin→channel map. */
146
184
  readonly adc?: {
147
185
  readonly nodeLabel: string;
148
186
  readonly channels: readonly ZephyrAdcChannel[];
@@ -150,6 +188,19 @@ export interface ZephyrChipDescriptor {
150
188
  readonly vrefMv: number;
151
189
  /** ADC resolution in bits. */
152
190
  readonly resolution: number;
191
+ /**
192
+ * Zephyr `enum adc_gain` macro for the channel setup, e.g.
193
+ * 'ADC_GAIN_1_4' (nRF SAADC default) or 'ADC_GAIN_1' (STM32 driver
194
+ * requires exactly this). Defaults to 'ADC_GAIN_1_4'.
195
+ */
196
+ readonly gain?: string;
197
+ /**
198
+ * Zephyr `enum adc_reference` macro, e.g. 'ADC_REF_INTERNAL'. Defaults to
199
+ * 'ADC_REF_INTERNAL' — on nRF that is the 0.6 V internal ref measured
200
+ * through the gain divider; the STM32 driver ALSO requires
201
+ * ADC_REF_INTERNAL (Zephyr maps it to the VREF+ pad) with vrefMv = VDDA.
202
+ */
203
+ readonly reference?: string;
153
204
  };
154
205
  /**
155
206
  * DAC: the DAC device node label + the pin→channel map. Present only on chips
@@ -8,6 +8,18 @@ export const XIAO_BLE = {
8
8
  id: 'xiao_ble',
9
9
  soc: 'nrf52840',
10
10
  gpioController: 'gpio0',
11
+ // GPIO is split across two devicetree controllers: gpio0 (P0.00–P0.31)
12
+ // and gpio1 (P1.00–P1.15, HAL pins 32–47). Declaring the split makes the
13
+ // raw path emit the port-relative index against gpio1 (P1.11 = raw 11),
14
+ // which NRF_GPIO_PIN_MAP(1, 11) resolves to absolute pin 43 — the same
15
+ // physical pin the old single-controller form reached only by accident:
16
+ // gpio0 + global 43 also maps to 43 (MAP(0, 43) = 43), but that form
17
+ // trips the generic layer's port_pin_mask __ASSERT ("Unsupported pin",
18
+ // gpio0's mask covers 0–31) on any assert-enabled build.
19
+ gpioControllers: [
20
+ { nodelabel: 'gpio0', minPin: 0, maxPin: 31 },
21
+ { nodelabel: 'gpio1', minPin: 32, maxPin: 47 },
22
+ ],
11
23
  gpio: {
12
24
  dtSpecs: [
13
25
  // Onboard RGB LEDs — active-low (GPIO_ACTIVE_LOW in xiao_ble_common.dtsi).
@@ -25,6 +25,17 @@ export interface KconfigUsage {
25
25
  /** PSRAM type ('opi' | 'quad') when the target board has PSRAM. Emits the
26
26
  * CONFIG_SPIRAM symbols so the ESP heap serves PSRAM for canvas allocations. */
27
27
  psram?: 'opi' | 'quad';
28
+ /** HAL pin numbers the program reads with adc.* — scanned from the emitted
29
+ * `__tc_adc<N>_setup()` calls at compile time. Only the overlay generator
30
+ * consumes this (to rewrite the ADC node's pinctrl-0 to the used channels
31
+ * on SoCs that need pad muxing, e.g. STM32); prj.conf ignores it. */
32
+ adcReadPins?: readonly number[];
33
+ /** HAL pin numbers the program drives with pwm.* — scanned from the
34
+ * emitted `__tc_pwm_*` spec references at compile time. Only the overlay
35
+ * generator consumes this (synthesized pwm-leds consumers + aliases are
36
+ * emitted per used pin, so the DT carries no dead channels); prj.conf
37
+ * ignores it. */
38
+ pwmUsedPins?: readonly number[];
28
39
  }
29
40
  /**
30
41
  * Resolve the Kconfig symbol→value map for a prj.conf. Returns a Map preserving
@@ -97,7 +97,7 @@ export function resolveKconfigFragments(usage, debug) {
97
97
  // (without it, Kconfig silently forces them all to n).
98
98
  m.set('CONFIG_NETWORKING', 'y');
99
99
  m.set('CONFIG_WIFI', 'y');
100
- m.set('CONFIG_WIFI_ESP32', 'y'); // ESP32-specific driver (sole WiFi target)
100
+ m.set('CONFIG_WIFI_ESP32', 'y'); // family-wide ESP32 driver (esp32/s3/c3/c6)
101
101
  m.set('CONFIG_NET_L2_ETHERNET', 'y');
102
102
  m.set('CONFIG_NET_IPV4', 'y');
103
103
  m.set('CONFIG_NET_UDP', 'y'); // transitive dep of NET_DHCPV4
@@ -53,6 +53,24 @@ export function generateOverlay(chip, usage, display, wiring, touch, diagnostics
53
53
  for (const c of chip.uart.controllers)
54
54
  block(c.nodeLabel);
55
55
  }
56
+ // PWM: synthesized specs (controller + channel, no board-shipped alias) get
57
+ // a pwm-leds consumer node + tc-pwm<pin> alias here — the lowering addresses
58
+ // the channel as PWM_DT_SPEC_GET(DT_ALIAS(tc-pwm<pin>)), so the alias must
59
+ // exist in the merged DT. pwmDtAlias (lowering/pwm.ts) derives the same
60
+ // name from the pin; the two sides cannot drift. Filtered to the pins the
61
+ // program actually drives (pwmUsedPins) so the DT carries no dead channels;
62
+ // an undefined list (prepare-time overlay) emits all specs.
63
+ if (usage.usesPwm && chip.pwm) {
64
+ emitPwmNodes(lines, chip, usage.pwmUsedPins);
65
+ }
66
+ // ADC: enable the ADC device node, and on SoCs whose channels carry a
67
+ // pinctrl label (STM32: the pad stays in GPIO mode unless the ADC node's
68
+ // pinctrl-0 muxes it analog), rewrite pinctrl-0 to exactly the channels the
69
+ // program reads. DT assignment replaces the whole property — the board
70
+ // default (one pad) is deliberately dropped in favor of the used set.
71
+ if (usage.usesAdc && chip.adc) {
72
+ emitAdcNode(lines, chip, usage.adcReadPins);
73
+ }
56
74
  // DAC: enable the chip's DAC device node when the program uses dac.*. The
57
75
  // lowering references DEVICE_DT_GET(DT_NODELABEL(<dac.device>)).
58
76
  if (usage.usesDac && chip.dac) {
@@ -106,6 +124,73 @@ export function generateOverlay(chip, usage, display, wiring, touch, diagnostics
106
124
  }
107
125
  return lines.join('\n');
108
126
  }
127
+ /**
128
+ * Emit the pwm-leds consumer node + aliases for synthesized PWM specs.
129
+ *
130
+ * A board DTS may enable a PWM controller (`pwm4`) without aliasing any
131
+ * channel — the STM32 pattern — while the lowering addresses channels via
132
+ * DT_ALIAS. Each synthesized spec gets a `pwm-leds` child whose `pwms` cell
133
+ * binds the controller+channel (+ period/polarity), and a `tc-pwm<pin>`
134
+ * alias pointing at it. Alias/label names are valid DT identifiers (lowercase
135
+ * alphanumerics + dash/underscore).
136
+ */
137
+ function emitPwmNodes(lines, chip, usedPins) {
138
+ const synthesized = (chip.pwm?.specs ?? []).filter((s) => s.controller && s.channel != null && !s.dtSpec).filter((s) => !usedPins || usedPins.includes(s.pin));
139
+ if (synthesized.length === 0)
140
+ return;
141
+ // Enable each distinct PWM controller node (idempotent when already okay).
142
+ for (const controller of [...new Set(synthesized.map((s) => s.controller))]) {
143
+ lines.push(`&${controller} {`);
144
+ lines.push(' status = "okay";');
145
+ lines.push('};');
146
+ lines.push('');
147
+ }
148
+ lines.push('/ {');
149
+ lines.push(' tc_pwm_leds: tc-pwm-leds {');
150
+ lines.push(' compatible = "pwm-leds";');
151
+ for (const s of synthesized) {
152
+ // 20 ms / 50 Hz default — the servo convention; the duty scaling in the
153
+ // lowering normalizes 0-255 against whatever period is baked in.
154
+ const period = s.periodNs ?? 20_000_000;
155
+ const polarity = s.polarity ?? 'PWM_POLARITY_NORMAL';
156
+ lines.push(` tc_pwm_${s.pin}: pwm-led-${s.pin} {`);
157
+ lines.push(` pwms = <&${s.controller} ${s.channel} ${period} ${polarity}>;`);
158
+ lines.push(' };');
159
+ }
160
+ lines.push(' };');
161
+ lines.push(' aliases {');
162
+ for (const s of synthesized) {
163
+ lines.push(` tc-pwm${s.pin} = &tc_pwm_${s.pin};`);
164
+ }
165
+ lines.push(' };');
166
+ lines.push('};');
167
+ lines.push('');
168
+ }
169
+ /**
170
+ * Emit the ADC device-node enable (+ optional pinctrl override). The pinctrl
171
+ * override applies only when the descriptor's channels carry pinctrl labels
172
+ * AND the caller knows which pins the program reads (compile-time regen scans
173
+ * the emitted `__tc_adc<N>_setup()` calls); the prepare-time overlay omits it
174
+ * and the compile regen rewrites the file before west runs.
175
+ */
176
+ function emitAdcNode(lines, chip, readPins) {
177
+ const adc = chip.adc;
178
+ const labeled = adc.channels.filter((c) => c.pinctrl);
179
+ const used = labeled.filter((c) => !readPins || readPins.includes(c.pin));
180
+ if (labeled.length > 0 && used.length > 0) {
181
+ lines.push(`&${adc.nodeLabel} {`);
182
+ lines.push(' status = "okay";');
183
+ lines.push(` pinctrl-0 = <${used.map((c) => `&${c.pinctrl}`).join(' ')}>;`);
184
+ lines.push(' pinctrl-names = "default";');
185
+ lines.push('};');
186
+ lines.push('');
187
+ return;
188
+ }
189
+ lines.push(`&${adc.nodeLabel} {`);
190
+ lines.push(' status = "okay";');
191
+ lines.push('};');
192
+ lines.push('');
193
+ }
109
194
  /**
110
195
  * Emit a display DT node definition. The node is attached to the profile's SPI
111
196
  * controller (default spi2, the ESP32's first user SPI controller) via a MIPI
@@ -1,41 +1,31 @@
1
1
  declare const _default: {
2
- hal: {
3
- [x: string]: {
4
- supported: boolean;
5
- ops: Record<string, "stub" | "polyfill" | "supported" | "unsupported" | "probe-inconclusive">;
6
- partialCoverage: boolean;
7
- unsupportedReason?: string | undefined;
8
- };
9
- raw?: unknown;
10
- } & {
11
- [k: string]: {
12
- supported: boolean;
13
- ops: Record<string, "stub" | "polyfill" | "supported" | "unsupported" | "probe-inconclusive">;
14
- partialCoverage: boolean;
15
- unsupportedReason?: string | undefined;
16
- };
17
- };
18
- packageName: string;
19
2
  schemaVersion: 1;
20
3
  frameworkId: string;
4
+ packageName: string;
21
5
  canonical: boolean;
22
6
  displayName: string;
23
7
  description: string;
24
8
  implementationMode: "from-scratch" | "extends-canonical" | "extends-other";
25
9
  entrypoint: {
26
- sourceExtension: "cpp" | "ino" | "cc" | "h";
27
10
  entrypointFunctionName: string;
28
11
  requiresLoopFunction: boolean;
12
+ sourceExtension: "cpp" | "ino" | "cc" | "h";
29
13
  generateHeaderFile: boolean;
30
14
  overrideBaseName?: string | undefined;
31
15
  outputSubdirectory?: string | undefined;
32
16
  customBridgeShim?: string | undefined;
33
17
  };
34
18
  profile: {
19
+ targets: string[];
35
20
  forcedIncludes: string[];
36
21
  symbolAliases: Record<string, string>;
37
- targets: string[];
38
22
  };
23
+ hal: Record<string, {
24
+ supported: boolean;
25
+ ops: Record<string, "unsupported" | "supported" | "polyfill" | "probe-inconclusive" | "stub">;
26
+ partialCoverage: boolean;
27
+ unsupportedReason?: string | undefined;
28
+ }>;
39
29
  polyfills: {
40
30
  emitted: {
41
31
  id: string;
@@ -59,20 +49,20 @@ declare const _default: {
59
49
  reexportedFrom?: string | undefined;
60
50
  };
61
51
  typeEmission: {
62
- needsIostream: boolean;
52
+ normalizeCppType: boolean;
53
+ mathHeader: "<math.h>" | "none" | "<Arduino.h>";
63
54
  needsStdString: boolean;
64
55
  needsStdVector: boolean;
56
+ needsIostream: boolean;
65
57
  needsStdFunction: boolean;
66
- mathHeader: "none" | "<math.h>" | "<Arduino.h>";
67
- normalizeCppType: boolean;
68
58
  stdlibSupport: {
69
59
  hasVector: boolean;
70
60
  hasString: boolean;
71
61
  hasIostream: boolean;
72
62
  hasExceptions: boolean;
73
63
  hasRTTI: boolean;
74
- recommendedArrayImpl: "std_vector" | "static_array";
75
- recommendedStringImpl: "std_string" | "static_string";
64
+ recommendedArrayImpl: "static_array" | "std_vector";
65
+ recommendedStringImpl: "static_string" | "std_string";
76
66
  };
77
67
  };
78
68
  ambientTypes: string[];
@@ -80,12 +70,6 @@ declare const _default: {
80
70
  hardwareTestGroups: string[];
81
71
  halResolutionTests: string[];
82
72
  };
83
- doctor?: {
84
- available: boolean;
85
- } | undefined;
86
- licenses?: {
87
- available: boolean;
88
- } | undefined;
89
73
  basedOn?: string | undefined;
90
74
  inheritsStrategyId?: string | undefined;
91
75
  libraryResolution?: {
@@ -95,6 +79,12 @@ declare const _default: {
95
79
  tryGenerateLibDecl: boolean;
96
80
  reexportedFrom?: string | undefined;
97
81
  } | undefined;
82
+ doctor?: {
83
+ available: boolean;
84
+ } | undefined;
85
+ licenses?: {
86
+ available: boolean;
87
+ } | undefined;
98
88
  compat?: {
99
89
  zephyr?: string | undefined;
100
90
  } | undefined;
@@ -45,8 +45,16 @@ export default defineFrameworkManifest({
45
45
  profile: {
46
46
  // Informational list of supported board targets. The manifest validator
47
47
  // never iterates this; chipForTarget (src/chips/index.ts) is the real
48
- // resolver. ESP32-S3 + plain ESP32 added alongside the nRF52840 MVP target.
49
- targets: ['xiao_ble', 'esp32s3_devkitc', 'esp32_devkitc'],
48
+ // resolver. ESP32-S3 + plain ESP32 added alongside the nRF52840 MVP target;
49
+ // rpi_pico / rpi_pico2 / esp32c3 / esp32c6 / blackpill_f411ce resolve via
50
+ // the board packages' zephyr chip data (resolveChipFromBoard), not the
51
+ // hardcoded chip registry.
52
+ targets: [
53
+ 'xiao_ble', 'esp32s3_devkitc', 'esp32_devkitc',
54
+ 'rpi_pico', 'rpi_pico2/rp2350a/m33',
55
+ 'esp32c3_devkitm/esp32c3', 'esp32c6_devkitc/esp32c6/hpcore',
56
+ 'blackpill_f411ce/stm32f411xe',
57
+ ],
50
58
  forcedIncludes: ['<zephyr/kernel.h>', '<zephyr/drivers/gpio.h>', '<cstdint>'],
51
59
  symbolAliases: {},
52
60
  },
@@ -204,7 +212,8 @@ export default defineFrameworkManifest({
204
212
  },
205
213
  },
206
214
  // ── Partial: WiFi (STA connect + scan + config via conn_mgr/net_mgmt) ────
207
- // ESP32-S3 only profileDiagnostics flags wifi usage on radioless chips.
215
+ // ESP32 family only (esp32, esp32s3, esp32c3, esp32c6) profileDiagnostics
216
+ // flags wifi usage on radioless chips (nRF52840, RP2040/RP2350).
208
217
  // AP mode, credential persistence, static IP, and event callbacks deferred.
209
218
  wifi: {
210
219
  supported: true,
@@ -4,11 +4,14 @@ import type { ZephyrChipDescriptor } from '../chips/types.js';
4
4
  export declare function adcChannelForPin(chip: ZephyrChipDescriptor, pin: number): number;
5
5
  /**
6
6
  * Emit the per-channel ADC setup state. One block per channel in the chip
7
- * descriptor, each guarded by a `static bool __tc_adc<N>_ready` so the first
8
- * read configures it and subsequent reads skip. Called from shimLines when
9
- * the program uses ADC.
7
+ * descriptor that the PROGRAM ACTUALLY READS (`usedPins`) an unread
8
+ * channel's `static` setup function would trip -Wunused-function in the
9
+ * single generated TU. When `usedPins` is omitted (probe paths with no
10
+ * program), every descriptor channel is emitted. Each block is guarded by a
11
+ * `static bool __tc_adc<N>_ready` so the first read configures it and
12
+ * subsequent reads skip. Called from shimLines when the program uses ADC.
10
13
  */
11
- export declare function adcInitLines(chip: ZephyrChipDescriptor): string[];
14
+ export declare function adcInitLines(chip: ZephyrChipDescriptor, usedPins?: ReadonlySet<number>): string[];
12
15
  /**
13
16
  * Resolve a HAL adc.* op to Zephyr C++.
14
17
  * Returns `{ code }` for statement ops, `{ expression }` for value-returning ops.