@typecad/framework-zephyr 1.0.0-alpha.16 → 1.0.0-alpha.19
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/LICENSE +202 -21
- package/README.md +49 -87
- package/dist/as-built.d.ts +2 -2
- package/dist/as-built.js +2 -2
- package/dist/audit.d.ts +111 -0
- package/dist/audit.js +416 -0
- package/dist/boardgen.d.ts +1 -9
- package/dist/boardgen.js +288 -47
- package/dist/chips/resolve.js +20 -0
- package/dist/chips/types.d.ts +22 -1
- package/dist/debug-codegen.js +1 -1
- package/dist/display/bindings.d.ts +55 -0
- package/dist/display/bindings.js +316 -0
- package/dist/display/gfx.d.ts +2 -3
- package/dist/display/gfx.js +166 -154
- package/dist/display/index.js +20 -1
- package/dist/display/mipi-dbi-host.d.ts +9 -0
- package/dist/display/mipi-dbi-host.js +174 -0
- package/dist/display/profiles.d.ts +109 -4
- package/dist/display/profiles.js +270 -7
- package/dist/display/touch-adapter.js +119 -49
- package/dist/display/ui-adapter-eink.d.ts +2 -0
- package/dist/display/ui-adapter-eink.js +4 -0
- package/dist/display/ui-adapter-gray.d.ts +8 -0
- package/dist/display/ui-adapter-gray.js +170 -0
- package/dist/display/ui-adapter-mono.d.ts +13 -0
- package/dist/display/ui-adapter-mono.js +230 -0
- package/dist/display/ui-adapter-native.d.ts +10 -0
- package/dist/display/ui-adapter-native.js +295 -0
- package/dist/display/ui-adapter-shared.d.ts +11 -0
- package/dist/display/ui-adapter-shared.js +122 -0
- package/dist/display/ui-adapter.js +510 -558
- package/dist/doctor.js +4 -4
- package/dist/dt-config/custom-board.js +2 -2
- package/dist/dt-config/kconfig.d.ts +62 -1
- package/dist/dt-config/kconfig.js +141 -38
- package/dist/dt-config/overlay.d.ts +15 -2
- package/dist/dt-config/overlay.js +433 -18
- package/dist/framework.manifest.d.ts +10 -4
- package/dist/framework.manifest.js +133 -17
- package/dist/index.d.ts +2 -0
- package/dist/index.js +14 -6
- package/dist/licenses.d.ts +2 -2
- package/dist/licenses.js +13 -92
- package/dist/lowering/can.d.ts +25 -0
- package/dist/lowering/can.js +97 -0
- package/dist/lowering/clock.d.ts +17 -0
- package/dist/lowering/clock.js +58 -0
- package/dist/lowering/fs.js +1 -1
- package/dist/lowering/gpio.js +0 -32
- package/dist/lowering/hid.d.ts +27 -0
- package/dist/lowering/hid.js +244 -0
- package/dist/lowering/http.js +264 -32
- package/dist/lowering/i2c.d.ts +8 -0
- package/dist/lowering/i2c.js +137 -5
- package/dist/lowering/i2s.d.ts +27 -0
- package/dist/lowering/i2s.js +98 -0
- package/dist/lowering/index.d.ts +9 -1
- package/dist/lowering/index.js +25 -1
- package/dist/lowering/interrupts.js +6 -0
- package/dist/lowering/matrix.d.ts +15 -0
- package/dist/lowering/matrix.js +63 -0
- package/dist/lowering/mqtt.js +110 -8
- package/dist/lowering/power.d.ts +3 -2
- package/dist/lowering/power.js +20 -45
- package/dist/lowering/pwm.js +25 -0
- package/dist/lowering/sensor.d.ts +2 -2
- package/dist/lowering/sensor.js +8 -4
- package/dist/lowering/strip.d.ts +16 -0
- package/dist/lowering/strip.js +70 -0
- package/dist/lowering/thread.js +5 -1
- package/dist/lowering/trace.d.ts +44 -0
- package/dist/lowering/trace.js +239 -0
- package/dist/lowering/uart.js +6 -1
- package/dist/lowering/usb.d.ts +3 -1
- package/dist/lowering/usb.js +16 -13
- package/dist/lowering/wdt.js +2 -29
- package/dist/sbom.d.ts +181 -0
- package/dist/sbom.js +901 -0
- package/dist/sdk/board-catalog-sync.d.ts +1 -3
- package/dist/sdk/board-catalog-sync.js +4 -10
- package/dist/strategy.d.ts +82 -46
- package/dist/strategy.js +639 -182
- package/dist/tmp-probe.d.ts +2 -0
- package/dist/tmp-probe.js +9 -0
- package/dist/toolchain/debug-config.d.ts +50 -90
- package/dist/toolchain/debug-config.js +239 -510
- package/dist/toolchain/env-check.d.ts +1 -3
- package/dist/toolchain/env-check.js +2 -7
- package/dist/toolchain/index.d.ts +28 -3
- package/dist/toolchain/index.js +498 -62
- package/dist/toolchain/runners.d.ts +16 -0
- package/dist/toolchain/runners.js +75 -0
- package/dist/toolchain/scaffold.d.ts +5 -2
- package/dist/toolchain/scaffold.js +79 -15
- package/dist/toolchain/west-discover.d.ts +6 -0
- package/dist/toolchain/west-discover.js +36 -13
- package/dist/toolchain/west-spawn.js +8 -2
- package/dist/west-inventory.d.ts +25 -0
- package/dist/west-inventory.js +97 -0
- package/installer/README.md +328 -328
- package/installer/install.sh +2 -2
- package/installer/templates/project/.typecad/activate-zephyr.ps1 +1 -1
- package/installer/templates/project/.typecad/activate-zephyr.sh +1 -1
- package/installer/templates/project/.vscode/settings.json +1 -1
- package/installer/templates/project/README.md +2 -2
- package/package.json +5 -5
- package/src/as-built.ts +206 -206
- package/src/audit.ts +529 -0
- package/src/boardgen.ts +265 -50
- package/src/chips/resolve.ts +21 -0
- package/src/chips/types.ts +10 -1
- package/src/display/bindings.ts +347 -0
- package/src/display/gfx.ts +318 -306
- package/src/display/index.ts +87 -70
- package/src/display/mipi-dbi-host.ts +183 -0
- package/src/display/profiles.ts +458 -139
- package/src/display/touch-adapter.ts +119 -49
- package/src/display/ui-adapter-eink.ts +13 -0
- package/src/display/ui-adapter-gray.ts +178 -0
- package/src/display/ui-adapter-mono.ts +238 -0
- package/src/display/ui-adapter-native.ts +304 -0
- package/src/display/ui-adapter-shared.ts +125 -0
- package/src/display/ui-adapter.ts +732 -781
- package/src/doctor.ts +4 -4
- package/src/dt-config/custom-board.ts +2 -2
- package/src/dt-config/kconfig.ts +647 -505
- package/src/dt-config/overlay.ts +1475 -1058
- package/src/framework.manifest.ts +659 -535
- package/src/index.ts +16 -6
- package/src/licenses.ts +346 -425
- package/src/lowering/can.ts +140 -0
- package/src/lowering/clock.ts +91 -0
- package/src/lowering/fs.ts +135 -135
- package/src/lowering/gpio.ts +0 -33
- package/src/lowering/hid.ts +261 -0
- package/src/lowering/http.ts +264 -32
- package/src/lowering/i2c.ts +142 -5
- package/src/lowering/i2s.ts +143 -0
- package/src/lowering/index.ts +18 -1
- package/src/lowering/interrupts.ts +6 -0
- package/src/lowering/matrix.ts +70 -0
- package/src/lowering/mqtt.ts +109 -8
- package/src/lowering/power.ts +41 -0
- package/src/lowering/pwm.ts +192 -167
- package/src/lowering/sensor.ts +159 -155
- package/src/lowering/strip.ts +81 -0
- package/src/lowering/thread.ts +5 -1
- package/src/lowering/trace.ts +270 -0
- package/src/lowering/uart.ts +6 -1
- package/src/lowering/usb.ts +16 -13
- package/src/lowering/wdt.ts +2 -25
- package/src/sbom.ts +1117 -0
- package/src/sdk/board-catalog-sync.ts +4 -25
- package/src/strategy.ts +2680 -2309
- package/src/toolchain/debug-config.ts +262 -522
- package/src/toolchain/env-check.ts +279 -285
- package/src/toolchain/index.ts +1792 -1359
- package/src/toolchain/runners.ts +80 -0
- package/src/toolchain/scaffold.ts +355 -296
- package/src/toolchain/west-discover.ts +35 -13
- package/src/toolchain/west-spawn.ts +174 -168
- package/src/west-inventory.ts +102 -0
- package/dist/async/timer-polyfill.d.ts +0 -10
- package/dist/async/timer-polyfill.js +0 -95
- package/dist/chips/board-overrides.d.ts +0 -7
- package/dist/chips/board-overrides.js +0 -11
- package/dist/chips/esp32.d.ts +0 -2
- package/dist/chips/esp32.js +0 -71
- package/dist/chips/esp32s3.d.ts +0 -2
- package/dist/chips/esp32s3.js +0 -103
- package/dist/chips/soc/.d.ts +0 -2
- package/dist/chips/soc/.js +0 -129
- package/dist/chips/soc/esp32.d.ts +0 -2
- package/dist/chips/soc/esp32.js +0 -120
- package/dist/chips/soc/esp32c3.d.ts +0 -2
- package/dist/chips/soc/esp32c3.js +0 -90
- package/dist/chips/soc/esp32c6.d.ts +0 -2
- package/dist/chips/soc/esp32c6.js +0 -109
- package/dist/chips/soc/esp32s3.d.ts +0 -2
- package/dist/chips/soc/esp32s3.js +0 -189
- package/dist/chips/soc/index.d.ts +0 -2
- package/dist/chips/soc/index.js +0 -23
- package/dist/chips/soc/nrf52840.d.ts +0 -2
- package/dist/chips/soc/nrf52840.js +0 -130
- package/dist/chips/soc/rp2040.d.ts +0 -2
- package/dist/chips/soc/rp2040.js +0 -141
- package/dist/chips/soc/rp2350a.d.ts +0 -2
- package/dist/chips/soc/rp2350a.js +0 -145
- package/dist/chips/soc/samd21g18a.d.ts +0 -2
- package/dist/chips/soc/samd21g18a.js +0 -143
- package/dist/chips/soc/stm32f411xe.d.ts +0 -2
- package/dist/chips/soc/stm32f411xe.js +0 -251
- package/dist/chips/xiao-ble.d.ts +0 -2
- package/dist/chips/xiao-ble.js +0 -100
- package/dist/lowering/pulse.d.ts +0 -7
- package/dist/lowering/pulse.js +0 -51
- package/dist/lowering/tone.d.ts +0 -10
- package/dist/lowering/tone.js +0 -63
- package/dist/lowering/worker-backing.d.ts +0 -14
- package/dist/lowering/worker-backing.js +0 -79
- package/dist/lowering/worker.d.ts +0 -6
- package/dist/lowering/worker.js +0 -14
- package/dist/sdk/board-data.generated.d.ts +0 -2
- package/dist/sdk/board-data.generated.js +0 -4
- package/dist/sdk/catalog-walker.d.ts +0 -90
- package/dist/sdk/catalog-walker.js +0 -682
- package/dist/sdk/dts-reader.d.ts +0 -83
- package/dist/sdk/dts-reader.js +0 -596
- package/src/debug-codegen.ts +0 -207
package/dist/strategy.js
CHANGED
|
@@ -25,6 +25,22 @@ import { syncBoardCatalog, ensureFreshBoardCatalog, } from './sdk/board-catalog-
|
|
|
25
25
|
import { setActiveChip, NO_BOARD_CHIP } from './chips/index.js';
|
|
26
26
|
import { resolveChipFromBoard } from './chips/resolve.js';
|
|
27
27
|
import { emitGpioDevDispatcher } from './chips/controllers.js';
|
|
28
|
+
/**
|
|
29
|
+
* Visit the program's transpile-resolved HAL ops with the same `visit` the IR
|
|
30
|
+
* walk uses. A HAL call inlined into another HAL call's template literal
|
|
31
|
+
* (`USB0.writeLine(\`n: ${gps.available()}\`)`) is lowered to C++ text while
|
|
32
|
+
* the IR is built — it never becomes a hal-op/hal-expr IR node, so the walk
|
|
33
|
+
* alone cannot see it. Such ops ride on ProgramIR.resolvedHalOps; wrapped as
|
|
34
|
+
* `{ operation: op }` they match the hal-op node shape every collector below
|
|
35
|
+
* tests for. Without this fold, shims keyed on these scans (UART RX rings,
|
|
36
|
+
* sensor device handles, per-controller bus state, ADC channels/overrides,
|
|
37
|
+
* dt specs) go undeclared while the baked text still references them — the
|
|
38
|
+
* `'__tc_dt_led0' was not declared` failure class.
|
|
39
|
+
*/
|
|
40
|
+
function visitResolvedHalOps(program, visit) {
|
|
41
|
+
for (const op of program?.resolvedHalOps ?? [])
|
|
42
|
+
visit({ operation: op });
|
|
43
|
+
}
|
|
28
44
|
/**
|
|
29
45
|
* Deep-walk the program IR and collect the HAL pin numbers the program
|
|
30
46
|
* actually touches for a peripheral family ('adc' | 'pwm') — the same walk
|
|
@@ -64,6 +80,7 @@ function collectUsedPins(program, kind, chip) {
|
|
|
64
80
|
}
|
|
65
81
|
};
|
|
66
82
|
visit(program);
|
|
83
|
+
visitResolvedHalOps(program, visit);
|
|
67
84
|
return pins;
|
|
68
85
|
}
|
|
69
86
|
/**
|
|
@@ -99,6 +116,7 @@ function collectAdcOverrideDevices(program) {
|
|
|
99
116
|
}
|
|
100
117
|
};
|
|
101
118
|
visit(program);
|
|
119
|
+
visitResolvedHalOps(program, visit);
|
|
102
120
|
return devices;
|
|
103
121
|
}
|
|
104
122
|
/**
|
|
@@ -136,6 +154,7 @@ function collectPwmOverrideSpecs(program) {
|
|
|
136
154
|
}
|
|
137
155
|
};
|
|
138
156
|
visit(program);
|
|
157
|
+
visitResolvedHalOps(program, visit);
|
|
139
158
|
return specs;
|
|
140
159
|
}
|
|
141
160
|
/**
|
|
@@ -149,6 +168,66 @@ function collectPwmOverrideSpecs(program) {
|
|
|
149
168
|
* -Wunused-variable (Zephyr builds with warnings-as-errors), the same
|
|
150
169
|
* hygiene collectUsedPins enforces for per-channel ADC/PWM state.
|
|
151
170
|
*/
|
|
171
|
+
/**
|
|
172
|
+
* Strips the program drives, keyed by SPI controller index → the
|
|
173
|
+
* construction chain length (the buffer size). Collected from strip.* ops.
|
|
174
|
+
*/
|
|
175
|
+
/** Which HID protocol the program drives — 'mixed' when verbs from BOTH
|
|
176
|
+
* classes appear (one interface per program is the v1 ceiling; the caller
|
|
177
|
+
* turns mixed into a #error so the build names it clearly). */
|
|
178
|
+
function collectHidProtocol(program) {
|
|
179
|
+
if (!program)
|
|
180
|
+
return undefined;
|
|
181
|
+
let kb = false;
|
|
182
|
+
let mouse = false;
|
|
183
|
+
const visit = (node) => {
|
|
184
|
+
if (!node || typeof node !== 'object')
|
|
185
|
+
return;
|
|
186
|
+
const n = node;
|
|
187
|
+
const op = n.operation;
|
|
188
|
+
if (op && typeof op === 'object') {
|
|
189
|
+
const name = op.operation;
|
|
190
|
+
if (typeof name === 'string') {
|
|
191
|
+
if (name.startsWith('hid.kb_'))
|
|
192
|
+
kb = true;
|
|
193
|
+
if (name.startsWith('hid.mouse_'))
|
|
194
|
+
mouse = true;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
for (const v of Object.values(n))
|
|
198
|
+
if (v && typeof v === 'object')
|
|
199
|
+
visit(v);
|
|
200
|
+
};
|
|
201
|
+
visit(program);
|
|
202
|
+
if (kb && mouse)
|
|
203
|
+
return 'mixed';
|
|
204
|
+
return kb ? 'keyboard' : mouse ? 'mouse' : undefined;
|
|
205
|
+
}
|
|
206
|
+
function collectStrips(program) {
|
|
207
|
+
if (!program)
|
|
208
|
+
return undefined;
|
|
209
|
+
const strips = new Map();
|
|
210
|
+
const visit = (node) => {
|
|
211
|
+
if (!node || typeof node !== 'object')
|
|
212
|
+
return;
|
|
213
|
+
const n = node;
|
|
214
|
+
const op = n.operation;
|
|
215
|
+
if (op && typeof op === 'object') {
|
|
216
|
+
const o = op;
|
|
217
|
+
if (o.operation === 'strip.set_pixel' || o.operation === 'strip.fill' || o.operation === 'strip.show') {
|
|
218
|
+
const m = String(o.bus ?? '').match(/(\d+)$/);
|
|
219
|
+
const idx = m ? parseInt(m[1], 10) : 0;
|
|
220
|
+
const count = Number(o.count ?? 1) || 1;
|
|
221
|
+
strips.set(idx, Math.max(strips.get(idx) ?? 0, count));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
for (const v of Object.values(n))
|
|
225
|
+
if (v && typeof v === 'object')
|
|
226
|
+
visit(v);
|
|
227
|
+
};
|
|
228
|
+
visit(program);
|
|
229
|
+
return strips;
|
|
230
|
+
}
|
|
152
231
|
function collectUsedBusIndices(program) {
|
|
153
232
|
if (!program)
|
|
154
233
|
return undefined;
|
|
@@ -185,6 +264,7 @@ function collectUsedBusIndices(program) {
|
|
|
185
264
|
}
|
|
186
265
|
};
|
|
187
266
|
visit(program);
|
|
267
|
+
visitResolvedHalOps(program, visit);
|
|
188
268
|
return indices;
|
|
189
269
|
}
|
|
190
270
|
/**
|
|
@@ -213,9 +293,13 @@ export function collectSensors(program) {
|
|
|
213
293
|
const spiHz = o.spiHz ?? 0;
|
|
214
294
|
const spiMode = o.spiMode ?? 0;
|
|
215
295
|
const alertPin = o.alertPin ?? -1;
|
|
296
|
+
const resolution = o.resolution ?? 12;
|
|
216
297
|
const key = `${part}|${bus}|${port}|${busKind}`;
|
|
217
|
-
|
|
218
|
-
|
|
298
|
+
const existing = sensors.get(key);
|
|
299
|
+
if (!existing)
|
|
300
|
+
sensors.set(key, { part, bus, port, busKind, spiHz, spiMode, alertPin, resolution });
|
|
301
|
+
else if (o.resolution !== undefined)
|
|
302
|
+
existing.resolution = resolution;
|
|
219
303
|
}
|
|
220
304
|
}
|
|
221
305
|
for (const v of Object.values(n)) {
|
|
@@ -228,8 +312,176 @@ export function collectSensors(program) {
|
|
|
228
312
|
}
|
|
229
313
|
};
|
|
230
314
|
visit(program);
|
|
315
|
+
visitResolvedHalOps(program, visit);
|
|
231
316
|
return sensors;
|
|
232
317
|
}
|
|
318
|
+
/**
|
|
319
|
+
* The I2S construction facts the program's ops carry (the shim's slab and
|
|
320
|
+
* init helpers size from these) — first-seen wins, keyed by instance.
|
|
321
|
+
*/
|
|
322
|
+
export function collectI2s(program) {
|
|
323
|
+
if (!program)
|
|
324
|
+
return undefined;
|
|
325
|
+
const out = new Map();
|
|
326
|
+
const visit = (node) => {
|
|
327
|
+
if (!node || typeof node !== 'object')
|
|
328
|
+
return;
|
|
329
|
+
const n = node;
|
|
330
|
+
const op = n.operation;
|
|
331
|
+
if (op && typeof op === 'object') {
|
|
332
|
+
const o = op;
|
|
333
|
+
const name = o.operation;
|
|
334
|
+
if (name === 'i2s.write' || name === 'i2s.read') {
|
|
335
|
+
// Raw instance (no clamp): the strategy loop folds usage through
|
|
336
|
+
// lowerI2s's clamp, so the facts must stay keyed by what the ops
|
|
337
|
+
// actually carry.
|
|
338
|
+
const inst = Number(o.instance ?? 0);
|
|
339
|
+
if (!out.has(inst)) {
|
|
340
|
+
out.set(inst, {
|
|
341
|
+
hz: Number(o.hz ?? 16000),
|
|
342
|
+
channels: Number(o.channels ?? 2),
|
|
343
|
+
bits: Number(o.bits ?? 16),
|
|
344
|
+
blockFrames: Number(o.blockFrames ?? 64),
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
for (const v of Object.values(n)) {
|
|
350
|
+
if (Array.isArray(v)) {
|
|
351
|
+
for (const item of v)
|
|
352
|
+
visit(item);
|
|
353
|
+
}
|
|
354
|
+
else if (v && typeof v === 'object')
|
|
355
|
+
visit(v);
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
visit(program);
|
|
359
|
+
visitResolvedHalOps(program, visit);
|
|
360
|
+
return out;
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Which can / i2s / clock verbs the program's ops actually use — the init
|
|
364
|
+
* blocks emit only the state those verbs reference (Zephyr's -Werror turns
|
|
365
|
+
* an unreferenced static function/variable into a build failure: a
|
|
366
|
+
* send-only CAN program must not carry the rx trampoline, a now()-only
|
|
367
|
+
* Clock program must not carry the set() conversion helper). Returns
|
|
368
|
+
* undefined when there is no program to scan (probe flows) — callers then
|
|
369
|
+
* fall back to emitting every controller with the full state set.
|
|
370
|
+
*/
|
|
371
|
+
export function collectVerbUsage(program) {
|
|
372
|
+
if (!program)
|
|
373
|
+
return undefined;
|
|
374
|
+
const out = {
|
|
375
|
+
can: new Map(),
|
|
376
|
+
i2s: new Map(),
|
|
377
|
+
clock: { set: false, now: false },
|
|
378
|
+
};
|
|
379
|
+
const canFlags = (inst) => {
|
|
380
|
+
let f = out.can.get(inst);
|
|
381
|
+
if (!f) {
|
|
382
|
+
f = { begin: false, send: false, onReceive: false };
|
|
383
|
+
out.can.set(inst, f);
|
|
384
|
+
}
|
|
385
|
+
return f;
|
|
386
|
+
};
|
|
387
|
+
const i2sFlags = (inst) => {
|
|
388
|
+
let f = out.i2s.get(inst);
|
|
389
|
+
if (!f) {
|
|
390
|
+
f = { write: false, read: false, readAt: false };
|
|
391
|
+
out.i2s.set(inst, f);
|
|
392
|
+
}
|
|
393
|
+
return f;
|
|
394
|
+
};
|
|
395
|
+
const visit = (node) => {
|
|
396
|
+
if (!node || typeof node !== 'object')
|
|
397
|
+
return;
|
|
398
|
+
const n = node;
|
|
399
|
+
const op = n.operation;
|
|
400
|
+
if (op && typeof op === 'object') {
|
|
401
|
+
const o = op;
|
|
402
|
+
const name = o.operation;
|
|
403
|
+
const inst = Number(o.instance ?? 0);
|
|
404
|
+
switch (name) {
|
|
405
|
+
case 'can.begin':
|
|
406
|
+
canFlags(inst).begin = true;
|
|
407
|
+
break;
|
|
408
|
+
case 'can.send':
|
|
409
|
+
canFlags(inst).send = true;
|
|
410
|
+
break;
|
|
411
|
+
case 'can.on_receive':
|
|
412
|
+
canFlags(inst).onReceive = true;
|
|
413
|
+
break;
|
|
414
|
+
case 'i2s.write':
|
|
415
|
+
i2sFlags(inst).write = true;
|
|
416
|
+
break;
|
|
417
|
+
case 'i2s.read':
|
|
418
|
+
i2sFlags(inst).read = true;
|
|
419
|
+
break;
|
|
420
|
+
case 'i2s.read_at':
|
|
421
|
+
i2sFlags(inst).readAt = true;
|
|
422
|
+
break;
|
|
423
|
+
case 'clock.set':
|
|
424
|
+
out.clock.set = true;
|
|
425
|
+
break;
|
|
426
|
+
case 'clock.now':
|
|
427
|
+
out.clock.now = true;
|
|
428
|
+
break;
|
|
429
|
+
default: break;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
for (const v of Object.values(n)) {
|
|
433
|
+
if (Array.isArray(v)) {
|
|
434
|
+
for (const item of v)
|
|
435
|
+
visit(item);
|
|
436
|
+
}
|
|
437
|
+
else if (v && typeof v === 'object')
|
|
438
|
+
visit(v);
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
visit(program);
|
|
442
|
+
visitResolvedHalOps(program, visit);
|
|
443
|
+
return out;
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Collect the distinct I2C responders the program's ops reference
|
|
447
|
+
* (i2c.resp_*), keyed `${bus}|${address}` — one target-mode address per
|
|
448
|
+
* constructed responder. Mirrors collectSpiTargets: the construction facts
|
|
449
|
+
* (rx/tx sizes) ride every op, so the first seen wins and the state block
|
|
450
|
+
* cannot drift from the call sites.
|
|
451
|
+
*/
|
|
452
|
+
export function collectI2cResponders(program) {
|
|
453
|
+
if (!program)
|
|
454
|
+
return undefined;
|
|
455
|
+
const responders = new Map();
|
|
456
|
+
const visit = (node) => {
|
|
457
|
+
if (!node || typeof node !== 'object')
|
|
458
|
+
return;
|
|
459
|
+
const n = node;
|
|
460
|
+
const op = n.operation;
|
|
461
|
+
if (op && typeof op === 'object') {
|
|
462
|
+
const o = op;
|
|
463
|
+
if (String(o.operation).startsWith('i2c.resp_')) {
|
|
464
|
+
const bus = String(o.bus ?? '');
|
|
465
|
+
const address = Number(o.address ?? 0);
|
|
466
|
+
const key = `${bus}|${address}`;
|
|
467
|
+
if (!responders.has(key)) {
|
|
468
|
+
responders.set(key, { bus, address, rx: Number(o.rx ?? 64), tx: Number(o.tx ?? 32) });
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
for (const v of Object.values(n)) {
|
|
473
|
+
if (Array.isArray(v)) {
|
|
474
|
+
for (const item of v)
|
|
475
|
+
visit(item);
|
|
476
|
+
}
|
|
477
|
+
else if (v && typeof v === 'object')
|
|
478
|
+
visit(v);
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
visit(program);
|
|
482
|
+
visitResolvedHalOps(program, visit);
|
|
483
|
+
return responders;
|
|
484
|
+
}
|
|
233
485
|
/**
|
|
234
486
|
* Collect the distinct thin SPI targets the program's ops reference
|
|
235
487
|
* (spi.transceive / spi.dev_write), keyed `${bus}|${cs}`. Mirrors
|
|
@@ -266,6 +518,7 @@ export function collectSpiTargets(program) {
|
|
|
266
518
|
}
|
|
267
519
|
};
|
|
268
520
|
visit(program);
|
|
521
|
+
visitResolvedHalOps(program, visit);
|
|
269
522
|
return targets;
|
|
270
523
|
}
|
|
271
524
|
/**
|
|
@@ -305,6 +558,7 @@ export function collectUartRings(program) {
|
|
|
305
558
|
}
|
|
306
559
|
};
|
|
307
560
|
visit(program);
|
|
561
|
+
visitResolvedHalOps(program, visit);
|
|
308
562
|
return rings;
|
|
309
563
|
}
|
|
310
564
|
/**
|
|
@@ -339,6 +593,7 @@ export function collectThreads(program) {
|
|
|
339
593
|
}
|
|
340
594
|
};
|
|
341
595
|
visit(program);
|
|
596
|
+
visitResolvedHalOps(program, visit);
|
|
342
597
|
return threads;
|
|
343
598
|
}
|
|
344
599
|
import { lowerHalOp } from './lowering/index.js';
|
|
@@ -347,10 +602,18 @@ import { pwmInitLines } from './lowering/pwm.js';
|
|
|
347
602
|
import { dacInitLines } from './lowering/dac.js';
|
|
348
603
|
import { fsInitLines } from './lowering/fs.js';
|
|
349
604
|
import { hwtimerInitLines } from './lowering/hwtimer.js';
|
|
350
|
-
import { i2cInitLines } from './lowering/i2c.js';
|
|
605
|
+
import { i2cInitLines, i2cResponderStateLines } from './lowering/i2c.js';
|
|
606
|
+
import { parseControllerIndex } from './lowering/util.js';
|
|
607
|
+
import { stripInitLines } from './lowering/strip.js';
|
|
608
|
+
import { hidInitLines } from './lowering/hid.js';
|
|
609
|
+
import { matrixInitLines } from './lowering/matrix.js';
|
|
610
|
+
import { clockInitLines } from './lowering/clock.js';
|
|
611
|
+
import { canInitLines } from './lowering/can.js';
|
|
612
|
+
import { i2sInitLines } from './lowering/i2s.js';
|
|
351
613
|
import { sensorStateLines } from './lowering/sensor.js';
|
|
352
614
|
import { spiTargetStateLines } from './lowering/spi.js';
|
|
353
615
|
import { threadStateLines } from './lowering/thread.js';
|
|
616
|
+
import { traceHeartbeatLines, uiFrameTraceLines, clampTraceIntervalMs } from './lowering/trace.js';
|
|
354
617
|
import { uartRingStateLines } from './lowering/uart.js';
|
|
355
618
|
import { spiInitLines } from './lowering/spi.js';
|
|
356
619
|
import { uartInitLines } from './lowering/uart.js';
|
|
@@ -385,13 +648,16 @@ import { httpInitLines } from './lowering/http.js';
|
|
|
385
648
|
import { mqttInitLines } from './lowering/mqtt.js';
|
|
386
649
|
import { preferencesInitLines } from './lowering/preferences.js';
|
|
387
650
|
import { randomInitLines } from './lowering/random.js';
|
|
388
|
-
import { generateZephyrInitCode, generateZephyrBreakpointCode, generateZephyrLogpointCode } from './debug-codegen.js';
|
|
389
651
|
import { generateStaticAsyncRuntime } from '@typecad/cuttlefish/api/shared';
|
|
390
652
|
import { resolveZephyrDisplayOp, newDisplayState } from './display/index.js';
|
|
391
653
|
import { buildDisplayRuntime } from './display/gfx.js';
|
|
392
|
-
import { ZEPHYR_DISPLAY_PROFILES, BUILT_IN_PROFILES } from './display/profiles.js';
|
|
654
|
+
import { ZEPHYR_DISPLAY_PROFILES, BUILT_IN_PROFILES, isDtCompatible, isMonoDisplay, isGrayDisplay, isEinkDisplay } from './display/profiles.js';
|
|
655
|
+
import { listBoundDisplayCompatibles } from './display/bindings.js';
|
|
393
656
|
import { zephyrDisplayAdapterGenerator } from './display/ui-adapter.js';
|
|
394
657
|
import { zephyrTouchAdapter } from './display/touch-adapter.js';
|
|
658
|
+
/** debugMode() memo — the boardgen facts lookup is not free and the method is
|
|
659
|
+
* called several times per build (transpile gate + artifact writers). */
|
|
660
|
+
const debugModeMemo = new Map();
|
|
395
661
|
export class ZephyrStrategy {
|
|
396
662
|
constructor() {
|
|
397
663
|
this.id = 'zephyr';
|
|
@@ -437,22 +703,7 @@ export class ZephyrStrategy {
|
|
|
437
703
|
setActiveChip(fromBoard ?? NO_BOARD_CHIP);
|
|
438
704
|
return fromBoard ?? NO_BOARD_CHIP;
|
|
439
705
|
}
|
|
440
|
-
/**
|
|
441
|
-
* Resolve the debug mode for the active target from the platform context.
|
|
442
|
-
* Mirrors resolveChip's target extraction so shimLines/forcedIncludes can
|
|
443
|
-
* gate the printf halt shim + console UART include to printf builds only
|
|
444
|
-
* (gdb builds use VS Code native breakpoints + #line markers, so the
|
|
445
|
-
* __tc_debug_wait_for_continue shim and its <zephyr/drivers/uart.h> include
|
|
446
|
-
* are dead code there).
|
|
447
|
-
*/
|
|
448
|
-
resolveDebugMode(ctx) {
|
|
449
|
-
const fd = ctx?.frameworkData;
|
|
450
|
-
const target = fd?.target ??
|
|
451
|
-
fd?.buildTarget;
|
|
452
|
-
return this.debugMode(target);
|
|
453
|
-
}
|
|
454
706
|
forcedIncludes(_program, ctx) {
|
|
455
|
-
const isPrintf = this.resolveDebugMode(ctx) === 'printf';
|
|
456
707
|
// <zephyr/kernel.h> for k_msleep / k_uptime_get_32 / k_busy_wait / printk.
|
|
457
708
|
// <zephyr/drivers/gpio.h> for the gpio_pin_*_dt / gpio_dt_spec API.
|
|
458
709
|
// <cstdint> because DIRECT_CPP_TYPE_MAP passes int32_t/uint8_t through
|
|
@@ -473,7 +724,7 @@ export class ZephyrStrategy {
|
|
|
473
724
|
// passes int32_t/uint8_t through verbatim.
|
|
474
725
|
const inc = ['<zephyr/kernel.h>', '<zephyr/drivers/gpio.h>', '<cstdint>'];
|
|
475
726
|
// <cstdio> backs the printf family only: __tc_print/__tc_println (emitted
|
|
476
|
-
// solely when
|
|
727
|
+
// solely when the test-runner preprocessor injected them — tracked via
|
|
477
728
|
// usedPolyfillHelpers), raw printf/snprintf in user code (usesCstdio), and
|
|
478
729
|
// the fs/preferences/uart shims (their lowerings snprintf into buffers).
|
|
479
730
|
// A program touching none of those needs no <cstdio>.
|
|
@@ -489,14 +740,30 @@ export class ZephyrStrategy {
|
|
|
489
740
|
inc.push('<zephyr/drivers/sensor.h>');
|
|
490
741
|
if (uses('usesSPI'))
|
|
491
742
|
inc.push('<zephyr/drivers/spi.h>');
|
|
743
|
+
if (uses('usesStrip'))
|
|
744
|
+
inc.push('<zephyr/drivers/led_strip.h>');
|
|
745
|
+
if (uses('usesHid')) {
|
|
746
|
+
inc.push('<zephyr/usb/class/hid.h>');
|
|
747
|
+
inc.push('<zephyr/usb/class/usbd_hid.h>');
|
|
748
|
+
inc.push('<zephyr/usb/usbd.h>');
|
|
749
|
+
inc.push('<zephyr/drivers/usb/usb_buf.h>');
|
|
750
|
+
}
|
|
751
|
+
if (uses('usesMatrix'))
|
|
752
|
+
inc.push('<zephyr/input/input.h>');
|
|
753
|
+
if (uses('usesPower'))
|
|
754
|
+
inc.push('<zephyr/sys/poweroff.h>');
|
|
755
|
+
// esp_sleep.h exists only on the espressif HAL — gate on the harvested
|
|
756
|
+
// RTC wake-timer fact (the includes' uses() reads op flags, not src).
|
|
757
|
+
if (uses('usesPower') && chip.powerWakeTimer)
|
|
758
|
+
inc.push('<esp_sleep.h>');
|
|
759
|
+
if (uses('usesClock'))
|
|
760
|
+
inc.push('<zephyr/drivers/rtc.h>');
|
|
761
|
+
if (uses('usesCan'))
|
|
762
|
+
inc.push('<zephyr/drivers/can.h>');
|
|
763
|
+
if (uses('usesI2s'))
|
|
764
|
+
inc.push('<zephyr/drivers/i2s.h>');
|
|
492
765
|
if (uses('usesUart'))
|
|
493
766
|
inc.push('<zephyr/drivers/uart.h>');
|
|
494
|
-
// uart.h is also needed by the printf-mode debug halt shim
|
|
495
|
-
// (__tc_debug_wait_for_continue polls the console UART) even when the
|
|
496
|
-
// program itself does not use the UART HAL. In gdb mode the shim is not
|
|
497
|
-
// emitted, so skip the include there to avoid pulling in an unused header.
|
|
498
|
-
if (isPrintf && !inc.includes('<zephyr/drivers/uart.h>'))
|
|
499
|
-
inc.push('<zephyr/drivers/uart.h>');
|
|
500
767
|
// USB CDC serial: the class instance is a UART device (uart.h); the
|
|
501
768
|
// device context macros + usbd_* API live in the next-stack header.
|
|
502
769
|
if (uses('usesUsb')) {
|
|
@@ -550,10 +817,11 @@ export class ZephyrStrategy {
|
|
|
550
817
|
// HTTP/S client: Zephyr's http_client_req runs over a pre-connected socket,
|
|
551
818
|
// so the shim pulls in the BSD socket + POSIX DNS surfaces alongside the
|
|
552
819
|
// http client/parser headers. TLS sec tags need tls_credentials; <cstring>
|
|
553
|
-
// /<cstdlib> back the shim's memcpy/strlen/new-nothrow usage (the
|
|
554
|
-
// shim only includes <cstdio>/<cstdint>
|
|
820
|
+
// /<cstdlib>/<new> back the shim's memcpy/strlen/new-nothrow usage (the
|
|
821
|
+
// core shim only includes <cstdio>/<cstdint> — std::nothrow is not
|
|
822
|
+
// guaranteed to arrive transitively).
|
|
555
823
|
if (uses('usesHttp'))
|
|
556
|
-
inc.push('<zephyr/net/socket.h>', '<zephyr/net/http/client.h>', '<zephyr/net/http/parser.h>', '<zephyr/net/tls_credentials.h>', '<zephyr/posix/sys/socket.h>', '<cstring>', '<cstdlib>');
|
|
824
|
+
inc.push('<zephyr/net/socket.h>', '<zephyr/net/http/client.h>', '<zephyr/net/http/parser.h>', '<zephyr/net/tls_credentials.h>', '<zephyr/posix/sys/socket.h>', '<cstring>', '<cstdlib>', '<new>');
|
|
557
825
|
// MQTT client: <zephyr/net/mqtt.h> for mqtt_connect/publish/subscribe, plus
|
|
558
826
|
// <zephyr/net/socket.h> for the zsock_* poll/getaddrinfo API the shim's poll
|
|
559
827
|
// thread uses, and <zephyr/net/tls_credentials.h> for the mqtts:// path
|
|
@@ -608,6 +876,10 @@ export class ZephyrStrategy {
|
|
|
608
876
|
return;
|
|
609
877
|
}
|
|
610
878
|
for (const v of Object.values(node)) {
|
|
879
|
+
if (typeof v === 'string' && v.includes(TOKEN)) {
|
|
880
|
+
found = true;
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
611
883
|
if (Array.isArray(v)) {
|
|
612
884
|
for (const item of v)
|
|
613
885
|
visit(item);
|
|
@@ -620,9 +892,17 @@ export class ZephyrStrategy {
|
|
|
620
892
|
return found;
|
|
621
893
|
}
|
|
622
894
|
/** Pins referenced by gpio.* hal-ops in the program IR. lowerGpio routes a
|
|
623
|
-
*
|
|
624
|
-
*
|
|
625
|
-
*
|
|
895
|
+
* pin to its devicetree spec by pin NUMBER, so the structured hal-op pins
|
|
896
|
+
* are the authoritative signal for which __tc_dt_* specs are needed —
|
|
897
|
+
* regardless of when the final call text is rendered.
|
|
898
|
+
*
|
|
899
|
+
* A gpio op interpolated into another HAL call's template literal
|
|
900
|
+
* (`USB0.writeLine(\`led: ${led.get()}\`)`) never becomes an IR node —
|
|
901
|
+
* resolveHALExprToText lowers it to C++ text while building the IR and bakes
|
|
902
|
+
* it into an __EMIT__ snprintf prelude, so the walk cannot see its pin. The
|
|
903
|
+
* spec NAME it references is still in that text; collectRawMatches finds it
|
|
904
|
+
* there, or the spec goes undeclared and the build fails on `__tc_dt_led0
|
|
905
|
+
* was not declared in this scope`. */
|
|
626
906
|
collectGpioPinUsage(program) {
|
|
627
907
|
const pins = new Set();
|
|
628
908
|
if (!program)
|
|
@@ -646,13 +926,18 @@ export class ZephyrStrategy {
|
|
|
646
926
|
}
|
|
647
927
|
};
|
|
648
928
|
visit(program);
|
|
929
|
+
visitResolvedHalOps(program, visit);
|
|
649
930
|
return pins;
|
|
650
931
|
}
|
|
651
932
|
/** Run `re` (global) against every raw string in the IR — raw expression
|
|
652
|
-
*
|
|
653
|
-
*
|
|
654
|
-
*
|
|
655
|
-
*
|
|
933
|
+
* values plus raw hal-op codes — returning capture group 1 of each match
|
|
934
|
+
* (the full match when the regex has no group). This is how references the
|
|
935
|
+
* text scanners must see but that never appear as IR call nodes are
|
|
936
|
+
* discovered: a rawCpp() escape hatch naming `__tc_dt_sw0` directly, and —
|
|
937
|
+
* the reason every string LEAF is scanned, not just raw nodes — the
|
|
938
|
+
* pre-lowered C++ text of a HAL call inlined into another HAL call's
|
|
939
|
+
* template literal, which the IR builder bakes into plain `string` payloads
|
|
940
|
+
* (__EMIT__ snprintf preludes, op data fields) rather than raw nodes. */
|
|
656
941
|
collectRawMatches(program, re) {
|
|
657
942
|
const found = new Set();
|
|
658
943
|
if (!program)
|
|
@@ -664,14 +949,10 @@ export class ZephyrStrategy {
|
|
|
664
949
|
const visit = (node) => {
|
|
665
950
|
if (!node || typeof node !== 'object')
|
|
666
951
|
return;
|
|
667
|
-
if (node.kind === 'raw' && typeof node.value === 'string')
|
|
668
|
-
scan(node.value);
|
|
669
|
-
if (node.operation && typeof node.operation === 'object'
|
|
670
|
-
&& node.operation.operation === 'raw' && typeof node.operation.code === 'string') {
|
|
671
|
-
scan(node.operation.code);
|
|
672
|
-
}
|
|
673
952
|
for (const v of Object.values(node)) {
|
|
674
|
-
if (
|
|
953
|
+
if (typeof v === 'string')
|
|
954
|
+
scan(v);
|
|
955
|
+
else if (Array.isArray(v)) {
|
|
675
956
|
for (const item of v)
|
|
676
957
|
visit(item);
|
|
677
958
|
}
|
|
@@ -700,7 +981,6 @@ export class ZephyrStrategy {
|
|
|
700
981
|
}
|
|
701
982
|
shimLines(program, ctx) {
|
|
702
983
|
const chip = this.resolveChip(ctx, program);
|
|
703
|
-
const isPrintf = this.resolveDebugMode(ctx) === 'printf';
|
|
704
984
|
const a = ctx?.analysis;
|
|
705
985
|
const uses = (f) => (a ? !!a[f] : true);
|
|
706
986
|
const helpers = a?.usedPolyfillHelpers;
|
|
@@ -764,7 +1044,7 @@ export class ZephyrStrategy {
|
|
|
764
1044
|
if (entryHasUI()) {
|
|
765
1045
|
guardBody.push('#ifndef PROGMEM', '#define PROGMEM', '#endif');
|
|
766
1046
|
}
|
|
767
|
-
// Test-runner console helpers:
|
|
1047
|
+
// Test-runner console helpers: the test-runner's Zephyr shim calls these
|
|
768
1048
|
// for protocol output. Overloaded for string (const char*) and numeric
|
|
769
1049
|
// (double) so the same call site works for markers and test values.
|
|
770
1050
|
// The fs lowering bakes __tc_println into its error paths too, so the
|
|
@@ -782,21 +1062,29 @@ export class ZephyrStrategy {
|
|
|
782
1062
|
'static inline char* __tc_fmt_num_buf(double v, char* out, size_t cap) {',
|
|
783
1063
|
' if (v != v) { snprintf(out, cap, "nan"); return out; }',
|
|
784
1064
|
' double a = v < 0 ? -v : v;',
|
|
785
|
-
' long long ip =
|
|
786
|
-
' long long fr =
|
|
1065
|
+
' long long ip = static_cast<long long>(a);',
|
|
1066
|
+
' long long fr = static_cast<long long>((a - static_cast<double>(ip)) * 1000000.0 + 0.5);',
|
|
787
1067
|
' if (fr >= 1000000LL) { ip += 1LL; fr = 0LL; }',
|
|
788
1068
|
' int used = 0;',
|
|
789
1069
|
' if (v < 0 && (ip != 0LL || fr != 0LL)) { out[used++] = \'-\'; out[used] = \'\\0\'; }',
|
|
790
|
-
' if (fr == 0LL) { snprintf(out + used, cap - (
|
|
1070
|
+
' if (fr == 0LL) { snprintf(out + used, cap - static_cast<size_t>(used), "%lld", ip); return out; }',
|
|
791
1071
|
' char fbuf[8];',
|
|
792
1072
|
' int len = snprintf(fbuf, sizeof(fbuf), "%06lld", fr);',
|
|
793
1073
|
' while (len > 0 && fbuf[len - 1] == \'0\') { fbuf[--len] = \'\\0\'; }',
|
|
794
|
-
' snprintf(out + used, cap - (
|
|
1074
|
+
' snprintf(out + used, cap - static_cast<size_t>(used), "%lld.%s", ip, fbuf);',
|
|
795
1075
|
' return out;',
|
|
796
1076
|
'}',
|
|
797
1077
|
'static void __tc_fmt_num(double v) { char __b[32]; printf("%s", __tc_fmt_num_buf(v, __b, sizeof(__b))); }',
|
|
798
1078
|
].join('\n'), 'inline void __tc_print(double v) { __tc_fmt_num(v); }', 'inline void __tc_println(const char* s) { printf("%s\\n", s); }', 'inline void __tc_println(double v) { __tc_fmt_num(v); printf("\\n"); }');
|
|
799
1079
|
}
|
|
1080
|
+
// Trace user-event helpers (Trace.mark / Trace.event → trace.mark /
|
|
1081
|
+
// trace.event ops): one [TR:EV:<uptime_ms>:<name>[:<value>] console line
|
|
1082
|
+
// per call, captured host-side as timeline events. Emitted
|
|
1083
|
+
// UNCONDITIONALLY like __tc_print — a Trace.mark() call site must never
|
|
1084
|
+
// fail to link because zephyr.trace was disabled; with no capture
|
|
1085
|
+
// listening the lines are ordinary console output. Value formatting
|
|
1086
|
+
// reuses __tc_fmt_num_buf (integer-only, the picolibc %g trap).
|
|
1087
|
+
guardBody.push('inline void __tc_trace_mark(const char* name) {', ' printf("[TR:EV:%u:%s\\n", static_cast<unsigned int>(k_uptime_get_32()), name);', '}', 'inline void __tc_trace_event(const char* name, double v) {', ' char __b[32];', ' printf("[TR:EV:%u:%s:%s\\n", static_cast<unsigned int>(k_uptime_get_32()), name, __tc_fmt_num_buf(v, __b, sizeof(__b)));', '}');
|
|
800
1088
|
// Serial-port write helper: writes a scalar to a UART/CDC device a byte at
|
|
801
1089
|
// a time. Overloaded on const char* (strings, snprintf buffers) and double
|
|
802
1090
|
// (numbers/booleans) so a single __tc_dev_put(dev, value) call site formats
|
|
@@ -812,12 +1100,29 @@ export class ZephyrStrategy {
|
|
|
812
1100
|
// declared controller's __tc_<bus>N_dev trips -Wunused-variable under
|
|
813
1101
|
// Zephyr's -Werror (collectUsedBusIndices; probes with no program emit all).
|
|
814
1102
|
const usedBuses = collectUsedBusIndices(program);
|
|
1103
|
+
// Verb-level usage for the can.*/i2s.*/clock.* init blocks (see
|
|
1104
|
+
// collectVerbUsage) — undefined when there is no program (probe flows
|
|
1105
|
+
// emit the full state set).
|
|
1106
|
+
const verbUsage = collectVerbUsage(program);
|
|
815
1107
|
if (uses('usesI2C') && chip.i2c) {
|
|
816
1108
|
for (let i = 0; i < chip.i2c.controllers.length; i++) {
|
|
817
1109
|
if (usedBuses && !usedBuses.i2c.has(i))
|
|
818
1110
|
continue;
|
|
819
1111
|
guardBody.push(...i2cInitLines(chip, i));
|
|
820
1112
|
}
|
|
1113
|
+
// I2C responders — one target-mode state block per constructed
|
|
1114
|
+
// responder (bus|address), against the controller handles above
|
|
1115
|
+
// (collectI2cResponders; the ops mark the bus used, so the handle
|
|
1116
|
+
// the registration references is always present).
|
|
1117
|
+
const responders = collectI2cResponders(program);
|
|
1118
|
+
if (responders) {
|
|
1119
|
+
for (const r of responders.values()) {
|
|
1120
|
+
const idx = parseControllerIndex(r.bus);
|
|
1121
|
+
if (idx >= chip.i2c.controllers.length)
|
|
1122
|
+
continue;
|
|
1123
|
+
guardBody.push(...i2cResponderStateLines(chip, idx, r.address, r.rx, r.tx));
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
821
1126
|
}
|
|
822
1127
|
if (uses('usesSPI') && chip.spi) {
|
|
823
1128
|
for (let i = 0; i < chip.spi.controllers.length; i++) {
|
|
@@ -826,6 +1131,79 @@ export class ZephyrStrategy {
|
|
|
826
1131
|
guardBody.push(...spiInitLines(chip, i));
|
|
827
1132
|
}
|
|
828
1133
|
}
|
|
1134
|
+
// LED strips — one buffer + device handle per driven SPI controller
|
|
1135
|
+
// (chain length from the construction facts).
|
|
1136
|
+
{
|
|
1137
|
+
const strips = collectStrips(program);
|
|
1138
|
+
if (uses('usesStrip') && strips) {
|
|
1139
|
+
for (const [idx, count] of strips) {
|
|
1140
|
+
guardBody.push(...stripInitLines(chip, idx, count));
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
// Key matrix — the input-subsystem trampoline (the DT node comes from
|
|
1145
|
+
// the overlay generator, driven by the toolchain's matrix scan).
|
|
1146
|
+
if (uses('usesMatrix')) {
|
|
1147
|
+
guardBody.push(...matrixInitLines());
|
|
1148
|
+
}
|
|
1149
|
+
// Wall-clock shim — the epoch<->civil helpers + the rtc alias handle
|
|
1150
|
+
// (the DT node comes from the overlay generator's shim synthesis).
|
|
1151
|
+
if (uses('usesClock')) {
|
|
1152
|
+
const clockUsage = verbUsage?.clock ?? { set: true, now: true };
|
|
1153
|
+
guardBody.push(...clockInitLines(clockUsage));
|
|
1154
|
+
}
|
|
1155
|
+
// CAN — per-controller device handle + the rx trampoline (the DT node
|
|
1156
|
+
// is the board's own can@ node, enabled by the overlay). Only the
|
|
1157
|
+
// controllers the program addresses, and only the state its verbs
|
|
1158
|
+
// reference (collectVerbUsage; probes with no program emit all). Usage
|
|
1159
|
+
// folds through the same clamp lowerCan applies to out-of-range
|
|
1160
|
+
// instances, so the state block for the clamped controller emits.
|
|
1161
|
+
if (uses('usesCan') && chip.can) {
|
|
1162
|
+
const nCtrl = chip.can.controllers.length;
|
|
1163
|
+
const canUsage = verbUsage?.can;
|
|
1164
|
+
const folded = Array.from({ length: nCtrl }, () => ({ begin: false, send: false, onReceive: false }));
|
|
1165
|
+
if (canUsage) {
|
|
1166
|
+
for (const [inst, flags] of canUsage) {
|
|
1167
|
+
const f = folded[Math.max(0, Math.min(inst, nCtrl - 1))];
|
|
1168
|
+
f.begin = f.begin || flags.begin;
|
|
1169
|
+
f.send = f.send || flags.send;
|
|
1170
|
+
f.onReceive = f.onReceive || flags.onReceive;
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
for (let i = 0; i < nCtrl; i++) {
|
|
1174
|
+
const f = folded[i];
|
|
1175
|
+
if (canUsage && !f.begin && !f.send && !f.onReceive)
|
|
1176
|
+
continue;
|
|
1177
|
+
guardBody.push(...canInitLines(chip, i, canUsage ? f : { begin: true, send: true, onReceive: true }));
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
// I2S — per-controller slab + block buffers + lazy direction inits,
|
|
1181
|
+
// sized from the construction facts the ops carry. Same used-instance
|
|
1182
|
+
// and clamp-fold discipline as CAN.
|
|
1183
|
+
if (uses('usesI2s') && chip.i2s) {
|
|
1184
|
+
const i2sFacts = collectI2s(program);
|
|
1185
|
+
const nCtrl = chip.i2s.controllers.length;
|
|
1186
|
+
const i2sUsage = verbUsage?.i2s;
|
|
1187
|
+
const folded = Array.from({ length: nCtrl }, () => ({ write: false, read: false, readAt: false }));
|
|
1188
|
+
if (i2sUsage) {
|
|
1189
|
+
for (const [inst, flags] of i2sUsage) {
|
|
1190
|
+
const f = folded[Math.max(0, Math.min(inst, nCtrl - 1))];
|
|
1191
|
+
f.write = f.write || flags.write;
|
|
1192
|
+
f.read = f.read || flags.read;
|
|
1193
|
+
f.readAt = f.readAt || flags.readAt;
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
for (let i = 0; i < nCtrl; i++) {
|
|
1197
|
+
const f = folded[i];
|
|
1198
|
+
if (i2sUsage && !f.write && !f.read && !f.readAt)
|
|
1199
|
+
continue;
|
|
1200
|
+
const facts = i2sFacts?.get(i);
|
|
1201
|
+
const channels = facts?.channels ?? 2;
|
|
1202
|
+
const bits = facts?.bits ?? 16;
|
|
1203
|
+
const blockFrames = facts?.blockFrames ?? 64;
|
|
1204
|
+
guardBody.push(...i2sInitLines(chip, i, blockFrames * channels * (bits / 8), i2sUsage ? f : { write: true, read: true, readAt: true }));
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
829
1207
|
if (uses('usesUart') && chip.uart) {
|
|
830
1208
|
for (let i = 0; i < chip.uart.controllers.length; i++) {
|
|
831
1209
|
if (usedBuses && !usedBuses.uart.has(i))
|
|
@@ -848,7 +1226,7 @@ export class ZephyrStrategy {
|
|
|
848
1226
|
const sensors = collectSensors(program);
|
|
849
1227
|
if (sensors) {
|
|
850
1228
|
for (const s of sensors.values()) {
|
|
851
|
-
guardBody.push(...sensorStateLines(s.part, s.bus, s.port, s.busKind, s.spiHz, s.spiMode, s.alertPin));
|
|
1229
|
+
guardBody.push(...sensorStateLines(s.part, s.bus, s.port, s.busKind, s.spiHz, s.spiMode, s.alertPin, s.resolution));
|
|
852
1230
|
}
|
|
853
1231
|
}
|
|
854
1232
|
}
|
|
@@ -874,10 +1252,52 @@ export class ZephyrStrategy {
|
|
|
874
1252
|
}
|
|
875
1253
|
}
|
|
876
1254
|
}
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
1255
|
+
// Trace heartbeat — the runtime-stats sampler behind `typecad-hal trace
|
|
1256
|
+
// capture`. Opt-in via zephyr.trace.enabled in typecad-hal.config.ts (the
|
|
1257
|
+
// config loader threads the zephyr record through platformContext). The
|
|
1258
|
+
// block compiles under CUTTLEFISH_ENTRY_TU, so split-file TUs carry no
|
|
1259
|
+
// second copy (a duplicate SYS_INIT would double every heartbeat).
|
|
1260
|
+
// UI-mounted programs additionally get the frame-stats block (always
|
|
1261
|
+
// defined, active only under CUTTLEFISH_TRACE_UI) so the ui_tick call
|
|
1262
|
+
// site's instrumentation never depends on the trace config.
|
|
1263
|
+
{
|
|
1264
|
+
const traceCfg = ctx
|
|
1265
|
+
?.zephyr?.trace;
|
|
1266
|
+
const traceEnabled = traceCfg?.enabled === true;
|
|
1267
|
+
if (traceEnabled && entryHasUI()) {
|
|
1268
|
+
guardBody.push('#if defined(CUTTLEFISH_ENTRY_TU)', '#define CUTTLEFISH_TRACE_UI 1', '#endif');
|
|
1269
|
+
}
|
|
1270
|
+
if (entryHasUI()) {
|
|
1271
|
+
guardBody.push(...uiFrameTraceLines(traceCfg?.alarms?.frameMaxMs));
|
|
1272
|
+
}
|
|
1273
|
+
if (traceEnabled) {
|
|
1274
|
+
guardBody.push(...traceHeartbeatLines(clampTraceIntervalMs(traceCfg?.intervalMs), entryHasUI(), traceCfg?.alarms));
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
// The usbd context is shared by CDC and HID; the CDC per-instance
|
|
1278
|
+
// state only emits when the program actually uses usb.* (an HID-only
|
|
1279
|
+
// program would otherwise carry an unused __tc_usb0_dev).
|
|
1280
|
+
if ((uses('usesUsb') || uses('usesHid')) && chip.usb) {
|
|
1281
|
+
guardBody.push(...usbdDeviceLines(chip, { hid: uses('usesHid') }));
|
|
1282
|
+
if (uses('usesUsb')) {
|
|
1283
|
+
for (let i = 0; i < chip.usb.cdcInstances; i++)
|
|
1284
|
+
guardBody.push(...usbInitLines(chip, i));
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
// USB HID — one interface per program; the protocol comes from which
|
|
1288
|
+
// class's verbs the program used (keyboard vs mouse). Requires the USB
|
|
1289
|
+
// device context (the same one CDC rides — the register helper calls
|
|
1290
|
+
// __tc_usbd_start).
|
|
1291
|
+
if (uses('usesHid') && chip.usb) {
|
|
1292
|
+
const protocol = collectHidProtocol(program) ?? 'keyboard';
|
|
1293
|
+
if (protocol === 'mixed') {
|
|
1294
|
+
// One zephyr,hid-device interface per program (the v1 ceiling) —
|
|
1295
|
+
// name it at compile time instead of an undefined-symbol error.
|
|
1296
|
+
guardBody.push('#error "typecad-hal: one HID interface per program (v1) — Keyboard and Mouse cannot be mixed in one app"');
|
|
1297
|
+
}
|
|
1298
|
+
else {
|
|
1299
|
+
guardBody.push(...hidInitLines(protocol));
|
|
1300
|
+
}
|
|
881
1301
|
}
|
|
882
1302
|
// PWM init also fires (with alias vars for the override pins) when a
|
|
883
1303
|
// program drives ONLY inline-override channels on a chip with no pwm
|
|
@@ -953,13 +1373,15 @@ export class ZephyrStrategy {
|
|
|
953
1373
|
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');
|
|
954
1374
|
}
|
|
955
1375
|
// Devicetree specs — one per board-defined GPIO pin, but ONLY for pins the
|
|
956
|
-
// program actually addresses (lowerGpio routes by pin number
|
|
957
|
-
// structured gpio.* hal-op pins
|
|
958
|
-
//
|
|
959
|
-
//
|
|
960
|
-
//
|
|
961
|
-
//
|
|
962
|
-
//
|
|
1376
|
+
// program actually addresses (lowerGpio routes by pin number; the
|
|
1377
|
+
// structured gpio.* hal-op pins plus the spec names appearing in any IR
|
|
1378
|
+
// string — including the pre-lowered text of a HAL call inlined into
|
|
1379
|
+
// another HAL call's template literal — are visible here) and aliases
|
|
1380
|
+
// named verbatim in raw code (rawCpp escape hatches). Emitted OUTSIDE the
|
|
1381
|
+
// single CUTTLEFISH_SHIM_DEFINED guard with a per-symbol guard: per-file
|
|
1382
|
+
// pin sets differ, and in a multi-header TU the first header's TU-wide
|
|
1383
|
+
// guard would otherwise hide the second header's specs. Without a program
|
|
1384
|
+
// (capability query), emit them all.
|
|
963
1385
|
const usedPins = this.collectGpioPinUsage(program);
|
|
964
1386
|
const dtTextRefs = this.collectRawMatches(program, /__tc_dt_([A-Za-z0-9_]+)/g);
|
|
965
1387
|
for (const spec of chip.gpio.dtSpecs) {
|
|
@@ -968,32 +1390,6 @@ export class ZephyrStrategy {
|
|
|
968
1390
|
const guard = `__TC_DT_${spec.dtSpec.replace(/[^A-Za-z0-9_]/g, '_').toUpperCase()}_SPEC`;
|
|
969
1391
|
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}`);
|
|
970
1392
|
}
|
|
971
|
-
// --- Debug-mode halt + per-breakpoint disable registry ---
|
|
972
|
-
//
|
|
973
|
-
// Printf mode only. In gdb mode the cuttlefish debug preprocessor is
|
|
974
|
-
// skipped (core emits #line markers + VS Code native breakpoints instead),
|
|
975
|
-
// so __tc_debug_wait_for_continue is never called — skip the shim and its
|
|
976
|
-
// <zephyr/drivers/uart.h> dependency entirely (forcedIncludes mirrors this).
|
|
977
|
-
//
|
|
978
|
-
// The cuttlefish debug preprocessor injects __tc_debug_wait_for_continue(id)
|
|
979
|
-
// calls at each breakpoint; without these definitions the emitted code
|
|
980
|
-
// would not link.
|
|
981
|
-
//
|
|
982
|
-
// Zephyr's minimal libc has no getchar()/EOF, so the halt polls the console
|
|
983
|
-
// UART directly via uart_poll_in on the system console device, yielding to
|
|
984
|
-
// the scheduler with k_msleep between polls so an unattended breakpoint
|
|
985
|
-
// does not starve the system. ENTER (or any non-'s' byte) = continue;
|
|
986
|
-
// 's'/'S' = skip this breakpoint for the rest of the run (records the id).
|
|
987
|
-
if (isPrintf) {
|
|
988
|
-
lines.push('#ifndef __TC_BP_DISABLED_DEFINED', '#define __TC_BP_DISABLED_DEFINED', 'static bool __tc_bp_disabled[256] = {0};', 'static inline bool __tc_bp_is_disabled(int id) { return id >= 0 && id < 256 && __tc_bp_disabled[id]; }',
|
|
989
|
-
// Console input: poll the UART console device. DEVICE_DT_GET(DT_CHOSEN(zephyr_console))
|
|
990
|
-
// resolves to the board's console (UART0 USB-CDC on the XIAO nRF52840).
|
|
991
|
-
// Not every board DTS declares a zephyr,console chosen (STM32MP1 M-side,
|
|
992
|
-
// display/carrier boards): there is nothing to print a prompt on and
|
|
993
|
-
// nothing to read a key from, so breakpoints auto-continue instead of
|
|
994
|
-
// hanging an unattended run.
|
|
995
|
-
'#if DT_HAS_CHOSEN(zephyr_console)', 'static inline char __tc_debug_wait_for_continue(int id) {', ' const struct device* __con = DEVICE_DT_GET(DT_CHOSEN(zephyr_console));', ' unsigned char __c = 0;', " while (uart_poll_in(__con, &__c) != 0) {", ' k_msleep(10);', ' }', " // Drain the rest of the typed line so the next breakpoint waits fresh.", " unsigned char __peek = 0;", " while (uart_poll_in(__con, &__peek) == 0 && __peek != '\\n') { (void)0; }", " if ((__c == 's') || (__c == 'S')) { if (id >= 0 && id < 256) __tc_bp_disabled[id] = true; }", ' return static_cast<char>(__c);', '}', '#else', 'static inline char __tc_debug_wait_for_continue(int id) { (void)id; return static_cast<char>(0); }', '#endif // DT_HAS_CHOSEN(zephyr_console)', '#endif // __TC_BP_DISABLED_DEFINED', '');
|
|
996
|
-
}
|
|
997
1393
|
// --- Zephyr entrypoint ---------------------------------------------------
|
|
998
1394
|
// No bridge here: the synthesizer emits main() itself (it keys off
|
|
999
1395
|
// entrypointFunctionName()="main" and requiresLoopFunction()=false), with
|
|
@@ -1023,7 +1419,11 @@ export class ZephyrStrategy {
|
|
|
1023
1419
|
// precede the wiring-ambient shims and the PinGroup factory there; the
|
|
1024
1420
|
// __tc_gpio_read body below is what still needs emitting here.)
|
|
1025
1421
|
if (this.needsGpioReadShim(program, ctx)) {
|
|
1026
|
-
lines.push('inline int __tc_gpio_read(int pin) { return gpio_pin_get_raw(__tc_gpio_dev(static_cast<uint32_t>(pin)), __tc_gpio_pin(static_cast<uint32_t>(pin))); }'
|
|
1422
|
+
lines.push('inline int __tc_gpio_read(int pin) { return gpio_pin_get_raw(__tc_gpio_dev(static_cast<uint32_t>(pin)), __tc_gpio_pin(static_cast<uint32_t>(pin))); }',
|
|
1423
|
+
// Runtime input configuration for ui.watchPin / ui.press setup
|
|
1424
|
+
// (the strategy's setPinMode lowers here). Idempotent — setup calls
|
|
1425
|
+
// it once per watched pin before the poll loop reads it.
|
|
1426
|
+
'inline void __tc_gpio_configure_input(uint32_t pin) { gpio_pin_configure(__tc_gpio_dev(pin), __tc_gpio_pin(pin), GPIO_INPUT | GPIO_PULL_UP); }');
|
|
1027
1427
|
}
|
|
1028
1428
|
// __tc_gpio_write / __tc_delay_us are only referenced via @typecad/safety
|
|
1029
1429
|
// lowering, so they stay gated on it.
|
|
@@ -1556,10 +1956,13 @@ export class ZephyrStrategy {
|
|
|
1556
1956
|
// ── Interrupt safety ─────────────────────────────────────────────────────
|
|
1557
1957
|
// Zephyr ISRs run above thread context: anything that sleeps (k_msleep),
|
|
1558
1958
|
// pends, or takes a driver lock is illegal there (asserted by the kernel in
|
|
1559
|
-
// debug builds; corrupts scheduler state otherwise). The
|
|
1560
|
-
//
|
|
1561
|
-
//
|
|
1562
|
-
//
|
|
1959
|
+
// debug builds; corrupts scheduler state otherwise). The keys below are
|
|
1960
|
+
// matched by cuttlefish's interrupt-analysis pass in two shapes: bare/qualified
|
|
1961
|
+
// callee names (call statements) and resolved hal-op payloads — the analyzer
|
|
1962
|
+
// maps timing.delay/delay_microseconds/sleep/busy_wait_us ops back to the
|
|
1963
|
+
// bare timing keys, and matches the op payload's port/bus field (UART0,
|
|
1964
|
+
// I2C0, SPI0, …) against these keys directly. The same keys ArduinoStrategy
|
|
1965
|
+
// uses.
|
|
1563
1966
|
isrUnsafeOperations() {
|
|
1564
1967
|
return new Map([
|
|
1565
1968
|
['delay', {
|
|
@@ -1698,14 +2101,14 @@ export class ZephyrStrategy {
|
|
|
1698
2101
|
catch (err) {
|
|
1699
2102
|
// A malformed facts/as-built file is the USER's error — surface it
|
|
1700
2103
|
// verbatim instead of the generic "cannot generate" below.
|
|
1701
|
-
if (err instanceof Error && (err.message.includes('
|
|
2104
|
+
if (err instanceof Error && (err.message.includes('typecad-hal.facts.json') || err.message.includes('as-built.json')))
|
|
1702
2105
|
throw err;
|
|
1703
2106
|
return undefined;
|
|
1704
2107
|
}
|
|
1705
2108
|
}
|
|
1706
2109
|
/**
|
|
1707
2110
|
* Regenerate the board catalog overlay from the user's own Zephyr tree —
|
|
1708
|
-
* `
|
|
2111
|
+
* `typecad-hal board sync`. After a `west update`, this is how new/changed/
|
|
1709
2112
|
* removed boards reach projects without a cuttlefish release. See
|
|
1710
2113
|
* src/sdk/board-catalog-sync.ts.
|
|
1711
2114
|
*/
|
|
@@ -1714,7 +2117,7 @@ export class ZephyrStrategy {
|
|
|
1714
2117
|
}
|
|
1715
2118
|
/**
|
|
1716
2119
|
* Refresh the overlay only when it is stale (provenance no longer matches
|
|
1717
|
-
* the tree) — the pre-step `
|
|
2120
|
+
* the tree) — the pre-step `typecad-hal board regen` runs so a regen after
|
|
1718
2121
|
* `west update` picks up the tree's boards automatically. Tree walk only
|
|
1719
2122
|
* happens when there is actual refreshing to do.
|
|
1720
2123
|
*/
|
|
@@ -1774,6 +2177,17 @@ export class ZephyrStrategy {
|
|
|
1774
2177
|
postIteration: 'k_msleep(1);',
|
|
1775
2178
|
};
|
|
1776
2179
|
}
|
|
2180
|
+
/**
|
|
2181
|
+
* Per-frame wall-time feed for the trace heartbeat's UI stats. The helper
|
|
2182
|
+
* is emitted unconditionally for UI programs (compiles to a no-op unless
|
|
2183
|
+
* zephyr.trace defined CUTTLEFISH_TRACE_UI), so this call is safe with
|
|
2184
|
+
* tracing on or off.
|
|
2185
|
+
*/
|
|
2186
|
+
uiFrameTimingLines(deltaMsExpr) {
|
|
2187
|
+
if (!entryHasUI())
|
|
2188
|
+
return null;
|
|
2189
|
+
return [`__tc_trace_ui_frame(static_cast<uint32_t>(${deltaMsExpr}));`];
|
|
2190
|
+
}
|
|
1777
2191
|
// ── Type aliases ────────────────────────────────────────────────────────
|
|
1778
2192
|
shouldSkipTypeAlias() {
|
|
1779
2193
|
return false;
|
|
@@ -1899,23 +2313,23 @@ export class ZephyrStrategy {
|
|
|
1899
2313
|
requiredIncludes: ['<cstring>'],
|
|
1900
2314
|
forwardDeclarations: [],
|
|
1901
2315
|
helperStructs: [],
|
|
1902
|
-
helperFunctions: [`
|
|
1903
|
-
// TypeCAD string method polyfills (Zephyr, minimal-libc).
|
|
1904
|
-
#ifndef CUTTLEFISH_STR_BUF_SIZE
|
|
1905
|
-
#define CUTTLEFISH_STR_BUF_SIZE 64
|
|
1906
|
-
#endif
|
|
1907
|
-
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; }
|
|
1908
|
-
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; }
|
|
1909
|
-
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; }
|
|
1910
|
-
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; }
|
|
1911
|
-
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; }
|
|
1912
|
-
const char* __tc_substring1(const char* s, int start) { return __tc_substring2(s, start, strlen(s)); }
|
|
1913
|
-
const char* __tc_slice2(const char* s, int start, int end) { return __tc_substring2(s, start, end); }
|
|
1914
|
-
const char* __tc_slice1(const char* s, int start) { return __tc_substring2(s, start, strlen(s)); }
|
|
1915
|
-
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; }
|
|
1916
|
-
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]; }
|
|
1917
|
-
int __tc_charCodeAt(const char* s, int idx) { return static_cast<int>(static_cast<unsigned char>(s[idx])); }
|
|
1918
|
-
int __tc_indexOf(const char* s, const char* needle) { const char* p = strstr(s, needle); return p ? static_cast<int>(p - s) : -1; }
|
|
2316
|
+
helperFunctions: [`
|
|
2317
|
+
// TypeCAD string method polyfills (Zephyr, minimal-libc).
|
|
2318
|
+
#ifndef CUTTLEFISH_STR_BUF_SIZE
|
|
2319
|
+
#define CUTTLEFISH_STR_BUF_SIZE 64
|
|
2320
|
+
#endif
|
|
2321
|
+
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; }
|
|
2322
|
+
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; }
|
|
2323
|
+
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; }
|
|
2324
|
+
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; }
|
|
2325
|
+
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; }
|
|
2326
|
+
const char* __tc_substring1(const char* s, int start) { return __tc_substring2(s, start, strlen(s)); }
|
|
2327
|
+
const char* __tc_slice2(const char* s, int start, int end) { return __tc_substring2(s, start, end); }
|
|
2328
|
+
const char* __tc_slice1(const char* s, int start) { return __tc_substring2(s, start, strlen(s)); }
|
|
2329
|
+
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; }
|
|
2330
|
+
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]; }
|
|
2331
|
+
int __tc_charCodeAt(const char* s, int idx) { return static_cast<int>(static_cast<unsigned char>(s[idx])); }
|
|
2332
|
+
int __tc_indexOf(const char* s, const char* needle) { const char* p = strstr(s, needle); return p ? static_cast<int>(p - s) : -1; }
|
|
1919
2333
|
`],
|
|
1920
2334
|
shimMacros: [],
|
|
1921
2335
|
dependencies: [],
|
|
@@ -1931,27 +2345,27 @@ int __tc_indexOf(const char* s, const char* needle) { const char* p = strstr(s,
|
|
|
1931
2345
|
requiredIncludes: [],
|
|
1932
2346
|
forwardDeclarations: [],
|
|
1933
2347
|
helperStructs: [],
|
|
1934
|
-
helperFunctions: [`
|
|
1935
|
-
#ifndef __TC_STATIC_ARRAY_DEFINED
|
|
1936
|
-
#define __TC_STATIC_ARRAY_DEFINED
|
|
1937
|
-
template<typename T, int N>
|
|
1938
|
-
struct __tc_StaticArray {
|
|
1939
|
-
T data[N];
|
|
1940
|
-
int _size;
|
|
1941
|
-
__tc_StaticArray() : _size(0) {}
|
|
1942
|
-
int length() const { return _size; }
|
|
1943
|
-
int size() const { return _size; }
|
|
1944
|
-
void push(T val) { if (_size < N) data[_size++] = val; }
|
|
1945
|
-
T pop() { return (_size > 0) ? data[--_size] : T(); }
|
|
1946
|
-
int indexOf(T val) const { for (int i = 0; i < _size; i++) if (data[i] == val) return i; return -1; }
|
|
1947
|
-
T& operator[](int i) { return data[i]; }
|
|
1948
|
-
const T& operator[](int i) const { return data[i]; }
|
|
1949
|
-
T* begin() { return &data[0]; }
|
|
1950
|
-
T* end() { return &data[_size]; }
|
|
1951
|
-
const T* begin() const { return &data[0]; }
|
|
1952
|
-
const T* end() const { return &data[_size]; }
|
|
1953
|
-
};
|
|
1954
|
-
#endif
|
|
2348
|
+
helperFunctions: [`
|
|
2349
|
+
#ifndef __TC_STATIC_ARRAY_DEFINED
|
|
2350
|
+
#define __TC_STATIC_ARRAY_DEFINED
|
|
2351
|
+
template<typename T, int N>
|
|
2352
|
+
struct __tc_StaticArray {
|
|
2353
|
+
T data[N];
|
|
2354
|
+
int _size;
|
|
2355
|
+
__tc_StaticArray() : _size(0) {}
|
|
2356
|
+
int length() const { return _size; }
|
|
2357
|
+
int size() const { return _size; }
|
|
2358
|
+
void push(T val) { if (_size < N) data[_size++] = val; }
|
|
2359
|
+
T pop() { return (_size > 0) ? data[--_size] : T(); }
|
|
2360
|
+
int indexOf(T val) const { for (int i = 0; i < _size; i++) if (data[i] == val) return i; return -1; }
|
|
2361
|
+
T& operator[](int i) { return data[i]; }
|
|
2362
|
+
const T& operator[](int i) const { return data[i]; }
|
|
2363
|
+
T* begin() { return &data[0]; }
|
|
2364
|
+
T* end() { return &data[_size]; }
|
|
2365
|
+
const T* begin() const { return &data[0]; }
|
|
2366
|
+
const T* end() const { return &data[_size]; }
|
|
2367
|
+
};
|
|
2368
|
+
#endif
|
|
1955
2369
|
`],
|
|
1956
2370
|
shimMacros: [],
|
|
1957
2371
|
dependencies: [],
|
|
@@ -2008,9 +2422,14 @@ struct __tc_StaticArray {
|
|
|
2008
2422
|
writeDigitalPin(pin, val) {
|
|
2009
2423
|
return `__tc_gpio_write(${pin}, ${val})`;
|
|
2010
2424
|
}
|
|
2011
|
-
setPinMode(
|
|
2012
|
-
//
|
|
2013
|
-
|
|
2425
|
+
setPinMode(pin, _mode) {
|
|
2426
|
+
// Runtime input configuration: Zephyr has no pinMode — the GPIO API
|
|
2427
|
+
// configures direction/pull explicitly. The mode argument is ignored:
|
|
2428
|
+
// every watchPin/press consumer wants an input with pull-up (buttons are
|
|
2429
|
+
// active-low), and __tc_gpio_configure_input hardcodes exactly that.
|
|
2430
|
+
// The helper's definition rides the GPIO read shim (same gate — UI builds
|
|
2431
|
+
// with pin watchers always carry the read surface too).
|
|
2432
|
+
return `__tc_gpio_configure_input(static_cast<uint32_t>(${pin}));`;
|
|
2014
2433
|
}
|
|
2015
2434
|
delayMs(ms) {
|
|
2016
2435
|
return `k_msleep(${ms})`;
|
|
@@ -2046,16 +2465,31 @@ struct __tc_StaticArray {
|
|
|
2046
2465
|
return resolveZephyrDisplayOp(op, this._displayState);
|
|
2047
2466
|
}
|
|
2048
2467
|
supportedDisplayDrivers() {
|
|
2049
|
-
|
|
2468
|
+
// Registry profiles plus every panel compatible with a display binding in
|
|
2469
|
+
// the user's Zephyr tree — the drop-in path (display.driver = compatible)
|
|
2470
|
+
// validates at mount like any curated profile. No tree discoverable →
|
|
2471
|
+
// registry only.
|
|
2472
|
+
const drivers = new Set(Object.keys(ZEPHYR_DISPLAY_PROFILES));
|
|
2473
|
+
try {
|
|
2474
|
+
for (const compatible of listBoundDisplayCompatibles())
|
|
2475
|
+
drivers.add(compatible);
|
|
2476
|
+
}
|
|
2477
|
+
catch {
|
|
2478
|
+
// Tree unreadable — the registry set stands.
|
|
2479
|
+
}
|
|
2480
|
+
return drivers;
|
|
2050
2481
|
}
|
|
2051
2482
|
// ── Strategy-owned display/touch adapter seam ────────────────────────────
|
|
2052
2483
|
// Zephyr owns its display + touch adapters: the UI display adapter bridges
|
|
2053
|
-
// the in-tree CuttlefishGFX class to the panel
|
|
2054
|
-
//
|
|
2055
|
-
//
|
|
2056
|
-
//
|
|
2057
|
-
//
|
|
2058
|
-
//
|
|
2484
|
+
// the in-tree CuttlefishGFX class to the panel via one of two transports
|
|
2485
|
+
// (per profile — 'direct-spi': per-controller init + wire format over
|
|
2486
|
+
// spi_write, see src/display/ui-adapter.ts; 'zephyr-display': Zephyr's
|
|
2487
|
+
// display API with in-tree panel drivers, see src/display/ui-adapter-native.ts),
|
|
2488
|
+
// and the touch adapters drive the FT6336U (I2C capacitive) and XPT2046
|
|
2489
|
+
// (SPI resistive) controllers via Zephyr's bus APIs (src/display/touch-adapter.ts).
|
|
2490
|
+
// Both live in this package so cuttlefish carries no Zephyr/Wiring-specific
|
|
2491
|
+
// display or touch knowledge. Mirrors ArduinoStrategy's provides*/resolve*
|
|
2492
|
+
// pattern.
|
|
2059
2493
|
providesDisplayAdapter() { return true; }
|
|
2060
2494
|
resolveDisplayAdapter(display) {
|
|
2061
2495
|
const code = zephyrDisplayAdapterGenerator(display);
|
|
@@ -2074,7 +2508,30 @@ struct __tc_StaticArray {
|
|
|
2074
2508
|
return new Map(Object.entries(BUILT_IN_PROFILES));
|
|
2075
2509
|
}
|
|
2076
2510
|
colorFormat() {
|
|
2077
|
-
|
|
2511
|
+
// The seeded display state is the truth after display.init (drop-in mono
|
|
2512
|
+
// panels synthesize 'mono'); the default profile keeps the pre-init
|
|
2513
|
+
// rgb565 answer byte-identical with history.
|
|
2514
|
+
return this._displayState.profile.colorFormat;
|
|
2515
|
+
}
|
|
2516
|
+
colorFormatForDriver(driver) {
|
|
2517
|
+
// Drop-in panel class from the compatible table (DATA): the 1bpp OLED
|
|
2518
|
+
// family lowers mono engine-side and the 16-gray family lowers gray8,
|
|
2519
|
+
// so colors flatten at build time instead of rgb565-lowering into an
|
|
2520
|
+
// adapter that cannot show them. Registry profiles carry their own
|
|
2521
|
+
// colorFormat; non-compatible driver ids keep the default.
|
|
2522
|
+
if (isDtCompatible(driver) && isMonoDisplay({ driver }))
|
|
2523
|
+
return 'mono';
|
|
2524
|
+
if (isDtCompatible(driver) && isEinkDisplay({ driver }))
|
|
2525
|
+
return 'mono';
|
|
2526
|
+
if (isDtCompatible(driver) && isGrayDisplay({ driver }))
|
|
2527
|
+
return 'gray8';
|
|
2528
|
+
return undefined;
|
|
2529
|
+
}
|
|
2530
|
+
displayClassForDriver(driver) {
|
|
2531
|
+
// The compatible table decides (DATA) — ssd16xx/uc81xx are e-ink.
|
|
2532
|
+
if (isDtCompatible(driver) && isEinkDisplay({ driver }))
|
|
2533
|
+
return 'eink';
|
|
2534
|
+
return undefined;
|
|
2078
2535
|
}
|
|
2079
2536
|
graphicsCapacity() {
|
|
2080
2537
|
return {
|
|
@@ -2085,43 +2542,43 @@ struct __tc_StaticArray {
|
|
|
2085
2542
|
};
|
|
2086
2543
|
}
|
|
2087
2544
|
// ── Debug ─────────────────────────────────────────────────────────────────
|
|
2088
|
-
//
|
|
2089
|
-
//
|
|
2090
|
-
//
|
|
2091
|
-
//
|
|
2092
|
-
// halt emitted in shimLines. See src/debug-codegen.ts.
|
|
2093
|
-
//
|
|
2094
|
-
// Target-selective: targets with a debug probe get native GDB source-level
|
|
2095
|
-
// debugging (core emits #line markers + skips printf instrumentation); the
|
|
2096
|
-
// rest fall back to the printk instrumentation path. The ESP32-S3 has a
|
|
2097
|
-
// built-in USB-JTAG (single-cable GDB via OpenOCD) so it selects 'gdb'.
|
|
2098
|
-
// The XIAO nRF52840 needs its J-Link wired up; its GDB path is a follow-on,
|
|
2099
|
-
// so it stays on printf for now.
|
|
2545
|
+
// Source-level debugging is the GDB flow only (the west-owned gdb server
|
|
2546
|
+
// serves F5 via cortex-debug's external-server mode — see
|
|
2547
|
+
// toolchain/debug-config.ts). The printf/Serial breakpoint instrumentation
|
|
2548
|
+
// pipeline was removed from the product.
|
|
2100
2549
|
debugMode(target) {
|
|
2101
|
-
// `target` is the Zephyr board id (optionally with a /qualifier suffix
|
|
2102
|
-
//
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
//
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2550
|
+
// `target` is the Zephyr board id (optionally with a /qualifier suffix).
|
|
2551
|
+
// gdb capability is a FACT of the board: its probe-method table carries a
|
|
2552
|
+
// debug-capable entry (bootloaders mark debug:false; SWD/JTAG probes and
|
|
2553
|
+
// built-in USB-JTAG carry debug:true) — the same table west's runner
|
|
2554
|
+
// selection uses. Boards without a table (or unresolvable targets, e.g.
|
|
2555
|
+
// generated custom boards) report 'none', and --debug fails with an
|
|
2556
|
+
// explicit diagnostic (transpile.ts) rather than building silently.
|
|
2557
|
+
const t = (target ?? '').trim();
|
|
2558
|
+
if (!t)
|
|
2559
|
+
return 'none';
|
|
2560
|
+
const memo = debugModeMemo.get(t);
|
|
2561
|
+
if (memo)
|
|
2562
|
+
return memo;
|
|
2563
|
+
let mode = 'none';
|
|
2564
|
+
try {
|
|
2565
|
+
const g = generateBoard(t);
|
|
2566
|
+
const constants = JSON.parse(g.boardJson).constants;
|
|
2567
|
+
for (let i = 0;; i++) {
|
|
2568
|
+
const id = constants[`zephyr.probeMethods.${i}.id`];
|
|
2569
|
+
if (typeof id !== 'string')
|
|
2570
|
+
break;
|
|
2571
|
+
if (constants[`zephyr.probeMethods.${i}.debug`] !== false) {
|
|
2572
|
+
mode = 'gdb';
|
|
2573
|
+
break;
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2577
|
+
catch {
|
|
2578
|
+
// Board not resolvable from facts — 'none' (never crash codegen over
|
|
2579
|
+
// the debug-capability gate).
|
|
2580
|
+
}
|
|
2581
|
+
debugModeMemo.set(t, mode);
|
|
2582
|
+
return mode;
|
|
2126
2583
|
}
|
|
2127
2584
|
}
|