@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
package/src/dt-config/overlay.ts
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import type { ZephyrChipDescriptor } from '../chips/types.js';
|
|
15
15
|
import type { ZephyrDisplayProfile } from '../display/profiles.js';
|
|
16
|
+
import { PANEL_CONTROLLER_DEFAULTS, panelControllerFor } from '../display/profiles.js';
|
|
16
17
|
import type { KconfigUsage } from './kconfig.js';
|
|
17
18
|
|
|
18
19
|
/**
|
|
@@ -40,12 +41,24 @@ export interface DisplayWiring {
|
|
|
40
41
|
backlightPin?: number;
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
/** Touch wiring from cuttlefish.config.ts (irq/resetPin/sda/scl
|
|
44
|
+
/** Touch wiring from cuttlefish.config.ts (irq/resetPin/sda/scl; cs for SPI
|
|
45
|
+
* resistive controllers, calibration for the XPT2046 DT binding). */
|
|
44
46
|
export interface TouchWiring {
|
|
47
|
+
/** Touch controller kind — selects the DT node shape (FT6336U node on I2C0
|
|
48
|
+
* vs XPT2046 node on the display's SPI bus). Default 'ft6336u'. */
|
|
49
|
+
controller?: 'ft6336u' | 'xpt2046';
|
|
45
50
|
irq?: number;
|
|
46
51
|
resetPin?: number;
|
|
47
52
|
sda?: number;
|
|
48
53
|
scl?: number;
|
|
54
|
+
/** XPT2046 only: SPI CS pin (second cs-gpios entry on the panel's bus). */
|
|
55
|
+
cs?: number;
|
|
56
|
+
/** XPT2046 only: raw ADC calibration — feeds the binding's min-x/max-x/
|
|
57
|
+
* min-y/max-y (required props). Defaults span the full 12-bit range. */
|
|
58
|
+
calibration?: { xMin: number; xMax: number; yMin: number; yMax: number };
|
|
59
|
+
/** XPT2046 only: pen-detect Z1 threshold (binding's z-threshold). Resistive
|
|
60
|
+
* panels need a few hundred 12-bit counts; default 400. */
|
|
61
|
+
minPressure?: number;
|
|
49
62
|
}
|
|
50
63
|
|
|
51
64
|
export function generateOverlay(
|
|
@@ -96,14 +109,23 @@ export function generateOverlay(
|
|
|
96
109
|
// controller with the panel's compatible string + dimensions. The pin
|
|
97
110
|
// wiring (cs/dc/rst) uses ESP32 GPIO defaults from the demo config; a
|
|
98
111
|
// real board overlay would carry its own binding.
|
|
99
|
-
|
|
112
|
+
// An XPT2046 on the same bus needs its CS as the second cs-gpios entry,
|
|
113
|
+
// so thread it into the display block (DT assignment replaces the whole
|
|
114
|
+
// property — both entries must be written together).
|
|
115
|
+
emitDisplayNode(
|
|
116
|
+
lines,
|
|
117
|
+
display,
|
|
118
|
+
wiring,
|
|
119
|
+
touch?.controller === 'xpt2046' ? (touch?.cs ?? DEFAULT_XPT2046_CS) : undefined,
|
|
120
|
+
);
|
|
100
121
|
}
|
|
101
|
-
// FT6336U
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
//
|
|
122
|
+
// Touch — FT6336U capacitive on I2C (references DT_NODELABEL(ft6336u)) or
|
|
123
|
+
// XPT2046 resistive on the display's SPI bus (references
|
|
124
|
+
// DT_NODELABEL(xpt2046)). Same rationale as the display node: the bare
|
|
125
|
+
// devkit has no such node. Gated on usesTouch (not usesI2c/chip.i2c) so it
|
|
126
|
+
// emits even when the chip descriptor doesn't declare bus controllers.
|
|
105
127
|
if (usage.usesTouch) {
|
|
106
|
-
emitTouchNode(lines, touch);
|
|
128
|
+
emitTouchNode(lines, touch, display);
|
|
107
129
|
}
|
|
108
130
|
|
|
109
131
|
// Preferences (ZMS settings backend): point the settings subsystem at the
|
|
@@ -138,11 +160,26 @@ export function generateOverlay(
|
|
|
138
160
|
}
|
|
139
161
|
|
|
140
162
|
/**
|
|
141
|
-
* Emit a display DT node definition. The node is attached to
|
|
142
|
-
* first user SPI controller) via a MIPI
|
|
143
|
-
* the display config (cs/dc/rst);
|
|
163
|
+
* Emit a display DT node definition. The node is attached to the profile's SPI
|
|
164
|
+
* controller (default spi2, the ESP32's first user SPI controller) via a MIPI
|
|
165
|
+
* DBI SPI bridge. Pin wiring comes from the display config (cs/dc/rst);
|
|
166
|
+
* defaults match the demo-st wiring if absent. The compatible string + node
|
|
167
|
+
* props come from the profile's panel controller (st7796s carries the required
|
|
168
|
+
* pgc/ngc gamma + madctl; ili9341's binding defaults everything else).
|
|
169
|
+
*
|
|
170
|
+
* spiTouchCs: when an XPT2046 SPI touch controller shares the bus, its CS is
|
|
171
|
+
* appended as the second cs-gpios entry (the touch node uses reg = <1>) — DT
|
|
172
|
+
* property assignment replaces, so both entries must be written together.
|
|
144
173
|
*/
|
|
145
|
-
function emitDisplayNode(
|
|
174
|
+
function emitDisplayNode(
|
|
175
|
+
lines: string[],
|
|
176
|
+
display: ZephyrDisplayProfile,
|
|
177
|
+
wiring?: DisplayWiring,
|
|
178
|
+
spiTouchCs?: number,
|
|
179
|
+
): void {
|
|
180
|
+
const bus = display.busLabel ?? 'spi2';
|
|
181
|
+
const controller = panelControllerFor(display);
|
|
182
|
+
const compatible = display.dtCompatible ?? PANEL_CONTROLLER_DEFAULTS[controller].dtCompatible;
|
|
146
183
|
const dc = wiring?.dc ?? 17;
|
|
147
184
|
const rst = wiring?.rst ?? 16;
|
|
148
185
|
const cs = wiring?.cs ?? 5;
|
|
@@ -163,7 +200,9 @@ function emitDisplayNode(lines: string[], display: ZephyrDisplayProfile, wiring?
|
|
|
163
200
|
// macros are used instead of the named SPIM2_*_GPIOxx tokens because the
|
|
164
201
|
// bindings header omits GPIOs 22-25 from those lists.
|
|
165
202
|
if (sck !== undefined && mosi !== undefined) {
|
|
166
|
-
|
|
203
|
+
// 'spi2' → pinctrl group 'spim2_default' (ESP32 SPI-master naming).
|
|
204
|
+
const pinctrlGroup = bus.replace(/^spi(\d)$/, 'spim$1') + '_default';
|
|
205
|
+
lines.push(`&${pinctrlGroup} {`);
|
|
167
206
|
lines.push(' group1 {');
|
|
168
207
|
lines.push(` pinmux = <ESP32_PINMUX(${miso ?? 19}, ESP_FSPIQ_IN, ESP_NOSIG)>,`);
|
|
169
208
|
lines.push(` <ESP32_PINMUX(${sck}, ESP_NOSIG, ESP_FSPICLK_OUT)>,`);
|
|
@@ -180,9 +219,9 @@ function emitDisplayNode(lines: string[], display: ZephyrDisplayProfile, wiring?
|
|
|
180
219
|
lines.push(' status = "okay";');
|
|
181
220
|
lines.push('};');
|
|
182
221
|
lines.push('');
|
|
183
|
-
lines.push(
|
|
222
|
+
lines.push(`&${bus} {`);
|
|
184
223
|
lines.push(' status = "okay";');
|
|
185
|
-
lines.push(` cs-gpios = <&${gpioController(cs)} ${cs} GPIO_ACTIVE_LOW
|
|
224
|
+
lines.push(` cs-gpios = <&${gpioController(cs)} ${cs} GPIO_ACTIVE_LOW>${spiTouchCs !== undefined ? `, <&${gpioController(spiTouchCs)} ${spiTouchCs} GPIO_ACTIVE_LOW>` : ''};`);
|
|
186
225
|
// Enable GDMA for the SPI2 host. The ESP32 SPI driver uses DMA only when
|
|
187
226
|
// dma-enabled is set AND dmas wires tx/rx channels to the GDMA controller;
|
|
188
227
|
// without it, transfers run PIO through the 64-byte FIFO (~4MHz effective at
|
|
@@ -198,25 +237,36 @@ function emitDisplayNode(lines: string[], display: ZephyrDisplayProfile, wiring?
|
|
|
198
237
|
lines.push('/ {');
|
|
199
238
|
lines.push(' mipi_dbi: mipi-dbi {');
|
|
200
239
|
lines.push(' compatible = "zephyr,mipi-dbi-spi";');
|
|
201
|
-
lines.push(
|
|
240
|
+
lines.push(` spi-dev = <&${bus}>;`);
|
|
202
241
|
lines.push(` dc-gpios = <&${gpioController(dc)} ${dc} GPIO_ACTIVE_HIGH>;`);
|
|
203
242
|
lines.push(` reset-gpios = <&${gpioController(rst)} ${rst} GPIO_ACTIVE_LOW>;`);
|
|
204
243
|
lines.push(' write-only;');
|
|
205
244
|
lines.push(' #address-cells = <1>;');
|
|
206
245
|
lines.push(' #size-cells = <0>;');
|
|
207
246
|
lines.push(` ${display.dtLabel}: display@0 {`);
|
|
208
|
-
lines.push(
|
|
247
|
+
lines.push(` compatible = "${compatible}";`);
|
|
209
248
|
lines.push(' reg = <0>;');
|
|
210
|
-
|
|
211
|
-
|
|
249
|
+
lines.push(` mipi-max-frequency = <${freq}>;`);
|
|
250
|
+
lines.push(' mipi-mode = "MIPI_DBI_MODE_SPI_4WIRE";');
|
|
251
|
+
// Required by the lcd-controller binding (Zephyr 4.x): 0 = RGB565,
|
|
252
|
+
// matching upstream ILI9341 boards (esp_wrover_kit) and the C++
|
|
253
|
+
// runtime, which drives these SPI TFTs as RGB565.
|
|
254
|
+
lines.push(' pixel-format = <0>;');
|
|
212
255
|
lines.push(` width = <${nativeW}>;`);
|
|
213
256
|
lines.push(` height = <${nativeH}>;`);
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
257
|
+
if (controller === 'st7796s') {
|
|
258
|
+
// MADCTL: rotation 1 (landscape, MV=1) + BGR bit, matching the adapter's
|
|
259
|
+
// direct-drive init (0x28). The DT copy keeps the stock driver's init
|
|
260
|
+
// consistent if it is ever exercised.
|
|
261
|
+
lines.push(' madctl = <0x28>;');
|
|
262
|
+
lines.push(' pgc = [f0 09 0b 06 04 2e 46 46 39 13 15 12 15 12];');
|
|
263
|
+
lines.push(' ngc = [f0 09 0b 06 04 2e 46 46 39 13 15 12 15 12];');
|
|
264
|
+
} else {
|
|
265
|
+
// ILI9341: the ilitek,ili9341 binding carries defaults for every register
|
|
266
|
+
// (gamma, power, porch) and expresses orientation via `rotation` (degrees)
|
|
267
|
+
// instead of a raw MADCTL — no panel-specific props are required.
|
|
268
|
+
lines.push(` rotation = <${display.rotation ?? 0}>;`);
|
|
269
|
+
}
|
|
220
270
|
lines.push(' };');
|
|
221
271
|
lines.push(' };');
|
|
222
272
|
lines.push('};');
|
|
@@ -243,13 +293,39 @@ function emitDisplayNode(lines: string[], display: ZephyrDisplayProfile, wiring?
|
|
|
243
293
|
}
|
|
244
294
|
}
|
|
245
295
|
|
|
296
|
+
/** Default XPT2046 CS/IRQ pins (ESP32-S3 GPIOs clear of the demo-st display
|
|
297
|
+
* wiring: 5/17/16/15 and the remuxed SPI pins). Config values override. */
|
|
298
|
+
const DEFAULT_XPT2046_CS = 6;
|
|
299
|
+
const DEFAULT_XPT2046_IRQ = 7;
|
|
300
|
+
|
|
246
301
|
/**
|
|
247
|
-
* Emit
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
302
|
+
* Emit the touch DT node for the configured controller.
|
|
303
|
+
*
|
|
304
|
+
* FT6336U (capacitive, I2C): node on i2c0 at the FT6336U default address
|
|
305
|
+
* (0x38); the C++ touch adapter reads it via i2c_write_read_dt.
|
|
306
|
+
*
|
|
307
|
+
* XPT2046 (resistive, SPI): node on the display's SPI bus as CS index 1. The
|
|
308
|
+
* in-tree xptek,xpt2046 binding (drivers/input) is register-matched for the
|
|
309
|
+
* raw SPI access — CONFIG_INPUT stays off, so the in-tree input driver does
|
|
310
|
+
* not build and the adapter owns the chip (same pattern as FT6336U reusing
|
|
311
|
+
* the ft5336 binding). The binding requires int-gpios, touchscreen-size-*,
|
|
312
|
+
* and min/max calibration props, so defaults are filled for anything the
|
|
313
|
+
* config omits.
|
|
251
314
|
*/
|
|
252
|
-
function emitTouchNode(
|
|
315
|
+
function emitTouchNode(
|
|
316
|
+
lines: string[],
|
|
317
|
+
touch: TouchWiring | undefined,
|
|
318
|
+
display: ZephyrDisplayProfile | undefined,
|
|
319
|
+
): void {
|
|
320
|
+
if (touch?.controller === 'xpt2046') {
|
|
321
|
+
emitXpt2046Node(lines, touch, display);
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
emitFt6336uNode(lines, touch);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/** FT6336U capacitive touch node on the first I2C controller. */
|
|
328
|
+
function emitFt6336uNode(lines: string[], touch?: TouchWiring): void {
|
|
253
329
|
const irq = touch?.irq ?? 15;
|
|
254
330
|
const resetPin = touch?.resetPin;
|
|
255
331
|
const sda = touch?.sda;
|
|
@@ -296,3 +372,52 @@ function emitTouchNode(lines: string[], touch?: TouchWiring): void {
|
|
|
296
372
|
lines.push('};');
|
|
297
373
|
lines.push('');
|
|
298
374
|
}
|
|
375
|
+
|
|
376
|
+
/** XPT2046 resistive touch node on the display's SPI bus (CS index 1). */
|
|
377
|
+
function emitXpt2046Node(
|
|
378
|
+
lines: string[],
|
|
379
|
+
touch: TouchWiring,
|
|
380
|
+
display: ZephyrDisplayProfile | undefined,
|
|
381
|
+
): void {
|
|
382
|
+
const bus = display?.busLabel ?? 'spi2';
|
|
383
|
+
const cs = touch.cs ?? DEFAULT_XPT2046_CS;
|
|
384
|
+
const irq = touch.irq ?? DEFAULT_XPT2046_IRQ;
|
|
385
|
+
const cal = touch.calibration;
|
|
386
|
+
// ESP32-S3 GPIOs 0-31 are on gpio0, 32-48 on gpio1.
|
|
387
|
+
const gpioController = (pin: number) => pin <= 31 ? 'gpio0' : 'gpio1';
|
|
388
|
+
const zThreshold = touch.minPressure ?? 400;
|
|
389
|
+
// touchscreen-size-* describe the panel the touch layer sits on (the display
|
|
390
|
+
// profile's effective size); a touch-only build falls back to the 12-bit
|
|
391
|
+
// full-scale range so the binding's required props still resolve.
|
|
392
|
+
const sizeX = display?.width ?? cal?.xMax ?? 320;
|
|
393
|
+
const sizeY = display?.height ?? cal?.yMax ?? 240;
|
|
394
|
+
// The display block already wrote cs-gpios with both entries (its CS at
|
|
395
|
+
// index 0, the touch CS at index 1). When there is no display block, enable
|
|
396
|
+
// the bus here with the touch CS as the only entry.
|
|
397
|
+
if (!display) {
|
|
398
|
+
lines.push(`&${bus} {`);
|
|
399
|
+
lines.push(' status = "okay";');
|
|
400
|
+
lines.push(` cs-gpios = <&${gpioController(cs)} ${cs} GPIO_ACTIVE_LOW>;`);
|
|
401
|
+
lines.push('};');
|
|
402
|
+
lines.push('');
|
|
403
|
+
}
|
|
404
|
+
lines.push(`&${bus} {`);
|
|
405
|
+
lines.push(' xpt2046: xpt2046@1 {');
|
|
406
|
+
lines.push(' compatible = "xptek,xpt2046";');
|
|
407
|
+
lines.push(' reg = <1>;');
|
|
408
|
+
// The XPT2046 datasheet max SPI clock is 2.5MHz — the panel bus may run at
|
|
409
|
+
// 80MHz, but this node's spi-max-frequency gates only its own transactions
|
|
410
|
+
// (the adapter's SPI_DT_SPEC picks it up).
|
|
411
|
+
lines.push(' spi-max-frequency = <2500000>;');
|
|
412
|
+
lines.push(` int-gpios = <&${gpioController(irq)} ${irq} GPIO_ACTIVE_LOW>;`);
|
|
413
|
+
lines.push(` touchscreen-size-x = <${sizeX}>;`);
|
|
414
|
+
lines.push(` touchscreen-size-y = <${sizeY}>;`);
|
|
415
|
+
lines.push(` min-x = <${cal?.xMin ?? 0}>;`);
|
|
416
|
+
lines.push(` max-x = <${cal?.xMax ?? 4095}>;`);
|
|
417
|
+
lines.push(` min-y = <${cal?.yMin ?? 0}>;`);
|
|
418
|
+
lines.push(` max-y = <${cal?.yMax ?? 4095}>;`);
|
|
419
|
+
lines.push(` z-threshold = <${zThreshold}>;`);
|
|
420
|
+
lines.push(' };');
|
|
421
|
+
lines.push('};');
|
|
422
|
+
lines.push('');
|
|
423
|
+
}
|
|
@@ -289,8 +289,13 @@ export default defineFrameworkManifest({
|
|
|
289
289
|
},
|
|
290
290
|
display: {
|
|
291
291
|
supported: true,
|
|
292
|
-
partialCoverage:
|
|
293
|
-
|
|
292
|
+
partialCoverage: true,
|
|
293
|
+
// Partial: mono profiles (ssd1306-zephyr) drive display.* ops via the
|
|
294
|
+
// direct GFX runtime only — no CuttlefishGFX UI rendering path. The
|
|
295
|
+
// ILI9341 UI adapter shares the ST7796S direct-drive transport with a
|
|
296
|
+
// per-controller init table (16-bit RGB565 wire format); hardware-tuned
|
|
297
|
+
// on ST7796S only. E-ink panels are out of scope at this time.
|
|
298
|
+
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.',
|
|
294
299
|
drivers: ['ili9341-zephyr', 'st7796-zephyr', 'ssd1306-zephyr'],
|
|
295
300
|
colorFormat: 'rgb565',
|
|
296
301
|
ops: {
|
|
@@ -499,6 +504,7 @@ export default defineFrameworkManifest({
|
|
|
499
504
|
polyfills: {
|
|
500
505
|
emitted: [
|
|
501
506
|
{ id: 'cuttlefish_halt', domain: 'standard', notes: 'Mapped to a k_msleep halt loop (exceptions disabled)' },
|
|
507
|
+
{ 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' },
|
|
502
508
|
{ id: 'string_methods', domain: 'embedded', notes: 'STL-free __tc_* string helpers (const char*, inline ASCII case conv, <cstring> only)' },
|
|
503
509
|
{ id: 'static_array', domain: 'embedded', notes: 'STL-free __tc_StaticArray<T,N> wrapper for no-<vector> mutated/struct array literals' },
|
|
504
510
|
{ id: 'timer_methods', domain: 'embedded', notes: 'k_timer + k_work pool (system workqueue); callbacks run in thread context' },
|
|
@@ -509,7 +515,7 @@ export default defineFrameworkManifest({
|
|
|
509
515
|
|
|
510
516
|
toolchain: {
|
|
511
517
|
backend: 'west',
|
|
512
|
-
operations: { prepare: true, compile: true, upload: true, monitor: true },
|
|
518
|
+
operations: { prepare: true, compile: true, upload: true, monitor: true, debug: true },
|
|
513
519
|
},
|
|
514
520
|
|
|
515
521
|
libraryResolution: {
|
package/src/lowering/ble.ts
CHANGED
|
@@ -393,7 +393,9 @@ export function lowerBle(op: HALOpIR): { code?: string; expression?: string } {
|
|
|
393
393
|
case 'ble.on_read':
|
|
394
394
|
// Store the typed read handler as void*; __tc_ble_attr_read casts it back
|
|
395
395
|
// to the right signature based on the char's type field. reinterpret_cast
|
|
396
|
-
// (not a C-style cast)
|
|
396
|
+
// (not a C-style cast) avoids M5-0-7, but M5-0-10 still flags it — the
|
|
397
|
+
// whole type-erased table is covered by a knownPatterns deviation on
|
|
398
|
+
// that rule (see rules.ts, "BLE type-erased callback table").
|
|
397
399
|
return { code: `__tc_ble.on_read[__tc_ble.current_char] = reinterpret_cast<void*>(${s(o.handler)});` };
|
|
398
400
|
case 'ble.on_write':
|
|
399
401
|
return { code: `__tc_ble.on_write[__tc_ble.current_char] = (${s(o.handler)});` };
|
package/src/strategy.ts
CHANGED
|
@@ -157,9 +157,10 @@ export class ZephyrStrategy implements PlatformStrategy {
|
|
|
157
157
|
// program-analysis usesStdString detector doesn't see types generated by
|
|
158
158
|
// the BLE lowering layer — so without forcing <string> here, any BLE server
|
|
159
159
|
// with a Utf8 characteristic fails to compile ('std::string does not name a
|
|
160
|
-
// type').
|
|
161
|
-
//
|
|
162
|
-
|
|
160
|
+
// type'). <cstdlib>/<cstring> (not <stdlib.h>/<string.h>) back the shim's
|
|
161
|
+
// strtol/strcmp/strncpy — the same AUTOSAR-compliant spelling the HTTP,
|
|
162
|
+
// MQTT, and Preferences paths below already use.
|
|
163
|
+
if (uses('usesBle')) inc.push('<cstdlib>', '<cstring>', '<string>', '<zephyr/bluetooth/bluetooth.h>', '<zephyr/bluetooth/conn.h>', '<zephyr/bluetooth/gatt.h>', '<zephyr/bluetooth/uuid.h>');
|
|
163
164
|
// Display: the analyzer's usesDisplay flag (set by display.* hal-ops) drives
|
|
164
165
|
// this include. When ctx.analysis is absent (capability query), uses()
|
|
165
166
|
// defaults to true so a real build never strips it.
|
|
@@ -407,21 +408,29 @@ export class ZephyrStrategy implements PlatformStrategy {
|
|
|
407
408
|
'}',
|
|
408
409
|
);
|
|
409
410
|
|
|
410
|
-
//
|
|
411
|
-
//
|
|
412
|
-
//
|
|
411
|
+
// GPIO read shim: the wiring_compat polyfill routes the UI runtime
|
|
412
|
+
// header's unconditional digitalRead() poll (init-press-input.ts) to
|
|
413
|
+
// __tc_gpio_read, so the definition must NOT be gated on @typecad/safety.
|
|
414
|
+
// The signature is `int` to match wiring_compat's forward declaration —
|
|
415
|
+
// a uint32_t definition alongside it would leave the declared int
|
|
416
|
+
// overload undefined (int wins overload resolution for small integer
|
|
417
|
+
// arguments).
|
|
413
418
|
//
|
|
414
|
-
// The pin is a RUNTIME value here (
|
|
415
|
-
//
|
|
416
|
-
// on a multi-controller SoC (ESP32-S3:
|
|
417
|
-
// Emit a tiny __tc_gpio_dev(pin)
|
|
418
|
-
// controller's device per pin;
|
|
419
|
-
// one-liner. Each DT_NODELABEL is
|
|
420
|
-
// it is always statically valid.
|
|
419
|
+
// The pin is a RUNTIME value here (the UI pin-watch table and safety's
|
|
420
|
+
// voter pass whatever pin they were handed), so the controller cannot be
|
|
421
|
+
// baked in as a single DT_NODELABEL on a multi-controller SoC (ESP32-S3:
|
|
422
|
+
// pins 0–31 → gpio0, 32–48 → gpio1). Emit a tiny __tc_gpio_dev(pin)
|
|
423
|
+
// dispatcher that resolves the owning controller's device per pin;
|
|
424
|
+
// single-controller SoCs collapse it to a one-liner. Each DT_NODELABEL is
|
|
425
|
+
// still compile-time-resolved per branch, so it is always statically valid.
|
|
426
|
+
lines.push(...emitGpioDevDispatcher(chip));
|
|
427
|
+
lines.push(
|
|
428
|
+
'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)); }',
|
|
429
|
+
);
|
|
430
|
+
// __tc_gpio_write / __tc_delay_us are only referenced via @typecad/safety
|
|
431
|
+
// lowering, so they stay gated on it.
|
|
421
432
|
if (program && programUsesSafety(program)) {
|
|
422
|
-
lines.push(...emitGpioDevDispatcher(chip));
|
|
423
433
|
lines.push(
|
|
424
|
-
'inline int __tc_gpio_read(uint32_t pin) { return gpio_pin_get_raw(__tc_gpio_dev(pin), pin); }',
|
|
425
434
|
'inline void __tc_gpio_write(uint32_t pin, uint32_t value) { gpio_pin_set_raw(__tc_gpio_dev(pin), pin, value); }',
|
|
426
435
|
'#ifndef __TC_DELAY_US_DEFINED',
|
|
427
436
|
'#define __TC_DELAY_US_DEFINED',
|
|
@@ -860,6 +869,62 @@ export class ZephyrStrategy implements PlatformStrategy {
|
|
|
860
869
|
return '';
|
|
861
870
|
}
|
|
862
871
|
|
|
872
|
+
// ── Interrupt safety ─────────────────────────────────────────────────────
|
|
873
|
+
// Zephyr ISRs run above thread context: anything that sleeps (k_msleep),
|
|
874
|
+
// pends, or takes a driver lock is illegal there (asserted by the kernel in
|
|
875
|
+
// debug builds; corrupts scheduler state otherwise). The names below are the
|
|
876
|
+
// IR-level callees cuttlefish's interrupt-analysis pass matches (the same
|
|
877
|
+
// keys ArduinoStrategy uses; timing.delay/delay_microseconds hal-ops are
|
|
878
|
+
// mapped back to the bare names by the analyzer itself).
|
|
879
|
+
isrUnsafeOperations(): Map<string, { reason: string; severity: 'warning' | 'info' }> {
|
|
880
|
+
return new Map<string, { reason: string; severity: 'warning' | 'info' }>([
|
|
881
|
+
['delay', {
|
|
882
|
+
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)',
|
|
883
|
+
severity: 'warning',
|
|
884
|
+
}],
|
|
885
|
+
['delayMicroseconds', {
|
|
886
|
+
reason: 'delayMicroseconds() busy-waits the CPU for the full delay, stalling every lower-priority interrupt and the scheduler for its duration',
|
|
887
|
+
severity: 'warning',
|
|
888
|
+
}],
|
|
889
|
+
['console.log', {
|
|
890
|
+
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',
|
|
891
|
+
severity: 'info',
|
|
892
|
+
}],
|
|
893
|
+
['console.error', {
|
|
894
|
+
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',
|
|
895
|
+
severity: 'info',
|
|
896
|
+
}],
|
|
897
|
+
['console.warn', {
|
|
898
|
+
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',
|
|
899
|
+
severity: 'info',
|
|
900
|
+
}],
|
|
901
|
+
['I2C0', {
|
|
902
|
+
reason: 'I2C transactions may sleep (driver locking + clock stretching) and are not callable from Zephyr interrupt context',
|
|
903
|
+
severity: 'warning',
|
|
904
|
+
}],
|
|
905
|
+
['I2C1', {
|
|
906
|
+
reason: 'I2C transactions may sleep (driver locking + clock stretching) and are not callable from Zephyr interrupt context',
|
|
907
|
+
severity: 'warning',
|
|
908
|
+
}],
|
|
909
|
+
['SPI0', {
|
|
910
|
+
reason: 'SPI transfers take driver locks and may wait on DMA completion — not safe in Zephyr interrupt context',
|
|
911
|
+
severity: 'warning',
|
|
912
|
+
}],
|
|
913
|
+
['SPI1', {
|
|
914
|
+
reason: 'SPI transfers take driver locks and may wait on DMA completion — not safe in Zephyr interrupt context',
|
|
915
|
+
severity: 'warning',
|
|
916
|
+
}],
|
|
917
|
+
['UART0', {
|
|
918
|
+
reason: 'UART output via uart_poll_out blocks until the TX FIFO has room — a full FIFO stalls the ISR',
|
|
919
|
+
severity: 'info',
|
|
920
|
+
}],
|
|
921
|
+
['UART1', {
|
|
922
|
+
reason: 'UART output via uart_poll_out blocks until the TX FIFO has room — a full FIFO stalls the ISR',
|
|
923
|
+
severity: 'info',
|
|
924
|
+
}],
|
|
925
|
+
]);
|
|
926
|
+
}
|
|
927
|
+
|
|
863
928
|
ambientTypeDeclarations(): string[] {
|
|
864
929
|
// Preferences is the only HAL surface the framework lowers that is used as
|
|
865
930
|
// a bare global (the HAL Preferences class is exported, but the canonical
|
|
@@ -1218,7 +1283,11 @@ struct __tc_StaticArray {
|
|
|
1218
1283
|
id: 'async_runtime',
|
|
1219
1284
|
domain: 'embedded',
|
|
1220
1285
|
requiredIncludes: [],
|
|
1221
|
-
|
|
1286
|
+
// Polyfill definitions emit before shimLines, but the runtime's
|
|
1287
|
+
// timer bodies call millis() (defined in shimLines) — declare it
|
|
1288
|
+
// first so the polyfill compiles even for programs whose source
|
|
1289
|
+
// has no explicit timing call.
|
|
1290
|
+
forwardDeclarations: ['unsigned long millis();'],
|
|
1222
1291
|
helperStructs: [generateStaticAsyncRuntime(8, this.getAsyncRuntimeConfig().waitForPinEdge)],
|
|
1223
1292
|
helperFunctions: [],
|
|
1224
1293
|
shimMacros: [],
|
|
@@ -1325,11 +1394,12 @@ struct __tc_StaticArray {
|
|
|
1325
1394
|
|
|
1326
1395
|
// ── Strategy-owned display/touch adapter seam ────────────────────────────
|
|
1327
1396
|
// Zephyr owns its display + touch adapters: the UI display adapter bridges
|
|
1328
|
-
// the in-tree CuttlefishGFX class to
|
|
1329
|
-
// src/display/ui-adapter.ts), and the
|
|
1330
|
-
//
|
|
1331
|
-
//
|
|
1332
|
-
//
|
|
1397
|
+
// the in-tree CuttlefishGFX class to the panel (per-controller init + wire
|
|
1398
|
+
// format, see src/display/ui-adapter.ts), and the touch adapters drive the
|
|
1399
|
+
// FT6336U (I2C capacitive) and XPT2046 (SPI resistive) controllers via
|
|
1400
|
+
// Zephyr's bus APIs (src/display/touch-adapter.ts). Both live in this
|
|
1401
|
+
// package so cuttlefish carries no Zephyr/Wiring-specific display or touch
|
|
1402
|
+
// knowledge. Mirrors ArduinoStrategy's provides*/resolve* pattern.
|
|
1333
1403
|
|
|
1334
1404
|
providesDisplayAdapter(): boolean { return true; }
|
|
1335
1405
|
|
package/src/toolchain/index.ts
CHANGED
|
@@ -206,12 +206,17 @@ export const Toolchain = {
|
|
|
206
206
|
// for either driver. Thread a non-default profile here only if a future
|
|
207
207
|
// board carries a display node under a different nodelabel.
|
|
208
208
|
const displayProfile = usesDisplay ? DEFAULT_ZEPHYR_DISPLAY_PROFILE : undefined;
|
|
209
|
+
// Touch controller kind comes from which DT nodelabel the emitted adapter
|
|
210
|
+
// references (FT6336U on I2C, XPT2046 on the display's SPI bus).
|
|
211
|
+
const usesTouch = uses('ft6336u') || uses('touch_');
|
|
212
|
+
const usesXpt = uses('xpt2046');
|
|
209
213
|
const overlay = generateOverlay(chip, {
|
|
210
214
|
usesI2c: uses('i2c_'),
|
|
211
215
|
usesSpi: uses('spi_'),
|
|
212
216
|
usesUart: uses('uart_'),
|
|
213
217
|
usesDisplay,
|
|
214
|
-
usesTouch:
|
|
218
|
+
usesTouch: usesTouch || usesXpt,
|
|
219
|
+
touchController: usesXpt ? 'xpt2046' : 'ft6336u',
|
|
215
220
|
}, displayProfile);
|
|
216
221
|
const overlayDir = join(projectRoot, 'boards');
|
|
217
222
|
mkdirSync(overlayDir, { recursive: true });
|
|
@@ -303,23 +308,49 @@ export const Toolchain = {
|
|
|
303
308
|
backlightPin: typeof dispCfg.backlightPin === 'number' ? dispCfg.backlightPin : undefined,
|
|
304
309
|
}
|
|
305
310
|
: undefined;
|
|
306
|
-
// Extract touch pin wiring
|
|
307
|
-
//
|
|
311
|
+
// Extract touch pin wiring from the config display.touch section so the
|
|
312
|
+
// DT overlay wires the bus + touch node. I2C (FT6336U) carries
|
|
313
|
+
// irq/resetPin/sda/scl; SPI (XPT2046) carries irq/cs + the calibration
|
|
314
|
+
// range the xptek,xpt2046 binding requires.
|
|
308
315
|
const touchCfg = dispCfg?.touch as Record<string, unknown> | undefined;
|
|
309
|
-
const
|
|
316
|
+
const isXpt = touchCfg?.library === 'XPT2046_Touchscreen';
|
|
317
|
+
const touchCal = touchCfg?.calibration as
|
|
318
|
+
{ xMin?: unknown; xMax?: unknown; yMin?: unknown; yMax?: unknown } | undefined;
|
|
319
|
+
const num = (v: unknown): number | undefined => (typeof v === 'number' ? v : undefined);
|
|
320
|
+
let touchWiring: TouchWiring | undefined = touchCfg
|
|
310
321
|
? {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
322
|
+
controller: isXpt ? 'xpt2046' : 'ft6336u',
|
|
323
|
+
irq: num(touchCfg.irq),
|
|
324
|
+
resetPin: num(touchCfg.resetPin),
|
|
325
|
+
sda: num(touchCfg.sda),
|
|
326
|
+
scl: num(touchCfg.scl),
|
|
327
|
+
cs: num(touchCfg.cs),
|
|
328
|
+
calibration: touchCal
|
|
329
|
+
? {
|
|
330
|
+
xMin: num(touchCal.xMin) ?? 0,
|
|
331
|
+
xMax: num(touchCal.xMax) ?? 4095,
|
|
332
|
+
yMin: num(touchCal.yMin) ?? 0,
|
|
333
|
+
yMax: num(touchCal.yMax) ?? 4095,
|
|
334
|
+
}
|
|
335
|
+
: undefined,
|
|
336
|
+
minPressure: num(touchCfg.minPressure),
|
|
315
337
|
}
|
|
316
338
|
: undefined;
|
|
339
|
+
// Touch controller kind for Kconfig (bus driver selection) and the DT
|
|
340
|
+
// node shape: from the config when available, else from the DT nodelabel
|
|
341
|
+
// the emitted adapter references. Forced onto touchWiring so a source
|
|
342
|
+
// scan match without a config section still emits the right node.
|
|
343
|
+
const usesXpt = isXpt || uses('xpt2046');
|
|
344
|
+
if (usesXpt) {
|
|
345
|
+
touchWiring = { controller: 'xpt2046', ...(touchWiring ?? {}) };
|
|
346
|
+
}
|
|
317
347
|
const overlay = generateOverlay(chip, {
|
|
318
348
|
usesI2c: uses('i2c_'),
|
|
319
349
|
usesSpi: uses('spi_'),
|
|
320
350
|
usesUart: uses('uart_'),
|
|
321
351
|
usesDisplay,
|
|
322
|
-
usesTouch: uses('ft6336u') || uses('touch_'),
|
|
352
|
+
usesTouch: uses('ft6336u') || uses('touch_') || usesXpt,
|
|
353
|
+
touchController: usesXpt ? 'xpt2046' : 'ft6336u',
|
|
323
354
|
psram: o.psram,
|
|
324
355
|
}, displayProfile, wiring, touchWiring);
|
|
325
356
|
const overlayDir = join(projectRoot, 'boards');
|
|
@@ -130,8 +130,10 @@ export function scaffoldZephyrProject(projectRoot: string, debug = false, userKc
|
|
|
130
130
|
'',
|
|
131
131
|
'project(zephyr_app)',
|
|
132
132
|
'',
|
|
133
|
-
'# Collect cuttlefish-emitted sources.',
|
|
134
|
-
'
|
|
133
|
+
'# Collect cuttlefish-emitted sources. CONFIGURE_DEPENDS makes CMake re-',
|
|
134
|
+
'# check the glob when the source set changes (e.g. the transpiler removes',
|
|
135
|
+
'# a stale entry), instead of linking a file list from the last configure.',
|
|
136
|
+
'file(GLOB app_sources CONFIGURE_DEPENDS src/*.cpp src/*.c)',
|
|
135
137
|
'',
|
|
136
138
|
'target_sources(app PRIVATE ${app_sources})',
|
|
137
139
|
// When PSRAM is configured, define BOARD_HAS_PSRAM so the UI runtime's
|
|
@@ -97,9 +97,12 @@ export function isZephyrBase(dir: string): boolean {
|
|
|
97
97
|
// ── Strategy 1: `west` on PATH ──────────────────────────────────────────────
|
|
98
98
|
|
|
99
99
|
export function discoverFromPath(): WestInstall | null {
|
|
100
|
+
// shell only on Windows (where.exe resolution through cmd) — an args array
|
|
101
|
+
// with shell: true triggers Node's DEP0190 deprecation warning on Linux/
|
|
102
|
+
// macOS, where `which` is a plain executable that needs no shell.
|
|
100
103
|
const which = spawnSync(IS_WIN ? 'where' : 'which', ['west'], {
|
|
101
104
|
encoding: 'utf8',
|
|
102
|
-
shell:
|
|
105
|
+
shell: IS_WIN,
|
|
103
106
|
windowsHide: true,
|
|
104
107
|
});
|
|
105
108
|
if (which.status !== 0) return null;
|