@typecad/framework-zephyr 1.0.0-alpha.12 → 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 +8 -0
- package/dist/display/profiles.js +19 -0
- package/dist/display/ui-adapter.d.ts +4 -0
- package/dist/display/ui-adapter.js +46 -0
- package/dist/dt-config/overlay.d.ts +8 -1
- package/dist/dt-config/overlay.js +19 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -0
- package/dist/lowering/gpio.js +7 -3
- package/dist/strategy.d.ts +20 -0
- package/dist/strategy.js +242 -118
- 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 +65 -14
- package/dist/toolchain/scaffold.js +40 -20
- package/package.json +4 -4
- package/src/display/index.ts +1 -1
- package/src/display/profiles.ts +23 -0
- package/src/display/ui-adapter.ts +51 -0
- package/src/dt-config/overlay.ts +450 -423
- package/src/index.ts +6 -0
- package/src/lowering/gpio.ts +7 -3
- package/src/strategy.ts +271 -122
- package/src/toolchain/debug-config.ts +137 -14
- package/src/toolchain/index.ts +67 -15
- package/src/toolchain/scaffold.ts +39 -18
package/src/index.ts
CHANGED
|
@@ -10,6 +10,12 @@ export { ZephyrStrategy as FrameworkStrategy } from './strategy.js';
|
|
|
10
10
|
export { ZephyrStrategy } from './strategy.js';
|
|
11
11
|
export { Toolchain } from './toolchain/index.js';
|
|
12
12
|
|
|
13
|
+
// Create-time starter debug artifacts. The `cuttlefish create` flow reads this
|
|
14
|
+
// optional named export off the loaded framework module (same loader pattern
|
|
15
|
+
// as doctor/licenses) and calls it for freshly scaffolded projects, so F5 in
|
|
16
|
+
// VS Code works before the first build. No-ops for non-GDB targets.
|
|
17
|
+
export { writeProjectDebugArtifacts } from './toolchain/debug-config.js';
|
|
18
|
+
|
|
13
19
|
// `cuttlefish doctor` — verify the installed Zephyr is reachable + inside the
|
|
14
20
|
// declared compat range, and preview board-target normalization. Re-exported
|
|
15
21
|
// under the dispatcher-facing alias `doctor` so the loader picks it up as
|
package/src/lowering/gpio.ts
CHANGED
|
@@ -136,9 +136,13 @@ function lowerGpioRaw(
|
|
|
136
136
|
case 'gpio.read':
|
|
137
137
|
return { expression: `gpio_pin_get_raw(${controller}, ${pin})` };
|
|
138
138
|
case 'gpio.toggle':
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
139
|
+
// Native atomic toggle — never read-modify-write. gpio_pin_get_raw on
|
|
140
|
+
// a direction-only output reads the input latch, which is undefined on
|
|
141
|
+
// SoCs that don't latch it. Zephyr's toggle API has no _raw variant —
|
|
142
|
+
// gpio_pin_toggle is the driver-level atomic toggle, and for pins
|
|
143
|
+
// configured without GPIO_ACTIVE_LOW the logical level equals the
|
|
144
|
+
// physical one, so it matches the get_raw/set_raw used elsewhere.
|
|
145
|
+
return { code: `gpio_pin_toggle(${controller}, ${pin});` };
|
|
142
146
|
default:
|
|
143
147
|
throw new Error(
|
|
144
148
|
`framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
|
package/src/strategy.ts
CHANGED
|
@@ -65,7 +65,7 @@ import { generateStaticAsyncRuntime } from '@typecad/cuttlefish/api/shared';
|
|
|
65
65
|
import { buildTimerPolyfill } from './async/timer-polyfill.js';
|
|
66
66
|
import { resolveZephyrDisplayOp, newDisplayState, type DisplayState } from './display/index.js';
|
|
67
67
|
import { buildDisplayRuntime } from './display/gfx.js';
|
|
68
|
-
import { ZEPHYR_DISPLAY_PROFILES } from './display/profiles.js';
|
|
68
|
+
import { ZEPHYR_DISPLAY_PROFILES, BUILT_IN_PROFILES } from './display/profiles.js';
|
|
69
69
|
import { zephyrDisplayAdapterGenerator } from './display/ui-adapter.js';
|
|
70
70
|
import { zephyrTouchAdapter } from './display/touch-adapter.js';
|
|
71
71
|
|
|
@@ -131,7 +131,22 @@ export class ZephyrStrategy implements PlatformStrategy {
|
|
|
131
131
|
// true so nothing is stripped — mirrors framework-esp32's forcedIncludes.
|
|
132
132
|
const a = (ctx as any)?.analysis;
|
|
133
133
|
const uses = (f: string): boolean => (a ? !!a[f] : true);
|
|
134
|
-
|
|
134
|
+
// <zephyr/drivers/gpio.h> and <cstdint> stay unconditional: gpio.h is
|
|
135
|
+
// cross-cutting (gpio/power/interrupt/spi/pulse lowerings + the DT-spec
|
|
136
|
+
// machinery all reference its API, and no single usesX flag owns it), and
|
|
137
|
+
// the fixed-width types come via <zephyr/kernel.h> regardless — DIRECT_CPP_TYPE_MAP
|
|
138
|
+
// passes int32_t/uint8_t through verbatim.
|
|
139
|
+
const inc: string[] = ['<zephyr/kernel.h>', '<zephyr/drivers/gpio.h>', '<cstdint>'];
|
|
140
|
+
// <cstdio> backs the printf family only: __tc_print/__tc_println (emitted
|
|
141
|
+
// solely when @typecad/expect's preprocessor injected them — tracked via
|
|
142
|
+
// usedPolyfillHelpers), raw printf/snprintf in user code (usesCstdio), and
|
|
143
|
+
// the fs/preferences/uart shims (their lowerings snprintf into buffers).
|
|
144
|
+
// A program touching none of those needs no <cstdio>.
|
|
145
|
+
const helpers = (a as { usedPolyfillHelpers?: Set<string> } | undefined)?.usedPolyfillHelpers;
|
|
146
|
+
const needsCstdio = uses('usesCstdio') || uses('usesFS') || uses('usesPreferences')
|
|
147
|
+
|| uses('usesUart')
|
|
148
|
+
|| !!helpers?.has('__tc_print') || !!helpers?.has('__tc_println');
|
|
149
|
+
if (needsCstdio) inc.push('<cstdio>');
|
|
135
150
|
if (uses('usesI2C')) inc.push('<zephyr/drivers/i2c.h>');
|
|
136
151
|
if (uses('usesSPI')) inc.push('<zephyr/drivers/spi.h>');
|
|
137
152
|
if (uses('usesUart')) inc.push('<zephyr/drivers/uart.h>');
|
|
@@ -247,77 +262,176 @@ export class ZephyrStrategy implements PlatformStrategy {
|
|
|
247
262
|
return found;
|
|
248
263
|
}
|
|
249
264
|
|
|
265
|
+
/** Pins referenced by gpio.* hal-ops in the program IR. lowerGpio routes a
|
|
266
|
+
* pin to its devicetree spec by pin NUMBER, so the structured hal-op pins
|
|
267
|
+
* are the authoritative signal for which __tc_dt_* specs are needed —
|
|
268
|
+
* regardless of when the final call text is rendered. */
|
|
269
|
+
private collectGpioPinUsage(program?: ProgramIR): Set<number> {
|
|
270
|
+
const pins = new Set<number>();
|
|
271
|
+
if (!program) return pins;
|
|
272
|
+
const visit = (node: any): void => {
|
|
273
|
+
if (!node || typeof node !== 'object') return;
|
|
274
|
+
if (node.operation && typeof node.operation === 'object'
|
|
275
|
+
&& typeof node.operation.operation === 'string'
|
|
276
|
+
&& node.operation.operation.startsWith('gpio.')
|
|
277
|
+
&& typeof node.operation.pin === 'number') {
|
|
278
|
+
pins.add(node.operation.pin);
|
|
279
|
+
}
|
|
280
|
+
for (const v of Object.values(node)) {
|
|
281
|
+
if (Array.isArray(v)) { for (const item of v) visit(item); }
|
|
282
|
+
else if (v && typeof v === 'object') visit(v);
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
visit(program);
|
|
286
|
+
return pins;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/** Run `re` (global) against every raw string in the IR — raw expression
|
|
290
|
+
* values plus raw hal-op codes — returning capture group 1 of each match
|
|
291
|
+
* (the full match when the regex has no group). This is how references the
|
|
292
|
+
* text scanners must see but that never appear as IR call nodes (e.g. a
|
|
293
|
+
* rawCpp() escape hatch naming `__tc_dt_sw0` directly) are discovered. */
|
|
294
|
+
private collectRawMatches(program: ProgramIR | undefined, re: RegExp): Set<string> {
|
|
295
|
+
const found = new Set<string>();
|
|
296
|
+
if (!program) return found;
|
|
297
|
+
const scan = (text: string): void => {
|
|
298
|
+
for (const m of text.matchAll(re)) found.add(m[1] ?? m[0]);
|
|
299
|
+
};
|
|
300
|
+
const visit = (node: any): void => {
|
|
301
|
+
if (!node || typeof node !== 'object') return;
|
|
302
|
+
if (node.kind === 'raw' && typeof node.value === 'string') scan(node.value);
|
|
303
|
+
if (node.operation && typeof node.operation === 'object'
|
|
304
|
+
&& node.operation.operation === 'raw' && typeof node.operation.code === 'string') {
|
|
305
|
+
scan(node.operation.code);
|
|
306
|
+
}
|
|
307
|
+
for (const v of Object.values(node)) {
|
|
308
|
+
if (Array.isArray(v)) { for (const item of v) visit(item); }
|
|
309
|
+
else if (v && typeof v === 'object') visit(v);
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
visit(program);
|
|
313
|
+
return found;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/** Whether the wiring-compat GPIO read surface (__tc_gpio_read definition,
|
|
317
|
+
* __tc_gpio_dev dispatcher, and the wiring_compat polyfill's digitalRead /
|
|
318
|
+
* HIGH / LOW macros) must be emitted. Consumers: user digitalRead() calls
|
|
319
|
+
* (usesDigitalRead), the @typecad/safety voter (calls __tc_gpio_read
|
|
320
|
+
* directly via lowered raw text), and the UI runtime header's
|
|
321
|
+
* unconditional digitalRead() poll (entryHasUI — build-global, so every TU
|
|
322
|
+
* in a UI build carries the macros). With no analysis present (capability
|
|
323
|
+
* query), default to emitting — same convention as the uses() helper. */
|
|
324
|
+
private needsGpioReadShim(program?: ProgramIR, ctx?: PlatformContext): boolean {
|
|
325
|
+
if (program && programUsesSafety(program)) return true;
|
|
326
|
+
if (entryHasUI()) return true;
|
|
327
|
+
const a = (ctx as any)?.analysis;
|
|
328
|
+
return a ? !!a.usesDigitalRead : true;
|
|
329
|
+
}
|
|
330
|
+
|
|
250
331
|
shimLines(program?: ProgramIR, ctx?: PlatformContext): string[] {
|
|
251
332
|
const chip = this.resolveChip(ctx, program);
|
|
252
333
|
const isPrintf = this.resolveDebugMode(ctx) === 'printf';
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
//
|
|
291
|
-
//
|
|
292
|
-
//
|
|
293
|
-
//
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
334
|
+
const a = (ctx as any)?.analysis;
|
|
335
|
+
const uses = (f: string): boolean => (a ? !!a[f] : true);
|
|
336
|
+
const helpers = (a as { usedPolyfillHelpers?: Set<string> } | undefined)?.usedPolyfillHelpers;
|
|
337
|
+
|
|
338
|
+
// --- Core shim, gated item by item on actual use ------------------------
|
|
339
|
+
// A minimal program (blink) uses none of these, and its output carries no
|
|
340
|
+
// shim block at all. Everything up to the #endif composes into one guard
|
|
341
|
+
// body; the guard itself is only stamped when the body is non-empty.
|
|
342
|
+
const guardBody: string[] = [];
|
|
343
|
+
// CUTTLEFISH_UNDEFINED: needed when the file references null/undefined
|
|
344
|
+
// literals (usesNullish), emits nullish helper CALLS (usesNullishHelper),
|
|
345
|
+
// or has async functions (the async state machine uses the macro for
|
|
346
|
+
// default waitFor* timeouts — not visible to the nullish scanners).
|
|
347
|
+
if (uses('usesNullish') || uses('usesNullishHelper') || uses('hasAsync')) {
|
|
348
|
+
guardBody.push(
|
|
349
|
+
'#ifndef CUTTLEFISH_UNDEFINED',
|
|
350
|
+
'#define CUTTLEFISH_UNDEFINED 0',
|
|
351
|
+
'#endif',
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
// Nullish helpers: only when the file actually emits cuttlefish_nullish /
|
|
355
|
+
// cuttlefish_exists CALLS (?? / ?. lowering). A file that only references
|
|
356
|
+
// null/undefined literals needs just the macro above — the same
|
|
357
|
+
// distinction the setup emitter's strip filter documents.
|
|
358
|
+
if (uses('usesNullishHelper')) {
|
|
359
|
+
guardBody.push(
|
|
360
|
+
'template<typename T> inline bool cuttlefish_is_nullish(const T& v) { return false; }',
|
|
361
|
+
'inline bool cuttlefish_is_nullish(long long v) { return v == CUTTLEFISH_UNDEFINED; }',
|
|
362
|
+
'inline bool cuttlefish_is_nullish(int v) { return v == CUTTLEFISH_UNDEFINED; }',
|
|
363
|
+
'inline bool cuttlefish_is_nullish(double v) { return v == static_cast<double>(CUTTLEFISH_UNDEFINED); }',
|
|
364
|
+
'inline bool cuttlefish_is_nullish(bool v) { return v == false; }',
|
|
365
|
+
'template<typename T> inline bool cuttlefish_is_nullish(T* v) { return v == nullptr; }',
|
|
366
|
+
'template<typename T> inline bool cuttlefish_exists(const T& v) { return !cuttlefish_is_nullish(v); }',
|
|
367
|
+
'template<typename T, typename U> inline T cuttlefish_nullish(const T& a, U b) { return !cuttlefish_is_nullish(a) ? a : (T)b; }',
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
// millis() backed by the Zephyr uptime counter. uint32_t return matches
|
|
371
|
+
// the Arduino API the shared runtime expects (wraps every ~49.7 days).
|
|
372
|
+
// Kept when the program reads the clock itself — usesWallClock,
|
|
373
|
+
// deliberately WITHOUT the delay() conflation usesMillis carries, because
|
|
374
|
+
// Zephyr's delay lowers straight to k_msleep — or has a hidden poller:
|
|
375
|
+
// async functions / the async runtime, the setInterval/setTimeout
|
|
376
|
+
// scheduler, or a mounted UI's per-frame tick.
|
|
377
|
+
if (uses('usesWallClock') || uses('hasAsync') || (!a || a.timerCallCount > 0)
|
|
378
|
+
|| this.programUsesAsyncRuntime(program) || entryHasUI()) {
|
|
379
|
+
guardBody.push(
|
|
380
|
+
'inline unsigned long millis() { return static_cast<unsigned long>(k_uptime_get_32()); }',
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
// PROGMEM: only the (Arduino-oriented) UI runtime header can reference it.
|
|
384
|
+
if (entryHasUI()) {
|
|
385
|
+
guardBody.push(
|
|
386
|
+
'#ifndef PROGMEM', '#define PROGMEM', '#endif',
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
// map()/constrain() Arduino-API helpers — dead code unless called. The
|
|
390
|
+
// setup emitter ORs entryHasUI() into usesConstrain before we see it (the
|
|
391
|
+
// UI runtime's progress/range draw calls constrain).
|
|
392
|
+
if (uses('usesMap')) {
|
|
393
|
+
guardBody.push(
|
|
394
|
+
'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; }',
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
if (uses('usesConstrain')) {
|
|
398
|
+
guardBody.push(
|
|
399
|
+
'inline long constrain(long x, long a, long b) { return x < a ? a : (x > b ? b : x); }',
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
// Test-runner console helpers: @typecad/expect's Zephyr shim calls these
|
|
403
|
+
// for protocol output. Overloaded for string (const char*) and numeric
|
|
404
|
+
// (double) so the same call site works for markers and test values.
|
|
405
|
+
// Emitted only when the expect preprocessor actually injected the calls
|
|
406
|
+
// (tracked as usedPolyfillHelpers).
|
|
407
|
+
if (!a || !!helpers?.has('__tc_print') || !!helpers?.has('__tc_println')) {
|
|
408
|
+
guardBody.push(
|
|
409
|
+
'inline void __tc_print(const char* s) { printf("%s", s); }',
|
|
410
|
+
'inline void __tc_print(double v) { printf("%g", v); }',
|
|
411
|
+
'inline void __tc_println(const char* s) { printf("%s\\n", s); }',
|
|
412
|
+
'inline void __tc_println(double v) { printf("%g\\n", v); }',
|
|
297
413
|
);
|
|
298
414
|
}
|
|
299
415
|
|
|
300
416
|
// Per-peripheral bus state — gated on the same ctx.analysis.usesX flags as
|
|
301
417
|
// forcedIncludes, so an unused peripheral emits no state (and its header is
|
|
302
418
|
// not included). Mirrors framework-esp32's shimLines espInit block.
|
|
303
|
-
const a = (ctx as any)?.analysis;
|
|
304
|
-
const uses = (f: string): boolean => (a ? !!a[f] : true);
|
|
305
419
|
if (uses('usesI2C') && chip.i2c) {
|
|
306
|
-
for (let i = 0; i < chip.i2c.controllers.length; i++)
|
|
420
|
+
for (let i = 0; i < chip.i2c.controllers.length; i++) guardBody.push(...i2cInitLines(chip, i));
|
|
307
421
|
}
|
|
308
422
|
if (uses('usesSPI') && chip.spi) {
|
|
309
|
-
for (let i = 0; i < chip.spi.controllers.length; i++)
|
|
423
|
+
for (let i = 0; i < chip.spi.controllers.length; i++) guardBody.push(...spiInitLines(chip, i));
|
|
310
424
|
}
|
|
311
425
|
if (uses('usesUart') && chip.uart) {
|
|
312
|
-
for (let i = 0; i < chip.uart.controllers.length; i++)
|
|
426
|
+
for (let i = 0; i < chip.uart.controllers.length; i++) guardBody.push(...uartInitLines(chip, i));
|
|
313
427
|
}
|
|
314
|
-
if (uses('usesADC') && chip.adc)
|
|
315
|
-
if (uses('usesPWM') && chip.pwm)
|
|
316
|
-
if (uses('usesDAC') && chip.dac)
|
|
317
|
-
if (uses('usesHwtimer') && chip.hwtimer)
|
|
318
|
-
if (uses('usesInterrupts'))
|
|
319
|
-
if (uses('usesWDT') && chip.wdt)
|
|
320
|
-
if (uses('usesBle'))
|
|
428
|
+
if (uses('usesADC') && chip.adc) guardBody.push(...adcInitLines(chip));
|
|
429
|
+
if (uses('usesPWM') && chip.pwm) guardBody.push(...pwmInitLines(chip));
|
|
430
|
+
if (uses('usesDAC') && chip.dac) guardBody.push(...dacInitLines(chip));
|
|
431
|
+
if (uses('usesHwtimer') && chip.hwtimer) guardBody.push(...hwtimerInitLines(chip));
|
|
432
|
+
if (uses('usesInterrupts')) guardBody.push(...interruptInitLines(chip));
|
|
433
|
+
if (uses('usesWDT') && chip.wdt) guardBody.push(...wdtInitLines(chip));
|
|
434
|
+
if (uses('usesBle')) guardBody.push(...bleInitLines());
|
|
321
435
|
// Display runtime (rect/text renderer): the DIRECT-call display path (user
|
|
322
436
|
// code calling screen.display.fillRect etc., no @typecad/ui). Emitted only
|
|
323
437
|
// when the program uses display.* but is NOT a UI program — the UI display
|
|
@@ -331,18 +445,52 @@ export class ZephyrStrategy implements PlatformStrategy {
|
|
|
331
445
|
// previously dead code).
|
|
332
446
|
if (uses('usesDisplay') && !entryHasUI()) {
|
|
333
447
|
const rt = buildDisplayRuntime(this._displayState.profile);
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
448
|
+
guardBody.push(...rt.stateLines);
|
|
449
|
+
guardBody.push(rt.fontTable);
|
|
450
|
+
guardBody.push(rt.helpers);
|
|
451
|
+
}
|
|
452
|
+
if (uses('usesWifi')) guardBody.push(...wifiInitLines());
|
|
453
|
+
if (uses('usesHttp')) guardBody.push(...httpInitLines());
|
|
454
|
+
if (uses('usesMqtt')) guardBody.push(...mqttInitLines());
|
|
455
|
+
if (uses('usesPreferences')) guardBody.push(...preferencesInitLines());
|
|
456
|
+
if (uses('usesFS')) guardBody.push(...fsInitLines());
|
|
457
|
+
if (uses('usesRandom')) guardBody.push(...randomInitLines());
|
|
458
|
+
|
|
459
|
+
const lines: string[] = [];
|
|
460
|
+
if (guardBody.length > 0) {
|
|
461
|
+
lines.push(
|
|
462
|
+
'// cuttlefish runtime shim. Wrapped in a single include guard so the',
|
|
463
|
+
'// block is safe to emit into multiple headers and .cpp files within',
|
|
464
|
+
'// one translation unit (a .cpp may #include several headers that each',
|
|
465
|
+
'// carry the shim). The guard ensures the definitions are seen exactly',
|
|
466
|
+
'// once per TU.',
|
|
467
|
+
'#ifndef CUTTLEFISH_SHIM_DEFINED',
|
|
468
|
+
'#define CUTTLEFISH_SHIM_DEFINED',
|
|
469
|
+
...guardBody,
|
|
470
|
+
'#endif // CUTTLEFISH_SHIM_DEFINED',
|
|
471
|
+
);
|
|
337
472
|
}
|
|
338
|
-
if (uses('usesWifi')) lines.push(...wifiInitLines());
|
|
339
|
-
if (uses('usesHttp')) lines.push(...httpInitLines());
|
|
340
|
-
if (uses('usesMqtt')) lines.push(...mqttInitLines());
|
|
341
|
-
if (uses('usesPreferences')) lines.push(...preferencesInitLines());
|
|
342
|
-
if (uses('usesFS')) lines.push(...fsInitLines());
|
|
343
|
-
if (uses('usesRandom')) lines.push(...randomInitLines());
|
|
344
473
|
|
|
345
|
-
|
|
474
|
+
// Devicetree specs — one per board-defined GPIO pin, but ONLY for pins the
|
|
475
|
+
// program actually addresses (lowerGpio routes by pin number, and the
|
|
476
|
+
// structured gpio.* hal-op pins are visible here) plus aliases named
|
|
477
|
+
// verbatim in raw code (rawCpp escape hatches). Emitted OUTSIDE the single
|
|
478
|
+
// CUTTLEFISH_SHIM_DEFINED guard with a per-symbol guard: per-file pin sets
|
|
479
|
+
// differ, and in a multi-header TU the first header's TU-wide guard would
|
|
480
|
+
// otherwise hide the second header's specs. Without a program (capability
|
|
481
|
+
// query), emit them all.
|
|
482
|
+
const usedPins = this.collectGpioPinUsage(program);
|
|
483
|
+
const dtTextRefs = this.collectRawMatches(program, /__tc_dt_([A-Za-z0-9_]+)/g);
|
|
484
|
+
for (const spec of chip.gpio.dtSpecs) {
|
|
485
|
+
if (program && !usedPins.has(spec.pin) && !dtTextRefs.has(spec.dtSpec)) continue;
|
|
486
|
+
const guard = `__TC_DT_${spec.dtSpec.replace(/[^A-Za-z0-9_]/g, '_').toUpperCase()}_SPEC`;
|
|
487
|
+
lines.push(
|
|
488
|
+
`#ifndef ${guard}`,
|
|
489
|
+
`#define ${guard}`,
|
|
490
|
+
`static const struct gpio_dt_spec __tc_dt_${spec.dtSpec} = GPIO_DT_SPEC_GET(DT_ALIAS(${spec.dtSpec}), gpios);`,
|
|
491
|
+
`#endif // ${guard}`,
|
|
492
|
+
);
|
|
493
|
+
}
|
|
346
494
|
|
|
347
495
|
// --- Debug-mode halt + per-breakpoint disable registry ---
|
|
348
496
|
//
|
|
@@ -408,9 +556,12 @@ export class ZephyrStrategy implements PlatformStrategy {
|
|
|
408
556
|
'}',
|
|
409
557
|
);
|
|
410
558
|
|
|
411
|
-
// GPIO read shim:
|
|
412
|
-
//
|
|
413
|
-
// __tc_gpio_read,
|
|
559
|
+
// GPIO read shim: emitted only when something actually reads a pin at
|
|
560
|
+
// runtime — user digitalRead() calls, the @typecad/safety voter (calls
|
|
561
|
+
// __tc_gpio_read directly), or the UI runtime header's digitalRead() poll
|
|
562
|
+
// (init-press-input.ts). A program that only writes/toggles GPIO needs
|
|
563
|
+
// neither the dispatcher nor the reader.
|
|
564
|
+
//
|
|
414
565
|
// The signature is `int` to match wiring_compat's forward declaration —
|
|
415
566
|
// a uint32_t definition alongside it would leave the declared int
|
|
416
567
|
// overload undefined (int wins overload resolution for small integer
|
|
@@ -423,10 +574,12 @@ export class ZephyrStrategy implements PlatformStrategy {
|
|
|
423
574
|
// dispatcher that resolves the owning controller's device per pin;
|
|
424
575
|
// single-controller SoCs collapse it to a one-liner. Each DT_NODELABEL is
|
|
425
576
|
// still compile-time-resolved per branch, so it is always statically valid.
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
577
|
+
if (this.needsGpioReadShim(program, ctx)) {
|
|
578
|
+
lines.push(...emitGpioDevDispatcher(chip));
|
|
579
|
+
lines.push(
|
|
580
|
+
'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)); }',
|
|
581
|
+
);
|
|
582
|
+
}
|
|
430
583
|
// __tc_gpio_write / __tc_delay_us are only referenced via @typecad/safety
|
|
431
584
|
// lowering, so they stay gated on it.
|
|
432
585
|
if (program && programUsesSafety(program)) {
|
|
@@ -1137,6 +1290,45 @@ export class ZephyrStrategy implements PlatformStrategy {
|
|
|
1137
1290
|
}
|
|
1138
1291
|
|
|
1139
1292
|
generateNativePolyfills(program?: ProgramIR, ctx?: PlatformContext): RuntimePolyfillIR[] {
|
|
1293
|
+
// wiring_compat (digitalRead/HIGH/LOW macros + the __tc_gpio_read forward
|
|
1294
|
+
// declaration) is emitted only when something reads a pin: user
|
|
1295
|
+
// digitalRead() calls, the @typecad/safety voter, or the UI runtime
|
|
1296
|
+
// header's unconditional digitalRead() poll (init-press-input.ts — the
|
|
1297
|
+
// loop body is dead when no pin watchers are configured but must
|
|
1298
|
+
// compile). needsGpioReadShim defaults to true without analysis so
|
|
1299
|
+
// capability queries keep seeing it.
|
|
1300
|
+
const wiringCompat: RuntimePolyfillIR = {
|
|
1301
|
+
// Wiring-compatibility shims for symbols the UI runtime header
|
|
1302
|
+
// references unconditionally (e.g. init-press-input.ts polls pin
|
|
1303
|
+
// watchers via digitalRead/HIGH/LOW even when none are configured —
|
|
1304
|
+
// the loop body is dead but must compile). Zephyr lowers GPIO through
|
|
1305
|
+
// its __tc_gpio_* helpers (defined in shimLines); these macros route
|
|
1306
|
+
// the Wiring tokens to them.
|
|
1307
|
+
kind: 'polyfill',
|
|
1308
|
+
id: 'wiring_compat',
|
|
1309
|
+
domain: 'standard' as const,
|
|
1310
|
+
requiredIncludes: [],
|
|
1311
|
+
forwardDeclarations: [
|
|
1312
|
+
// Forward-declared so the digitalRead macro (below) can reference it
|
|
1313
|
+
// before the shim block defines the body. The shim emits the full
|
|
1314
|
+
// definition via gpio_pin_get_raw.
|
|
1315
|
+
'int __tc_gpio_read(int pin);',
|
|
1316
|
+
],
|
|
1317
|
+
helperStructs: [],
|
|
1318
|
+
helperFunctions: [],
|
|
1319
|
+
shimMacros: [
|
|
1320
|
+
'#ifndef HIGH',
|
|
1321
|
+
'#define HIGH 1',
|
|
1322
|
+
'#endif',
|
|
1323
|
+
'#ifndef LOW',
|
|
1324
|
+
'#define LOW 0',
|
|
1325
|
+
'#endif',
|
|
1326
|
+
'#ifndef digitalRead',
|
|
1327
|
+
'#define digitalRead(pin) __tc_gpio_read(pin)',
|
|
1328
|
+
'#endif',
|
|
1329
|
+
],
|
|
1330
|
+
dependencies: [],
|
|
1331
|
+
};
|
|
1140
1332
|
const polyfills: RuntimePolyfillIR[] = [
|
|
1141
1333
|
{
|
|
1142
1334
|
kind: 'polyfill',
|
|
@@ -1151,38 +1343,7 @@ export class ZephyrStrategy implements PlatformStrategy {
|
|
|
1151
1343
|
shimMacros: [],
|
|
1152
1344
|
dependencies: [],
|
|
1153
1345
|
},
|
|
1154
|
-
|
|
1155
|
-
// Wiring-compatibility shims for symbols the UI runtime header
|
|
1156
|
-
// references unconditionally (e.g. init-press-input.ts polls pin
|
|
1157
|
-
// watchers via digitalRead/HIGH/LOW even when none are configured —
|
|
1158
|
-
// the loop body is dead but must compile). Zephyr lowers GPIO through
|
|
1159
|
-
// its __tc_gpio_* helpers (defined in shimLines); these macros route
|
|
1160
|
-
// the Wiring tokens to them.
|
|
1161
|
-
kind: 'polyfill',
|
|
1162
|
-
id: 'wiring_compat',
|
|
1163
|
-
domain: 'standard' as const,
|
|
1164
|
-
requiredIncludes: [],
|
|
1165
|
-
forwardDeclarations: [
|
|
1166
|
-
// Forward-declared so the digitalRead macro (below) can reference it
|
|
1167
|
-
// before the shim block defines the body. The shim emits the full
|
|
1168
|
-
// definition via gpio_pin_get_raw.
|
|
1169
|
-
'int __tc_gpio_read(int pin);',
|
|
1170
|
-
],
|
|
1171
|
-
helperStructs: [],
|
|
1172
|
-
helperFunctions: [],
|
|
1173
|
-
shimMacros: [
|
|
1174
|
-
'#ifndef HIGH',
|
|
1175
|
-
'#define HIGH 1',
|
|
1176
|
-
'#endif',
|
|
1177
|
-
'#ifndef LOW',
|
|
1178
|
-
'#define LOW 0',
|
|
1179
|
-
'#endif',
|
|
1180
|
-
'#ifndef digitalRead',
|
|
1181
|
-
'#define digitalRead(pin) __tc_gpio_read(pin)',
|
|
1182
|
-
'#endif',
|
|
1183
|
-
],
|
|
1184
|
-
dependencies: [],
|
|
1185
|
-
},
|
|
1346
|
+
...(this.needsGpioReadShim(program, ctx) ? [wiringCompat] : []),
|
|
1186
1347
|
{
|
|
1187
1348
|
// STL-free string-method polyfills. String methods (.toUpperCase(),
|
|
1188
1349
|
// .includes(), .substring(), …) lower at IR level to __tc_* helpers for
|
|
@@ -1417,22 +1578,10 @@ struct __tc_StaticArray {
|
|
|
1417
1578
|
// Named display-profile registry: maps config `profile` values (e.g.
|
|
1418
1579
|
// "st7796-zephyr") to the shared DisplayProfile shape so transpile.ts can
|
|
1419
1580
|
// resolve them per-framework. The Zephyr profiles are DT-binding descriptors;
|
|
1420
|
-
//
|
|
1421
|
-
//
|
|
1581
|
+
// BUILT_IN_PROFILES (display/profiles.ts) is the single DT-binding →
|
|
1582
|
+
// shared-shape mapping, shared with the preview's registry loader.
|
|
1422
1583
|
getProfileRegistry(): Map<string, DisplayProfile> {
|
|
1423
|
-
|
|
1424
|
-
for (const [name, p] of Object.entries(ZEPHYR_DISPLAY_PROFILES)) {
|
|
1425
|
-
m.set(name, {
|
|
1426
|
-
driver: p.driver,
|
|
1427
|
-
width: p.width,
|
|
1428
|
-
height: p.height,
|
|
1429
|
-
nativeWidth: p.nativeWidth,
|
|
1430
|
-
nativeHeight: p.nativeHeight,
|
|
1431
|
-
colorFormat: p.colorFormat,
|
|
1432
|
-
rotation: p.rotation ?? 1,
|
|
1433
|
-
});
|
|
1434
|
-
}
|
|
1435
|
-
return m;
|
|
1584
|
+
return new Map(Object.entries(BUILT_IN_PROFILES));
|
|
1436
1585
|
}
|
|
1437
1586
|
|
|
1438
1587
|
colorFormat(): 'rgb565' | 'rgb666' | 'rgb888' | 'mono' {
|