@typecad/framework-zephyr 1.0.0-alpha.10 → 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/chips/esp32.js +12 -0
- package/dist/chips/types.d.ts +30 -0
- package/dist/chips/xiao-ble.js +6 -0
- package/dist/display/gfx.d.ts +12 -3
- package/dist/display/gfx.js +130 -17
- package/dist/display/profiles.d.ts +25 -0
- package/dist/display/profiles.js +30 -0
- package/dist/display/touch-adapter.d.ts +3 -4
- package/dist/display/touch-adapter.js +119 -16
- package/dist/display/ui-adapter.js +308 -139
- package/dist/doctor.d.ts +3 -3
- package/dist/doctor.js +56 -29
- package/dist/dt-config/kconfig.d.ts +8 -1
- package/dist/dt-config/kconfig.js +40 -5
- package/dist/dt-config/overlay.d.ts +18 -1
- package/dist/dt-config/overlay.js +124 -26
- package/dist/framework.manifest.d.ts +29 -28
- package/dist/framework.manifest.js +46 -17
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -0
- package/dist/licenses.d.ts +59 -0
- package/dist/licenses.js +347 -0
- package/dist/lowering/ble.js +3 -1
- package/dist/lowering/dac.d.ts +15 -0
- package/dist/lowering/dac.js +69 -0
- package/dist/lowering/fs.d.ts +16 -0
- package/dist/lowering/fs.js +121 -0
- package/dist/lowering/hwtimer.d.ts +15 -0
- package/dist/lowering/hwtimer.js +84 -0
- package/dist/lowering/index.d.ts +4 -1
- package/dist/lowering/index.js +12 -3
- package/dist/strategy.d.ts +4 -0
- package/dist/strategy.js +275 -35
- package/dist/toolchain/compat.js +10 -1
- package/dist/toolchain/env-check.d.ts +93 -0
- package/dist/toolchain/env-check.js +190 -0
- package/dist/toolchain/index.js +39 -9
- package/dist/toolchain/scaffold.js +7 -2
- package/dist/toolchain/west-discover.d.ts +11 -3
- package/dist/toolchain/west-discover.js +84 -7
- package/dist/toolchain/west-spawn.js +15 -0
- package/package.json +4 -4
- package/src/chips/esp32.ts +12 -0
- package/src/chips/types.ts +29 -0
- package/src/chips/xiao-ble.ts +6 -0
- package/src/display/gfx.ts +135 -19
- package/src/display/profiles.ts +53 -0
- package/src/display/touch-adapter.ts +119 -15
- package/src/display/ui-adapter.ts +311 -139
- package/src/doctor.ts +77 -56
- package/src/dt-config/kconfig.ts +45 -6
- package/src/dt-config/overlay.ts +159 -29
- package/src/framework.manifest.ts +47 -17
- package/src/index.ts +6 -0
- package/src/licenses.ts +425 -0
- package/src/lowering/ble.ts +3 -1
- package/src/lowering/dac.ts +82 -0
- package/src/lowering/fs.ts +127 -0
- package/src/lowering/hwtimer.ts +101 -0
- package/src/lowering/index.ts +9 -2
- package/src/strategy.ts +271 -35
- package/src/toolchain/compat.ts +154 -145
- package/src/toolchain/env-check.ts +285 -0
- package/src/toolchain/index.ts +40 -9
- package/src/toolchain/scaffold.ts +7 -2
- package/src/toolchain/west-discover.ts +92 -9
- package/src/toolchain/west-spawn.ts +15 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Hardware-timer lowering — Zephyr counter API
|
|
3
|
+
//
|
|
4
|
+
// HAL `hwtimer.*` (instance → frequency/overflow/start/stop) maps onto Zephyr's
|
|
5
|
+
// counter driver (<zephyr/drivers/counter.h>). A chip declares the counter
|
|
6
|
+
// devices it exposes (e.g. nRF RTC1 — RTC0 is kernel-owned) via
|
|
7
|
+
// `hwtimer.controllers[instance].nodeLabel`.
|
|
8
|
+
//
|
|
9
|
+
// Frequency model: a Zephyr counter has a fixed clock; the HAL
|
|
10
|
+
// set_frequency(hz) is realized as a top value of counter_freq/hz with an alarm
|
|
11
|
+
// callback (the on_overflow handler). start() arms both the top value and the
|
|
12
|
+
// callback so the call order (setFrequency → onOverflow → start, or any
|
|
13
|
+
// permutation) is handled uniformly.
|
|
14
|
+
//
|
|
15
|
+
// Targets without a free counter omit `hwtimer`; usage lowers to a comment and
|
|
16
|
+
// profileDiagnostics flags it. The JS setInterval/setTimeout polyfill
|
|
17
|
+
// (k_timer) is a separate surface and is unaffected.
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
|
|
21
|
+
import type { ZephyrChipDescriptor } from '../chips/types.js';
|
|
22
|
+
|
|
23
|
+
/** Per-instance emitted symbol stems. */
|
|
24
|
+
function devVar(instance: number): string {
|
|
25
|
+
return `__tc_hw_dev_${instance}`;
|
|
26
|
+
}
|
|
27
|
+
function hzVar(instance: number): string {
|
|
28
|
+
return `__tc_hw_hz_${instance}`;
|
|
29
|
+
}
|
|
30
|
+
function cbVar(instance: number): string {
|
|
31
|
+
return `__tc_hw_cb_${instance}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Emit the per-instance counter device handles + frequency/callback state.
|
|
36
|
+
* Called from shimLines when the program uses hardware timers.
|
|
37
|
+
*/
|
|
38
|
+
export function hwtimerInitLines(chip: ZephyrChipDescriptor): string[] {
|
|
39
|
+
const controllers = chip.hwtimer?.controllers ?? [];
|
|
40
|
+
if (controllers.length === 0) return [];
|
|
41
|
+
const lines: string[] = ['// CUTTLEFISH_HWTIMER_BEGIN'];
|
|
42
|
+
controllers.forEach((c, i) => {
|
|
43
|
+
lines.push(
|
|
44
|
+
`static const struct device* ${devVar(i)} = DEVICE_DT_GET(DT_NODELABEL(${c.nodeLabel}));`,
|
|
45
|
+
`static uint32_t ${hzVar(i)} = 1;`,
|
|
46
|
+
`static counter_top_callback_t ${cbVar(i)} = NULL;`,
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
lines.push('// CUTTLEFISH_HWTIMER_END');
|
|
50
|
+
return lines;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Resolve a HAL hwtimer.* op to Zephyr C++.
|
|
55
|
+
* Returns `{ code }` for statement ops.
|
|
56
|
+
*/
|
|
57
|
+
export function lowerHwtimer(
|
|
58
|
+
op: HALOpIR,
|
|
59
|
+
chip: ZephyrChipDescriptor,
|
|
60
|
+
): { code?: string; expression?: string } {
|
|
61
|
+
const o = op as any;
|
|
62
|
+
const controllers = chip.hwtimer?.controllers ?? [];
|
|
63
|
+
|
|
64
|
+
// No counter on this target. Comment + (in profileDiagnostics) a clear error.
|
|
65
|
+
if (controllers.length === 0) {
|
|
66
|
+
return { code: `/* hwtimer instance ${o.instance}: no counter device on ${chip.id} */` };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const instance = typeof o.instance === 'number' ? o.instance : parseInt(String(o.instance), 10);
|
|
70
|
+
if (isNaN(instance) || instance < 0 || instance >= controllers.length) {
|
|
71
|
+
return { code: `/* hwtimer instance ${o.instance}: out of range on ${chip.id} */` };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
switch (op.operation) {
|
|
75
|
+
case 'hwtimer.set_frequency':
|
|
76
|
+
return { code: `${hzVar(instance)} = ${o.hz};` };
|
|
77
|
+
case 'hwtimer.on_overflow':
|
|
78
|
+
return { code: `${cbVar(instance)} = (${o.handler});` };
|
|
79
|
+
case 'hwtimer.start':
|
|
80
|
+
// Arm the top value (counter_freq / desired_hz) + the overflow callback,
|
|
81
|
+
// then start the counter. Doing both here handles any call order — the
|
|
82
|
+
// HAL typical sequence (setFrequency → onOverflow → start) and permutations.
|
|
83
|
+
return {
|
|
84
|
+
code: [
|
|
85
|
+
`{`,
|
|
86
|
+
` uint32_t __f = counter_get_frequency(${devVar(instance)});`,
|
|
87
|
+
` uint32_t __top = __f ? (__f / ${hzVar(instance)}) : 0U;`,
|
|
88
|
+
` if (__top > 0U) { (void)counter_set_top_value(${devVar(instance)}, __top, ${cbVar(instance)}, NULL); }`,
|
|
89
|
+
` (void)counter_start(${devVar(instance)});`,
|
|
90
|
+
`}`,
|
|
91
|
+
].join(' '),
|
|
92
|
+
};
|
|
93
|
+
case 'hwtimer.stop':
|
|
94
|
+
return { code: `(void)counter_stop(${devVar(instance)});` };
|
|
95
|
+
default:
|
|
96
|
+
throw new Error(
|
|
97
|
+
`framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
|
|
98
|
+
`Open an issue or use rawCpp() to emit it manually.`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
}
|
package/src/lowering/index.ts
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
//
|
|
4
4
|
// Routes a HALOpIR to the per-category lowering module by category prefix
|
|
5
5
|
// (e.g. 'gpio.write' → lowerGpio). Returns undefined for categories the
|
|
6
|
-
// framework does not lower (display/
|
|
7
|
-
//
|
|
6
|
+
// framework does not lower (display/mdns/ota/rmt/
|
|
7
|
+
// capacitive/temp/espnow/crypto/i2s/twai/usb/eth/pcnt/mcpwm — see
|
|
8
8
|
// the manifest), so the transpiler falls back and the manifest validator
|
|
9
9
|
// cross-checks the unsupported categories. (board.* is registered below but is
|
|
10
10
|
// a dead-letter — its values are constant-folded at IR-build time.)
|
|
@@ -38,11 +38,15 @@ import { lowerMqtt } from './mqtt.js';
|
|
|
38
38
|
import { lowerPreferences } from './preferences.js';
|
|
39
39
|
import { lowerBoard } from './board.js';
|
|
40
40
|
import { lowerRandom } from './random.js';
|
|
41
|
+
import { lowerDac } from './dac.js';
|
|
42
|
+
import { lowerFs } from './fs.js';
|
|
43
|
+
import { lowerHwtimer } from './hwtimer.js';
|
|
41
44
|
|
|
42
45
|
export {
|
|
43
46
|
lowerGpio, lowerTiming, lowerAdc, lowerPwm, lowerI2c, lowerSpi, lowerUart,
|
|
44
47
|
lowerInterrupt, lowerWdt, lowerPower, lowerTone, lowerPulseOrShift, lowerBle,
|
|
45
48
|
lowerWifi, lowerHttp, lowerMqtt, lowerPreferences, lowerBoard, lowerRandom,
|
|
49
|
+
lowerDac, lowerFs, lowerHwtimer,
|
|
46
50
|
};
|
|
47
51
|
|
|
48
52
|
/**
|
|
@@ -58,11 +62,13 @@ export function lowerHalOp(
|
|
|
58
62
|
if (op.operation.startsWith('timing.')) return lowerTiming(op);
|
|
59
63
|
if (op.operation.startsWith('adc.')) return lowerAdc(op, chip);
|
|
60
64
|
if (op.operation.startsWith('pwm.')) return lowerPwm(op, chip);
|
|
65
|
+
if (op.operation.startsWith('dac.')) return lowerDac(op, chip);
|
|
61
66
|
if (op.operation.startsWith('i2c.')) return lowerI2c(op, chip);
|
|
62
67
|
if (op.operation.startsWith('spi.')) return lowerSpi(op, chip);
|
|
63
68
|
if (op.operation.startsWith('uart.')) return lowerUart(op);
|
|
64
69
|
if (op.operation.startsWith('interrupt.')) return lowerInterrupt(op, chip);
|
|
65
70
|
if (op.operation.startsWith('wdt.')) return lowerWdt(op);
|
|
71
|
+
if (op.operation.startsWith('hwtimer.')) return lowerHwtimer(op, chip);
|
|
66
72
|
if (op.operation.startsWith('power.')) return lowerPower(op);
|
|
67
73
|
if (op.operation.startsWith('tone.')) return lowerTone(op, chip);
|
|
68
74
|
// pulse.* and shift.* share a bit-bang lowering module.
|
|
@@ -74,6 +80,7 @@ export function lowerHalOp(
|
|
|
74
80
|
if (op.operation.startsWith('http.')) return lowerHttp(op);
|
|
75
81
|
if (op.operation.startsWith('mqtt.')) return lowerMqtt(op);
|
|
76
82
|
if (op.operation.startsWith('preferences.')) return lowerPreferences(op);
|
|
83
|
+
if (op.operation.startsWith('fs.')) return lowerFs(op);
|
|
77
84
|
// board.resolve is constant-folded at IR-build time; lowerBoard is the
|
|
78
85
|
// dead-letter reached only on an unresolvable path.
|
|
79
86
|
if (op.operation.startsWith('board.')) return lowerBoard(op);
|
package/src/strategy.ts
CHANGED
|
@@ -36,7 +36,9 @@ import type {
|
|
|
36
36
|
} from '@typecad/cuttlefish/api/shared';
|
|
37
37
|
import { DEFAULT_STDLIB_SUPPORT } from '@typecad/cuttlefish/api/shared';
|
|
38
38
|
import { buildWorkerRuntimePolyfill } from '@typecad/cuttlefish/api/shared';
|
|
39
|
+
import { applyStringMethodRewrites } from '@typecad/cuttlefish/api/shared';
|
|
39
40
|
import { programUsesSafety } from '@typecad/cuttlefish/api';
|
|
41
|
+
import { entryHasUI } from '@typecad/cuttlefish/ui-hook';
|
|
40
42
|
import { chipForTarget, setActiveChip, getActiveChip } from './chips/index.js';
|
|
41
43
|
import { resolveChipFromBoard } from './chips/resolve.js';
|
|
42
44
|
import { emitGpioDevDispatcher } from './chips/controllers.js';
|
|
@@ -44,6 +46,9 @@ import { lowerHalOp } from './lowering/index.js';
|
|
|
44
46
|
import { buildZephyrWorkerBacking } from './lowering/worker-backing.js';
|
|
45
47
|
import { adcInitLines } from './lowering/adc.js';
|
|
46
48
|
import { pwmInitLines } from './lowering/pwm.js';
|
|
49
|
+
import { dacInitLines } from './lowering/dac.js';
|
|
50
|
+
import { fsInitLines } from './lowering/fs.js';
|
|
51
|
+
import { hwtimerInitLines } from './lowering/hwtimer.js';
|
|
47
52
|
import { i2cInitLines } from './lowering/i2c.js';
|
|
48
53
|
import { spiInitLines } from './lowering/spi.js';
|
|
49
54
|
import { uartInitLines } from './lowering/uart.js';
|
|
@@ -137,6 +142,13 @@ export class ZephyrStrategy implements PlatformStrategy {
|
|
|
137
142
|
if (isPrintf && !inc.includes('<zephyr/drivers/uart.h>')) inc.push('<zephyr/drivers/uart.h>');
|
|
138
143
|
if (uses('usesADC')) inc.push('<zephyr/drivers/adc.h>');
|
|
139
144
|
if (uses('usesPWM')) inc.push('<zephyr/drivers/pwm.h>');
|
|
145
|
+
if (uses('usesDAC')) inc.push('<zephyr/drivers/dac.h>');
|
|
146
|
+
// Filesystem: littlefs on the storage partition. <cstring> backs the
|
|
147
|
+
// shim's strlen; the storage/flash_map + fs/littlefs headers carry the
|
|
148
|
+
// FIXED_PARTITION_ID macro + FS_LITTLEFS_DECLARE_DEFAULT_CONFIG the shim uses.
|
|
149
|
+
if (uses('usesFS')) inc.push('<zephyr/fs/fs.h>', '<zephyr/fs/littlefs.h>', '<zephyr/storage/flash_map.h>', '<cstring>');
|
|
150
|
+
// Hardware timers via the counter driver.
|
|
151
|
+
if (uses('usesHwtimer')) inc.push('<zephyr/drivers/counter.h>');
|
|
140
152
|
if (uses('usesWDT')) inc.push('<zephyr/drivers/watchdog.h>');
|
|
141
153
|
if (uses('usesPower')) inc.push('<zephyr/pm/pm.h>', '<zephyr/pm/state.h>', '<zephyr/pm/policy.h>');
|
|
142
154
|
// BLE: the bt_* GATT API + the flat-string headers the shim uses. <string>
|
|
@@ -145,9 +157,10 @@ export class ZephyrStrategy implements PlatformStrategy {
|
|
|
145
157
|
// program-analysis usesStdString detector doesn't see types generated by
|
|
146
158
|
// the BLE lowering layer — so without forcing <string> here, any BLE server
|
|
147
159
|
// with a Utf8 characteristic fails to compile ('std::string does not name a
|
|
148
|
-
// type').
|
|
149
|
-
//
|
|
150
|
-
|
|
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>');
|
|
151
164
|
// Display: the analyzer's usesDisplay flag (set by display.* hal-ops) drives
|
|
152
165
|
// this include. When ctx.analysis is absent (capability query), uses()
|
|
153
166
|
// defaults to true so a real build never strips it.
|
|
@@ -300,19 +313,23 @@ export class ZephyrStrategy implements PlatformStrategy {
|
|
|
300
313
|
}
|
|
301
314
|
if (uses('usesADC') && chip.adc) lines.push(...adcInitLines(chip));
|
|
302
315
|
if (uses('usesPWM') && chip.pwm) lines.push(...pwmInitLines(chip));
|
|
316
|
+
if (uses('usesDAC') && chip.dac) lines.push(...dacInitLines(chip));
|
|
317
|
+
if (uses('usesHwtimer') && chip.hwtimer) lines.push(...hwtimerInitLines(chip));
|
|
303
318
|
if (uses('usesInterrupts')) lines.push(...interruptInitLines(chip));
|
|
304
319
|
if (uses('usesWDT') && chip.wdt) lines.push(...wdtInitLines(chip));
|
|
305
320
|
if (uses('usesBle')) lines.push(...bleInitLines());
|
|
306
|
-
// Display runtime (
|
|
307
|
-
//
|
|
308
|
-
// display
|
|
309
|
-
//
|
|
310
|
-
//
|
|
311
|
-
//
|
|
312
|
-
//
|
|
313
|
-
//
|
|
314
|
-
//
|
|
315
|
-
|
|
321
|
+
// Display runtime (rect/text renderer): the DIRECT-call display path (user
|
|
322
|
+
// code calling screen.display.fillRect etc., no @typecad/ui). Emitted only
|
|
323
|
+
// when the program uses display.* but is NOT a UI program — the UI display
|
|
324
|
+
// adapter (emitted by cuttlefish's emitUIRuntime, solely under entryHasUI())
|
|
325
|
+
// defines the same display_init symbol, so emitting both would collide.
|
|
326
|
+
// `providesDisplayAdapter()` is a static capability (always true here) and
|
|
327
|
+
// does NOT track whether the adapter is actually emitted for THIS build, so
|
|
328
|
+
// the per-program UI signal (entryHasUI) is the correct gate. Without this,
|
|
329
|
+
// a direct display.* program has no definition for display_init/
|
|
330
|
+
// display_fill_rect/draw_rect/draw_text/flush (the gfx runtime was
|
|
331
|
+
// previously dead code).
|
|
332
|
+
if (uses('usesDisplay') && !entryHasUI()) {
|
|
316
333
|
const rt = buildDisplayRuntime(this._displayState.profile);
|
|
317
334
|
lines.push(...rt.stateLines);
|
|
318
335
|
lines.push(rt.fontTable);
|
|
@@ -322,6 +339,7 @@ export class ZephyrStrategy implements PlatformStrategy {
|
|
|
322
339
|
if (uses('usesHttp')) lines.push(...httpInitLines());
|
|
323
340
|
if (uses('usesMqtt')) lines.push(...mqttInitLines());
|
|
324
341
|
if (uses('usesPreferences')) lines.push(...preferencesInitLines());
|
|
342
|
+
if (uses('usesFS')) lines.push(...fsInitLines());
|
|
325
343
|
if (uses('usesRandom')) lines.push(...randomInitLines());
|
|
326
344
|
|
|
327
345
|
lines.push('#endif // CUTTLEFISH_SHIM_DEFINED');
|
|
@@ -390,21 +408,29 @@ export class ZephyrStrategy implements PlatformStrategy {
|
|
|
390
408
|
'}',
|
|
391
409
|
);
|
|
392
410
|
|
|
393
|
-
//
|
|
394
|
-
//
|
|
395
|
-
//
|
|
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).
|
|
396
418
|
//
|
|
397
|
-
// The pin is a RUNTIME value here (
|
|
398
|
-
//
|
|
399
|
-
// on a multi-controller SoC (ESP32-S3:
|
|
400
|
-
// Emit a tiny __tc_gpio_dev(pin)
|
|
401
|
-
// controller's device per pin;
|
|
402
|
-
// one-liner. Each DT_NODELABEL is
|
|
403
|
-
// 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.
|
|
404
432
|
if (program && programUsesSafety(program)) {
|
|
405
|
-
lines.push(...emitGpioDevDispatcher(chip));
|
|
406
433
|
lines.push(
|
|
407
|
-
'inline int __tc_gpio_read(uint32_t pin) { return gpio_pin_get_raw(__tc_gpio_dev(pin), pin); }',
|
|
408
434
|
'inline void __tc_gpio_write(uint32_t pin, uint32_t value) { gpio_pin_set_raw(__tc_gpio_dev(pin), pin, value); }',
|
|
409
435
|
'#ifndef __TC_DELAY_US_DEFINED',
|
|
410
436
|
'#define __TC_DELAY_US_DEFINED',
|
|
@@ -429,6 +455,8 @@ export class ZephyrStrategy implements PlatformStrategy {
|
|
|
429
455
|
const outputPins = new Set<number>();
|
|
430
456
|
const adcReadPins = new Set<number>();
|
|
431
457
|
const interruptPins = new Set<number>();
|
|
458
|
+
const dacPins = new Set<number>();
|
|
459
|
+
const hwtimerInstances = new Set<number>();
|
|
432
460
|
let usesWifiOps = false;
|
|
433
461
|
let usesHttpOps = false;
|
|
434
462
|
let usesMqttOps = false;
|
|
@@ -449,6 +477,15 @@ export class ZephyrStrategy implements PlatformStrategy {
|
|
|
449
477
|
if (op.operation === 'interrupt.attach' && typeof op.pin === 'number') {
|
|
450
478
|
interruptPins.add(op.pin);
|
|
451
479
|
}
|
|
480
|
+
if (op.operation === 'dac.write' && typeof op.pin === 'number') {
|
|
481
|
+
dacPins.add(op.pin);
|
|
482
|
+
}
|
|
483
|
+
if (typeof op.operation === 'string' && op.operation.startsWith('hwtimer.')) {
|
|
484
|
+
const inst = typeof op.instance === 'number'
|
|
485
|
+
? op.instance
|
|
486
|
+
: parseInt(String(op.instance), 10);
|
|
487
|
+
if (!isNaN(inst)) hwtimerInstances.add(inst);
|
|
488
|
+
}
|
|
452
489
|
if (typeof op.operation === 'string' && op.operation.startsWith('wifi.')) {
|
|
453
490
|
usesWifiOps = true;
|
|
454
491
|
}
|
|
@@ -510,6 +547,56 @@ export class ZephyrStrategy implements PlatformStrategy {
|
|
|
510
547
|
}
|
|
511
548
|
}
|
|
512
549
|
|
|
550
|
+
// ── DAC pin validity ────────────────────────────────────────────────────
|
|
551
|
+
// dac.write resolves a HAL pin to a channel via the chip descriptor's
|
|
552
|
+
// dac.channels map. A pin not in that map lowers to a comment (silent
|
|
553
|
+
// no-op), and a chip without a `dac` entry (nRF52840, ESP32-S3) has no DAC
|
|
554
|
+
// at all. Flag either case so the user gets a clear message instead of a
|
|
555
|
+
// pin that silently does nothing.
|
|
556
|
+
if (dacPins.size > 0) {
|
|
557
|
+
const dacChannels = new Set((chip.dac?.channels ?? []).map((c) => c.pin));
|
|
558
|
+
for (const pin of dacPins) {
|
|
559
|
+
if (!dacChannels.has(pin)) {
|
|
560
|
+
diags.push({
|
|
561
|
+
severity: 'error',
|
|
562
|
+
code: 'zephyr-dac-pin-unavailable',
|
|
563
|
+
message: `GPIO ${pin} is not a DAC channel on ${chip.id} and cannot be driven with dac.write.`,
|
|
564
|
+
hint: dacChannels.size > 0
|
|
565
|
+
? `Use a DAC-capable pin. On ${chip.id}: ${[...dacChannels].sort((x, y) => x - y).join(', ')}.`
|
|
566
|
+
: `${chip.id} has no DAC. Use an esp32_devkitc target (ESP32 DAC on GPIO25/26).`,
|
|
567
|
+
source: program.fileName,
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// ── Hardware-timer instance validity ────────────────────────────────────
|
|
574
|
+
// hwtimer.* resolves the instance index to a counter device via the chip
|
|
575
|
+
// descriptor's hwtimer.controllers. A chip without that entry (or an
|
|
576
|
+
// out-of-range instance) lowers to a comment — flag it so the user knows
|
|
577
|
+
// the timer will never fire.
|
|
578
|
+
if (hwtimerInstances.size > 0) {
|
|
579
|
+
const controllerCount = chip.hwtimer?.controllers.length ?? 0;
|
|
580
|
+
for (const inst of hwtimerInstances) {
|
|
581
|
+
if (controllerCount === 0) {
|
|
582
|
+
diags.push({
|
|
583
|
+
severity: 'error',
|
|
584
|
+
code: 'zephyr-hwtimer-unavailable',
|
|
585
|
+
message: `Hardware timer instance ${inst} is used but ${chip.id} exposes no free counter device.`,
|
|
586
|
+
hint: `${chip.id} declares no hwtimer.controllers. Use a target with a free counter (e.g. nRF RTC1).`,
|
|
587
|
+
source: program.fileName,
|
|
588
|
+
});
|
|
589
|
+
} else if (inst < 0 || inst >= controllerCount) {
|
|
590
|
+
diags.push({
|
|
591
|
+
severity: 'error',
|
|
592
|
+
code: 'zephyr-hwtimer-instance-out-of-range',
|
|
593
|
+
message: `Hardware timer instance ${inst} is out of range on ${chip.id} (0..${controllerCount - 1}).`,
|
|
594
|
+
source: program.fileName,
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
513
600
|
// ── WiFi target validity ────────────────────────────────────────────────
|
|
514
601
|
// WiFi ops require a chip with a WiFi radio. The ESP32-S3 descriptor sets
|
|
515
602
|
// wifi.supported; the XIAO nRF52840 omits it (no radio). Flag wifi usage on
|
|
@@ -656,6 +743,18 @@ export class ZephyrStrategy implements PlatformStrategy {
|
|
|
656
743
|
v = v.replace(/\bundefined\b/g, 'CUTTLEFISH_UNDEFINED');
|
|
657
744
|
v = v.replace(/\bnull\b/g, 'CUTTLEFISH_UNDEFINED');
|
|
658
745
|
}
|
|
746
|
+
// String-method lowering is shared across targets (see string-method-
|
|
747
|
+
// registry). Zephyr's strings are const char*, so the __tc_* helpers this
|
|
748
|
+
// rewrites to (defined by the string_methods polyfill) take const char*.
|
|
749
|
+
// includes/startsWith lower to inline strstr/strncmp (matching framework-
|
|
750
|
+
// arduino); everything else → a __tc_* helper call.
|
|
751
|
+
v = applyStringMethodRewrites(v, {
|
|
752
|
+
wrapReceiverFor: new Set(['indexOf']),
|
|
753
|
+
special: {
|
|
754
|
+
includes: (recv, args) => `(strstr(${recv}, ${args[0]}) != NULL)`,
|
|
755
|
+
startsWith: (recv, args) => `(strncmp(${recv}, ${args[0]}, strlen(${args[0]})) == 0)`,
|
|
756
|
+
},
|
|
757
|
+
});
|
|
659
758
|
return v;
|
|
660
759
|
}
|
|
661
760
|
|
|
@@ -770,6 +869,62 @@ export class ZephyrStrategy implements PlatformStrategy {
|
|
|
770
869
|
return '';
|
|
771
870
|
}
|
|
772
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
|
+
|
|
773
928
|
ambientTypeDeclarations(): string[] {
|
|
774
929
|
// Preferences is the only HAL surface the framework lowers that is used as
|
|
775
930
|
// a bare global (the HAL Preferences class is exported, but the canonical
|
|
@@ -962,16 +1117,23 @@ export class ZephyrStrategy implements PlatformStrategy {
|
|
|
962
1117
|
}
|
|
963
1118
|
|
|
964
1119
|
// ── Polyfills ───────────────────────────────────────────────────────────
|
|
965
|
-
//
|
|
966
|
-
//
|
|
967
|
-
//
|
|
1120
|
+
// Zephyr is a no-STL target (hasVector/hasString = false), so array/string
|
|
1121
|
+
// literals lower to __tc_StaticArray / const char* and string methods lower
|
|
1122
|
+
// to __tc_* helpers — both need STL-free definitions emitted here (there is
|
|
1123
|
+
// no shared-runtime fallback; the pipeline sources 100% of polyfills from
|
|
1124
|
+
// generateNativePolyfills). Mirrors framework-arduino's AVR polyfills.
|
|
968
1125
|
|
|
969
1126
|
nativePolyfills(): Set<string> {
|
|
970
1127
|
// cuttlefish_halt: always (the runtime header may reference it).
|
|
1128
|
+
// string_methods / static_array: STL-free array + string helpers a no-STL
|
|
1129
|
+
// target needs (mutated/struct array literals + any string method).
|
|
971
1130
|
// timer_methods: k_timer/k_work pool for setInterval/setTimeout (gated on
|
|
972
1131
|
// timerCallCount at emit time in generateNativePolyfills).
|
|
973
1132
|
// async_runtime: heap-free static Promise/microtask runtime (no STL needed).
|
|
974
|
-
return new Set<string>([
|
|
1133
|
+
return new Set<string>([
|
|
1134
|
+
'cuttlefish_halt', 'wiring_compat', 'string_methods', 'static_array',
|
|
1135
|
+
'timer_methods', 'async_runtime',
|
|
1136
|
+
]);
|
|
975
1137
|
}
|
|
976
1138
|
|
|
977
1139
|
generateNativePolyfills(program?: ProgramIR, ctx?: PlatformContext): RuntimePolyfillIR[] {
|
|
@@ -1021,6 +1183,75 @@ export class ZephyrStrategy implements PlatformStrategy {
|
|
|
1021
1183
|
],
|
|
1022
1184
|
dependencies: [],
|
|
1023
1185
|
},
|
|
1186
|
+
{
|
|
1187
|
+
// STL-free string-method polyfills. String methods (.toUpperCase(),
|
|
1188
|
+
// .includes(), .substring(), …) lower at IR level to __tc_* helpers for
|
|
1189
|
+
// every target; this supplies their definitions. Minimal-libc friendly:
|
|
1190
|
+
// only <cstring> primitives (no <cctype> — case conversion is inline
|
|
1191
|
+
// ASCII so the polyfill is self-contained). Mirrors framework-arduino.
|
|
1192
|
+
kind: 'polyfill',
|
|
1193
|
+
id: 'string_methods',
|
|
1194
|
+
domain: 'embedded' as const,
|
|
1195
|
+
requiredIncludes: ['<cstring>'],
|
|
1196
|
+
forwardDeclarations: [],
|
|
1197
|
+
helperStructs: [],
|
|
1198
|
+
helperFunctions: [`
|
|
1199
|
+
// TypeCAD string method polyfills (Zephyr, minimal-libc).
|
|
1200
|
+
#ifndef CUTTLEFISH_STR_BUF_SIZE
|
|
1201
|
+
#define CUTTLEFISH_STR_BUF_SIZE 64
|
|
1202
|
+
#endif
|
|
1203
|
+
bool __tc_endsWith(const char* s, const char* suffix) { int sl = strlen(s), tl = strlen(suffix); return sl >= tl && strcmp(s + sl - tl, suffix) == 0; }
|
|
1204
|
+
const char* __tc_toUpperCase(const char* s) { static char buf[2][CUTTLEFISH_STR_BUF_SIZE]; static uint8_t slot = 0; slot ^= 1; char* b = buf[slot]; strncpy(b, s, CUTTLEFISH_STR_BUF_SIZE - 1); b[CUTTLEFISH_STR_BUF_SIZE - 1] = '\\0'; for (char* p = b; *p; p++) { if (*p >= 'a' && *p <= 'z') { *p = static_cast<char>(*p - 32); } } return b; }
|
|
1205
|
+
const char* __tc_toLowerCase(const char* s) { static char buf[2][CUTTLEFISH_STR_BUF_SIZE]; static uint8_t slot = 0; slot ^= 1; char* b = buf[slot]; strncpy(b, s, CUTTLEFISH_STR_BUF_SIZE - 1); b[CUTTLEFISH_STR_BUF_SIZE - 1] = '\\0'; for (char* p = b; *p; p++) { if (*p >= 'A' && *p <= 'Z') { *p = static_cast<char>(*p + 32); } } return b; }
|
|
1206
|
+
const char* __tc_trim(const char* s) { static char buf[2][CUTTLEFISH_STR_BUF_SIZE]; static uint8_t slot = 0; slot ^= 1; char* b = buf[slot]; while (*s == ' ' || *s == '\\t' || *s == '\\n' || *s == '\\r') s++; int len = strlen(s); while (len > 0 && (s[len-1] == ' ' || s[len-1] == '\\t' || s[len-1] == '\\n' || s[len-1] == '\\r')) len--; int cplen = len < CUTTLEFISH_STR_BUF_SIZE - 1 ? len : CUTTLEFISH_STR_BUF_SIZE - 1; strncpy(b, s, cplen); b[cplen] = '\\0'; return b; }
|
|
1207
|
+
const char* __tc_substring2(const char* s, int start, int end) { static char buf[2][CUTTLEFISH_STR_BUF_SIZE]; static uint8_t slot = 0; slot ^= 1; char* b = buf[slot]; int slen = strlen(s); if (start < 0) start = 0; if (end > slen) end = slen; if (end < start) end = start; int len = end - start; if (len >= CUTTLEFISH_STR_BUF_SIZE) len = CUTTLEFISH_STR_BUF_SIZE - 1; strncpy(b, s + start, len); b[len] = '\\0'; return b; }
|
|
1208
|
+
const char* __tc_substring1(const char* s, int start) { return __tc_substring2(s, start, strlen(s)); }
|
|
1209
|
+
const char* __tc_slice2(const char* s, int start, int end) { return __tc_substring2(s, start, end); }
|
|
1210
|
+
const char* __tc_slice1(const char* s, int start) { return __tc_substring2(s, start, strlen(s)); }
|
|
1211
|
+
const char* __tc_replace(const char* s, const char* old, const char* repl) { static char buf[2][CUTTLEFISH_STR_BUF_SIZE]; static uint8_t slot = 0; slot ^= 1; char* b = buf[slot]; const char* pos = strstr(s, old); if (!pos) { strncpy(b, s, CUTTLEFISH_STR_BUF_SIZE - 1); b[CUTTLEFISH_STR_BUF_SIZE - 1] = '\\0'; return b; } int beforeLen = static_cast<int>(pos - s); int oldLen = static_cast<int>(strlen(old)); int replLen = static_cast<int>(strlen(repl)); if (beforeLen + replLen + static_cast<int>(strlen(pos + oldLen)) >= CUTTLEFISH_STR_BUF_SIZE) { strncpy(b, s, CUTTLEFISH_STR_BUF_SIZE - 1); b[CUTTLEFISH_STR_BUF_SIZE - 1] = '\\0'; return b; } memcpy(b, s, beforeLen); memcpy(b + beforeLen, repl, replLen); strcpy(b + beforeLen + replLen, pos + oldLen); return b; }
|
|
1212
|
+
const char* __tc_charAt(const char* s, int idx) { static char buf[2][2]; static uint8_t slot = 0; slot ^= 1; buf[slot][0] = s[idx]; buf[slot][1] = '\\0'; return buf[slot]; }
|
|
1213
|
+
int __tc_charCodeAt(const char* s, int idx) { return static_cast<int>(static_cast<unsigned char>(s[idx])); }
|
|
1214
|
+
int __tc_indexOf(const char* s, const char* needle) { const char* p = strstr(s, needle); return p ? static_cast<int>(p - s) : -1; }
|
|
1215
|
+
`],
|
|
1216
|
+
shimMacros: [],
|
|
1217
|
+
dependencies: [],
|
|
1218
|
+
},
|
|
1219
|
+
{
|
|
1220
|
+
// STL-free fixed-size array wrapper. Mutated/struct-element array
|
|
1221
|
+
// literals and array methods (.push/.pop/.map/.filter) lower to
|
|
1222
|
+
// __tc_StaticArray<T,N>; this supplies the template. Idempotent guard
|
|
1223
|
+
// so a redefinition is a no-op. Mirrors framework-arduino.
|
|
1224
|
+
kind: 'polyfill',
|
|
1225
|
+
id: 'static_array',
|
|
1226
|
+
domain: 'embedded' as const,
|
|
1227
|
+
requiredIncludes: [],
|
|
1228
|
+
forwardDeclarations: [],
|
|
1229
|
+
helperStructs: [],
|
|
1230
|
+
helperFunctions: [`
|
|
1231
|
+
#ifndef __TC_STATIC_ARRAY_DEFINED
|
|
1232
|
+
#define __TC_STATIC_ARRAY_DEFINED
|
|
1233
|
+
template<typename T, int N>
|
|
1234
|
+
struct __tc_StaticArray {
|
|
1235
|
+
T data[N];
|
|
1236
|
+
int _size;
|
|
1237
|
+
__tc_StaticArray() : _size(0) {}
|
|
1238
|
+
int length() const { return _size; }
|
|
1239
|
+
int size() const { return _size; }
|
|
1240
|
+
void push(T val) { if (_size < N) data[_size++] = val; }
|
|
1241
|
+
T pop() { return (_size > 0) ? data[--_size] : T(); }
|
|
1242
|
+
int indexOf(T val) const { for (int i = 0; i < _size; i++) if (data[i] == val) return i; return -1; }
|
|
1243
|
+
T& operator[](int i) { return data[i]; }
|
|
1244
|
+
const T& operator[](int i) const { return data[i]; }
|
|
1245
|
+
T* begin() { return &data[0]; }
|
|
1246
|
+
T* end() { return &data[_size]; }
|
|
1247
|
+
const T* begin() const { return &data[0]; }
|
|
1248
|
+
const T* end() const { return &data[_size]; }
|
|
1249
|
+
};
|
|
1250
|
+
#endif
|
|
1251
|
+
`],
|
|
1252
|
+
shimMacros: [],
|
|
1253
|
+
dependencies: [],
|
|
1254
|
+
},
|
|
1024
1255
|
];
|
|
1025
1256
|
|
|
1026
1257
|
// Worker-offload runtime (Phase 1). Emitted only when the program uses
|
|
@@ -1052,7 +1283,11 @@ export class ZephyrStrategy implements PlatformStrategy {
|
|
|
1052
1283
|
id: 'async_runtime',
|
|
1053
1284
|
domain: 'embedded',
|
|
1054
1285
|
requiredIncludes: [],
|
|
1055
|
-
|
|
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();'],
|
|
1056
1291
|
helperStructs: [generateStaticAsyncRuntime(8, this.getAsyncRuntimeConfig().waitForPinEdge)],
|
|
1057
1292
|
helperFunctions: [],
|
|
1058
1293
|
shimMacros: [],
|
|
@@ -1159,11 +1394,12 @@ export class ZephyrStrategy implements PlatformStrategy {
|
|
|
1159
1394
|
|
|
1160
1395
|
// ── Strategy-owned display/touch adapter seam ────────────────────────────
|
|
1161
1396
|
// Zephyr owns its display + touch adapters: the UI display adapter bridges
|
|
1162
|
-
// the in-tree CuttlefishGFX class to
|
|
1163
|
-
// src/display/ui-adapter.ts), and the
|
|
1164
|
-
//
|
|
1165
|
-
//
|
|
1166
|
-
//
|
|
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.
|
|
1167
1403
|
|
|
1168
1404
|
providesDisplayAdapter(): boolean { return true; }
|
|
1169
1405
|
|