@typecad/framework-zephyr 1.0.0-alpha.11 → 1.0.0-alpha.12
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/dist/display/profiles.d.ts +25 -0
- package/dist/display/profiles.js +17 -0
- package/dist/display/touch-adapter.d.ts +3 -4
- package/dist/display/touch-adapter.js +119 -16
- package/dist/display/ui-adapter.js +302 -139
- package/dist/dt-config/kconfig.d.ts +5 -1
- package/dist/dt-config/kconfig.js +22 -5
- package/dist/dt-config/overlay.d.ts +18 -1
- package/dist/dt-config/overlay.js +119 -26
- package/dist/framework.manifest.d.ts +29 -28
- package/dist/framework.manifest.js +9 -3
- package/dist/lowering/ble.js +3 -1
- package/dist/strategy.d.ts +4 -0
- package/dist/strategy.js +89 -21
- package/dist/toolchain/index.js +39 -9
- package/dist/toolchain/scaffold.js +4 -2
- package/dist/toolchain/west-discover.js +4 -1
- package/package.json +4 -4
- package/src/display/profiles.ts +40 -0
- package/src/display/touch-adapter.ts +119 -15
- package/src/display/ui-adapter.ts +306 -139
- package/src/dt-config/kconfig.ts +26 -6
- package/src/dt-config/overlay.ts +154 -29
- package/src/framework.manifest.ts +9 -3
- package/src/lowering/ble.ts +3 -1
- package/src/strategy.ts +91 -21
- package/src/toolchain/index.ts +40 -9
- package/src/toolchain/scaffold.ts +4 -2
- package/src/toolchain/west-discover.ts +4 -1
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
// that brings external capabilities into a build. (Arduino does it by parsing
|
|
11
11
|
// library headers into .d.ts; Zephyr does it by enabling DT nodes + Kconfig.)
|
|
12
12
|
// ---------------------------------------------------------------------------
|
|
13
|
+
import { PANEL_CONTROLLER_DEFAULTS, panelControllerFor } from '../display/profiles.js';
|
|
13
14
|
export function generateOverlay(chip, usage, display, wiring, touch) {
|
|
14
15
|
const lines = [
|
|
15
16
|
'/* Auto-generated by @typecad/framework-zephyr from cuttlefish.config.ts. */',
|
|
@@ -53,14 +54,18 @@ export function generateOverlay(chip, usage, display, wiring, touch) {
|
|
|
53
54
|
// controller with the panel's compatible string + dimensions. The pin
|
|
54
55
|
// wiring (cs/dc/rst) uses ESP32 GPIO defaults from the demo config; a
|
|
55
56
|
// real board overlay would carry its own binding.
|
|
56
|
-
|
|
57
|
+
// An XPT2046 on the same bus needs its CS as the second cs-gpios entry,
|
|
58
|
+
// so thread it into the display block (DT assignment replaces the whole
|
|
59
|
+
// property — both entries must be written together).
|
|
60
|
+
emitDisplayNode(lines, display, wiring, touch?.controller === 'xpt2046' ? (touch?.cs ?? DEFAULT_XPT2046_CS) : undefined);
|
|
57
61
|
}
|
|
58
|
-
// FT6336U
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
+
// Touch — FT6336U capacitive on I2C (references DT_NODELABEL(ft6336u)) or
|
|
63
|
+
// XPT2046 resistive on the display's SPI bus (references
|
|
64
|
+
// DT_NODELABEL(xpt2046)). Same rationale as the display node: the bare
|
|
65
|
+
// devkit has no such node. Gated on usesTouch (not usesI2c/chip.i2c) so it
|
|
66
|
+
// emits even when the chip descriptor doesn't declare bus controllers.
|
|
62
67
|
if (usage.usesTouch) {
|
|
63
|
-
emitTouchNode(lines, touch);
|
|
68
|
+
emitTouchNode(lines, touch, display);
|
|
64
69
|
}
|
|
65
70
|
// Preferences (ZMS settings backend): point the settings subsystem at the
|
|
66
71
|
// board's storage_partition. The backend looks for /chosen
|
|
@@ -91,11 +96,21 @@ export function generateOverlay(chip, usage, display, wiring, touch) {
|
|
|
91
96
|
return lines.join('\n');
|
|
92
97
|
}
|
|
93
98
|
/**
|
|
94
|
-
* Emit a display DT node definition. The node is attached to
|
|
95
|
-
* first user SPI controller) via a MIPI
|
|
96
|
-
* the display config (cs/dc/rst);
|
|
99
|
+
* Emit a display DT node definition. The node is attached to the profile's SPI
|
|
100
|
+
* controller (default spi2, the ESP32's first user SPI controller) via a MIPI
|
|
101
|
+
* DBI SPI bridge. Pin wiring comes from the display config (cs/dc/rst);
|
|
102
|
+
* defaults match the demo-st wiring if absent. The compatible string + node
|
|
103
|
+
* props come from the profile's panel controller (st7796s carries the required
|
|
104
|
+
* pgc/ngc gamma + madctl; ili9341's binding defaults everything else).
|
|
105
|
+
*
|
|
106
|
+
* spiTouchCs: when an XPT2046 SPI touch controller shares the bus, its CS is
|
|
107
|
+
* appended as the second cs-gpios entry (the touch node uses reg = <1>) — DT
|
|
108
|
+
* property assignment replaces, so both entries must be written together.
|
|
97
109
|
*/
|
|
98
|
-
function emitDisplayNode(lines, display, wiring) {
|
|
110
|
+
function emitDisplayNode(lines, display, wiring, spiTouchCs) {
|
|
111
|
+
const bus = display.busLabel ?? 'spi2';
|
|
112
|
+
const controller = panelControllerFor(display);
|
|
113
|
+
const compatible = display.dtCompatible ?? PANEL_CONTROLLER_DEFAULTS[controller].dtCompatible;
|
|
99
114
|
const dc = wiring?.dc ?? 17;
|
|
100
115
|
const rst = wiring?.rst ?? 16;
|
|
101
116
|
const cs = wiring?.cs ?? 5;
|
|
@@ -116,7 +131,9 @@ function emitDisplayNode(lines, display, wiring) {
|
|
|
116
131
|
// macros are used instead of the named SPIM2_*_GPIOxx tokens because the
|
|
117
132
|
// bindings header omits GPIOs 22-25 from those lists.
|
|
118
133
|
if (sck !== undefined && mosi !== undefined) {
|
|
119
|
-
|
|
134
|
+
// 'spi2' → pinctrl group 'spim2_default' (ESP32 SPI-master naming).
|
|
135
|
+
const pinctrlGroup = bus.replace(/^spi(\d)$/, 'spim$1') + '_default';
|
|
136
|
+
lines.push(`&${pinctrlGroup} {`);
|
|
120
137
|
lines.push(' group1 {');
|
|
121
138
|
lines.push(` pinmux = <ESP32_PINMUX(${miso ?? 19}, ESP_FSPIQ_IN, ESP_NOSIG)>,`);
|
|
122
139
|
lines.push(` <ESP32_PINMUX(${sck}, ESP_NOSIG, ESP_FSPICLK_OUT)>,`);
|
|
@@ -133,9 +150,9 @@ function emitDisplayNode(lines, display, wiring) {
|
|
|
133
150
|
lines.push(' status = "okay";');
|
|
134
151
|
lines.push('};');
|
|
135
152
|
lines.push('');
|
|
136
|
-
lines.push(
|
|
153
|
+
lines.push(`&${bus} {`);
|
|
137
154
|
lines.push(' status = "okay";');
|
|
138
|
-
lines.push(` cs-gpios = <&${gpioController(cs)} ${cs} GPIO_ACTIVE_LOW
|
|
155
|
+
lines.push(` cs-gpios = <&${gpioController(cs)} ${cs} GPIO_ACTIVE_LOW>${spiTouchCs !== undefined ? `, <&${gpioController(spiTouchCs)} ${spiTouchCs} GPIO_ACTIVE_LOW>` : ''};`);
|
|
139
156
|
// Enable GDMA for the SPI2 host. The ESP32 SPI driver uses DMA only when
|
|
140
157
|
// dma-enabled is set AND dmas wires tx/rx channels to the GDMA controller;
|
|
141
158
|
// without it, transfers run PIO through the 64-byte FIFO (~4MHz effective at
|
|
@@ -151,25 +168,37 @@ function emitDisplayNode(lines, display, wiring) {
|
|
|
151
168
|
lines.push('/ {');
|
|
152
169
|
lines.push(' mipi_dbi: mipi-dbi {');
|
|
153
170
|
lines.push(' compatible = "zephyr,mipi-dbi-spi";');
|
|
154
|
-
lines.push(
|
|
171
|
+
lines.push(` spi-dev = <&${bus}>;`);
|
|
155
172
|
lines.push(` dc-gpios = <&${gpioController(dc)} ${dc} GPIO_ACTIVE_HIGH>;`);
|
|
156
173
|
lines.push(` reset-gpios = <&${gpioController(rst)} ${rst} GPIO_ACTIVE_LOW>;`);
|
|
157
174
|
lines.push(' write-only;');
|
|
158
175
|
lines.push(' #address-cells = <1>;');
|
|
159
176
|
lines.push(' #size-cells = <0>;');
|
|
160
177
|
lines.push(` ${display.dtLabel}: display@0 {`);
|
|
161
|
-
lines.push(
|
|
178
|
+
lines.push(` compatible = "${compatible}";`);
|
|
162
179
|
lines.push(' reg = <0>;');
|
|
163
180
|
lines.push(` mipi-max-frequency = <${freq}>;`);
|
|
164
181
|
lines.push(' mipi-mode = "MIPI_DBI_MODE_SPI_4WIRE";');
|
|
182
|
+
// Required by the lcd-controller binding (Zephyr 4.x): 0 = RGB565,
|
|
183
|
+
// matching upstream ILI9341 boards (esp_wrover_kit) and the C++
|
|
184
|
+
// runtime, which drives these SPI TFTs as RGB565.
|
|
185
|
+
lines.push(' pixel-format = <0>;');
|
|
165
186
|
lines.push(` width = <${nativeW}>;`);
|
|
166
187
|
lines.push(` height = <${nativeH}>;`);
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
188
|
+
if (controller === 'st7796s') {
|
|
189
|
+
// MADCTL: rotation 1 (landscape, MV=1) + BGR bit, matching the adapter's
|
|
190
|
+
// direct-drive init (0x28). The DT copy keeps the stock driver's init
|
|
191
|
+
// consistent if it is ever exercised.
|
|
192
|
+
lines.push(' madctl = <0x28>;');
|
|
193
|
+
lines.push(' pgc = [f0 09 0b 06 04 2e 46 46 39 13 15 12 15 12];');
|
|
194
|
+
lines.push(' ngc = [f0 09 0b 06 04 2e 46 46 39 13 15 12 15 12];');
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
// ILI9341: the ilitek,ili9341 binding carries defaults for every register
|
|
198
|
+
// (gamma, power, porch) and expresses orientation via `rotation` (degrees)
|
|
199
|
+
// instead of a raw MADCTL — no panel-specific props are required.
|
|
200
|
+
lines.push(` rotation = <${display.rotation ?? 0}>;`);
|
|
201
|
+
}
|
|
173
202
|
lines.push(' };');
|
|
174
203
|
lines.push(' };');
|
|
175
204
|
lines.push('};');
|
|
@@ -195,13 +224,33 @@ function emitDisplayNode(lines, display, wiring) {
|
|
|
195
224
|
lines.push('');
|
|
196
225
|
}
|
|
197
226
|
}
|
|
227
|
+
/** Default XPT2046 CS/IRQ pins (ESP32-S3 GPIOs clear of the demo-st display
|
|
228
|
+
* wiring: 5/17/16/15 and the remuxed SPI pins). Config values override. */
|
|
229
|
+
const DEFAULT_XPT2046_CS = 6;
|
|
230
|
+
const DEFAULT_XPT2046_IRQ = 7;
|
|
198
231
|
/**
|
|
199
|
-
* Emit
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
232
|
+
* Emit the touch DT node for the configured controller.
|
|
233
|
+
*
|
|
234
|
+
* FT6336U (capacitive, I2C): node on i2c0 at the FT6336U default address
|
|
235
|
+
* (0x38); the C++ touch adapter reads it via i2c_write_read_dt.
|
|
236
|
+
*
|
|
237
|
+
* XPT2046 (resistive, SPI): node on the display's SPI bus as CS index 1. The
|
|
238
|
+
* in-tree xptek,xpt2046 binding (drivers/input) is register-matched for the
|
|
239
|
+
* raw SPI access — CONFIG_INPUT stays off, so the in-tree input driver does
|
|
240
|
+
* not build and the adapter owns the chip (same pattern as FT6336U reusing
|
|
241
|
+
* the ft5336 binding). The binding requires int-gpios, touchscreen-size-*,
|
|
242
|
+
* and min/max calibration props, so defaults are filled for anything the
|
|
243
|
+
* config omits.
|
|
203
244
|
*/
|
|
204
|
-
function emitTouchNode(lines, touch) {
|
|
245
|
+
function emitTouchNode(lines, touch, display) {
|
|
246
|
+
if (touch?.controller === 'xpt2046') {
|
|
247
|
+
emitXpt2046Node(lines, touch, display);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
emitFt6336uNode(lines, touch);
|
|
251
|
+
}
|
|
252
|
+
/** FT6336U capacitive touch node on the first I2C controller. */
|
|
253
|
+
function emitFt6336uNode(lines, touch) {
|
|
205
254
|
const irq = touch?.irq ?? 15;
|
|
206
255
|
const resetPin = touch?.resetPin;
|
|
207
256
|
const sda = touch?.sda;
|
|
@@ -249,3 +298,47 @@ function emitTouchNode(lines, touch) {
|
|
|
249
298
|
lines.push('};');
|
|
250
299
|
lines.push('');
|
|
251
300
|
}
|
|
301
|
+
/** XPT2046 resistive touch node on the display's SPI bus (CS index 1). */
|
|
302
|
+
function emitXpt2046Node(lines, touch, display) {
|
|
303
|
+
const bus = display?.busLabel ?? 'spi2';
|
|
304
|
+
const cs = touch.cs ?? DEFAULT_XPT2046_CS;
|
|
305
|
+
const irq = touch.irq ?? DEFAULT_XPT2046_IRQ;
|
|
306
|
+
const cal = touch.calibration;
|
|
307
|
+
// ESP32-S3 GPIOs 0-31 are on gpio0, 32-48 on gpio1.
|
|
308
|
+
const gpioController = (pin) => pin <= 31 ? 'gpio0' : 'gpio1';
|
|
309
|
+
const zThreshold = touch.minPressure ?? 400;
|
|
310
|
+
// touchscreen-size-* describe the panel the touch layer sits on (the display
|
|
311
|
+
// profile's effective size); a touch-only build falls back to the 12-bit
|
|
312
|
+
// full-scale range so the binding's required props still resolve.
|
|
313
|
+
const sizeX = display?.width ?? cal?.xMax ?? 320;
|
|
314
|
+
const sizeY = display?.height ?? cal?.yMax ?? 240;
|
|
315
|
+
// The display block already wrote cs-gpios with both entries (its CS at
|
|
316
|
+
// index 0, the touch CS at index 1). When there is no display block, enable
|
|
317
|
+
// the bus here with the touch CS as the only entry.
|
|
318
|
+
if (!display) {
|
|
319
|
+
lines.push(`&${bus} {`);
|
|
320
|
+
lines.push(' status = "okay";');
|
|
321
|
+
lines.push(` cs-gpios = <&${gpioController(cs)} ${cs} GPIO_ACTIVE_LOW>;`);
|
|
322
|
+
lines.push('};');
|
|
323
|
+
lines.push('');
|
|
324
|
+
}
|
|
325
|
+
lines.push(`&${bus} {`);
|
|
326
|
+
lines.push(' xpt2046: xpt2046@1 {');
|
|
327
|
+
lines.push(' compatible = "xptek,xpt2046";');
|
|
328
|
+
lines.push(' reg = <1>;');
|
|
329
|
+
// The XPT2046 datasheet max SPI clock is 2.5MHz — the panel bus may run at
|
|
330
|
+
// 80MHz, but this node's spi-max-frequency gates only its own transactions
|
|
331
|
+
// (the adapter's SPI_DT_SPEC picks it up).
|
|
332
|
+
lines.push(' spi-max-frequency = <2500000>;');
|
|
333
|
+
lines.push(` int-gpios = <&${gpioController(irq)} ${irq} GPIO_ACTIVE_LOW>;`);
|
|
334
|
+
lines.push(` touchscreen-size-x = <${sizeX}>;`);
|
|
335
|
+
lines.push(` touchscreen-size-y = <${sizeY}>;`);
|
|
336
|
+
lines.push(` min-x = <${cal?.xMin ?? 0}>;`);
|
|
337
|
+
lines.push(` max-x = <${cal?.xMax ?? 4095}>;`);
|
|
338
|
+
lines.push(` min-y = <${cal?.yMin ?? 0}>;`);
|
|
339
|
+
lines.push(` max-y = <${cal?.yMax ?? 4095}>;`);
|
|
340
|
+
lines.push(` z-threshold = <${zThreshold}>;`);
|
|
341
|
+
lines.push(' };');
|
|
342
|
+
lines.push('};');
|
|
343
|
+
lines.push('');
|
|
344
|
+
}
|
|
@@ -1,40 +1,40 @@
|
|
|
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;
|
|
2
19
|
schemaVersion: 1;
|
|
3
20
|
frameworkId: string;
|
|
4
|
-
packageName: string;
|
|
5
21
|
canonical: boolean;
|
|
6
22
|
displayName: string;
|
|
7
23
|
description: string;
|
|
8
24
|
implementationMode: "from-scratch" | "extends-canonical" | "extends-other";
|
|
9
25
|
entrypoint: {
|
|
26
|
+
sourceExtension: "cpp" | "ino" | "cc" | "h";
|
|
10
27
|
entrypointFunctionName: string;
|
|
11
28
|
requiresLoopFunction: boolean;
|
|
12
|
-
sourceExtension: "ino" | "cc" | "cpp" | "h";
|
|
13
29
|
generateHeaderFile: boolean;
|
|
14
30
|
overrideBaseName?: string | undefined;
|
|
15
31
|
outputSubdirectory?: string | undefined;
|
|
16
32
|
customBridgeShim?: string | undefined;
|
|
17
33
|
};
|
|
18
34
|
profile: {
|
|
19
|
-
targets: string[];
|
|
20
35
|
forcedIncludes: string[];
|
|
21
36
|
symbolAliases: Record<string, string>;
|
|
22
|
-
|
|
23
|
-
hal: {
|
|
24
|
-
[x: string]: {
|
|
25
|
-
supported: boolean;
|
|
26
|
-
ops: Record<string, "supported" | "stub" | "unsupported" | "probe-inconclusive" | "polyfill">;
|
|
27
|
-
partialCoverage: boolean;
|
|
28
|
-
unsupportedReason?: string | undefined;
|
|
29
|
-
};
|
|
30
|
-
raw?: unknown;
|
|
31
|
-
} & {
|
|
32
|
-
[k: string]: {
|
|
33
|
-
supported: boolean;
|
|
34
|
-
ops: Record<string, "supported" | "stub" | "unsupported" | "probe-inconclusive" | "polyfill">;
|
|
35
|
-
partialCoverage: boolean;
|
|
36
|
-
unsupportedReason?: string | undefined;
|
|
37
|
-
};
|
|
37
|
+
targets: string[];
|
|
38
38
|
};
|
|
39
39
|
polyfills: {
|
|
40
40
|
emitted: {
|
|
@@ -54,16 +54,17 @@ declare const _default: {
|
|
|
54
54
|
compile: boolean;
|
|
55
55
|
upload: boolean;
|
|
56
56
|
monitor: boolean;
|
|
57
|
+
debug?: boolean | undefined;
|
|
57
58
|
};
|
|
58
59
|
reexportedFrom?: string | undefined;
|
|
59
60
|
};
|
|
60
61
|
typeEmission: {
|
|
61
|
-
|
|
62
|
-
mathHeader: "none" | "<math.h>" | "<Arduino.h>";
|
|
62
|
+
needsIostream: boolean;
|
|
63
63
|
needsStdString: boolean;
|
|
64
64
|
needsStdVector: boolean;
|
|
65
|
-
needsIostream: boolean;
|
|
66
65
|
needsStdFunction: boolean;
|
|
66
|
+
mathHeader: "none" | "<math.h>" | "<Arduino.h>";
|
|
67
|
+
normalizeCppType: boolean;
|
|
67
68
|
stdlibSupport: {
|
|
68
69
|
hasVector: boolean;
|
|
69
70
|
hasString: boolean;
|
|
@@ -79,6 +80,12 @@ declare const _default: {
|
|
|
79
80
|
hardwareTestGroups: string[];
|
|
80
81
|
halResolutionTests: string[];
|
|
81
82
|
};
|
|
83
|
+
doctor?: {
|
|
84
|
+
available: boolean;
|
|
85
|
+
} | undefined;
|
|
86
|
+
licenses?: {
|
|
87
|
+
available: boolean;
|
|
88
|
+
} | undefined;
|
|
82
89
|
basedOn?: string | undefined;
|
|
83
90
|
inheritsStrategyId?: string | undefined;
|
|
84
91
|
libraryResolution?: {
|
|
@@ -88,12 +95,6 @@ declare const _default: {
|
|
|
88
95
|
tryGenerateLibDecl: boolean;
|
|
89
96
|
reexportedFrom?: string | undefined;
|
|
90
97
|
} | undefined;
|
|
91
|
-
doctor?: {
|
|
92
|
-
available: boolean;
|
|
93
|
-
} | undefined;
|
|
94
|
-
licenses?: {
|
|
95
|
-
available: boolean;
|
|
96
|
-
} | undefined;
|
|
97
98
|
compat?: {
|
|
98
99
|
zephyr?: string | undefined;
|
|
99
100
|
} | undefined;
|
|
@@ -280,8 +280,13 @@ export default defineFrameworkManifest({
|
|
|
280
280
|
},
|
|
281
281
|
display: {
|
|
282
282
|
supported: true,
|
|
283
|
-
partialCoverage:
|
|
284
|
-
|
|
283
|
+
partialCoverage: true,
|
|
284
|
+
// Partial: mono profiles (ssd1306-zephyr) drive display.* ops via the
|
|
285
|
+
// direct GFX runtime only — no CuttlefishGFX UI rendering path. The
|
|
286
|
+
// ILI9341 UI adapter shares the ST7796S direct-drive transport with a
|
|
287
|
+
// per-controller init table (16-bit RGB565 wire format); hardware-tuned
|
|
288
|
+
// on ST7796S only. E-ink panels are out of scope at this time.
|
|
289
|
+
unsupportedReason: 'Mono panels (ssd1306) are direct-op only (no UI rendering); ili9341 UI path is ported but not yet hardware-verified; e-ink is out of scope at this time.',
|
|
285
290
|
drivers: ['ili9341-zephyr', 'st7796-zephyr', 'ssd1306-zephyr'],
|
|
286
291
|
colorFormat: 'rgb565',
|
|
287
292
|
ops: {
|
|
@@ -484,6 +489,7 @@ export default defineFrameworkManifest({
|
|
|
484
489
|
polyfills: {
|
|
485
490
|
emitted: [
|
|
486
491
|
{ id: 'cuttlefish_halt', domain: 'standard', notes: 'Mapped to a k_msleep halt loop (exceptions disabled)' },
|
|
492
|
+
{ id: 'wiring_compat', domain: 'standard', notes: 'HIGH/LOW/digitalRead/etc. macros routing Wiring tokens (referenced unconditionally by the UI runtime header) to the __tc_gpio_* helpers' },
|
|
487
493
|
{ id: 'string_methods', domain: 'embedded', notes: 'STL-free __tc_* string helpers (const char*, inline ASCII case conv, <cstring> only)' },
|
|
488
494
|
{ id: 'static_array', domain: 'embedded', notes: 'STL-free __tc_StaticArray<T,N> wrapper for no-<vector> mutated/struct array literals' },
|
|
489
495
|
{ id: 'timer_methods', domain: 'embedded', notes: 'k_timer + k_work pool (system workqueue); callbacks run in thread context' },
|
|
@@ -493,7 +499,7 @@ export default defineFrameworkManifest({
|
|
|
493
499
|
},
|
|
494
500
|
toolchain: {
|
|
495
501
|
backend: 'west',
|
|
496
|
-
operations: { prepare: true, compile: true, upload: true, monitor: true },
|
|
502
|
+
operations: { prepare: true, compile: true, upload: true, monitor: true, debug: true },
|
|
497
503
|
},
|
|
498
504
|
libraryResolution: {
|
|
499
505
|
isFrameworkLibraryImport: false,
|
package/dist/lowering/ble.js
CHANGED
|
@@ -388,7 +388,9 @@ export function lowerBle(op) {
|
|
|
388
388
|
case 'ble.on_read':
|
|
389
389
|
// Store the typed read handler as void*; __tc_ble_attr_read casts it back
|
|
390
390
|
// to the right signature based on the char's type field. reinterpret_cast
|
|
391
|
-
// (not a C-style cast)
|
|
391
|
+
// (not a C-style cast) avoids M5-0-7, but M5-0-10 still flags it — the
|
|
392
|
+
// whole type-erased table is covered by a knownPatterns deviation on
|
|
393
|
+
// that rule (see rules.ts, "BLE type-erased callback table").
|
|
392
394
|
return { code: `__tc_ble.on_read[__tc_ble.current_char] = reinterpret_cast<void*>(${s(o.handler)});` };
|
|
393
395
|
case 'ble.on_write':
|
|
394
396
|
return { code: `__tc_ble.on_write[__tc_ble.current_char] = (${s(o.handler)});` };
|
package/dist/strategy.d.ts
CHANGED
|
@@ -78,6 +78,10 @@ export declare class ZephyrStrategy implements PlatformStrategy {
|
|
|
78
78
|
passthroughMacroNames(): ReadonlySet<string>;
|
|
79
79
|
apiReservedEnumNames(): ReadonlySet<string>;
|
|
80
80
|
apiReservedEnumGuard(): string;
|
|
81
|
+
isrUnsafeOperations(): Map<string, {
|
|
82
|
+
reason: string;
|
|
83
|
+
severity: 'warning' | 'info';
|
|
84
|
+
}>;
|
|
81
85
|
ambientTypeDeclarations(): string[];
|
|
82
86
|
needsIostream(): boolean;
|
|
83
87
|
needsStdString(): boolean;
|
package/dist/strategy.js
CHANGED
|
@@ -150,10 +150,11 @@ export class ZephyrStrategy {
|
|
|
150
150
|
// program-analysis usesStdString detector doesn't see types generated by
|
|
151
151
|
// the BLE lowering layer — so without forcing <string> here, any BLE server
|
|
152
152
|
// with a Utf8 characteristic fails to compile ('std::string does not name a
|
|
153
|
-
// type').
|
|
154
|
-
//
|
|
153
|
+
// type'). <cstdlib>/<cstring> (not <stdlib.h>/<string.h>) back the shim's
|
|
154
|
+
// strtol/strcmp/strncpy — the same AUTOSAR-compliant spelling the HTTP,
|
|
155
|
+
// MQTT, and Preferences paths below already use.
|
|
155
156
|
if (uses('usesBle'))
|
|
156
|
-
inc.push('<
|
|
157
|
+
inc.push('<cstdlib>', '<cstring>', '<string>', '<zephyr/bluetooth/bluetooth.h>', '<zephyr/bluetooth/conn.h>', '<zephyr/bluetooth/gatt.h>', '<zephyr/bluetooth/uuid.h>');
|
|
157
158
|
// Display: the analyzer's usesDisplay flag (set by display.* hal-ops) drives
|
|
158
159
|
// this include. When ctx.analysis is absent (capability query), uses()
|
|
159
160
|
// defaults to true so a real build never strips it.
|
|
@@ -377,20 +378,27 @@ export class ZephyrStrategy {
|
|
|
377
378
|
// separate translation unit when generateHeaderFile() splits them into the
|
|
378
379
|
// header.
|
|
379
380
|
lines.push('extern void setup(void);', 'extern void loop(void);', '', 'int main(void) {', ' setup();', ' for (;;) {', ' loop();', ' k_msleep(1);', ' }', ' return 0;', '}');
|
|
380
|
-
//
|
|
381
|
-
//
|
|
382
|
-
//
|
|
381
|
+
// GPIO read shim: the wiring_compat polyfill routes the UI runtime
|
|
382
|
+
// header's unconditional digitalRead() poll (init-press-input.ts) to
|
|
383
|
+
// __tc_gpio_read, so the definition must NOT be gated on @typecad/safety.
|
|
384
|
+
// The signature is `int` to match wiring_compat's forward declaration —
|
|
385
|
+
// a uint32_t definition alongside it would leave the declared int
|
|
386
|
+
// overload undefined (int wins overload resolution for small integer
|
|
387
|
+
// arguments).
|
|
383
388
|
//
|
|
384
|
-
// The pin is a RUNTIME value here (
|
|
385
|
-
//
|
|
386
|
-
// on a multi-controller SoC (ESP32-S3:
|
|
387
|
-
// Emit a tiny __tc_gpio_dev(pin)
|
|
388
|
-
// controller's device per pin;
|
|
389
|
-
// one-liner. Each DT_NODELABEL is
|
|
390
|
-
// it is always statically valid.
|
|
389
|
+
// The pin is a RUNTIME value here (the UI pin-watch table and safety's
|
|
390
|
+
// voter pass whatever pin they were handed), so the controller cannot be
|
|
391
|
+
// baked in as a single DT_NODELABEL on a multi-controller SoC (ESP32-S3:
|
|
392
|
+
// pins 0–31 → gpio0, 32–48 → gpio1). Emit a tiny __tc_gpio_dev(pin)
|
|
393
|
+
// dispatcher that resolves the owning controller's device per pin;
|
|
394
|
+
// single-controller SoCs collapse it to a one-liner. Each DT_NODELABEL is
|
|
395
|
+
// still compile-time-resolved per branch, so it is always statically valid.
|
|
396
|
+
lines.push(...emitGpioDevDispatcher(chip));
|
|
397
|
+
lines.push('inline int __tc_gpio_read(int pin) { return gpio_pin_get_raw(__tc_gpio_dev(static_cast<uint32_t>(pin)), static_cast<gpio_pin_t>(pin)); }');
|
|
398
|
+
// __tc_gpio_write / __tc_delay_us are only referenced via @typecad/safety
|
|
399
|
+
// lowering, so they stay gated on it.
|
|
391
400
|
if (program && programUsesSafety(program)) {
|
|
392
|
-
lines.push(
|
|
393
|
-
lines.push('inline int __tc_gpio_read(uint32_t pin) { return gpio_pin_get_raw(__tc_gpio_dev(pin), pin); }', 'inline void __tc_gpio_write(uint32_t pin, uint32_t value) { gpio_pin_set_raw(__tc_gpio_dev(pin), pin, value); }', '#ifndef __TC_DELAY_US_DEFINED', '#define __TC_DELAY_US_DEFINED', 'inline void __tc_delay_us(uint32_t us) { k_busy_wait(us); }', '#endif');
|
|
401
|
+
lines.push('inline void __tc_gpio_write(uint32_t pin, uint32_t value) { gpio_pin_set_raw(__tc_gpio_dev(pin), pin, value); }', '#ifndef __TC_DELAY_US_DEFINED', '#define __TC_DELAY_US_DEFINED', 'inline void __tc_delay_us(uint32_t us) { k_busy_wait(us); }', '#endif');
|
|
394
402
|
}
|
|
395
403
|
return lines;
|
|
396
404
|
}
|
|
@@ -780,6 +788,61 @@ export class ZephyrStrategy {
|
|
|
780
788
|
apiReservedEnumGuard() {
|
|
781
789
|
return '';
|
|
782
790
|
}
|
|
791
|
+
// ── Interrupt safety ─────────────────────────────────────────────────────
|
|
792
|
+
// Zephyr ISRs run above thread context: anything that sleeps (k_msleep),
|
|
793
|
+
// pends, or takes a driver lock is illegal there (asserted by the kernel in
|
|
794
|
+
// debug builds; corrupts scheduler state otherwise). The names below are the
|
|
795
|
+
// IR-level callees cuttlefish's interrupt-analysis pass matches (the same
|
|
796
|
+
// keys ArduinoStrategy uses; timing.delay/delay_microseconds hal-ops are
|
|
797
|
+
// mapped back to the bare names by the analyzer itself).
|
|
798
|
+
isrUnsafeOperations() {
|
|
799
|
+
return new Map([
|
|
800
|
+
['delay', {
|
|
801
|
+
reason: 'delay() lowers to k_msleep(), which sleeps the calling thread — illegal in Zephyr interrupt context (submit a k_work item or arm a k_timer instead)',
|
|
802
|
+
severity: 'warning',
|
|
803
|
+
}],
|
|
804
|
+
['delayMicroseconds', {
|
|
805
|
+
reason: 'delayMicroseconds() busy-waits the CPU for the full delay, stalling every lower-priority interrupt and the scheduler for its duration',
|
|
806
|
+
severity: 'warning',
|
|
807
|
+
}],
|
|
808
|
+
['console.log', {
|
|
809
|
+
reason: 'console output lowers to printk(), which is ISR-legal but slow and lock-protected (CONFIG_PRINTK_SYNC) — it adds jitter to every interrupt behind it',
|
|
810
|
+
severity: 'info',
|
|
811
|
+
}],
|
|
812
|
+
['console.error', {
|
|
813
|
+
reason: 'console output lowers to printk(), which is ISR-legal but slow and lock-protected (CONFIG_PRINTK_SYNC) — it adds jitter to every interrupt behind it',
|
|
814
|
+
severity: 'info',
|
|
815
|
+
}],
|
|
816
|
+
['console.warn', {
|
|
817
|
+
reason: 'console output lowers to printk(), which is ISR-legal but slow and lock-protected (CONFIG_PRINTK_SYNC) — it adds jitter to every interrupt behind it',
|
|
818
|
+
severity: 'info',
|
|
819
|
+
}],
|
|
820
|
+
['I2C0', {
|
|
821
|
+
reason: 'I2C transactions may sleep (driver locking + clock stretching) and are not callable from Zephyr interrupt context',
|
|
822
|
+
severity: 'warning',
|
|
823
|
+
}],
|
|
824
|
+
['I2C1', {
|
|
825
|
+
reason: 'I2C transactions may sleep (driver locking + clock stretching) and are not callable from Zephyr interrupt context',
|
|
826
|
+
severity: 'warning',
|
|
827
|
+
}],
|
|
828
|
+
['SPI0', {
|
|
829
|
+
reason: 'SPI transfers take driver locks and may wait on DMA completion — not safe in Zephyr interrupt context',
|
|
830
|
+
severity: 'warning',
|
|
831
|
+
}],
|
|
832
|
+
['SPI1', {
|
|
833
|
+
reason: 'SPI transfers take driver locks and may wait on DMA completion — not safe in Zephyr interrupt context',
|
|
834
|
+
severity: 'warning',
|
|
835
|
+
}],
|
|
836
|
+
['UART0', {
|
|
837
|
+
reason: 'UART output via uart_poll_out blocks until the TX FIFO has room — a full FIFO stalls the ISR',
|
|
838
|
+
severity: 'info',
|
|
839
|
+
}],
|
|
840
|
+
['UART1', {
|
|
841
|
+
reason: 'UART output via uart_poll_out blocks until the TX FIFO has room — a full FIFO stalls the ISR',
|
|
842
|
+
severity: 'info',
|
|
843
|
+
}],
|
|
844
|
+
]);
|
|
845
|
+
}
|
|
783
846
|
ambientTypeDeclarations() {
|
|
784
847
|
// Preferences is the only HAL surface the framework lowers that is used as
|
|
785
848
|
// a bare global (the HAL Preferences class is exported, but the canonical
|
|
@@ -1098,7 +1161,11 @@ struct __tc_StaticArray {
|
|
|
1098
1161
|
id: 'async_runtime',
|
|
1099
1162
|
domain: 'embedded',
|
|
1100
1163
|
requiredIncludes: [],
|
|
1101
|
-
|
|
1164
|
+
// Polyfill definitions emit before shimLines, but the runtime's
|
|
1165
|
+
// timer bodies call millis() (defined in shimLines) — declare it
|
|
1166
|
+
// first so the polyfill compiles even for programs whose source
|
|
1167
|
+
// has no explicit timing call.
|
|
1168
|
+
forwardDeclarations: ['unsigned long millis();'],
|
|
1102
1169
|
helperStructs: [generateStaticAsyncRuntime(8, this.getAsyncRuntimeConfig().waitForPinEdge)],
|
|
1103
1170
|
helperFunctions: [],
|
|
1104
1171
|
shimMacros: [],
|
|
@@ -1184,11 +1251,12 @@ struct __tc_StaticArray {
|
|
|
1184
1251
|
}
|
|
1185
1252
|
// ── Strategy-owned display/touch adapter seam ────────────────────────────
|
|
1186
1253
|
// Zephyr owns its display + touch adapters: the UI display adapter bridges
|
|
1187
|
-
// the in-tree CuttlefishGFX class to
|
|
1188
|
-
// src/display/ui-adapter.ts), and the
|
|
1189
|
-
//
|
|
1190
|
-
//
|
|
1191
|
-
//
|
|
1254
|
+
// the in-tree CuttlefishGFX class to the panel (per-controller init + wire
|
|
1255
|
+
// format, see src/display/ui-adapter.ts), and the touch adapters drive the
|
|
1256
|
+
// FT6336U (I2C capacitive) and XPT2046 (SPI resistive) controllers via
|
|
1257
|
+
// Zephyr's bus APIs (src/display/touch-adapter.ts). Both live in this
|
|
1258
|
+
// package so cuttlefish carries no Zephyr/Wiring-specific display or touch
|
|
1259
|
+
// knowledge. Mirrors ArduinoStrategy's provides*/resolve* pattern.
|
|
1192
1260
|
providesDisplayAdapter() { return true; }
|
|
1193
1261
|
resolveDisplayAdapter(display) {
|
|
1194
1262
|
const code = zephyrDisplayAdapterGenerator(display);
|
package/dist/toolchain/index.js
CHANGED
|
@@ -185,12 +185,17 @@ export const Toolchain = {
|
|
|
185
185
|
// for either driver. Thread a non-default profile here only if a future
|
|
186
186
|
// board carries a display node under a different nodelabel.
|
|
187
187
|
const displayProfile = usesDisplay ? DEFAULT_ZEPHYR_DISPLAY_PROFILE : undefined;
|
|
188
|
+
// Touch controller kind comes from which DT nodelabel the emitted adapter
|
|
189
|
+
// references (FT6336U on I2C, XPT2046 on the display's SPI bus).
|
|
190
|
+
const usesTouch = uses('ft6336u') || uses('touch_');
|
|
191
|
+
const usesXpt = uses('xpt2046');
|
|
188
192
|
const overlay = generateOverlay(chip, {
|
|
189
193
|
usesI2c: uses('i2c_'),
|
|
190
194
|
usesSpi: uses('spi_'),
|
|
191
195
|
usesUart: uses('uart_'),
|
|
192
196
|
usesDisplay,
|
|
193
|
-
usesTouch:
|
|
197
|
+
usesTouch: usesTouch || usesXpt,
|
|
198
|
+
touchController: usesXpt ? 'xpt2046' : 'ft6336u',
|
|
194
199
|
}, displayProfile);
|
|
195
200
|
const overlayDir = join(projectRoot, 'boards');
|
|
196
201
|
mkdirSync(overlayDir, { recursive: true });
|
|
@@ -274,23 +279,48 @@ export const Toolchain = {
|
|
|
274
279
|
backlightPin: typeof dispCfg.backlightPin === 'number' ? dispCfg.backlightPin : undefined,
|
|
275
280
|
}
|
|
276
281
|
: undefined;
|
|
277
|
-
// Extract touch pin wiring
|
|
278
|
-
//
|
|
282
|
+
// Extract touch pin wiring from the config display.touch section so the
|
|
283
|
+
// DT overlay wires the bus + touch node. I2C (FT6336U) carries
|
|
284
|
+
// irq/resetPin/sda/scl; SPI (XPT2046) carries irq/cs + the calibration
|
|
285
|
+
// range the xptek,xpt2046 binding requires.
|
|
279
286
|
const touchCfg = dispCfg?.touch;
|
|
280
|
-
const
|
|
287
|
+
const isXpt = touchCfg?.library === 'XPT2046_Touchscreen';
|
|
288
|
+
const touchCal = touchCfg?.calibration;
|
|
289
|
+
const num = (v) => (typeof v === 'number' ? v : undefined);
|
|
290
|
+
let touchWiring = touchCfg
|
|
281
291
|
? {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
292
|
+
controller: isXpt ? 'xpt2046' : 'ft6336u',
|
|
293
|
+
irq: num(touchCfg.irq),
|
|
294
|
+
resetPin: num(touchCfg.resetPin),
|
|
295
|
+
sda: num(touchCfg.sda),
|
|
296
|
+
scl: num(touchCfg.scl),
|
|
297
|
+
cs: num(touchCfg.cs),
|
|
298
|
+
calibration: touchCal
|
|
299
|
+
? {
|
|
300
|
+
xMin: num(touchCal.xMin) ?? 0,
|
|
301
|
+
xMax: num(touchCal.xMax) ?? 4095,
|
|
302
|
+
yMin: num(touchCal.yMin) ?? 0,
|
|
303
|
+
yMax: num(touchCal.yMax) ?? 4095,
|
|
304
|
+
}
|
|
305
|
+
: undefined,
|
|
306
|
+
minPressure: num(touchCfg.minPressure),
|
|
286
307
|
}
|
|
287
308
|
: undefined;
|
|
309
|
+
// Touch controller kind for Kconfig (bus driver selection) and the DT
|
|
310
|
+
// node shape: from the config when available, else from the DT nodelabel
|
|
311
|
+
// the emitted adapter references. Forced onto touchWiring so a source
|
|
312
|
+
// scan match without a config section still emits the right node.
|
|
313
|
+
const usesXpt = isXpt || uses('xpt2046');
|
|
314
|
+
if (usesXpt) {
|
|
315
|
+
touchWiring = { controller: 'xpt2046', ...(touchWiring ?? {}) };
|
|
316
|
+
}
|
|
288
317
|
const overlay = generateOverlay(chip, {
|
|
289
318
|
usesI2c: uses('i2c_'),
|
|
290
319
|
usesSpi: uses('spi_'),
|
|
291
320
|
usesUart: uses('uart_'),
|
|
292
321
|
usesDisplay,
|
|
293
|
-
usesTouch: uses('ft6336u') || uses('touch_'),
|
|
322
|
+
usesTouch: uses('ft6336u') || uses('touch_') || usesXpt,
|
|
323
|
+
touchController: usesXpt ? 'xpt2046' : 'ft6336u',
|
|
294
324
|
psram: o.psram,
|
|
295
325
|
}, displayProfile, wiring, touchWiring);
|
|
296
326
|
const overlayDir = join(projectRoot, 'boards');
|
|
@@ -128,8 +128,10 @@ export function scaffoldZephyrProject(projectRoot, debug = false, userKconfig, p
|
|
|
128
128
|
'',
|
|
129
129
|
'project(zephyr_app)',
|
|
130
130
|
'',
|
|
131
|
-
'# Collect cuttlefish-emitted sources.',
|
|
132
|
-
'
|
|
131
|
+
'# Collect cuttlefish-emitted sources. CONFIGURE_DEPENDS makes CMake re-',
|
|
132
|
+
'# check the glob when the source set changes (e.g. the transpiler removes',
|
|
133
|
+
'# a stale entry), instead of linking a file list from the last configure.',
|
|
134
|
+
'file(GLOB app_sources CONFIGURE_DEPENDS src/*.cpp src/*.c)',
|
|
133
135
|
'',
|
|
134
136
|
'target_sources(app PRIVATE ${app_sources})',
|
|
135
137
|
// When PSRAM is configured, define BOARD_HAS_PSRAM so the UI runtime's
|
|
@@ -67,9 +67,12 @@ export function isZephyrBase(dir) {
|
|
|
67
67
|
}
|
|
68
68
|
// ── Strategy 1: `west` on PATH ──────────────────────────────────────────────
|
|
69
69
|
export function discoverFromPath() {
|
|
70
|
+
// shell only on Windows (where.exe resolution through cmd) — an args array
|
|
71
|
+
// with shell: true triggers Node's DEP0190 deprecation warning on Linux/
|
|
72
|
+
// macOS, where `which` is a plain executable that needs no shell.
|
|
70
73
|
const which = spawnSync(IS_WIN ? 'where' : 'which', ['west'], {
|
|
71
74
|
encoding: 'utf8',
|
|
72
|
-
shell:
|
|
75
|
+
shell: IS_WIN,
|
|
73
76
|
windowsHide: true,
|
|
74
77
|
});
|
|
75
78
|
if (which.status !== 0)
|