@typecad/framework-zephyr 1.0.0-alpha.11 → 1.0.0-alpha.13
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/index.d.ts +1 -1
- package/dist/display/index.js +1 -1
- package/dist/display/profiles.d.ts +33 -0
- package/dist/display/profiles.js +36 -0
- package/dist/display/touch-adapter.d.ts +3 -4
- package/dist/display/touch-adapter.js +119 -16
- package/dist/display/ui-adapter.d.ts +4 -0
- package/dist/display/ui-adapter.js +348 -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 +26 -2
- package/dist/dt-config/overlay.js +138 -27
- package/dist/framework.manifest.d.ts +29 -28
- package/dist/framework.manifest.js +9 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -0
- package/dist/lowering/ble.js +3 -1
- package/dist/lowering/gpio.js +7 -3
- package/dist/strategy.d.ts +24 -0
- package/dist/strategy.js +326 -134
- package/dist/toolchain/debug-config.d.ts +43 -2
- package/dist/toolchain/debug-config.js +129 -17
- package/dist/toolchain/index.d.ts +13 -0
- package/dist/toolchain/index.js +104 -23
- package/dist/toolchain/scaffold.js +40 -18
- package/dist/toolchain/west-discover.js +4 -1
- package/package.json +4 -4
- package/src/display/index.ts +1 -1
- package/src/display/profiles.ts +63 -0
- package/src/display/touch-adapter.ts +119 -15
- package/src/display/ui-adapter.ts +357 -139
- package/src/dt-config/kconfig.ts +26 -6
- package/src/dt-config/overlay.ts +450 -298
- package/src/framework.manifest.ts +9 -3
- package/src/index.ts +6 -0
- package/src/lowering/ble.ts +3 -1
- package/src/lowering/gpio.ts +7 -3
- package/src/strategy.ts +355 -136
- package/src/toolchain/debug-config.ts +137 -14
- package/src/toolchain/index.ts +107 -24
- package/src/toolchain/scaffold.ts +39 -16
- package/src/toolchain/west-discover.ts +4 -1
package/dist/strategy.js
CHANGED
|
@@ -44,7 +44,7 @@ import { generateStaticAsyncRuntime } from '@typecad/cuttlefish/api/shared';
|
|
|
44
44
|
import { buildTimerPolyfill } from './async/timer-polyfill.js';
|
|
45
45
|
import { resolveZephyrDisplayOp, newDisplayState } from './display/index.js';
|
|
46
46
|
import { buildDisplayRuntime } from './display/gfx.js';
|
|
47
|
-
import { ZEPHYR_DISPLAY_PROFILES } from './display/profiles.js';
|
|
47
|
+
import { ZEPHYR_DISPLAY_PROFILES, BUILT_IN_PROFILES } from './display/profiles.js';
|
|
48
48
|
import { zephyrDisplayAdapterGenerator } from './display/ui-adapter.js';
|
|
49
49
|
import { zephyrTouchAdapter } from './display/touch-adapter.js';
|
|
50
50
|
export class ZephyrStrategy {
|
|
@@ -113,7 +113,23 @@ export class ZephyrStrategy {
|
|
|
113
113
|
// true so nothing is stripped — mirrors framework-esp32's forcedIncludes.
|
|
114
114
|
const a = ctx?.analysis;
|
|
115
115
|
const uses = (f) => (a ? !!a[f] : true);
|
|
116
|
-
|
|
116
|
+
// <zephyr/drivers/gpio.h> and <cstdint> stay unconditional: gpio.h is
|
|
117
|
+
// cross-cutting (gpio/power/interrupt/spi/pulse lowerings + the DT-spec
|
|
118
|
+
// machinery all reference its API, and no single usesX flag owns it), and
|
|
119
|
+
// the fixed-width types come via <zephyr/kernel.h> regardless — DIRECT_CPP_TYPE_MAP
|
|
120
|
+
// passes int32_t/uint8_t through verbatim.
|
|
121
|
+
const inc = ['<zephyr/kernel.h>', '<zephyr/drivers/gpio.h>', '<cstdint>'];
|
|
122
|
+
// <cstdio> backs the printf family only: __tc_print/__tc_println (emitted
|
|
123
|
+
// solely when @typecad/expect's preprocessor injected them — tracked via
|
|
124
|
+
// usedPolyfillHelpers), raw printf/snprintf in user code (usesCstdio), and
|
|
125
|
+
// the fs/preferences/uart shims (their lowerings snprintf into buffers).
|
|
126
|
+
// A program touching none of those needs no <cstdio>.
|
|
127
|
+
const helpers = a?.usedPolyfillHelpers;
|
|
128
|
+
const needsCstdio = uses('usesCstdio') || uses('usesFS') || uses('usesPreferences')
|
|
129
|
+
|| uses('usesUart')
|
|
130
|
+
|| !!helpers?.has('__tc_print') || !!helpers?.has('__tc_println');
|
|
131
|
+
if (needsCstdio)
|
|
132
|
+
inc.push('<cstdio>');
|
|
117
133
|
if (uses('usesI2C'))
|
|
118
134
|
inc.push('<zephyr/drivers/i2c.h>');
|
|
119
135
|
if (uses('usesSPI'))
|
|
@@ -150,10 +166,11 @@ export class ZephyrStrategy {
|
|
|
150
166
|
// program-analysis usesStdString detector doesn't see types generated by
|
|
151
167
|
// the BLE lowering layer — so without forcing <string> here, any BLE server
|
|
152
168
|
// with a Utf8 characteristic fails to compile ('std::string does not name a
|
|
153
|
-
// type').
|
|
154
|
-
//
|
|
169
|
+
// type'). <cstdlib>/<cstring> (not <stdlib.h>/<string.h>) back the shim's
|
|
170
|
+
// strtol/strcmp/strncpy — the same AUTOSAR-compliant spelling the HTTP,
|
|
171
|
+
// MQTT, and Preferences paths below already use.
|
|
155
172
|
if (uses('usesBle'))
|
|
156
|
-
inc.push('<
|
|
173
|
+
inc.push('<cstdlib>', '<cstring>', '<string>', '<zephyr/bluetooth/bluetooth.h>', '<zephyr/bluetooth/conn.h>', '<zephyr/bluetooth/gatt.h>', '<zephyr/bluetooth/uuid.h>');
|
|
157
174
|
// Display: the analyzer's usesDisplay flag (set by display.* hal-ops) drives
|
|
158
175
|
// this include. When ctx.analysis is absent (capability query), uses()
|
|
159
176
|
// defaults to true so a real build never strips it.
|
|
@@ -238,83 +255,171 @@ export class ZephyrStrategy {
|
|
|
238
255
|
visit(program);
|
|
239
256
|
return found;
|
|
240
257
|
}
|
|
258
|
+
/** Pins referenced by gpio.* hal-ops in the program IR. lowerGpio routes a
|
|
259
|
+
* pin to its devicetree spec by pin NUMBER, so the structured hal-op pins
|
|
260
|
+
* are the authoritative signal for which __tc_dt_* specs are needed —
|
|
261
|
+
* regardless of when the final call text is rendered. */
|
|
262
|
+
collectGpioPinUsage(program) {
|
|
263
|
+
const pins = new Set();
|
|
264
|
+
if (!program)
|
|
265
|
+
return pins;
|
|
266
|
+
const visit = (node) => {
|
|
267
|
+
if (!node || typeof node !== 'object')
|
|
268
|
+
return;
|
|
269
|
+
if (node.operation && typeof node.operation === 'object'
|
|
270
|
+
&& typeof node.operation.operation === 'string'
|
|
271
|
+
&& node.operation.operation.startsWith('gpio.')
|
|
272
|
+
&& typeof node.operation.pin === 'number') {
|
|
273
|
+
pins.add(node.operation.pin);
|
|
274
|
+
}
|
|
275
|
+
for (const v of Object.values(node)) {
|
|
276
|
+
if (Array.isArray(v)) {
|
|
277
|
+
for (const item of v)
|
|
278
|
+
visit(item);
|
|
279
|
+
}
|
|
280
|
+
else if (v && typeof v === 'object')
|
|
281
|
+
visit(v);
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
visit(program);
|
|
285
|
+
return pins;
|
|
286
|
+
}
|
|
287
|
+
/** Run `re` (global) against every raw string in the IR — raw expression
|
|
288
|
+
* values plus raw hal-op codes — returning capture group 1 of each match
|
|
289
|
+
* (the full match when the regex has no group). This is how references the
|
|
290
|
+
* text scanners must see but that never appear as IR call nodes (e.g. a
|
|
291
|
+
* rawCpp() escape hatch naming `__tc_dt_sw0` directly) are discovered. */
|
|
292
|
+
collectRawMatches(program, re) {
|
|
293
|
+
const found = new Set();
|
|
294
|
+
if (!program)
|
|
295
|
+
return found;
|
|
296
|
+
const scan = (text) => {
|
|
297
|
+
for (const m of text.matchAll(re))
|
|
298
|
+
found.add(m[1] ?? m[0]);
|
|
299
|
+
};
|
|
300
|
+
const visit = (node) => {
|
|
301
|
+
if (!node || typeof node !== 'object')
|
|
302
|
+
return;
|
|
303
|
+
if (node.kind === 'raw' && typeof node.value === 'string')
|
|
304
|
+
scan(node.value);
|
|
305
|
+
if (node.operation && typeof node.operation === 'object'
|
|
306
|
+
&& node.operation.operation === 'raw' && typeof node.operation.code === 'string') {
|
|
307
|
+
scan(node.operation.code);
|
|
308
|
+
}
|
|
309
|
+
for (const v of Object.values(node)) {
|
|
310
|
+
if (Array.isArray(v)) {
|
|
311
|
+
for (const item of v)
|
|
312
|
+
visit(item);
|
|
313
|
+
}
|
|
314
|
+
else if (v && typeof v === 'object')
|
|
315
|
+
visit(v);
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
visit(program);
|
|
319
|
+
return found;
|
|
320
|
+
}
|
|
321
|
+
/** Whether the wiring-compat GPIO read surface (__tc_gpio_read definition,
|
|
322
|
+
* __tc_gpio_dev dispatcher, and the wiring_compat polyfill's digitalRead /
|
|
323
|
+
* HIGH / LOW macros) must be emitted. Consumers: user digitalRead() calls
|
|
324
|
+
* (usesDigitalRead), the @typecad/safety voter (calls __tc_gpio_read
|
|
325
|
+
* directly via lowered raw text), and the UI runtime header's
|
|
326
|
+
* unconditional digitalRead() poll (entryHasUI — build-global, so every TU
|
|
327
|
+
* in a UI build carries the macros). With no analysis present (capability
|
|
328
|
+
* query), default to emitting — same convention as the uses() helper. */
|
|
329
|
+
needsGpioReadShim(program, ctx) {
|
|
330
|
+
if (program && programUsesSafety(program))
|
|
331
|
+
return true;
|
|
332
|
+
if (entryHasUI())
|
|
333
|
+
return true;
|
|
334
|
+
const a = ctx?.analysis;
|
|
335
|
+
return a ? !!a.usesDigitalRead : true;
|
|
336
|
+
}
|
|
241
337
|
shimLines(program, ctx) {
|
|
242
338
|
const chip = this.resolveChip(ctx, program);
|
|
243
339
|
const isPrintf = this.resolveDebugMode(ctx) === 'printf';
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
'
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
'inline
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
// (
|
|
282
|
-
//
|
|
283
|
-
//
|
|
284
|
-
|
|
285
|
-
|
|
340
|
+
const a = ctx?.analysis;
|
|
341
|
+
const uses = (f) => (a ? !!a[f] : true);
|
|
342
|
+
const helpers = a?.usedPolyfillHelpers;
|
|
343
|
+
// --- Core shim, gated item by item on actual use ------------------------
|
|
344
|
+
// A minimal program (blink) uses none of these, and its output carries no
|
|
345
|
+
// shim block at all. Everything up to the #endif composes into one guard
|
|
346
|
+
// body; the guard itself is only stamped when the body is non-empty.
|
|
347
|
+
const guardBody = [];
|
|
348
|
+
// CUTTLEFISH_UNDEFINED: needed when the file references null/undefined
|
|
349
|
+
// literals (usesNullish), emits nullish helper CALLS (usesNullishHelper),
|
|
350
|
+
// or has async functions (the async state machine uses the macro for
|
|
351
|
+
// default waitFor* timeouts — not visible to the nullish scanners).
|
|
352
|
+
if (uses('usesNullish') || uses('usesNullishHelper') || uses('hasAsync')) {
|
|
353
|
+
guardBody.push('#ifndef CUTTLEFISH_UNDEFINED', '#define CUTTLEFISH_UNDEFINED 0', '#endif');
|
|
354
|
+
}
|
|
355
|
+
// Nullish helpers: only when the file actually emits cuttlefish_nullish /
|
|
356
|
+
// cuttlefish_exists CALLS (?? / ?. lowering). A file that only references
|
|
357
|
+
// null/undefined literals needs just the macro above — the same
|
|
358
|
+
// distinction the setup emitter's strip filter documents.
|
|
359
|
+
if (uses('usesNullishHelper')) {
|
|
360
|
+
guardBody.push('template<typename T> inline bool cuttlefish_is_nullish(const T& v) { return false; }', 'inline bool cuttlefish_is_nullish(long long v) { return v == CUTTLEFISH_UNDEFINED; }', 'inline bool cuttlefish_is_nullish(int v) { return v == CUTTLEFISH_UNDEFINED; }', 'inline bool cuttlefish_is_nullish(double v) { return v == static_cast<double>(CUTTLEFISH_UNDEFINED); }', 'inline bool cuttlefish_is_nullish(bool v) { return v == false; }', 'template<typename T> inline bool cuttlefish_is_nullish(T* v) { return v == nullptr; }', 'template<typename T> inline bool cuttlefish_exists(const T& v) { return !cuttlefish_is_nullish(v); }', 'template<typename T, typename U> inline T cuttlefish_nullish(const T& a, U b) { return !cuttlefish_is_nullish(a) ? a : (T)b; }');
|
|
361
|
+
}
|
|
362
|
+
// millis() backed by the Zephyr uptime counter. uint32_t return matches
|
|
363
|
+
// the Arduino API the shared runtime expects (wraps every ~49.7 days).
|
|
364
|
+
// Kept when the program reads the clock itself — usesWallClock,
|
|
365
|
+
// deliberately WITHOUT the delay() conflation usesMillis carries, because
|
|
366
|
+
// Zephyr's delay lowers straight to k_msleep — or has a hidden poller:
|
|
367
|
+
// async functions / the async runtime, the setInterval/setTimeout
|
|
368
|
+
// scheduler, or a mounted UI's per-frame tick.
|
|
369
|
+
if (uses('usesWallClock') || uses('hasAsync') || (!a || a.timerCallCount > 0)
|
|
370
|
+
|| this.programUsesAsyncRuntime(program) || entryHasUI()) {
|
|
371
|
+
guardBody.push('inline unsigned long millis() { return static_cast<unsigned long>(k_uptime_get_32()); }');
|
|
372
|
+
}
|
|
373
|
+
// PROGMEM: only the (Arduino-oriented) UI runtime header can reference it.
|
|
374
|
+
if (entryHasUI()) {
|
|
375
|
+
guardBody.push('#ifndef PROGMEM', '#define PROGMEM', '#endif');
|
|
376
|
+
}
|
|
377
|
+
// map()/constrain() Arduino-API helpers — dead code unless called. The
|
|
378
|
+
// setup emitter ORs entryHasUI() into usesConstrain before we see it (the
|
|
379
|
+
// UI runtime's progress/range draw calls constrain).
|
|
380
|
+
if (uses('usesMap')) {
|
|
381
|
+
guardBody.push('inline long map(long x, long in_min, long in_max, long out_min, long out_max) { return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; }');
|
|
382
|
+
}
|
|
383
|
+
if (uses('usesConstrain')) {
|
|
384
|
+
guardBody.push('inline long constrain(long x, long a, long b) { return x < a ? a : (x > b ? b : x); }');
|
|
385
|
+
}
|
|
386
|
+
// Test-runner console helpers: @typecad/expect's Zephyr shim calls these
|
|
387
|
+
// for protocol output. Overloaded for string (const char*) and numeric
|
|
388
|
+
// (double) so the same call site works for markers and test values.
|
|
389
|
+
// Emitted only when the expect preprocessor actually injected the calls
|
|
390
|
+
// (tracked as usedPolyfillHelpers).
|
|
391
|
+
if (!a || !!helpers?.has('__tc_print') || !!helpers?.has('__tc_println')) {
|
|
392
|
+
guardBody.push('inline void __tc_print(const char* s) { printf("%s", s); }', 'inline void __tc_print(double v) { printf("%g", v); }', 'inline void __tc_println(const char* s) { printf("%s\\n", s); }', 'inline void __tc_println(double v) { printf("%g\\n", v); }');
|
|
286
393
|
}
|
|
287
394
|
// Per-peripheral bus state — gated on the same ctx.analysis.usesX flags as
|
|
288
395
|
// forcedIncludes, so an unused peripheral emits no state (and its header is
|
|
289
396
|
// not included). Mirrors framework-esp32's shimLines espInit block.
|
|
290
|
-
const a = ctx?.analysis;
|
|
291
|
-
const uses = (f) => (a ? !!a[f] : true);
|
|
292
397
|
if (uses('usesI2C') && chip.i2c) {
|
|
293
398
|
for (let i = 0; i < chip.i2c.controllers.length; i++)
|
|
294
|
-
|
|
399
|
+
guardBody.push(...i2cInitLines(chip, i));
|
|
295
400
|
}
|
|
296
401
|
if (uses('usesSPI') && chip.spi) {
|
|
297
402
|
for (let i = 0; i < chip.spi.controllers.length; i++)
|
|
298
|
-
|
|
403
|
+
guardBody.push(...spiInitLines(chip, i));
|
|
299
404
|
}
|
|
300
405
|
if (uses('usesUart') && chip.uart) {
|
|
301
406
|
for (let i = 0; i < chip.uart.controllers.length; i++)
|
|
302
|
-
|
|
407
|
+
guardBody.push(...uartInitLines(chip, i));
|
|
303
408
|
}
|
|
304
409
|
if (uses('usesADC') && chip.adc)
|
|
305
|
-
|
|
410
|
+
guardBody.push(...adcInitLines(chip));
|
|
306
411
|
if (uses('usesPWM') && chip.pwm)
|
|
307
|
-
|
|
412
|
+
guardBody.push(...pwmInitLines(chip));
|
|
308
413
|
if (uses('usesDAC') && chip.dac)
|
|
309
|
-
|
|
414
|
+
guardBody.push(...dacInitLines(chip));
|
|
310
415
|
if (uses('usesHwtimer') && chip.hwtimer)
|
|
311
|
-
|
|
416
|
+
guardBody.push(...hwtimerInitLines(chip));
|
|
312
417
|
if (uses('usesInterrupts'))
|
|
313
|
-
|
|
418
|
+
guardBody.push(...interruptInitLines(chip));
|
|
314
419
|
if (uses('usesWDT') && chip.wdt)
|
|
315
|
-
|
|
420
|
+
guardBody.push(...wdtInitLines(chip));
|
|
316
421
|
if (uses('usesBle'))
|
|
317
|
-
|
|
422
|
+
guardBody.push(...bleInitLines());
|
|
318
423
|
// Display runtime (rect/text renderer): the DIRECT-call display path (user
|
|
319
424
|
// code calling screen.display.fillRect etc., no @typecad/ui). Emitted only
|
|
320
425
|
// when the program uses display.* but is NOT a UI program — the UI display
|
|
@@ -328,23 +433,42 @@ export class ZephyrStrategy {
|
|
|
328
433
|
// previously dead code).
|
|
329
434
|
if (uses('usesDisplay') && !entryHasUI()) {
|
|
330
435
|
const rt = buildDisplayRuntime(this._displayState.profile);
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
436
|
+
guardBody.push(...rt.stateLines);
|
|
437
|
+
guardBody.push(rt.fontTable);
|
|
438
|
+
guardBody.push(rt.helpers);
|
|
334
439
|
}
|
|
335
440
|
if (uses('usesWifi'))
|
|
336
|
-
|
|
441
|
+
guardBody.push(...wifiInitLines());
|
|
337
442
|
if (uses('usesHttp'))
|
|
338
|
-
|
|
443
|
+
guardBody.push(...httpInitLines());
|
|
339
444
|
if (uses('usesMqtt'))
|
|
340
|
-
|
|
445
|
+
guardBody.push(...mqttInitLines());
|
|
341
446
|
if (uses('usesPreferences'))
|
|
342
|
-
|
|
447
|
+
guardBody.push(...preferencesInitLines());
|
|
343
448
|
if (uses('usesFS'))
|
|
344
|
-
|
|
449
|
+
guardBody.push(...fsInitLines());
|
|
345
450
|
if (uses('usesRandom'))
|
|
346
|
-
|
|
347
|
-
lines
|
|
451
|
+
guardBody.push(...randomInitLines());
|
|
452
|
+
const lines = [];
|
|
453
|
+
if (guardBody.length > 0) {
|
|
454
|
+
lines.push('// cuttlefish runtime shim. Wrapped in a single include guard so the', '// block is safe to emit into multiple headers and .cpp files within', '// one translation unit (a .cpp may #include several headers that each', '// carry the shim). The guard ensures the definitions are seen exactly', '// once per TU.', '#ifndef CUTTLEFISH_SHIM_DEFINED', '#define CUTTLEFISH_SHIM_DEFINED', ...guardBody, '#endif // CUTTLEFISH_SHIM_DEFINED');
|
|
455
|
+
}
|
|
456
|
+
// Devicetree specs — one per board-defined GPIO pin, but ONLY for pins the
|
|
457
|
+
// program actually addresses (lowerGpio routes by pin number, and the
|
|
458
|
+
// structured gpio.* hal-op pins are visible here) plus aliases named
|
|
459
|
+
// verbatim in raw code (rawCpp escape hatches). Emitted OUTSIDE the single
|
|
460
|
+
// CUTTLEFISH_SHIM_DEFINED guard with a per-symbol guard: per-file pin sets
|
|
461
|
+
// differ, and in a multi-header TU the first header's TU-wide guard would
|
|
462
|
+
// otherwise hide the second header's specs. Without a program (capability
|
|
463
|
+
// query), emit them all.
|
|
464
|
+
const usedPins = this.collectGpioPinUsage(program);
|
|
465
|
+
const dtTextRefs = this.collectRawMatches(program, /__tc_dt_([A-Za-z0-9_]+)/g);
|
|
466
|
+
for (const spec of chip.gpio.dtSpecs) {
|
|
467
|
+
if (program && !usedPins.has(spec.pin) && !dtTextRefs.has(spec.dtSpec))
|
|
468
|
+
continue;
|
|
469
|
+
const guard = `__TC_DT_${spec.dtSpec.replace(/[^A-Za-z0-9_]/g, '_').toUpperCase()}_SPEC`;
|
|
470
|
+
lines.push(`#ifndef ${guard}`, `#define ${guard}`, `static const struct gpio_dt_spec __tc_dt_${spec.dtSpec} = GPIO_DT_SPEC_GET(DT_ALIAS(${spec.dtSpec}), gpios);`, `#endif // ${guard}`);
|
|
471
|
+
}
|
|
348
472
|
// --- Debug-mode halt + per-breakpoint disable registry ---
|
|
349
473
|
//
|
|
350
474
|
// Printf mode only. In gdb mode the cuttlefish debug preprocessor is
|
|
@@ -377,20 +501,32 @@ export class ZephyrStrategy {
|
|
|
377
501
|
// separate translation unit when generateHeaderFile() splits them into the
|
|
378
502
|
// header.
|
|
379
503
|
lines.push('extern void setup(void);', 'extern void loop(void);', '', 'int main(void) {', ' setup();', ' for (;;) {', ' loop();', ' k_msleep(1);', ' }', ' return 0;', '}');
|
|
380
|
-
//
|
|
381
|
-
//
|
|
382
|
-
//
|
|
504
|
+
// GPIO read shim: emitted only when something actually reads a pin at
|
|
505
|
+
// runtime — user digitalRead() calls, the @typecad/safety voter (calls
|
|
506
|
+
// __tc_gpio_read directly), or the UI runtime header's digitalRead() poll
|
|
507
|
+
// (init-press-input.ts). A program that only writes/toggles GPIO needs
|
|
508
|
+
// neither the dispatcher nor the reader.
|
|
383
509
|
//
|
|
384
|
-
// The
|
|
385
|
-
//
|
|
386
|
-
//
|
|
387
|
-
//
|
|
388
|
-
//
|
|
389
|
-
//
|
|
390
|
-
//
|
|
391
|
-
|
|
510
|
+
// The signature is `int` to match wiring_compat's forward declaration —
|
|
511
|
+
// a uint32_t definition alongside it would leave the declared int
|
|
512
|
+
// overload undefined (int wins overload resolution for small integer
|
|
513
|
+
// arguments).
|
|
514
|
+
//
|
|
515
|
+
// The pin is a RUNTIME value here (the UI pin-watch table and safety's
|
|
516
|
+
// voter pass whatever pin they were handed), so the controller cannot be
|
|
517
|
+
// baked in as a single DT_NODELABEL on a multi-controller SoC (ESP32-S3:
|
|
518
|
+
// pins 0–31 → gpio0, 32–48 → gpio1). Emit a tiny __tc_gpio_dev(pin)
|
|
519
|
+
// dispatcher that resolves the owning controller's device per pin;
|
|
520
|
+
// single-controller SoCs collapse it to a one-liner. Each DT_NODELABEL is
|
|
521
|
+
// still compile-time-resolved per branch, so it is always statically valid.
|
|
522
|
+
if (this.needsGpioReadShim(program, ctx)) {
|
|
392
523
|
lines.push(...emitGpioDevDispatcher(chip));
|
|
393
|
-
lines.push('inline int __tc_gpio_read(
|
|
524
|
+
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)); }');
|
|
525
|
+
}
|
|
526
|
+
// __tc_gpio_write / __tc_delay_us are only referenced via @typecad/safety
|
|
527
|
+
// lowering, so they stay gated on it.
|
|
528
|
+
if (program && programUsesSafety(program)) {
|
|
529
|
+
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
530
|
}
|
|
395
531
|
return lines;
|
|
396
532
|
}
|
|
@@ -780,6 +916,61 @@ export class ZephyrStrategy {
|
|
|
780
916
|
apiReservedEnumGuard() {
|
|
781
917
|
return '';
|
|
782
918
|
}
|
|
919
|
+
// ── Interrupt safety ─────────────────────────────────────────────────────
|
|
920
|
+
// Zephyr ISRs run above thread context: anything that sleeps (k_msleep),
|
|
921
|
+
// pends, or takes a driver lock is illegal there (asserted by the kernel in
|
|
922
|
+
// debug builds; corrupts scheduler state otherwise). The names below are the
|
|
923
|
+
// IR-level callees cuttlefish's interrupt-analysis pass matches (the same
|
|
924
|
+
// keys ArduinoStrategy uses; timing.delay/delay_microseconds hal-ops are
|
|
925
|
+
// mapped back to the bare names by the analyzer itself).
|
|
926
|
+
isrUnsafeOperations() {
|
|
927
|
+
return new Map([
|
|
928
|
+
['delay', {
|
|
929
|
+
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)',
|
|
930
|
+
severity: 'warning',
|
|
931
|
+
}],
|
|
932
|
+
['delayMicroseconds', {
|
|
933
|
+
reason: 'delayMicroseconds() busy-waits the CPU for the full delay, stalling every lower-priority interrupt and the scheduler for its duration',
|
|
934
|
+
severity: 'warning',
|
|
935
|
+
}],
|
|
936
|
+
['console.log', {
|
|
937
|
+
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',
|
|
938
|
+
severity: 'info',
|
|
939
|
+
}],
|
|
940
|
+
['console.error', {
|
|
941
|
+
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',
|
|
942
|
+
severity: 'info',
|
|
943
|
+
}],
|
|
944
|
+
['console.warn', {
|
|
945
|
+
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',
|
|
946
|
+
severity: 'info',
|
|
947
|
+
}],
|
|
948
|
+
['I2C0', {
|
|
949
|
+
reason: 'I2C transactions may sleep (driver locking + clock stretching) and are not callable from Zephyr interrupt context',
|
|
950
|
+
severity: 'warning',
|
|
951
|
+
}],
|
|
952
|
+
['I2C1', {
|
|
953
|
+
reason: 'I2C transactions may sleep (driver locking + clock stretching) and are not callable from Zephyr interrupt context',
|
|
954
|
+
severity: 'warning',
|
|
955
|
+
}],
|
|
956
|
+
['SPI0', {
|
|
957
|
+
reason: 'SPI transfers take driver locks and may wait on DMA completion — not safe in Zephyr interrupt context',
|
|
958
|
+
severity: 'warning',
|
|
959
|
+
}],
|
|
960
|
+
['SPI1', {
|
|
961
|
+
reason: 'SPI transfers take driver locks and may wait on DMA completion — not safe in Zephyr interrupt context',
|
|
962
|
+
severity: 'warning',
|
|
963
|
+
}],
|
|
964
|
+
['UART0', {
|
|
965
|
+
reason: 'UART output via uart_poll_out blocks until the TX FIFO has room — a full FIFO stalls the ISR',
|
|
966
|
+
severity: 'info',
|
|
967
|
+
}],
|
|
968
|
+
['UART1', {
|
|
969
|
+
reason: 'UART output via uart_poll_out blocks until the TX FIFO has room — a full FIFO stalls the ISR',
|
|
970
|
+
severity: 'info',
|
|
971
|
+
}],
|
|
972
|
+
]);
|
|
973
|
+
}
|
|
783
974
|
ambientTypeDeclarations() {
|
|
784
975
|
// Preferences is the only HAL surface the framework lowers that is used as
|
|
785
976
|
// a bare global (the HAL Preferences class is exported, but the canonical
|
|
@@ -954,6 +1145,45 @@ export class ZephyrStrategy {
|
|
|
954
1145
|
]);
|
|
955
1146
|
}
|
|
956
1147
|
generateNativePolyfills(program, ctx) {
|
|
1148
|
+
// wiring_compat (digitalRead/HIGH/LOW macros + the __tc_gpio_read forward
|
|
1149
|
+
// declaration) is emitted only when something reads a pin: user
|
|
1150
|
+
// digitalRead() calls, the @typecad/safety voter, or the UI runtime
|
|
1151
|
+
// header's unconditional digitalRead() poll (init-press-input.ts — the
|
|
1152
|
+
// loop body is dead when no pin watchers are configured but must
|
|
1153
|
+
// compile). needsGpioReadShim defaults to true without analysis so
|
|
1154
|
+
// capability queries keep seeing it.
|
|
1155
|
+
const wiringCompat = {
|
|
1156
|
+
// Wiring-compatibility shims for symbols the UI runtime header
|
|
1157
|
+
// references unconditionally (e.g. init-press-input.ts polls pin
|
|
1158
|
+
// watchers via digitalRead/HIGH/LOW even when none are configured —
|
|
1159
|
+
// the loop body is dead but must compile). Zephyr lowers GPIO through
|
|
1160
|
+
// its __tc_gpio_* helpers (defined in shimLines); these macros route
|
|
1161
|
+
// the Wiring tokens to them.
|
|
1162
|
+
kind: 'polyfill',
|
|
1163
|
+
id: 'wiring_compat',
|
|
1164
|
+
domain: 'standard',
|
|
1165
|
+
requiredIncludes: [],
|
|
1166
|
+
forwardDeclarations: [
|
|
1167
|
+
// Forward-declared so the digitalRead macro (below) can reference it
|
|
1168
|
+
// before the shim block defines the body. The shim emits the full
|
|
1169
|
+
// definition via gpio_pin_get_raw.
|
|
1170
|
+
'int __tc_gpio_read(int pin);',
|
|
1171
|
+
],
|
|
1172
|
+
helperStructs: [],
|
|
1173
|
+
helperFunctions: [],
|
|
1174
|
+
shimMacros: [
|
|
1175
|
+
'#ifndef HIGH',
|
|
1176
|
+
'#define HIGH 1',
|
|
1177
|
+
'#endif',
|
|
1178
|
+
'#ifndef LOW',
|
|
1179
|
+
'#define LOW 0',
|
|
1180
|
+
'#endif',
|
|
1181
|
+
'#ifndef digitalRead',
|
|
1182
|
+
'#define digitalRead(pin) __tc_gpio_read(pin)',
|
|
1183
|
+
'#endif',
|
|
1184
|
+
],
|
|
1185
|
+
dependencies: [],
|
|
1186
|
+
};
|
|
957
1187
|
const polyfills = [
|
|
958
1188
|
{
|
|
959
1189
|
kind: 'polyfill',
|
|
@@ -968,38 +1198,7 @@ export class ZephyrStrategy {
|
|
|
968
1198
|
shimMacros: [],
|
|
969
1199
|
dependencies: [],
|
|
970
1200
|
},
|
|
971
|
-
|
|
972
|
-
// Wiring-compatibility shims for symbols the UI runtime header
|
|
973
|
-
// references unconditionally (e.g. init-press-input.ts polls pin
|
|
974
|
-
// watchers via digitalRead/HIGH/LOW even when none are configured —
|
|
975
|
-
// the loop body is dead but must compile). Zephyr lowers GPIO through
|
|
976
|
-
// its __tc_gpio_* helpers (defined in shimLines); these macros route
|
|
977
|
-
// the Wiring tokens to them.
|
|
978
|
-
kind: 'polyfill',
|
|
979
|
-
id: 'wiring_compat',
|
|
980
|
-
domain: 'standard',
|
|
981
|
-
requiredIncludes: [],
|
|
982
|
-
forwardDeclarations: [
|
|
983
|
-
// Forward-declared so the digitalRead macro (below) can reference it
|
|
984
|
-
// before the shim block defines the body. The shim emits the full
|
|
985
|
-
// definition via gpio_pin_get_raw.
|
|
986
|
-
'int __tc_gpio_read(int pin);',
|
|
987
|
-
],
|
|
988
|
-
helperStructs: [],
|
|
989
|
-
helperFunctions: [],
|
|
990
|
-
shimMacros: [
|
|
991
|
-
'#ifndef HIGH',
|
|
992
|
-
'#define HIGH 1',
|
|
993
|
-
'#endif',
|
|
994
|
-
'#ifndef LOW',
|
|
995
|
-
'#define LOW 0',
|
|
996
|
-
'#endif',
|
|
997
|
-
'#ifndef digitalRead',
|
|
998
|
-
'#define digitalRead(pin) __tc_gpio_read(pin)',
|
|
999
|
-
'#endif',
|
|
1000
|
-
],
|
|
1001
|
-
dependencies: [],
|
|
1002
|
-
},
|
|
1201
|
+
...(this.needsGpioReadShim(program, ctx) ? [wiringCompat] : []),
|
|
1003
1202
|
{
|
|
1004
1203
|
// STL-free string-method polyfills. String methods (.toUpperCase(),
|
|
1005
1204
|
// .includes(), .substring(), …) lower at IR level to __tc_* helpers for
|
|
@@ -1098,7 +1297,11 @@ struct __tc_StaticArray {
|
|
|
1098
1297
|
id: 'async_runtime',
|
|
1099
1298
|
domain: 'embedded',
|
|
1100
1299
|
requiredIncludes: [],
|
|
1101
|
-
|
|
1300
|
+
// Polyfill definitions emit before shimLines, but the runtime's
|
|
1301
|
+
// timer bodies call millis() (defined in shimLines) — declare it
|
|
1302
|
+
// first so the polyfill compiles even for programs whose source
|
|
1303
|
+
// has no explicit timing call.
|
|
1304
|
+
forwardDeclarations: ['unsigned long millis();'],
|
|
1102
1305
|
helperStructs: [generateStaticAsyncRuntime(8, this.getAsyncRuntimeConfig().waitForPinEdge)],
|
|
1103
1306
|
helperFunctions: [],
|
|
1104
1307
|
shimMacros: [],
|
|
@@ -1184,11 +1387,12 @@ struct __tc_StaticArray {
|
|
|
1184
1387
|
}
|
|
1185
1388
|
// ── Strategy-owned display/touch adapter seam ────────────────────────────
|
|
1186
1389
|
// 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
|
-
//
|
|
1390
|
+
// the in-tree CuttlefishGFX class to the panel (per-controller init + wire
|
|
1391
|
+
// format, see src/display/ui-adapter.ts), and the touch adapters drive the
|
|
1392
|
+
// FT6336U (I2C capacitive) and XPT2046 (SPI resistive) controllers via
|
|
1393
|
+
// Zephyr's bus APIs (src/display/touch-adapter.ts). Both live in this
|
|
1394
|
+
// package so cuttlefish carries no Zephyr/Wiring-specific display or touch
|
|
1395
|
+
// knowledge. Mirrors ArduinoStrategy's provides*/resolve* pattern.
|
|
1192
1396
|
providesDisplayAdapter() { return true; }
|
|
1193
1397
|
resolveDisplayAdapter(display) {
|
|
1194
1398
|
const code = zephyrDisplayAdapterGenerator(display);
|
|
@@ -1201,22 +1405,10 @@ struct __tc_StaticArray {
|
|
|
1201
1405
|
// Named display-profile registry: maps config `profile` values (e.g.
|
|
1202
1406
|
// "st7796-zephyr") to the shared DisplayProfile shape so transpile.ts can
|
|
1203
1407
|
// resolve them per-framework. The Zephyr profiles are DT-binding descriptors;
|
|
1204
|
-
//
|
|
1205
|
-
//
|
|
1408
|
+
// BUILT_IN_PROFILES (display/profiles.ts) is the single DT-binding →
|
|
1409
|
+
// shared-shape mapping, shared with the preview's registry loader.
|
|
1206
1410
|
getProfileRegistry() {
|
|
1207
|
-
|
|
1208
|
-
for (const [name, p] of Object.entries(ZEPHYR_DISPLAY_PROFILES)) {
|
|
1209
|
-
m.set(name, {
|
|
1210
|
-
driver: p.driver,
|
|
1211
|
-
width: p.width,
|
|
1212
|
-
height: p.height,
|
|
1213
|
-
nativeWidth: p.nativeWidth,
|
|
1214
|
-
nativeHeight: p.nativeHeight,
|
|
1215
|
-
colorFormat: p.colorFormat,
|
|
1216
|
-
rotation: p.rotation ?? 1,
|
|
1217
|
-
});
|
|
1218
|
-
}
|
|
1219
|
-
return m;
|
|
1411
|
+
return new Map(Object.entries(BUILT_IN_PROFILES));
|
|
1220
1412
|
}
|
|
1221
1413
|
colorFormat() {
|
|
1222
1414
|
return 'rgb565';
|