@typecad/framework-zephyr 1.0.0-alpha.12 → 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.
- package/README.md +13 -1
- package/dist/chips/controllers.d.ts +28 -8
- package/dist/chips/controllers.js +49 -12
- package/dist/chips/resolve.js +32 -6
- package/dist/chips/types.d.ts +63 -12
- package/dist/chips/xiao-ble.js +12 -0
- package/dist/display/index.d.ts +1 -1
- package/dist/display/index.js +1 -1
- package/dist/display/profiles.d.ts +8 -0
- package/dist/display/profiles.js +19 -0
- package/dist/display/ui-adapter.d.ts +4 -0
- package/dist/display/ui-adapter.js +46 -0
- package/dist/dt-config/kconfig.d.ts +11 -0
- package/dist/dt-config/kconfig.js +1 -1
- package/dist/dt-config/overlay.d.ts +8 -1
- package/dist/dt-config/overlay.js +104 -1
- package/dist/framework.manifest.d.ts +20 -30
- package/dist/framework.manifest.js +12 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -0
- package/dist/lowering/adc.d.ts +7 -4
- package/dist/lowering/adc.js +25 -11
- package/dist/lowering/gpio.js +15 -8
- package/dist/lowering/mqtt.js +9 -1
- package/dist/lowering/pulse.js +7 -7
- package/dist/lowering/pwm.d.ts +21 -3
- package/dist/lowering/pwm.js +28 -4
- package/dist/lowering/spi.js +2 -2
- package/dist/lowering/tone.js +3 -2
- package/dist/lowering/wifi.js +28 -5
- package/dist/strategy.d.ts +20 -0
- package/dist/strategy.js +295 -119
- package/dist/toolchain/debug-config.d.ts +43 -2
- package/dist/toolchain/debug-config.js +129 -17
- package/dist/toolchain/index.d.ts +14 -1
- package/dist/toolchain/index.js +146 -20
- package/dist/toolchain/scaffold.d.ts +9 -0
- package/dist/toolchain/scaffold.js +84 -19
- package/dist/toolchain/west-discover.d.ts +4 -1
- package/dist/toolchain/west-discover.js +2 -0
- package/dist/toolchain/west-spawn.js +17 -5
- package/package.json +5 -5
- package/src/chips/controllers.ts +61 -12
- package/src/chips/resolve.ts +32 -5
- package/src/chips/types.ts +63 -12
- package/src/chips/xiao-ble.ts +82 -70
- package/src/display/index.ts +1 -1
- package/src/display/profiles.ts +23 -0
- package/src/display/ui-adapter.ts +51 -0
- package/src/dt-config/kconfig.ts +12 -1
- package/src/dt-config/overlay.ts +123 -0
- package/src/framework.manifest.ts +12 -3
- package/src/index.ts +6 -0
- package/src/lowering/adc.ts +28 -12
- package/src/lowering/gpio.ts +15 -8
- package/src/lowering/mqtt.ts +9 -1
- package/src/lowering/pulse.ts +7 -7
- package/src/lowering/pwm.ts +29 -4
- package/src/lowering/spi.ts +2 -2
- package/src/lowering/tone.ts +3 -3
- package/src/lowering/wifi.ts +29 -5
- package/src/strategy.ts +320 -123
- package/src/toolchain/debug-config.ts +137 -14
- package/src/toolchain/index.ts +645 -513
- package/src/toolchain/scaffold.ts +81 -17
- package/src/toolchain/west-discover.ts +321 -316
- package/src/toolchain/west-spawn.ts +17 -5
package/src/chips/xiao-ble.ts
CHANGED
|
@@ -1,70 +1,82 @@
|
|
|
1
|
-
// ---------------------------------------------------------------------------
|
|
2
|
-
// Seeed Studio XIAO nRF52840 — Zephyr board descriptor
|
|
3
|
-
//
|
|
4
|
-
// Board target: `xiao_ble` (mainline Zephyr, boards/seeed/xiao_ble).
|
|
5
|
-
// The onboard user LED is active-low and exposed as the DT alias `led0`.
|
|
6
|
-
// ---------------------------------------------------------------------------
|
|
7
|
-
|
|
8
|
-
import type { ZephyrChipDescriptor } from './types.js';
|
|
9
|
-
|
|
10
|
-
export const XIAO_BLE: ZephyrChipDescriptor = {
|
|
11
|
-
id: 'xiao_ble',
|
|
12
|
-
soc: 'nrf52840',
|
|
13
|
-
gpioController: 'gpio0',
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
nodeLabel: '
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Seeed Studio XIAO nRF52840 — Zephyr board descriptor
|
|
3
|
+
//
|
|
4
|
+
// Board target: `xiao_ble` (mainline Zephyr, boards/seeed/xiao_ble).
|
|
5
|
+
// The onboard user LED is active-low and exposed as the DT alias `led0`.
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
|
|
8
|
+
import type { ZephyrChipDescriptor } from './types.js';
|
|
9
|
+
|
|
10
|
+
export const XIAO_BLE: ZephyrChipDescriptor = {
|
|
11
|
+
id: 'xiao_ble',
|
|
12
|
+
soc: 'nrf52840',
|
|
13
|
+
gpioController: 'gpio0',
|
|
14
|
+
// GPIO is split across two devicetree controllers: gpio0 (P0.00–P0.31)
|
|
15
|
+
// and gpio1 (P1.00–P1.15, HAL pins 32–47). Declaring the split makes the
|
|
16
|
+
// raw path emit the port-relative index against gpio1 (P1.11 = raw 11),
|
|
17
|
+
// which NRF_GPIO_PIN_MAP(1, 11) resolves to absolute pin 43 — the same
|
|
18
|
+
// physical pin the old single-controller form reached only by accident:
|
|
19
|
+
// gpio0 + global 43 also maps to 43 (MAP(0, 43) = 43), but that form
|
|
20
|
+
// trips the generic layer's port_pin_mask __ASSERT ("Unsupported pin",
|
|
21
|
+
// gpio0's mask covers 0–31) on any assert-enabled build.
|
|
22
|
+
gpioControllers: [
|
|
23
|
+
{ nodelabel: 'gpio0', minPin: 0, maxPin: 31 },
|
|
24
|
+
{ nodelabel: 'gpio1', minPin: 32, maxPin: 47 },
|
|
25
|
+
],
|
|
26
|
+
gpio: {
|
|
27
|
+
dtSpecs: [
|
|
28
|
+
// Onboard RGB LEDs — active-low (GPIO_ACTIVE_LOW in xiao_ble_common.dtsi).
|
|
29
|
+
// Verified against the board's `aliases { led0 = &led0; led1 = &led1; led2 = &led2; }`.
|
|
30
|
+
{ pin: 26, dtSpec: 'led0' }, // Red (P0.26)
|
|
31
|
+
{ pin: 30, dtSpec: 'led1' }, // Green (P0.30)
|
|
32
|
+
{ pin: 6, dtSpec: 'led2' }, // Blue (P0.06)
|
|
33
|
+
],
|
|
34
|
+
// The XIAO nRF52840 has a user button on P0.04, but mainline Zephyr's
|
|
35
|
+
// xiao_ble board (verified against Zephyr 4.3.99) does NOT expose it as a
|
|
36
|
+
// DT `sw0` alias — there is no gpio-keys node in xiao_ble.dts. Emitting
|
|
37
|
+
// GPIO_DT_SPEC_GET(DT_ALIAS(sw0), gpios) therefore fails to compile
|
|
38
|
+
// ('DT_N_ALIAS_sw0_... was not declared'). Until a button node + alias is
|
|
39
|
+
// added (via an overlay or an upstream board update), interruptPins stays
|
|
40
|
+
// empty: attachInterrupt on this board lowers to the no-DT-spec comment
|
|
41
|
+
// fallback rather than a hard compile error.
|
|
42
|
+
interruptPins: [],
|
|
43
|
+
},
|
|
44
|
+
// The XIAO connector wiring (from seeed_xiao_connector.dtsi + xiao_ble-pinctrl.dtsi):
|
|
45
|
+
// xiao_i2c → i2c1 (SDA P0.04/D4, SCL P0.05/D5)
|
|
46
|
+
// xiao_spi → spi2 (SCK P1.13/D8, MOSI P1.15/D10, MISO P1.14/D9)
|
|
47
|
+
// xiao_serial → uart0 (TX P1.11/D6, RX P1.12/D7)
|
|
48
|
+
// The board DTS leaves i2c0/spi0/spi1 disabled (shared instances).
|
|
49
|
+
i2c: {
|
|
50
|
+
controllers: [{ nodeLabel: 'i2c1' }],
|
|
51
|
+
},
|
|
52
|
+
spi: {
|
|
53
|
+
controllers: [{ nodeLabel: 'spi2' }],
|
|
54
|
+
},
|
|
55
|
+
uart: {
|
|
56
|
+
controllers: [{ nodeLabel: 'uart0' }],
|
|
57
|
+
},
|
|
58
|
+
pwm: {
|
|
59
|
+
// pwm-led0 drives the board PWM LED (PWM_OUT0 on P0.17, inverted).
|
|
60
|
+
specs: [{ pin: 17, dtSpec: 'pwm-led0' }],
|
|
61
|
+
},
|
|
62
|
+
adc: {
|
|
63
|
+
// SAADC node is `adc`; no pre-declared channels. XIAO D0–D3 = AIN0–AIN3
|
|
64
|
+
// (P0.02/P0.03/P0.28/P0.29). Internal VREF ~0.6V with VDD/4 gain ⇒ 3000mV.
|
|
65
|
+
nodeLabel: 'adc',
|
|
66
|
+
resolution: 12,
|
|
67
|
+
vrefMv: 3000,
|
|
68
|
+
channels: [
|
|
69
|
+
{ pin: 2, channel: 0 }, // P0.02 / D0 / AIN0
|
|
70
|
+
{ pin: 3, channel: 1 }, // P0.03 / D1 / AIN1
|
|
71
|
+
{ pin: 28, channel: 2 }, // P0.28 / D2 / AIN2
|
|
72
|
+
{ pin: 29, channel: 3 }, // P0.29 / D3 / AIN3
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
wdt: { nodeLabel: 'wdt0' },
|
|
76
|
+
// Hardware timer: nRF RTC1 is the free counter (RTC0 is kernel-owned by the
|
|
77
|
+
// softdevice/clock driver). The hwtimer lowering drives it as a Zephyr
|
|
78
|
+
// counter device (counter_start/stop + a top-value alarm for set_frequency).
|
|
79
|
+
// Verified against the nRF52840 SoC dtsi (rtc0/rtc1 nodes). The kernel uses
|
|
80
|
+
// RTC0 for the system tick; RTC1 is available for application use.
|
|
81
|
+
hwtimer: { controllers: [{ nodeLabel: 'rtc1' }] },
|
|
82
|
+
};
|
package/src/display/index.ts
CHANGED
|
@@ -18,7 +18,7 @@ import { DEFAULT_ZEPHYR_DISPLAY_PROFILE, type ZephyrDisplayProfile } from './pro
|
|
|
18
18
|
// and consumers can reach them from the package barrel.
|
|
19
19
|
export { zephyrUiDisplayAdapter, zephyrDisplayAdapterGenerator } from './ui-adapter.js';
|
|
20
20
|
export { zephyrTouchAdapter } from './touch-adapter.js';
|
|
21
|
-
export { ZEPHYR_DISPLAY_PROFILES, DEFAULT_ZEPHYR_DISPLAY_PROFILE } from './profiles.js';
|
|
21
|
+
export { ZEPHYR_DISPLAY_PROFILES, DEFAULT_ZEPHYR_DISPLAY_PROFILE, BUILT_IN_PROFILES } from './profiles.js';
|
|
22
22
|
export type { ZephyrDisplayProfile } from './profiles.js';
|
|
23
23
|
|
|
24
24
|
export interface DisplayState {
|
package/src/display/profiles.ts
CHANGED
|
@@ -114,3 +114,26 @@ export const ZEPHYR_DISPLAY_PROFILES: Record<string, ZephyrDisplayProfile> = {
|
|
|
114
114
|
/** The default profile used when resolveDisplayOp is probed without a display.init. */
|
|
115
115
|
export const DEFAULT_ZEPHYR_DISPLAY_PROFILE: ZephyrDisplayProfile =
|
|
116
116
|
ZEPHYR_DISPLAY_PROFILES['ili9341-zephyr'];
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* The Zephyr profiles mapped to the shared DisplayProfile shape — the single
|
|
120
|
+
* mapping, so no consumer needs to know the DT-binding descriptor layout.
|
|
121
|
+
* The strategy's getProfileRegistry() and the preview's profile-registry
|
|
122
|
+
* loader both consume this (the same role `BUILT_IN_PROFILES` plays in
|
|
123
|
+
* framework-arduino's displays modules).
|
|
124
|
+
*/
|
|
125
|
+
export const BUILT_IN_PROFILES: Record<string, import('@typecad/cuttlefish/api/shared').DisplayProfile> =
|
|
126
|
+
Object.fromEntries(
|
|
127
|
+
Object.entries(ZEPHYR_DISPLAY_PROFILES).map(([name, p]) => [
|
|
128
|
+
name,
|
|
129
|
+
{
|
|
130
|
+
driver: p.driver,
|
|
131
|
+
width: p.width,
|
|
132
|
+
height: p.height,
|
|
133
|
+
nativeWidth: p.nativeWidth,
|
|
134
|
+
nativeHeight: p.nativeHeight,
|
|
135
|
+
colorFormat: p.colorFormat,
|
|
136
|
+
rotation: p.rotation ?? 1,
|
|
137
|
+
},
|
|
138
|
+
]),
|
|
139
|
+
);
|
|
@@ -46,6 +46,10 @@ export interface ZephyrDisplayReadbackOptions {
|
|
|
46
46
|
scanlineSync?: boolean;
|
|
47
47
|
/** MISO/SDO GPIO; readback is disabled when it is not explicitly wired. */
|
|
48
48
|
miso?: number;
|
|
49
|
+
/** Tearing-effect GPIO (panel TE output). When set, panel updates wait for
|
|
50
|
+
* the TE frame pulse instead of GET_SCANLINE readback — no MISO required.
|
|
51
|
+
* The overlay adds te-gpios to the display DT node from this. */
|
|
52
|
+
tearingEffectPin?: number;
|
|
49
53
|
}
|
|
50
54
|
|
|
51
55
|
export function zephyrUiDisplayAdapter(
|
|
@@ -74,6 +78,9 @@ export function zephyrUiDisplayAdapter(
|
|
|
74
78
|
// react badly to GSCAN reads. Both an explicit opt-in and an explicit MISO
|
|
75
79
|
// pin are required before emitting an active synchronization path.
|
|
76
80
|
const scanlineSync = readback.scanlineSync === true && readback.miso !== undefined;
|
|
81
|
+
// TE (hardware tearing-effect) sync: strictly opt-in via a configured GPIO.
|
|
82
|
+
// Preferred over GET_SCANLINE when wired — no readback traffic, no MISO.
|
|
83
|
+
const tePin = typeof readback.tearingEffectPin === 'number' ? readback.tearingEffectPin : undefined;
|
|
77
84
|
|
|
78
85
|
const includes = [
|
|
79
86
|
`// --- Zephyr UI display adapter (${profile.driver}) ---`,
|
|
@@ -140,6 +147,22 @@ export function zephyrUiDisplayAdapter(
|
|
|
140
147
|
`// controller-specific validation are required; otherwise the display stays`,
|
|
141
148
|
`// on the existing retained/composited path with no extra SPI reads.`,
|
|
142
149
|
`static const bool __tc_pnl_scanline_sync = ${scanlineSync ? 'true' : 'false'};`,
|
|
150
|
+
`// Tearing-effect (TE) hardware sync: the panel pulses its TE line once`,
|
|
151
|
+
`// per frame. When te-gpios is present on the display DT node, panel`,
|
|
152
|
+
`// updates arm on the TE edge — tear-free writes with no MISO readback.`,
|
|
153
|
+
`#if DT_NODE_HAS_PROP(DT_NODELABEL(${dtLabel}), te_gpios)`,
|
|
154
|
+
`#define __TC_TE_SYNC 1`,
|
|
155
|
+
`static const struct gpio_dt_spec __tc_te =`,
|
|
156
|
+
` GPIO_DT_SPEC_GET(DT_NODELABEL(${dtLabel}), te_gpios);`,
|
|
157
|
+
`static struct gpio_callback __tc_te_cb;`,
|
|
158
|
+
`static volatile uint32_t __tc_te_count = 0;`,
|
|
159
|
+
`static void __tc_te_isr(const struct device* port, struct gpio_callback* cb, uint32_t pins) {`,
|
|
160
|
+
` (void)port; (void)cb; (void)pins;`,
|
|
161
|
+
` __tc_te_count++;`,
|
|
162
|
+
`}`,
|
|
163
|
+
`#else`,
|
|
164
|
+
`#define __TC_TE_SYNC 0`,
|
|
165
|
+
`#endif`,
|
|
143
166
|
`// Stashed address window from the last setAddrWindow call. The runtime`,
|
|
144
167
|
`// calls setAddrWindow + writePixels as a matched pair, so we stash the rect`,
|
|
145
168
|
`// here and consume it in writePixels.`,
|
|
@@ -157,6 +180,18 @@ export function zephyrUiDisplayAdapter(
|
|
|
157
180
|
// with DT_HAS_ALIAS (the safe primitive for an alias that may be absent —
|
|
158
181
|
// DT_NODE_HAS_STATUS(DT_ALIAS(...)) is version-dependent when the alias is
|
|
159
182
|
// missing and can fail the build).
|
|
183
|
+
// TE pin: input + rising-edge interrupt (the ST7796 TE pulse), then tell
|
|
184
|
+
// the controller to drive the line (TEON 0x35, mode 1 = vertical sync only).
|
|
185
|
+
const teInit = tePin !== undefined ? `#if __TC_TE_SYNC
|
|
186
|
+
if (device_is_ready(__tc_te.port)) {
|
|
187
|
+
gpio_pin_configure_dt(&__tc_te, GPIO_INPUT);
|
|
188
|
+
gpio_init_callback(&__tc_te_cb, __tc_te_isr, BIT(__tc_te.pin));
|
|
189
|
+
(void)gpio_add_callback(__tc_te.port, &__tc_te_cb);
|
|
190
|
+
(void)gpio_pin_interrupt_configure_dt(&__tc_te, GPIO_INT_EDGE_RISING);
|
|
191
|
+
__tc_pnl_cmd1(0x35, 0x01); // TEON: TE output = vsync pulse
|
|
192
|
+
}
|
|
193
|
+
#endif` : '';
|
|
194
|
+
|
|
160
195
|
const blInit = backlightAlias
|
|
161
196
|
? `#if DT_HAS_ALIAS(${backlightAlias})\n const struct gpio_dt_spec __bl = GPIO_DT_SPEC_GET(DT_ALIAS(${backlightAlias}), gpios);\n if (device_is_ready(__bl.port)) { gpio_pin_configure_dt(&__bl, GPIO_OUTPUT_ACTIVE); }\n#endif`
|
|
162
197
|
: '';
|
|
@@ -479,6 +514,19 @@ static uint16_t __tc_pnl_read_scanline(void) {
|
|
|
479
514
|
// have no safe post-rectangle interval, so they retain the normal single-burst
|
|
480
515
|
// behavior; the caller's framebuffer still prevents intermediate software frames.
|
|
481
516
|
static void __tc_pnl_wait_for_safe_rect(int16_t y, int16_t rh) {
|
|
517
|
+
#if __TC_TE_SYNC
|
|
518
|
+
// TE variant: arm on the next frame pulse, then start the burst — the write
|
|
519
|
+
// chases the scan beam from the top of the rect. Bounded so a stuck TE line
|
|
520
|
+
// can never hang the UI loop.
|
|
521
|
+
if (rh < 8 || y < 0) return;
|
|
522
|
+
uint32_t __was = __tc_te_count;
|
|
523
|
+
uint32_t __deadline = k_uptime_get_32() + 25U;
|
|
524
|
+
while (__tc_te_count == __was) {
|
|
525
|
+
if (static_cast<int32_t>(k_uptime_get_32() - __deadline) >= 0) break;
|
|
526
|
+
k_msleep(0);
|
|
527
|
+
}
|
|
528
|
+
return;
|
|
529
|
+
#endif
|
|
482
530
|
if (!__tc_pnl_scanline_sync || rh < 8 || y < 0) return;
|
|
483
531
|
int16_t __last = static_cast<int16_t>(y + rh - 1);
|
|
484
532
|
if (__last >= static_cast<int16_t>(${h} - 2)) return;
|
|
@@ -578,8 +626,10 @@ const CuttlefishPanelOps __tc_display_ops = {
|
|
|
578
626
|
CuttlefishGFX __tc_display(&__tc_display_ops, nullptr);
|
|
579
627
|
${initBlock}
|
|
580
628
|
// ── display_init (called from setup) ────────────────────────────────────
|
|
629
|
+
|
|
581
630
|
static inline void display_init() {
|
|
582
631
|
printk("TC_DISPLAY: device ready\\n");
|
|
632
|
+
${teInit}
|
|
583
633
|
${blInit}
|
|
584
634
|
gpio_pin_configure_dt(&__tc_pnl_cs, GPIO_OUTPUT);
|
|
585
635
|
gpio_pin_configure_dt(&__tc_pnl_dc, GPIO_OUTPUT);
|
|
@@ -726,5 +776,6 @@ export const zephyrDisplayAdapterGenerator: DisplayAdapterGenerator = (display)
|
|
|
726
776
|
return zephyrUiDisplayAdapter(profile, {
|
|
727
777
|
scanlineSync: display.scanlineSync,
|
|
728
778
|
miso: display.spiPins?.miso,
|
|
779
|
+
tearingEffectPin: (display as { tearingEffectPin?: number }).tearingEffectPin,
|
|
729
780
|
});
|
|
730
781
|
};
|
package/src/dt-config/kconfig.ts
CHANGED
|
@@ -35,6 +35,17 @@ export interface KconfigUsage {
|
|
|
35
35
|
/** PSRAM type ('opi' | 'quad') when the target board has PSRAM. Emits the
|
|
36
36
|
* CONFIG_SPIRAM symbols so the ESP heap serves PSRAM for canvas allocations. */
|
|
37
37
|
psram?: 'opi' | 'quad';
|
|
38
|
+
/** HAL pin numbers the program reads with adc.* — scanned from the emitted
|
|
39
|
+
* `__tc_adc<N>_setup()` calls at compile time. Only the overlay generator
|
|
40
|
+
* consumes this (to rewrite the ADC node's pinctrl-0 to the used channels
|
|
41
|
+
* on SoCs that need pad muxing, e.g. STM32); prj.conf ignores it. */
|
|
42
|
+
adcReadPins?: readonly number[];
|
|
43
|
+
/** HAL pin numbers the program drives with pwm.* — scanned from the
|
|
44
|
+
* emitted `__tc_pwm_*` spec references at compile time. Only the overlay
|
|
45
|
+
* generator consumes this (synthesized pwm-leds consumers + aliases are
|
|
46
|
+
* emitted per used pin, so the DT carries no dead channels); prj.conf
|
|
47
|
+
* ignores it. */
|
|
48
|
+
pwmUsedPins?: readonly number[];
|
|
38
49
|
}
|
|
39
50
|
|
|
40
51
|
/**
|
|
@@ -123,7 +134,7 @@ export function resolveKconfigFragments(
|
|
|
123
134
|
// (without it, Kconfig silently forces them all to n).
|
|
124
135
|
m.set('CONFIG_NETWORKING', 'y');
|
|
125
136
|
m.set('CONFIG_WIFI', 'y');
|
|
126
|
-
m.set('CONFIG_WIFI_ESP32', 'y'); //
|
|
137
|
+
m.set('CONFIG_WIFI_ESP32', 'y'); // family-wide ESP32 driver (esp32/s3/c3/c6)
|
|
127
138
|
m.set('CONFIG_NET_L2_ETHERNET', 'y');
|
|
128
139
|
m.set('CONFIG_NET_IPV4', 'y');
|
|
129
140
|
m.set('CONFIG_NET_UDP', 'y'); // transitive dep of NET_DHCPV4
|
package/src/dt-config/overlay.ts
CHANGED
|
@@ -25,6 +25,9 @@ export interface DisplayWiring {
|
|
|
25
25
|
cs?: number;
|
|
26
26
|
dc?: number;
|
|
27
27
|
rst?: number;
|
|
28
|
+
/** Tearing-effect (TE) GPIO from display.tearingEffectPin — emitted as
|
|
29
|
+
* te-gpios on the display DT node. Opt-in; most boards don't wire TE. */
|
|
30
|
+
tearingEffectPin?: number;
|
|
28
31
|
spiFrequency?: number;
|
|
29
32
|
/** SPI bus pins. When present, the overlay remuxes the SPI controller's
|
|
30
33
|
* pinctrl to these pins (the board defaults rarely match a breakout's
|
|
@@ -61,13 +64,30 @@ export interface TouchWiring {
|
|
|
61
64
|
minPressure?: number;
|
|
62
65
|
}
|
|
63
66
|
|
|
67
|
+
export interface OverlayDiagnostic {
|
|
68
|
+
severity: "warning" | "error";
|
|
69
|
+
message: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
64
72
|
export function generateOverlay(
|
|
65
73
|
chip: ZephyrChipDescriptor,
|
|
66
74
|
usage: KconfigUsage,
|
|
67
75
|
display: ZephyrDisplayProfile | undefined,
|
|
68
76
|
wiring?: DisplayWiring,
|
|
69
77
|
touch?: TouchWiring,
|
|
78
|
+
diagnostics: OverlayDiagnostic[] = [],
|
|
70
79
|
): string {
|
|
80
|
+
// An I2C touch controller with no explicit bus pins: the overlay enables
|
|
81
|
+
// i2c0 and instantiates the node, but nothing remuxes the controller to
|
|
82
|
+
// the wired SDA/SCL (the board's default I2C pins rarely match a
|
|
83
|
+
// breakout). Every I2C read then fails and touch silently does nothing —
|
|
84
|
+
// surface it at build time instead of leaving it to a multimeter.
|
|
85
|
+
if (touch && touch.controller !== 'xpt2046' && (touch.sda === undefined || touch.scl === undefined)) {
|
|
86
|
+
diagnostics.push({
|
|
87
|
+
severity: "warning",
|
|
88
|
+
message: `touch: I2C controller '${touch.controller}' has no sda/scl pins in cuttlefish.config.ts — the overlay enables the bus without a pin assignment, so the controller may never answer. Add touch.sda and touch.scl (the board's default I2C pins are rarely the wired ones).`,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
71
91
|
const lines: string[] = [
|
|
72
92
|
'/* Auto-generated by @typecad/framework-zephyr from cuttlefish.config.ts. */',
|
|
73
93
|
'/* Enables peripherals the program uses. West merges this over the board DT. */',
|
|
@@ -97,6 +117,24 @@ export function generateOverlay(
|
|
|
97
117
|
if (usage.usesUart && chip.uart) {
|
|
98
118
|
for (const c of chip.uart.controllers) block(c.nodeLabel);
|
|
99
119
|
}
|
|
120
|
+
// PWM: synthesized specs (controller + channel, no board-shipped alias) get
|
|
121
|
+
// a pwm-leds consumer node + tc-pwm<pin> alias here — the lowering addresses
|
|
122
|
+
// the channel as PWM_DT_SPEC_GET(DT_ALIAS(tc-pwm<pin>)), so the alias must
|
|
123
|
+
// exist in the merged DT. pwmDtAlias (lowering/pwm.ts) derives the same
|
|
124
|
+
// name from the pin; the two sides cannot drift. Filtered to the pins the
|
|
125
|
+
// program actually drives (pwmUsedPins) so the DT carries no dead channels;
|
|
126
|
+
// an undefined list (prepare-time overlay) emits all specs.
|
|
127
|
+
if (usage.usesPwm && chip.pwm) {
|
|
128
|
+
emitPwmNodes(lines, chip, usage.pwmUsedPins);
|
|
129
|
+
}
|
|
130
|
+
// ADC: enable the ADC device node, and on SoCs whose channels carry a
|
|
131
|
+
// pinctrl label (STM32: the pad stays in GPIO mode unless the ADC node's
|
|
132
|
+
// pinctrl-0 muxes it analog), rewrite pinctrl-0 to exactly the channels the
|
|
133
|
+
// program reads. DT assignment replaces the whole property — the board
|
|
134
|
+
// default (one pad) is deliberately dropped in favor of the used set.
|
|
135
|
+
if (usage.usesAdc && chip.adc) {
|
|
136
|
+
emitAdcNode(lines, chip, usage.adcReadPins);
|
|
137
|
+
}
|
|
100
138
|
// DAC: enable the chip's DAC device node when the program uses dac.*. The
|
|
101
139
|
// lowering references DEVICE_DT_GET(DT_NODELABEL(<dac.device>)).
|
|
102
140
|
if (usage.usesDac && chip.dac) {
|
|
@@ -159,6 +197,84 @@ export function generateOverlay(
|
|
|
159
197
|
return lines.join('\n');
|
|
160
198
|
}
|
|
161
199
|
|
|
200
|
+
/**
|
|
201
|
+
* Emit the pwm-leds consumer node + aliases for synthesized PWM specs.
|
|
202
|
+
*
|
|
203
|
+
* A board DTS may enable a PWM controller (`pwm4`) without aliasing any
|
|
204
|
+
* channel — the STM32 pattern — while the lowering addresses channels via
|
|
205
|
+
* DT_ALIAS. Each synthesized spec gets a `pwm-leds` child whose `pwms` cell
|
|
206
|
+
* binds the controller+channel (+ period/polarity), and a `tc-pwm<pin>`
|
|
207
|
+
* alias pointing at it. Alias/label names are valid DT identifiers (lowercase
|
|
208
|
+
* alphanumerics + dash/underscore).
|
|
209
|
+
*/
|
|
210
|
+
function emitPwmNodes(
|
|
211
|
+
lines: string[],
|
|
212
|
+
chip: ZephyrChipDescriptor,
|
|
213
|
+
usedPins?: readonly number[],
|
|
214
|
+
): void {
|
|
215
|
+
const synthesized = (chip.pwm?.specs ?? []).filter(
|
|
216
|
+
(s) => s.controller && s.channel != null && !s.dtSpec,
|
|
217
|
+
).filter((s) => !usedPins || usedPins.includes(s.pin));
|
|
218
|
+
if (synthesized.length === 0) return;
|
|
219
|
+
// Enable each distinct PWM controller node (idempotent when already okay).
|
|
220
|
+
for (const controller of [...new Set(synthesized.map((s) => s.controller!))]) {
|
|
221
|
+
lines.push(`&${controller} {`);
|
|
222
|
+
lines.push(' status = "okay";');
|
|
223
|
+
lines.push('};');
|
|
224
|
+
lines.push('');
|
|
225
|
+
}
|
|
226
|
+
lines.push('/ {');
|
|
227
|
+
lines.push(' tc_pwm_leds: tc-pwm-leds {');
|
|
228
|
+
lines.push(' compatible = "pwm-leds";');
|
|
229
|
+
for (const s of synthesized) {
|
|
230
|
+
// 20 ms / 50 Hz default — the servo convention; the duty scaling in the
|
|
231
|
+
// lowering normalizes 0-255 against whatever period is baked in.
|
|
232
|
+
const period = s.periodNs ?? 20_000_000;
|
|
233
|
+
const polarity = s.polarity ?? 'PWM_POLARITY_NORMAL';
|
|
234
|
+
lines.push(` tc_pwm_${s.pin}: pwm-led-${s.pin} {`);
|
|
235
|
+
lines.push(` pwms = <&${s.controller} ${s.channel} ${period} ${polarity}>;`);
|
|
236
|
+
lines.push(' };');
|
|
237
|
+
}
|
|
238
|
+
lines.push(' };');
|
|
239
|
+
lines.push(' aliases {');
|
|
240
|
+
for (const s of synthesized) {
|
|
241
|
+
lines.push(` tc-pwm${s.pin} = &tc_pwm_${s.pin};`);
|
|
242
|
+
}
|
|
243
|
+
lines.push(' };');
|
|
244
|
+
lines.push('};');
|
|
245
|
+
lines.push('');
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Emit the ADC device-node enable (+ optional pinctrl override). The pinctrl
|
|
250
|
+
* override applies only when the descriptor's channels carry pinctrl labels
|
|
251
|
+
* AND the caller knows which pins the program reads (compile-time regen scans
|
|
252
|
+
* the emitted `__tc_adc<N>_setup()` calls); the prepare-time overlay omits it
|
|
253
|
+
* and the compile regen rewrites the file before west runs.
|
|
254
|
+
*/
|
|
255
|
+
function emitAdcNode(
|
|
256
|
+
lines: string[],
|
|
257
|
+
chip: ZephyrChipDescriptor,
|
|
258
|
+
readPins?: readonly number[],
|
|
259
|
+
): void {
|
|
260
|
+
const adc = chip.adc!;
|
|
261
|
+
const labeled = adc.channels.filter((c) => c.pinctrl);
|
|
262
|
+
const used = labeled.filter((c) => !readPins || readPins.includes(c.pin));
|
|
263
|
+
if (labeled.length > 0 && used.length > 0) {
|
|
264
|
+
lines.push(`&${adc.nodeLabel} {`);
|
|
265
|
+
lines.push(' status = "okay";');
|
|
266
|
+
lines.push(` pinctrl-0 = <${used.map((c) => `&${c.pinctrl}`).join(' ')}>;`);
|
|
267
|
+
lines.push(' pinctrl-names = "default";');
|
|
268
|
+
lines.push('};');
|
|
269
|
+
lines.push('');
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
lines.push(`&${adc.nodeLabel} {`);
|
|
273
|
+
lines.push(' status = "okay";');
|
|
274
|
+
lines.push('};');
|
|
275
|
+
lines.push('');
|
|
276
|
+
}
|
|
277
|
+
|
|
162
278
|
/**
|
|
163
279
|
* Emit a display DT node definition. The node is attached to the profile's SPI
|
|
164
280
|
* controller (default spi2, the ESP32's first user SPI controller) via a MIPI
|
|
@@ -246,6 +362,13 @@ function emitDisplayNode(
|
|
|
246
362
|
lines.push(` ${display.dtLabel}: display@0 {`);
|
|
247
363
|
lines.push(` compatible = "${compatible}";`);
|
|
248
364
|
lines.push(' reg = <0>;');
|
|
365
|
+
if (wiring?.tearingEffectPin !== undefined) {
|
|
366
|
+
const tePin = wiring.tearingEffectPin!;
|
|
367
|
+
// Tearing-effect input on the display node: GPIO_DT_SPEC_GET(
|
|
368
|
+
// DT_NODELABEL(display0), te_gpios) in the adapter. Opt-in —
|
|
369
|
+
// most modules don't break the TE pad out.
|
|
370
|
+
lines.push(` te-gpios = <&${gpioController(tePin)} ${tePin} GPIO_ACTIVE_HIGH>;`);
|
|
371
|
+
}
|
|
249
372
|
lines.push(` mipi-max-frequency = <${freq}>;`);
|
|
250
373
|
lines.push(' mipi-mode = "MIPI_DBI_MODE_SPI_4WIRE";');
|
|
251
374
|
// Required by the lcd-controller binding (Zephyr 4.x): 0 = RGB565,
|
|
@@ -50,8 +50,16 @@ export default defineFrameworkManifest({
|
|
|
50
50
|
profile: {
|
|
51
51
|
// Informational list of supported board targets. The manifest validator
|
|
52
52
|
// never iterates this; chipForTarget (src/chips/index.ts) is the real
|
|
53
|
-
// resolver. ESP32-S3 + plain ESP32 added alongside the nRF52840 MVP target
|
|
54
|
-
|
|
53
|
+
// resolver. ESP32-S3 + plain ESP32 added alongside the nRF52840 MVP target;
|
|
54
|
+
// rpi_pico / rpi_pico2 / esp32c3 / esp32c6 / blackpill_f411ce resolve via
|
|
55
|
+
// the board packages' zephyr chip data (resolveChipFromBoard), not the
|
|
56
|
+
// hardcoded chip registry.
|
|
57
|
+
targets: [
|
|
58
|
+
'xiao_ble', 'esp32s3_devkitc', 'esp32_devkitc',
|
|
59
|
+
'rpi_pico', 'rpi_pico2/rp2350a/m33',
|
|
60
|
+
'esp32c3_devkitm/esp32c3', 'esp32c6_devkitc/esp32c6/hpcore',
|
|
61
|
+
'blackpill_f411ce/stm32f411xe',
|
|
62
|
+
],
|
|
55
63
|
forcedIncludes: ['<zephyr/kernel.h>', '<zephyr/drivers/gpio.h>', '<cstdint>'],
|
|
56
64
|
symbolAliases: {},
|
|
57
65
|
},
|
|
@@ -213,7 +221,8 @@ export default defineFrameworkManifest({
|
|
|
213
221
|
},
|
|
214
222
|
},
|
|
215
223
|
// ── Partial: WiFi (STA connect + scan + config via conn_mgr/net_mgmt) ────
|
|
216
|
-
// ESP32
|
|
224
|
+
// ESP32 family only (esp32, esp32s3, esp32c3, esp32c6) — profileDiagnostics
|
|
225
|
+
// flags wifi usage on radioless chips (nRF52840, RP2040/RP2350).
|
|
217
226
|
// AP mode, credential persistence, static IP, and event callbacks deferred.
|
|
218
227
|
wifi: {
|
|
219
228
|
supported: true,
|
package/src/index.ts
CHANGED
|
@@ -10,6 +10,12 @@ export { ZephyrStrategy as FrameworkStrategy } from './strategy.js';
|
|
|
10
10
|
export { ZephyrStrategy } from './strategy.js';
|
|
11
11
|
export { Toolchain } from './toolchain/index.js';
|
|
12
12
|
|
|
13
|
+
// Create-time starter debug artifacts. The `cuttlefish create` flow reads this
|
|
14
|
+
// optional named export off the loaded framework module (same loader pattern
|
|
15
|
+
// as doctor/licenses) and calls it for freshly scaffolded projects, so F5 in
|
|
16
|
+
// VS Code works before the first build. No-ops for non-GDB targets.
|
|
17
|
+
export { writeProjectDebugArtifacts } from './toolchain/debug-config.js';
|
|
18
|
+
|
|
13
19
|
// `cuttlefish doctor` — verify the installed Zephyr is reachable + inside the
|
|
14
20
|
// declared compat range, and preview board-target normalization. Re-exported
|
|
15
21
|
// under the dispatcher-facing alias `doctor` so the loader picks it up as
|
package/src/lowering/adc.ts
CHANGED
|
@@ -23,25 +23,38 @@ export function adcChannelForPin(chip: ZephyrChipDescriptor, pin: number): numbe
|
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* Emit the per-channel ADC setup state. One block per channel in the chip
|
|
26
|
-
* descriptor
|
|
27
|
-
*
|
|
28
|
-
*
|
|
26
|
+
* descriptor that the PROGRAM ACTUALLY READS (`usedPins`) — an unread
|
|
27
|
+
* channel's `static` setup function would trip -Wunused-function in the
|
|
28
|
+
* single generated TU. When `usedPins` is omitted (probe paths with no
|
|
29
|
+
* program), every descriptor channel is emitted. Each block is guarded by a
|
|
30
|
+
* `static bool __tc_adc<N>_ready` so the first read configures it and
|
|
31
|
+
* subsequent reads skip. Called from shimLines when the program uses ADC.
|
|
29
32
|
*/
|
|
30
|
-
export function adcInitLines(chip: ZephyrChipDescriptor): string[] {
|
|
33
|
+
export function adcInitLines(chip: ZephyrChipDescriptor, usedPins?: ReadonlySet<number>): string[] {
|
|
31
34
|
const dev = `DEVICE_DT_GET(DT_NODELABEL(${chip.adc?.nodeLabel ?? 'adc'}))`;
|
|
32
35
|
const res = chip.adc?.resolution ?? 12;
|
|
33
36
|
const vref = chip.adc?.vrefMv ?? 3000;
|
|
37
|
+
// Gain/reference are SoC-specific: the nRF SAADC scheme (gain 1/4 against
|
|
38
|
+
// the 0.6V internal ref, vref-mv 3000 = VDD) is the default; the STM32
|
|
39
|
+
// driver requires exactly ADC_GAIN_1 + ADC_REF_INTERNAL (Zephyr maps
|
|
40
|
+
// "internal" to the VREF+ pad) with vref-mv = VDDA. The descriptor carries
|
|
41
|
+
// the SoC's pair so the emitted channel setup validates in the driver.
|
|
42
|
+
const gain = chip.adc?.gain ?? 'ADC_GAIN_1_4';
|
|
43
|
+
const reference = chip.adc?.reference ?? 'ADC_REF_INTERNAL';
|
|
44
|
+
const channels = (chip.adc?.channels ?? []).filter(
|
|
45
|
+
(c) => !usedPins || usedPins.has(c.pin),
|
|
46
|
+
);
|
|
34
47
|
const lines: string[] = ['// CUTTLEFISH_ADC_BEGIN'];
|
|
35
48
|
lines.push(`static const struct device* __tc_adc_dev = ${dev};`);
|
|
36
|
-
for (const c of
|
|
49
|
+
for (const c of channels) {
|
|
37
50
|
const n = c.channel;
|
|
38
51
|
lines.push(
|
|
39
52
|
`static bool __tc_adc${n}_ready = false;`,
|
|
40
53
|
`static void __tc_adc${n}_setup(void) {`,
|
|
41
54
|
` if (__tc_adc${n}_ready) return;`,
|
|
42
55
|
` const struct adc_channel_cfg cfg = {`,
|
|
43
|
-
` .gain =
|
|
44
|
-
` .reference =
|
|
56
|
+
` .gain = ${gain},`,
|
|
57
|
+
` .reference = ${reference},`,
|
|
45
58
|
` .acquisition_time = ADC_ACQ_TIME_DEFAULT,`,
|
|
46
59
|
` .channel_id = ${n},`,
|
|
47
60
|
` .differential = 0,`,
|
|
@@ -68,6 +81,8 @@ export function lowerAdc(
|
|
|
68
81
|
const o = op as any;
|
|
69
82
|
const res = chip.adc?.resolution ?? 12;
|
|
70
83
|
const vref = chip.adc?.vrefMv ?? 3000;
|
|
84
|
+
const gain = chip.adc?.gain ?? 'ADC_GAIN_1_4';
|
|
85
|
+
const reference = chip.adc?.reference ?? 'ADC_REF_INTERNAL';
|
|
71
86
|
|
|
72
87
|
switch (op.operation) {
|
|
73
88
|
case 'adc.read': {
|
|
@@ -79,10 +94,11 @@ export function lowerAdc(
|
|
|
79
94
|
}
|
|
80
95
|
case 'adc.read_voltage': {
|
|
81
96
|
const ch = adcChannelForPin(chip, o.pin);
|
|
82
|
-
// Read raw, convert to millivolts via adc_raw_to_millivolts
|
|
83
|
-
//
|
|
97
|
+
// Read raw, convert to millivolts via adc_raw_to_millivolts with the
|
|
98
|
+
// descriptor's gain (raw_to_millivolts divides out the gain the channel
|
|
99
|
+
// was set up with). Returns mV as int.
|
|
84
100
|
return {
|
|
85
|
-
expression: `({ __tc_adc${ch}_setup(); int16_t __b = 0; struct adc_sequence __s = { .channels = BIT(${ch}), .buffer = &__b, .buffer_size = sizeof(__b), .resolution = ${res} }; adc_read(__tc_adc_dev, &__s); int32_t __v = __b; adc_raw_to_millivolts(${vref},
|
|
101
|
+
expression: `({ __tc_adc${ch}_setup(); int16_t __b = 0; struct adc_sequence __s = { .channels = BIT(${ch}), .buffer = &__b, .buffer_size = sizeof(__b), .resolution = ${res} }; adc_read(__tc_adc_dev, &__s); int32_t __v = __b; adc_raw_to_millivolts(${vref}, ${gain}, ${res}, &__v); __v; })`,
|
|
86
102
|
};
|
|
87
103
|
}
|
|
88
104
|
case 'adc.get_resolution':
|
|
@@ -90,9 +106,9 @@ export function lowerAdc(
|
|
|
90
106
|
case 'adc.set_reference':
|
|
91
107
|
// Zephyr configures the reference at channel-setup time; runtime switching
|
|
92
108
|
// would require re-setup. Record the intent as a no-op statement.
|
|
93
|
-
return { code: `/* adc.set_reference(${o.reference}): configured at channel setup (
|
|
109
|
+
return { code: `/* adc.set_reference(${o.reference}): configured at channel setup (${reference}) */` };
|
|
94
110
|
case 'adc.get_reference':
|
|
95
|
-
return { expression: `0 /* DEFAULT (
|
|
111
|
+
return { expression: `0 /* DEFAULT (${reference}) */` };
|
|
96
112
|
default:
|
|
97
113
|
throw new Error(
|
|
98
114
|
`framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
|