@typecad/framework-zephyr 1.0.0-alpha.14 → 1.0.0-alpha.16
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/README.md +22 -47
- package/dist/as-built.d.ts +33 -0
- package/dist/as-built.js +188 -0
- package/dist/async/timer-polyfill.js +1 -1
- package/dist/boardgen.d.ts +128 -0
- package/dist/boardgen.js +1366 -0
- package/dist/chips/board-overrides.d.ts +7 -0
- package/dist/chips/board-overrides.js +11 -0
- package/dist/chips/esp32s3.js +49 -0
- package/dist/chips/index.d.ts +6 -12
- package/dist/chips/index.js +18 -34
- package/dist/chips/resolve.d.ts +16 -2
- package/dist/chips/resolve.js +267 -10
- package/dist/chips/soc/.d.ts +2 -0
- package/dist/chips/soc/.js +129 -0
- package/dist/chips/soc/esp32.d.ts +2 -0
- package/dist/chips/soc/esp32.js +120 -0
- package/dist/chips/soc/esp32c3.d.ts +2 -0
- package/dist/chips/soc/esp32c3.js +90 -0
- package/dist/chips/soc/esp32c6.d.ts +2 -0
- package/dist/chips/soc/esp32c6.js +109 -0
- package/dist/chips/soc/esp32s3.d.ts +2 -0
- package/dist/chips/soc/esp32s3.js +189 -0
- package/dist/chips/soc/index.d.ts +2 -0
- package/dist/chips/soc/index.js +23 -0
- package/dist/chips/soc/nrf52840.d.ts +2 -0
- package/dist/chips/soc/nrf52840.js +130 -0
- package/dist/chips/soc/rp2040.d.ts +2 -0
- package/dist/chips/soc/rp2040.js +141 -0
- package/dist/chips/soc/rp2350a.d.ts +2 -0
- package/dist/chips/soc/rp2350a.js +145 -0
- package/dist/chips/soc/samd21g18a.d.ts +2 -0
- package/dist/chips/soc/samd21g18a.js +143 -0
- package/dist/chips/soc/stm32f411xe.d.ts +2 -0
- package/dist/chips/soc/stm32f411xe.js +251 -0
- package/dist/chips/types.d.ts +319 -0
- package/dist/chips/xiao-ble.js +21 -0
- package/dist/display/profiles.js +1 -1
- package/dist/doctor.js +38 -4
- package/dist/dt-config/custom-board.d.ts +23 -0
- package/dist/dt-config/custom-board.js +227 -0
- package/dist/dt-config/kconfig.d.ts +72 -1
- package/dist/dt-config/kconfig.js +115 -26
- package/dist/dt-config/overlay.js +548 -28
- package/dist/framework.manifest.d.ts +3 -3
- package/dist/framework.manifest.js +122 -185
- package/dist/index.d.ts +4 -1
- package/dist/index.js +8 -2
- package/dist/lowering/adc.d.ts +20 -2
- package/dist/lowering/adc.js +121 -29
- package/dist/lowering/ble.js +31 -28
- package/dist/lowering/dac.js +9 -9
- package/dist/lowering/fs.js +18 -10
- package/dist/lowering/gpio.d.ts +2 -0
- package/dist/lowering/gpio.js +82 -5
- package/dist/lowering/http.js +39 -14
- package/dist/lowering/hwtimer.d.ts +4 -0
- package/dist/lowering/hwtimer.js +35 -10
- package/dist/lowering/i2c.js +46 -54
- package/dist/lowering/index.d.ts +5 -5
- package/dist/lowering/index.js +15 -16
- package/dist/lowering/interrupts.d.ts +22 -5
- package/dist/lowering/interrupts.js +104 -22
- package/dist/lowering/mqtt.js +3 -5
- package/dist/lowering/power.js +0 -4
- package/dist/lowering/preferences.js +49 -77
- package/dist/lowering/pulse.js +0 -31
- package/dist/lowering/pwm.d.ts +12 -1
- package/dist/lowering/pwm.js +78 -14
- package/dist/lowering/random.js +3 -3
- package/dist/lowering/sensor.d.ts +37 -0
- package/dist/lowering/sensor.js +119 -0
- package/dist/lowering/spi.d.ts +12 -0
- package/dist/lowering/spi.js +65 -40
- package/dist/lowering/thread.d.ts +14 -0
- package/dist/lowering/thread.js +55 -0
- package/dist/lowering/timing.js +20 -46
- package/dist/lowering/tone.js +16 -4
- package/dist/lowering/uart.d.ts +13 -0
- package/dist/lowering/uart.js +72 -51
- package/dist/lowering/usb.d.ts +34 -0
- package/dist/lowering/usb.js +206 -0
- package/dist/lowering/wdt.d.ts +1 -1
- package/dist/lowering/wdt.js +17 -7
- package/dist/lowering/wifi.d.ts +3 -2
- package/dist/lowering/wifi.js +97 -111
- package/dist/sdk/board-catalog-sync.d.ts +16 -0
- package/dist/sdk/board-catalog-sync.js +41 -0
- package/dist/sdk/board-data.generated.d.ts +2 -0
- package/dist/sdk/board-data.generated.js +4 -0
- package/dist/sdk/catalog-walker.d.ts +90 -0
- package/dist/sdk/catalog-walker.js +682 -0
- package/dist/sdk/dts-reader.d.ts +83 -0
- package/dist/sdk/dts-reader.js +596 -0
- package/dist/strategy.d.ts +136 -17
- package/dist/strategy.js +839 -222
- package/dist/toolchain/bossac-touch.d.ts +18 -0
- package/dist/toolchain/bossac-touch.js +88 -0
- package/dist/toolchain/compat.d.ts +2 -2
- package/dist/toolchain/compat.js +8 -3
- package/dist/toolchain/debug-config.d.ts +21 -26
- package/dist/toolchain/debug-config.js +226 -76
- package/dist/toolchain/env-check.d.ts +4 -4
- package/dist/toolchain/env-check.js +5 -5
- package/dist/toolchain/index.d.ts +115 -1
- package/dist/toolchain/index.js +680 -71
- package/dist/toolchain/scaffold.js +39 -7
- package/dist/toolchain/west-discover.d.ts +4 -0
- package/dist/toolchain/west-discover.js +17 -6
- package/installer/CHANGELOG.md +473 -0
- package/installer/README.md +328 -0
- package/installer/environment.yml +46 -0
- package/installer/etc/conda/activate.d/zephyr.bat +7 -0
- package/installer/etc/conda/activate.d/zephyr.ps1 +12 -0
- package/installer/etc/conda/activate.d/zephyr.sh +20 -0
- package/installer/etc/conda/deactivate.d/zephyr.bat +6 -0
- package/installer/etc/conda/deactivate.d/zephyr.ps1 +6 -0
- package/installer/etc/conda/deactivate.d/zephyr.sh +4 -0
- package/installer/install.mjs +580 -0
- package/installer/install.ps1 +619 -0
- package/installer/install.sh +310 -0
- package/installer/lib/detect-platform.sh +62 -0
- package/installer/lib/fetch-sdk.sh +304 -0
- package/installer/lib/init-workspace.sh +153 -0
- package/installer/lib/write-activation.sh +76 -0
- package/installer/patches/tfm-c-include-separators.patch +19 -0
- package/installer/patches/zephyr-tfm-nordic-zephyr-include.patch +24 -0
- package/installer/templates/project/.typecad/activate-zephyr.ps1 +31 -0
- package/installer/templates/project/.typecad/activate-zephyr.sh +31 -0
- package/installer/templates/project/.vscode/settings.json +30 -0
- package/installer/templates/project/README.md +58 -0
- package/installer/versions.env +110 -0
- package/package.json +15 -15
- package/src/as-built.ts +206 -0
- package/src/boardgen.ts +1428 -0
- package/src/chips/index.ts +35 -54
- package/src/chips/resolve.ts +285 -10
- package/src/chips/types.ts +325 -1
- package/src/display/profiles.ts +1 -1
- package/src/doctor.ts +110 -77
- package/src/dt-config/custom-board.ts +325 -0
- package/src/dt-config/kconfig.ts +165 -18
- package/src/dt-config/overlay.ts +537 -25
- package/src/framework.manifest.ts +123 -185
- package/src/index.ts +57 -41
- package/src/lowering/adc.ts +222 -118
- package/src/lowering/ble.ts +32 -28
- package/src/lowering/dac.ts +82 -82
- package/src/lowering/fs.ts +18 -10
- package/src/lowering/gpio.ts +242 -155
- package/src/lowering/http.ts +512 -488
- package/src/lowering/hwtimer.ts +139 -101
- package/src/lowering/i2c.ts +116 -126
- package/src/lowering/index.ts +91 -93
- package/src/lowering/interrupts.ts +113 -21
- package/src/lowering/mqtt.ts +3 -5
- package/src/lowering/preferences.ts +327 -354
- package/src/lowering/pwm.ts +167 -99
- package/src/lowering/random.ts +3 -3
- package/src/lowering/sensor.ts +155 -0
- package/src/lowering/spi.ts +164 -124
- package/src/lowering/thread.ts +63 -0
- package/src/lowering/timing.ts +46 -72
- package/src/lowering/uart.ts +73 -51
- package/src/lowering/usb.ts +221 -0
- package/src/lowering/wdt.ts +95 -84
- package/src/lowering/wifi.ts +97 -112
- package/src/sdk/board-catalog-sync.ts +62 -0
- package/src/strategy.ts +848 -237
- package/src/toolchain/bossac-touch.ts +112 -0
- package/src/toolchain/compat.ts +8 -3
- package/src/toolchain/debug-config.ts +676 -522
- package/src/toolchain/env-check.ts +9 -9
- package/src/toolchain/index.ts +1359 -645
- package/src/toolchain/scaffold.ts +38 -7
- package/src/toolchain/west-discover.ts +19 -6
- package/src/async/timer-polyfill.ts +0 -107
- package/src/chips/esp32.ts +0 -74
- package/src/chips/esp32s3.ts +0 -57
- package/src/chips/xiao-ble.ts +0 -82
- package/src/lowering/power.ts +0 -70
- package/src/lowering/pulse.ts +0 -93
- package/src/lowering/tone.ts +0 -60
- package/src/lowering/worker-backing.ts +0 -94
- package/src/lowering/worker.ts +0 -17
package/dist/strategy.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
// ---------------------------------------------------------------------------
|
|
2
2
|
// ZephyrStrategy — Zephyr RTOS target
|
|
3
3
|
//
|
|
4
|
-
// Outputs C++ built with `west` (the Zephyr build tool).
|
|
5
|
-
//
|
|
6
|
-
//
|
|
4
|
+
// Outputs C++ built with `west` (the Zephyr build tool). Zephyr is a standard
|
|
5
|
+
// C main()-based RTOS, so the program's entrypoint IS main(): top-level
|
|
6
|
+
// statements lower straight into it. Worker programs keep the scaffolded
|
|
7
|
+
// top-level `while (true)`; event-driven programs (UI / async / native
|
|
8
|
+
// timers) end with a scheduler loop in main() that ticks the UI runtime and
|
|
9
|
+
// pumps microtasks each frame, yielding to the kernel with k_msleep(1).
|
|
7
10
|
//
|
|
8
11
|
// GPIO is lowered through devicetree specs (gpio_pin_*_dt) so an active-low
|
|
9
12
|
// LED's polarity is honored by the DT flags, not by the generated code. See
|
|
@@ -14,11 +17,12 @@
|
|
|
14
17
|
// are covered by the TypeCAD Runtime Exception (see RUNTIME_EXCEPTION.md at the
|
|
15
18
|
// repository root) and are not subject to the license of this tool source.
|
|
16
19
|
// ---------------------------------------------------------------------------
|
|
17
|
-
import { buildWorkerRuntimePolyfill } from '@typecad/cuttlefish/api/shared';
|
|
18
20
|
import { applyStringMethodRewrites } from '@typecad/cuttlefish/api/shared';
|
|
19
21
|
import { programUsesSafety } from '@typecad/cuttlefish/api';
|
|
20
22
|
import { entryHasUI } from '@typecad/cuttlefish/ui-hook';
|
|
21
|
-
import {
|
|
23
|
+
import { generateBoard, generateBoardModuleFromContract } from './boardgen.js';
|
|
24
|
+
import { syncBoardCatalog, ensureFreshBoardCatalog, } from './sdk/board-catalog-sync.js';
|
|
25
|
+
import { setActiveChip, NO_BOARD_CHIP } from './chips/index.js';
|
|
22
26
|
import { resolveChipFromBoard } from './chips/resolve.js';
|
|
23
27
|
import { emitGpioDevDispatcher } from './chips/controllers.js';
|
|
24
28
|
/**
|
|
@@ -29,15 +33,11 @@ import { emitGpioDevDispatcher } from './chips/controllers.js';
|
|
|
29
33
|
* hygiene: every emitted function/variable is referenced). `undefined`
|
|
30
34
|
* (no program — probe paths) means "no information": callers emit every
|
|
31
35
|
* descriptor channel, preserving probe behavior.
|
|
32
|
-
*
|
|
33
|
-
* `pwm` also covers tone.* — the tone lowering drives the descriptor's
|
|
34
|
-
* first PWM spec regardless of pin, so any tone op marks it used.
|
|
35
36
|
*/
|
|
36
37
|
function collectUsedPins(program, kind, chip) {
|
|
37
38
|
if (!program)
|
|
38
39
|
return undefined;
|
|
39
40
|
const pins = new Set();
|
|
40
|
-
let usesTone = false;
|
|
41
41
|
const visit = (node) => {
|
|
42
42
|
if (!node || typeof node !== 'object')
|
|
43
43
|
return;
|
|
@@ -48,13 +48,11 @@ function collectUsedPins(program, kind, chip) {
|
|
|
48
48
|
const name = o.operation;
|
|
49
49
|
const pin = o.pin;
|
|
50
50
|
if (typeof name === 'string' && typeof pin === 'number') {
|
|
51
|
-
if (kind === 'adc' && (name === 'adc.read' || name === 'adc.read_voltage'))
|
|
51
|
+
if (kind === 'adc' && (name === 'adc.read' || name === 'adc.read_voltage' || name === 'adc.read_raw' || name === 'adc.read_mv'))
|
|
52
52
|
pins.add(pin);
|
|
53
53
|
if (kind === 'pwm' && name.startsWith('pwm.'))
|
|
54
54
|
pins.add(pin);
|
|
55
55
|
}
|
|
56
|
-
if (kind === 'pwm' && typeof name === 'string' && name.startsWith('tone.'))
|
|
57
|
-
usesTone = true;
|
|
58
56
|
}
|
|
59
57
|
for (const v of Object.values(n)) {
|
|
60
58
|
if (Array.isArray(v)) {
|
|
@@ -66,24 +64,320 @@ function collectUsedPins(program, kind, chip) {
|
|
|
66
64
|
}
|
|
67
65
|
};
|
|
68
66
|
visit(program);
|
|
69
|
-
if (kind === 'pwm' && usesTone) {
|
|
70
|
-
const first = chip?.pwm?.specs[0];
|
|
71
|
-
if (first)
|
|
72
|
-
pins.add(first.pin);
|
|
73
|
-
}
|
|
74
67
|
return pins;
|
|
75
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* The inline-override escape hatch: ADC ops carrying construction-time
|
|
71
|
+
* device overrides (hal/adc-pin.ts opts) name DT device labels the manifest
|
|
72
|
+
* may not know — the shim's init block must declare their device handles
|
|
73
|
+
* (the reads reference them) and fire even when the chip has no adc facts
|
|
74
|
+
* at all.
|
|
75
|
+
*/
|
|
76
|
+
function collectAdcOverrideDevices(program) {
|
|
77
|
+
const devices = new Set();
|
|
78
|
+
if (!program)
|
|
79
|
+
return devices;
|
|
80
|
+
const visit = (node) => {
|
|
81
|
+
if (!node || typeof node !== 'object')
|
|
82
|
+
return;
|
|
83
|
+
const n = node;
|
|
84
|
+
const op = n.operation;
|
|
85
|
+
if (op && typeof op === 'object') {
|
|
86
|
+
const o = op;
|
|
87
|
+
if (typeof o.operation === 'string' && o.operation.startsWith('adc.')
|
|
88
|
+
&& typeof o.deviceOverride === 'string' && o.deviceOverride !== '') {
|
|
89
|
+
devices.add(o.deviceOverride);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
for (const v of Object.values(n)) {
|
|
93
|
+
if (Array.isArray(v)) {
|
|
94
|
+
for (const item of v)
|
|
95
|
+
visit(item);
|
|
96
|
+
}
|
|
97
|
+
else if (v && typeof v === 'object')
|
|
98
|
+
visit(v);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
visit(program);
|
|
102
|
+
return devices;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* PWM construction-time overrides (the escape hatch): controller+channel
|
|
106
|
+
* vouched for by the user on pins the manifest may not map. The shim emits
|
|
107
|
+
* their alias vars; the overlay regen's marker merge creates the DT nodes.
|
|
108
|
+
*/
|
|
109
|
+
function collectPwmOverrideSpecs(program) {
|
|
110
|
+
const specs = [];
|
|
111
|
+
if (!program)
|
|
112
|
+
return specs;
|
|
113
|
+
const seen = new Set();
|
|
114
|
+
const visit = (node) => {
|
|
115
|
+
if (!node || typeof node !== 'object')
|
|
116
|
+
return;
|
|
117
|
+
const n = node;
|
|
118
|
+
const op = n.operation;
|
|
119
|
+
if (op && typeof op === 'object') {
|
|
120
|
+
const o = op;
|
|
121
|
+
const ctrl = typeof o.controllerOverride === 'string' && o.controllerOverride !== '' ? o.controllerOverride : undefined;
|
|
122
|
+
const ch = typeof o.channelOverride === 'number' && o.channelOverride >= 0 ? o.channelOverride : undefined;
|
|
123
|
+
if (typeof o.operation === 'string' && o.operation.startsWith('pwm.')
|
|
124
|
+
&& typeof o.pin === 'number' && (ctrl || ch !== undefined) && !seen.has(o.pin)) {
|
|
125
|
+
seen.add(o.pin);
|
|
126
|
+
specs.push({ pin: o.pin, controller: ctrl ?? 'pwm0', channel: ch ?? 0 });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
for (const v of Object.values(n)) {
|
|
130
|
+
if (Array.isArray(v)) {
|
|
131
|
+
for (const item of v)
|
|
132
|
+
visit(item);
|
|
133
|
+
}
|
|
134
|
+
else if (v && typeof v === 'object')
|
|
135
|
+
visit(v);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
visit(program);
|
|
139
|
+
return specs;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Which I2C/SPI/UART controller indexes the program actually drives (parsed
|
|
143
|
+
* from the ops' bus/port id, same as the lowerings' parseControllerIndex).
|
|
144
|
+
* `undefined` (no program — probe paths) means "no information": callers
|
|
145
|
+
* emit every declared controller, preserving probe behavior.
|
|
146
|
+
*
|
|
147
|
+
* The per-controller state blocks are emitted only for these indexes — an
|
|
148
|
+
* unused declared controller's `__tc_<bus>N_dev` would trip -Werror's
|
|
149
|
+
* -Wunused-variable (Zephyr builds with warnings-as-errors), the same
|
|
150
|
+
* hygiene collectUsedPins enforces for per-channel ADC/PWM state.
|
|
151
|
+
*/
|
|
152
|
+
function collectUsedBusIndices(program) {
|
|
153
|
+
if (!program)
|
|
154
|
+
return undefined;
|
|
155
|
+
const indices = { i2c: new Set(), spi: new Set(), uart: new Set() };
|
|
156
|
+
const visit = (node) => {
|
|
157
|
+
if (!node || typeof node !== 'object')
|
|
158
|
+
return;
|
|
159
|
+
const n = node;
|
|
160
|
+
const op = n.operation;
|
|
161
|
+
if (op && typeof op === 'object') {
|
|
162
|
+
const o = op;
|
|
163
|
+
const name = o.operation;
|
|
164
|
+
if (typeof name === 'string') {
|
|
165
|
+
const m = name.match(/^(i2c|spi|uart)\./);
|
|
166
|
+
if (m) {
|
|
167
|
+
const bus = m[1];
|
|
168
|
+
const id = typeof o.bus === 'string' ? o.bus : typeof o.port === 'string' ? o.port : undefined;
|
|
169
|
+
// parseControllerIndex() (what the lowering uses to pick the state
|
|
170
|
+
// prefix) maps a digitless alias like 'Wire'/'SPI'/'Serial' to
|
|
171
|
+
// controller 0 — the collector must agree, or the state block for
|
|
172
|
+
// controller 0 is skipped and every __tc_<bus>0_* reference dangles.
|
|
173
|
+
const idx = id?.match(/(\d+)$/);
|
|
174
|
+
indices[bus].add(idx ? parseInt(idx[1], 10) : 0);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
for (const v of Object.values(n)) {
|
|
179
|
+
if (Array.isArray(v)) {
|
|
180
|
+
for (const item of v)
|
|
181
|
+
visit(item);
|
|
182
|
+
}
|
|
183
|
+
else if (v && typeof v === 'object')
|
|
184
|
+
visit(v);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
visit(program);
|
|
188
|
+
return indices;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Collect the distinct DT-bound sensors the program's ops reference, as
|
|
192
|
+
* `${part}|${bus}|${address}` keys (same shape as peripheral-usage's
|
|
193
|
+
* sensorPartsUsed). Feeds the per-sensor device-handle state block — only
|
|
194
|
+
* constructed sensors emit state, and only sensors whose ops were emitted
|
|
195
|
+
* get a DT node.
|
|
196
|
+
*/
|
|
197
|
+
export function collectSensors(program) {
|
|
198
|
+
if (!program)
|
|
199
|
+
return undefined;
|
|
200
|
+
const sensors = new Map();
|
|
201
|
+
const visit = (node) => {
|
|
202
|
+
if (!node || typeof node !== 'object')
|
|
203
|
+
return;
|
|
204
|
+
const n = node;
|
|
205
|
+
const op = n.operation;
|
|
206
|
+
if (op && typeof op === 'object') {
|
|
207
|
+
const o = op;
|
|
208
|
+
if (o.operation === 'sensor.fetch' || o.operation === 'sensor.get') {
|
|
209
|
+
const part = String(o.part ?? '');
|
|
210
|
+
const bus = String(o.bus ?? '');
|
|
211
|
+
const port = o.port ?? 0;
|
|
212
|
+
const busKind = String(o.busKind ?? 'i2c');
|
|
213
|
+
const spiHz = o.spiHz ?? 0;
|
|
214
|
+
const spiMode = o.spiMode ?? 0;
|
|
215
|
+
const alertPin = o.alertPin ?? -1;
|
|
216
|
+
const key = `${part}|${bus}|${port}|${busKind}`;
|
|
217
|
+
if (!sensors.has(key))
|
|
218
|
+
sensors.set(key, { part, bus, port, busKind, spiHz, spiMode, alertPin });
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
for (const v of Object.values(n)) {
|
|
222
|
+
if (Array.isArray(v)) {
|
|
223
|
+
for (const item of v)
|
|
224
|
+
visit(item);
|
|
225
|
+
}
|
|
226
|
+
else if (v && typeof v === 'object')
|
|
227
|
+
visit(v);
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
visit(program);
|
|
231
|
+
return sensors;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Collect the distinct thin SPI targets the program's ops reference
|
|
235
|
+
* (spi.transceive / spi.dev_write), keyed `${bus}|${cs}`. Mirrors
|
|
236
|
+
* collectSensors: only constructed targets whose ops were emitted get a
|
|
237
|
+
* spi_dt_spec state block and a DT child node.
|
|
238
|
+
*/
|
|
239
|
+
export function collectSpiTargets(program) {
|
|
240
|
+
if (!program)
|
|
241
|
+
return undefined;
|
|
242
|
+
const targets = new Map();
|
|
243
|
+
const visit = (node) => {
|
|
244
|
+
if (!node || typeof node !== 'object')
|
|
245
|
+
return;
|
|
246
|
+
const n = node;
|
|
247
|
+
const op = n.operation;
|
|
248
|
+
if (op && typeof op === 'object') {
|
|
249
|
+
const o = op;
|
|
250
|
+
if (o.operation === 'spi.transceive' || o.operation === 'spi.dev_write' || o.operation === 'spi.reg_read') {
|
|
251
|
+
const bus = String(o.bus ?? '');
|
|
252
|
+
const cs = o.cs ?? 0;
|
|
253
|
+
const key = `${bus}|${cs}`;
|
|
254
|
+
if (!targets.has(key)) {
|
|
255
|
+
targets.set(key, { bus, cs, hz: o.hz ?? 0, mode: o.mode ?? 0 });
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
for (const v of Object.values(n)) {
|
|
260
|
+
if (Array.isArray(v)) {
|
|
261
|
+
for (const item of v)
|
|
262
|
+
visit(item);
|
|
263
|
+
}
|
|
264
|
+
else if (v && typeof v === 'object')
|
|
265
|
+
visit(v);
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
visit(program);
|
|
269
|
+
return targets;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Collect the distinct UART RX rings the program reads (uart.rx_* ops),
|
|
273
|
+
* keyed by controller index. Mirrors collectSpiTargets: only ports whose RX
|
|
274
|
+
* ops were emitted get a ring + ISR (an unused ISR is -Wunused-function
|
|
275
|
+
* under Zephyr's -Werror).
|
|
276
|
+
*/
|
|
277
|
+
export function collectUartRings(program) {
|
|
278
|
+
if (!program)
|
|
279
|
+
return undefined;
|
|
280
|
+
const rings = new Map();
|
|
281
|
+
const visit = (node) => {
|
|
282
|
+
if (!node || typeof node !== 'object')
|
|
283
|
+
return;
|
|
284
|
+
const n = node;
|
|
285
|
+
const op = n.operation;
|
|
286
|
+
if (op && typeof op === 'object') {
|
|
287
|
+
const o = op;
|
|
288
|
+
if (o.operation === 'uart.rx_arm' || o.operation === 'uart.rx_available'
|
|
289
|
+
|| o.operation === 'uart.rx_peek' || o.operation === 'uart.rx_read') {
|
|
290
|
+
const port = String(o.port ?? 'UART0');
|
|
291
|
+
const m = port.match(/(UART|uart)(\d+)/) ?? port.match(/(\d+)/);
|
|
292
|
+
const index = m ? parseInt(m[m.length - 1], 10) : 0;
|
|
293
|
+
if (!rings.has(index)) {
|
|
294
|
+
rings.set(index, { index, ring: o.ring ?? 64 });
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
for (const v of Object.values(n)) {
|
|
299
|
+
if (Array.isArray(v)) {
|
|
300
|
+
for (const item of v)
|
|
301
|
+
visit(item);
|
|
302
|
+
}
|
|
303
|
+
else if (v && typeof v === 'object')
|
|
304
|
+
visit(v);
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
visit(program);
|
|
308
|
+
return rings;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Collect the distinct threads the program STARTS (thread.start ops), keyed
|
|
312
|
+
* by instance. Only started threads emit state — see shimLines.
|
|
313
|
+
*/
|
|
314
|
+
export function collectThreads(program) {
|
|
315
|
+
if (!program)
|
|
316
|
+
return undefined;
|
|
317
|
+
const threads = new Map();
|
|
318
|
+
const visit = (node) => {
|
|
319
|
+
if (!node || typeof node !== 'object')
|
|
320
|
+
return;
|
|
321
|
+
const n = node;
|
|
322
|
+
const op = n.operation;
|
|
323
|
+
if (op && typeof op === 'object') {
|
|
324
|
+
const o = op;
|
|
325
|
+
if (o.operation === 'thread.start') {
|
|
326
|
+
const instance = o.instance ?? 0;
|
|
327
|
+
if (!threads.has(instance)) {
|
|
328
|
+
threads.set(instance, { instance, stackBytes: o.stackBytes ?? 2048, priority: o.priority ?? 5 });
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
for (const v of Object.values(n)) {
|
|
333
|
+
if (Array.isArray(v)) {
|
|
334
|
+
for (const item of v)
|
|
335
|
+
visit(item);
|
|
336
|
+
}
|
|
337
|
+
else if (v && typeof v === 'object')
|
|
338
|
+
visit(v);
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
visit(program);
|
|
342
|
+
return threads;
|
|
343
|
+
}
|
|
76
344
|
import { lowerHalOp } from './lowering/index.js';
|
|
77
|
-
import {
|
|
78
|
-
import { adcInitLines } from './lowering/adc.js';
|
|
345
|
+
import { adcInitLines, adcChannelForPin } from './lowering/adc.js';
|
|
79
346
|
import { pwmInitLines } from './lowering/pwm.js';
|
|
80
347
|
import { dacInitLines } from './lowering/dac.js';
|
|
81
348
|
import { fsInitLines } from './lowering/fs.js';
|
|
82
349
|
import { hwtimerInitLines } from './lowering/hwtimer.js';
|
|
83
350
|
import { i2cInitLines } from './lowering/i2c.js';
|
|
351
|
+
import { sensorStateLines } from './lowering/sensor.js';
|
|
352
|
+
import { spiTargetStateLines } from './lowering/spi.js';
|
|
353
|
+
import { threadStateLines } from './lowering/thread.js';
|
|
354
|
+
import { uartRingStateLines } from './lowering/uart.js';
|
|
84
355
|
import { spiInitLines } from './lowering/spi.js';
|
|
85
356
|
import { uartInitLines } from './lowering/uart.js';
|
|
86
|
-
import {
|
|
357
|
+
import { usbInitLines, usbdDeviceLines } from './lowering/usb.js';
|
|
358
|
+
/**
|
|
359
|
+
* STM32F4 boot-time DBGMCU setup: set DBG_SLEEP|DBG_STOP|DBG_STANDBY
|
|
360
|
+
* (DBGMCU_CR @ 0xE0042004, bits 0–2) so SWD stays attachable while the app
|
|
361
|
+
* sleeps. RCC_APB1ENR (0x40023840) bit 18 clocks the DBGMCU first — F4 gates
|
|
362
|
+
* register access behind it. Raw-register form (not the STM32 LL headers) so
|
|
363
|
+
* the shim stays include-light; AUTOSAR-clean via reinterpret_cast.
|
|
364
|
+
*/
|
|
365
|
+
function stm32f4DbgmcuLines() {
|
|
366
|
+
return [
|
|
367
|
+
'// CUTTLEFISH_STM32_DBGMCU_BEGIN',
|
|
368
|
+
'#include <zephyr/init.h>',
|
|
369
|
+
'static int __tc_stm32_dbgmcu_keep_swd_alive(void) {',
|
|
370
|
+
' volatile uint32_t* const rcc_apb1enr = reinterpret_cast<volatile uint32_t*>(0x40023840);',
|
|
371
|
+
' *rcc_apb1enr = *rcc_apb1enr | (1UL << 18);',
|
|
372
|
+
' volatile uint32_t* const dbgmcu_cr = reinterpret_cast<volatile uint32_t*>(0xE0042004);',
|
|
373
|
+
' *dbgmcu_cr = *dbgmcu_cr | 0x7u;',
|
|
374
|
+
' return 0;',
|
|
375
|
+
'}',
|
|
376
|
+
'SYS_INIT(__tc_stm32_dbgmcu_keep_swd_alive, PRE_KERNEL_1, 0);',
|
|
377
|
+
'// CUTTLEFISH_STM32_DBGMCU_END',
|
|
378
|
+
];
|
|
379
|
+
}
|
|
380
|
+
import { interruptInitLines, collectInterruptPins } from './lowering/interrupts.js';
|
|
87
381
|
import { wdtInitLines } from './lowering/wdt.js';
|
|
88
382
|
import { bleInitLines } from './lowering/ble.js';
|
|
89
383
|
import { wifiInitLines } from './lowering/wifi.js';
|
|
@@ -93,7 +387,6 @@ import { preferencesInitLines } from './lowering/preferences.js';
|
|
|
93
387
|
import { randomInitLines } from './lowering/random.js';
|
|
94
388
|
import { generateZephyrInitCode, generateZephyrBreakpointCode, generateZephyrLogpointCode } from './debug-codegen.js';
|
|
95
389
|
import { generateStaticAsyncRuntime } from '@typecad/cuttlefish/api/shared';
|
|
96
|
-
import { buildTimerPolyfill } from './async/timer-polyfill.js';
|
|
97
390
|
import { resolveZephyrDisplayOp, newDisplayState } from './display/index.js';
|
|
98
391
|
import { buildDisplayRuntime } from './display/gfx.js';
|
|
99
392
|
import { ZEPHYR_DISPLAY_PROFILES, BUILT_IN_PROFILES } from './display/profiles.js';
|
|
@@ -103,11 +396,6 @@ export class ZephyrStrategy {
|
|
|
103
396
|
constructor() {
|
|
104
397
|
this.id = 'zephyr';
|
|
105
398
|
this._largeEnumNames = new Set();
|
|
106
|
-
// ── Worker offload backing (Phase 1) ─────────────────────────────────────
|
|
107
|
-
// Delegates to the Zephyr backing (worker-backing.ts): k_work system
|
|
108
|
-
// workqueue + k_sem for completion. k_sem provides the kernel memory barrier
|
|
109
|
-
// the dual-core contract requires (the worker runs on a workqueue thread).
|
|
110
|
-
this._workerBacking = buildZephyrWorkerBacking();
|
|
111
399
|
// Display state (mirrors Arduino's _displayCtx). Seeded on display.init; the
|
|
112
400
|
// validator-probe path seeds the default profile lazily.
|
|
113
401
|
this._displayState = newDisplayState();
|
|
@@ -118,24 +406,36 @@ export class ZephyrStrategy {
|
|
|
118
406
|
* by the methods that need the descriptor (shimLines, resolveHALOperation
|
|
119
407
|
* via lowerHalOp).
|
|
120
408
|
*
|
|
121
|
-
*
|
|
122
|
-
* fields (via boardConstants)
|
|
123
|
-
*
|
|
409
|
+
* The chip descriptor derives from the board/MCU manifest's zephyr
|
|
410
|
+
* fields (via boardConstants). There is no registry fallback — a board
|
|
411
|
+
* that never generated a manifest stays NO_BOARD_CHIP.
|
|
124
412
|
*/
|
|
413
|
+
/**
|
|
414
|
+
* Eagerly resolve + cache the chip for `program`. The emitter calls this
|
|
415
|
+
* before rendering any bodies — hal-op lowering reads the module-global
|
|
416
|
+
* chip cache, and the lazy resolve (inside shimLines) runs later than the
|
|
417
|
+
* first lowered statement.
|
|
418
|
+
*/
|
|
419
|
+
prepareChip(program, ctx) {
|
|
420
|
+
this.resolveChip(ctx, program);
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Contract-board generation: a custom PCB spec (typecad contract) names
|
|
424
|
+
* its wired pads and bus families; the SoC's bus controller labels come
|
|
425
|
+
* from the installed Zephyr tree's soc dtsi. Same builder as every board.
|
|
426
|
+
*/
|
|
427
|
+
generateContractBoardModule(opts) {
|
|
428
|
+
return generateBoardModuleFromContract(opts);
|
|
429
|
+
}
|
|
125
430
|
resolveChip(ctx, program) {
|
|
126
|
-
//
|
|
431
|
+
// The chip view reconstructs from the project's generated board
|
|
432
|
+
// manifest (boardgen's zephyr.* constants) — the one path, for every
|
|
433
|
+
// board. No curated registry, no target-based fallback: a program with
|
|
434
|
+
// no board data resolves NO_BOARD_CHIP and the lowering reports
|
|
435
|
+
// unsupported per subsystem.
|
|
127
436
|
const fromBoard = resolveChipFromBoard(program?.boardConstants);
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
return fromBoard;
|
|
131
|
-
}
|
|
132
|
-
// 2. Fall back to frameworkData.buildTarget → hardcoded registry
|
|
133
|
-
const fd = ctx?.frameworkData;
|
|
134
|
-
const target = fd?.target ??
|
|
135
|
-
fd?.buildTarget;
|
|
136
|
-
const chip = chipForTarget(target);
|
|
137
|
-
setActiveChip(chip);
|
|
138
|
-
return chip;
|
|
437
|
+
setActiveChip(fromBoard ?? NO_BOARD_CHIP);
|
|
438
|
+
return fromBoard ?? NO_BOARD_CHIP;
|
|
139
439
|
}
|
|
140
440
|
/**
|
|
141
441
|
* Resolve the debug mode for the active target from the platform context.
|
|
@@ -165,6 +465,7 @@ export class ZephyrStrategy {
|
|
|
165
465
|
// true so nothing is stripped — mirrors framework-esp32's forcedIncludes.
|
|
166
466
|
const a = ctx?.analysis;
|
|
167
467
|
const uses = (f) => (a ? !!a[f] : true);
|
|
468
|
+
const chip = this.resolveChip(ctx, _program);
|
|
168
469
|
// <zephyr/drivers/gpio.h> and <cstdint> stay unconditional: gpio.h is
|
|
169
470
|
// cross-cutting (gpio/power/interrupt/spi/pulse lowerings + the DT-spec
|
|
170
471
|
// machinery all reference its API, and no single usesX flag owns it), and
|
|
@@ -178,12 +479,14 @@ export class ZephyrStrategy {
|
|
|
178
479
|
// A program touching none of those needs no <cstdio>.
|
|
179
480
|
const helpers = a?.usedPolyfillHelpers;
|
|
180
481
|
const needsCstdio = uses('usesCstdio') || uses('usesFS') || uses('usesPreferences')
|
|
181
|
-
|| uses('usesUart')
|
|
482
|
+
|| uses('usesUart') || uses('usesUsb')
|
|
182
483
|
|| !!helpers?.has('__tc_print') || !!helpers?.has('__tc_println');
|
|
183
484
|
if (needsCstdio)
|
|
184
485
|
inc.push('<cstdio>');
|
|
185
486
|
if (uses('usesI2C'))
|
|
186
487
|
inc.push('<zephyr/drivers/i2c.h>');
|
|
488
|
+
if (uses('usesSensor'))
|
|
489
|
+
inc.push('<zephyr/drivers/sensor.h>');
|
|
187
490
|
if (uses('usesSPI'))
|
|
188
491
|
inc.push('<zephyr/drivers/spi.h>');
|
|
189
492
|
if (uses('usesUart'))
|
|
@@ -194,6 +497,17 @@ export class ZephyrStrategy {
|
|
|
194
497
|
// emitted, so skip the include there to avoid pulling in an unused header.
|
|
195
498
|
if (isPrintf && !inc.includes('<zephyr/drivers/uart.h>'))
|
|
196
499
|
inc.push('<zephyr/drivers/uart.h>');
|
|
500
|
+
// USB CDC serial: the class instance is a UART device (uart.h); the
|
|
501
|
+
// device context macros + usbd_* API live in the next-stack header.
|
|
502
|
+
if (uses('usesUsb')) {
|
|
503
|
+
if (!inc.includes('<zephyr/drivers/uart.h>'))
|
|
504
|
+
inc.push('<zephyr/drivers/uart.h>');
|
|
505
|
+
inc.push('<zephyr/usb/usbd.h>');
|
|
506
|
+
// touch-to-reset shim callback reboots via NVIC_SystemReset()
|
|
507
|
+
// (RAM-retaining Cortex-M reset — no CONFIG_REBOOT needed).
|
|
508
|
+
if (chip?.usb?.touchReset)
|
|
509
|
+
inc.push('<cmsis_core.h>');
|
|
510
|
+
}
|
|
197
511
|
if (uses('usesADC'))
|
|
198
512
|
inc.push('<zephyr/drivers/adc.h>');
|
|
199
513
|
if (uses('usesPWM'))
|
|
@@ -208,10 +522,8 @@ export class ZephyrStrategy {
|
|
|
208
522
|
// Hardware timers via the counter driver.
|
|
209
523
|
if (uses('usesHwtimer'))
|
|
210
524
|
inc.push('<zephyr/drivers/counter.h>');
|
|
211
|
-
if (uses('usesWDT'))
|
|
525
|
+
if (uses('usesWDT') || uses('usesWdt'))
|
|
212
526
|
inc.push('<zephyr/drivers/watchdog.h>');
|
|
213
|
-
if (uses('usesPower'))
|
|
214
|
-
inc.push('<zephyr/pm/pm.h>', '<zephyr/pm/state.h>', '<zephyr/pm/policy.h>');
|
|
215
527
|
// BLE: the bt_* GATT API + the flat-string headers the shim uses. <string>
|
|
216
528
|
// is needed because a Utf8 (BleValueType.Utf8) read handler lowers to a
|
|
217
529
|
// std::string-returning function (the string literal return type), and the
|
|
@@ -411,64 +723,178 @@ export class ZephyrStrategy {
|
|
|
411
723
|
if (uses('usesNullishHelper')) {
|
|
412
724
|
guardBody.push('template<typename T> inline bool cuttlefish_is_nullish(const T& v) { return false; }', 'inline bool cuttlefish_is_nullish(long long v) { return v == CUTTLEFISH_UNDEFINED; }', 'inline bool cuttlefish_is_nullish(int v) { return v == CUTTLEFISH_UNDEFINED; }', 'inline bool cuttlefish_is_nullish(double v) { return v == static_cast<double>(CUTTLEFISH_UNDEFINED); }', 'inline bool cuttlefish_is_nullish(bool v) { return v == false; }', 'template<typename T> inline bool cuttlefish_is_nullish(T* v) { return v == nullptr; }', 'template<typename T> inline bool cuttlefish_exists(const T& v) { return !cuttlefish_is_nullish(v); }', 'template<typename T, typename U> inline T cuttlefish_nullish(const T& a, U b) { return !cuttlefish_is_nullish(a) ? a : (T)b; }');
|
|
413
725
|
}
|
|
414
|
-
//
|
|
415
|
-
// the
|
|
416
|
-
//
|
|
417
|
-
//
|
|
418
|
-
//
|
|
419
|
-
//
|
|
420
|
-
// scheduler, or a mounted UI's per-frame tick.
|
|
726
|
+
// The runtime clock contract: __tc_now_ms() is the monotonic ms clock
|
|
727
|
+
// the shared runtimes (UI per-frame tick, async timers, scheduler)
|
|
728
|
+
// and currentTimeMillis() lower onto. Emitted whenever anything could
|
|
729
|
+
// read the clock: explicit Time calls, async, the interval/timeout
|
|
730
|
+
// scheduler, or a mounted UI. uint32_t wraps every ~49.7 days — every
|
|
731
|
+
// consumer compares by subtraction, so the wrap is harmless.
|
|
421
732
|
if (uses('usesWallClock') || uses('hasAsync') || (!a || a.timerCallCount > 0)
|
|
422
733
|
|| this.programUsesAsyncRuntime(program) || entryHasUI()) {
|
|
423
|
-
guardBody.push('inline
|
|
734
|
+
guardBody.push('inline uint32_t __tc_now_ms(void) { return k_uptime_get_32(); }');
|
|
424
735
|
}
|
|
425
|
-
//
|
|
426
|
-
|
|
427
|
-
|
|
736
|
+
// Num fluent math (Num.abs/min/max) + the free math trio.
|
|
737
|
+
// On Zephyr newlib none of these
|
|
738
|
+
// names are macros, so the underscore-escape rewrite the
|
|
739
|
+
// strategy needs is not. abs(long)/min/max deliberately overload (not
|
|
740
|
+
// shadow) newlib's abs(int) — exact-match int calls still resolve to the
|
|
741
|
+
// libc overload.
|
|
742
|
+
if (uses('usesNum')) {
|
|
743
|
+
guardBody.push('struct __tc_Num {', ' static long abs(long x) { return x < 0 ? -x : x; }', ' static long min(long a, long b) { return a < b ? a : b; }', ' static long max(long a, long b) { return a > b ? a : b; }',
|
|
744
|
+
// The core emitter escapes abs/min/max member calls to a trailing
|
|
745
|
+
// underscore (C-library collision) — provide the escaped spellings so
|
|
746
|
+
// Num.abs_()/Num.min_()/Num.max_() resolve too.
|
|
747
|
+
' static long abs_(long x) { return x < 0 ? -x : x; }', ' static long min_(long a, long b) { return a < b ? a : b; }', ' static long max_(long a, long b) { return a > b ? a : b; }', '} Num;', 'inline long abs(long v) { return v < 0 ? -v : v; }', 'inline long min(long a, long b) { return a < b ? a : b; }', 'inline long max(long a, long b) { return a > b ? a : b; }',
|
|
748
|
+
// Escaped free-function spellings — the escape applies to bare call
|
|
749
|
+
// sites on some paths (abs_(-42)); cover both.
|
|
750
|
+
'inline long abs_(long v) { return v < 0 ? -v : v; }', 'inline long min_(long a, long b) { return a < b ? a : b; }', 'inline long max_(long a, long b) { return a > b ? a : b; }');
|
|
428
751
|
}
|
|
429
|
-
//
|
|
430
|
-
//
|
|
431
|
-
//
|
|
432
|
-
|
|
433
|
-
|
|
752
|
+
// Random PRNG shim: the Random namespace lowers to the __tc_rand_* PRNG.
|
|
753
|
+
// Emitted when the program actually uses the random.* ops, so a minimal
|
|
754
|
+
// program's shim stays empty. The dispatcher + PRNG helpers must precede
|
|
755
|
+
// the shims that call them (the guard block emits in push order, and
|
|
756
|
+
// C++ needs the definitions before use).
|
|
757
|
+
if (this.needsGpioReadShim(program, ctx)) {
|
|
758
|
+
guardBody.push(...emitGpioDevDispatcher(chip));
|
|
434
759
|
}
|
|
435
|
-
if (uses('
|
|
436
|
-
guardBody.push(
|
|
760
|
+
if (uses('usesRandom')) {
|
|
761
|
+
guardBody.push(...randomInitLines());
|
|
762
|
+
}
|
|
763
|
+
// PROGMEM note: only some UI runtime headers reference it.
|
|
764
|
+
if (entryHasUI()) {
|
|
765
|
+
guardBody.push('#ifndef PROGMEM', '#define PROGMEM', '#endif');
|
|
437
766
|
}
|
|
438
767
|
// Test-runner console helpers: @typecad/expect's Zephyr shim calls these
|
|
439
768
|
// for protocol output. Overloaded for string (const char*) and numeric
|
|
440
769
|
// (double) so the same call site works for markers and test values.
|
|
441
|
-
//
|
|
442
|
-
//
|
|
443
|
-
|
|
444
|
-
|
|
770
|
+
// The fs lowering bakes __tc_println into its error paths too, so the
|
|
771
|
+
// overloads are emitted unconditionally — ctx.analysis can be absent or
|
|
772
|
+
// stale on UI programs, and missing definitions would be a link error.
|
|
773
|
+
//
|
|
774
|
+
// The numeric form formats via INTEGER conversions only: libc float
|
|
775
|
+
// printf is not dependable across SDKs — the 0.17.5 toolchain swapped
|
|
776
|
+
// newlib for picolibc, whose default build silently prints NOTHING for
|
|
777
|
+
// %g (the same trap as newlib-nano's -u _printf_float), which emptied
|
|
778
|
+
// every [TC:EXPECT:...:value:] line. Integer %lld works in every libc
|
|
779
|
+
// configuration, and the host parser accepts plain fixed-point.
|
|
780
|
+
{
|
|
781
|
+
guardBody.push('inline void __tc_print(const char* s) { printf("%s", s); }', [
|
|
782
|
+
'static inline char* __tc_fmt_num_buf(double v, char* out, size_t cap) {',
|
|
783
|
+
' if (v != v) { snprintf(out, cap, "nan"); return out; }',
|
|
784
|
+
' double a = v < 0 ? -v : v;',
|
|
785
|
+
' long long ip = (long long)a;',
|
|
786
|
+
' long long fr = (long long)((a - (double)ip) * 1000000.0 + 0.5);',
|
|
787
|
+
' if (fr >= 1000000LL) { ip += 1LL; fr = 0LL; }',
|
|
788
|
+
' int used = 0;',
|
|
789
|
+
' if (v < 0 && (ip != 0LL || fr != 0LL)) { out[used++] = \'-\'; out[used] = \'\\0\'; }',
|
|
790
|
+
' if (fr == 0LL) { snprintf(out + used, cap - (size_t)used, "%lld", ip); return out; }',
|
|
791
|
+
' char fbuf[8];',
|
|
792
|
+
' int len = snprintf(fbuf, sizeof(fbuf), "%06lld", fr);',
|
|
793
|
+
' while (len > 0 && fbuf[len - 1] == \'0\') { fbuf[--len] = \'\\0\'; }',
|
|
794
|
+
' snprintf(out + used, cap - (size_t)used, "%lld.%s", ip, fbuf);',
|
|
795
|
+
' return out;',
|
|
796
|
+
'}',
|
|
797
|
+
'static void __tc_fmt_num(double v) { char __b[32]; printf("%s", __tc_fmt_num_buf(v, __b, sizeof(__b))); }',
|
|
798
|
+
].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
|
+
}
|
|
800
|
+
// Serial-port write helper: writes a scalar to a UART/CDC device a byte at
|
|
801
|
+
// a time. Overloaded on const char* (strings, snprintf buffers) and double
|
|
802
|
+
// (numbers/booleans) so a single __tc_dev_put(dev, value) call site formats
|
|
803
|
+
// any writable scalar — the same overload contract __tc_print uses. Emitted
|
|
804
|
+
// when the program writes a UART or USB port (both include uart.h).
|
|
805
|
+
if (uses('usesUart') || uses('usesUsb')) {
|
|
806
|
+
guardBody.push('static inline void __tc_dev_put(const struct device* dev, const char* s) {', ' for (; *s != \'\\0\'; ++s) { uart_poll_out(dev, *s); }', '}', 'static inline void __tc_dev_put(const struct device* dev, double v) {', ' char __b[32];', ' __tc_dev_put(dev, __tc_fmt_num_buf(v, __b, sizeof(__b)));', '}');
|
|
445
807
|
}
|
|
446
808
|
// Per-peripheral bus state — gated on the same ctx.analysis.usesX flags as
|
|
447
809
|
// forcedIncludes, so an unused peripheral emits no state (and its header is
|
|
448
|
-
// not included). Mirrors framework-esp32's shimLines espInit block.
|
|
810
|
+
// not included). Mirrors framework-esp32's shimLines espInit block. Within
|
|
811
|
+
// a bus, only the instances the program drives get state — an unused
|
|
812
|
+
// declared controller's __tc_<bus>N_dev trips -Wunused-variable under
|
|
813
|
+
// Zephyr's -Werror (collectUsedBusIndices; probes with no program emit all).
|
|
814
|
+
const usedBuses = collectUsedBusIndices(program);
|
|
449
815
|
if (uses('usesI2C') && chip.i2c) {
|
|
450
|
-
for (let i = 0; i < chip.i2c.controllers.length; i++)
|
|
816
|
+
for (let i = 0; i < chip.i2c.controllers.length; i++) {
|
|
817
|
+
if (usedBuses && !usedBuses.i2c.has(i))
|
|
818
|
+
continue;
|
|
451
819
|
guardBody.push(...i2cInitLines(chip, i));
|
|
820
|
+
}
|
|
452
821
|
}
|
|
453
822
|
if (uses('usesSPI') && chip.spi) {
|
|
454
|
-
for (let i = 0; i < chip.spi.controllers.length; i++)
|
|
823
|
+
for (let i = 0; i < chip.spi.controllers.length; i++) {
|
|
824
|
+
if (usedBuses && !usedBuses.spi.has(i))
|
|
825
|
+
continue;
|
|
455
826
|
guardBody.push(...spiInitLines(chip, i));
|
|
827
|
+
}
|
|
456
828
|
}
|
|
457
829
|
if (uses('usesUart') && chip.uart) {
|
|
458
|
-
for (let i = 0; i < chip.uart.controllers.length; i++)
|
|
830
|
+
for (let i = 0; i < chip.uart.controllers.length; i++) {
|
|
831
|
+
if (usedBuses && !usedBuses.uart.has(i))
|
|
832
|
+
continue;
|
|
459
833
|
guardBody.push(...uartInitLines(chip, i));
|
|
834
|
+
}
|
|
835
|
+
// Thin UART RX rings — one interrupt-drained ring per port the program
|
|
836
|
+
// reads (uart.rx_* ops). Emitted after the device handles it references.
|
|
837
|
+
const rings = collectUartRings(program);
|
|
838
|
+
if (rings) {
|
|
839
|
+
for (const r of rings.values()) {
|
|
840
|
+
guardBody.push(...uartRingStateLines(r.index, r.ring));
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
// DT-bound sensors — one state block per constructed part (device handle
|
|
845
|
+
// + sensor_value scratch). The lowering's __tc_sensor_* references and the
|
|
846
|
+
// overlay's DT child nodes derive from the same facts (lowering/sensor.ts).
|
|
847
|
+
if (uses('usesSensor')) {
|
|
848
|
+
const sensors = collectSensors(program);
|
|
849
|
+
if (sensors) {
|
|
850
|
+
for (const s of sensors.values()) {
|
|
851
|
+
guardBody.push(...sensorStateLines(s.part, s.bus, s.port, s.busKind, s.spiHz, s.spiMode, s.alertPin));
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
// Thin SPI targets — one spi_dt_spec per constructed target, against the
|
|
856
|
+
// DT child node the overlay emits. Same shared-facts discipline (the
|
|
857
|
+
// tc-spit-cfg comment is the overlay scanner's channel).
|
|
858
|
+
if (uses('usesSPI')) {
|
|
859
|
+
const targets = collectSpiTargets(program);
|
|
860
|
+
if (targets) {
|
|
861
|
+
for (const t of targets.values()) {
|
|
862
|
+
guardBody.push(...spiTargetStateLines(t.bus, t.cs, t.hz, t.mode));
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
// Thin Threads — one stack + k_thread + trampoline per started slot.
|
|
867
|
+
// Keyed on thread.start ops only: join() without a prior start() on the
|
|
868
|
+
// same index is a user error that surfaces as the undefined slot symbol.
|
|
869
|
+
{
|
|
870
|
+
const threads = collectThreads(program);
|
|
871
|
+
if (threads) {
|
|
872
|
+
for (const t of threads.values()) {
|
|
873
|
+
guardBody.push(...threadStateLines(t.instance, t.stackBytes));
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
if (uses('usesUsb') && chip.usb) {
|
|
878
|
+
guardBody.push(...usbdDeviceLines(chip));
|
|
879
|
+
for (let i = 0; i < chip.usb.cdcInstances; i++)
|
|
880
|
+
guardBody.push(...usbInitLines(chip, i));
|
|
881
|
+
}
|
|
882
|
+
// PWM init also fires (with alias vars for the override pins) when a
|
|
883
|
+
// program drives ONLY inline-override channels on a chip with no pwm
|
|
884
|
+
// facts — the lowered calls reference those aliases.
|
|
885
|
+
{
|
|
886
|
+
const pwmUserSpecs = collectPwmOverrideSpecs(program);
|
|
887
|
+
if ((uses('usesPWM') && chip.pwm) || pwmUserSpecs.length > 0) {
|
|
888
|
+
guardBody.push(...pwmInitLines(chip, collectUsedPins(program, 'pwm', chip), pwmUserSpecs));
|
|
889
|
+
}
|
|
460
890
|
}
|
|
461
|
-
if (uses('usesADC') && chip.adc)
|
|
462
|
-
guardBody.push(...adcInitLines(chip, collectUsedPins(program, 'adc')));
|
|
463
|
-
if (uses('usesPWM') && chip.pwm)
|
|
464
|
-
guardBody.push(...pwmInitLines(chip, collectUsedPins(program, 'pwm', chip)));
|
|
465
891
|
if (uses('usesDAC') && chip.dac)
|
|
466
892
|
guardBody.push(...dacInitLines(chip));
|
|
467
893
|
if (uses('usesHwtimer') && chip.hwtimer)
|
|
468
894
|
guardBody.push(...hwtimerInitLines(chip));
|
|
469
895
|
if (uses('usesInterrupts'))
|
|
470
|
-
guardBody.push(...interruptInitLines(chip));
|
|
471
|
-
if (uses('usesWDT') && chip.wdt)
|
|
896
|
+
guardBody.push(...interruptInitLines(chip, program ? collectInterruptPins(program) : undefined));
|
|
897
|
+
if ((uses('usesWDT') || uses('usesWdt')) && chip.wdt)
|
|
472
898
|
guardBody.push(...wdtInitLines(chip));
|
|
473
899
|
if (uses('usesBle'))
|
|
474
900
|
guardBody.push(...bleInitLines());
|
|
@@ -499,8 +925,29 @@ export class ZephyrStrategy {
|
|
|
499
925
|
guardBody.push(...preferencesInitLines());
|
|
500
926
|
if (uses('usesFS'))
|
|
501
927
|
guardBody.push(...fsInitLines());
|
|
502
|
-
|
|
503
|
-
|
|
928
|
+
// The ADC init block also fires (with the override devices' handles)
|
|
929
|
+
// when a program uses ONLY inline-override reads on a chip with no adc
|
|
930
|
+
// facts — otherwise those reads reference undeclared device handles.
|
|
931
|
+
{
|
|
932
|
+
const adcOverrideDevices = collectAdcOverrideDevices(program);
|
|
933
|
+
if ((uses('usesADC') && chip.adc) || adcOverrideDevices.size > 0) {
|
|
934
|
+
guardBody.push(...adcInitLines(chip, collectUsedPins(program, 'adc'), adcOverrideDevices));
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
// STM32F4: keep the core debug port alive across WFI sleep. The DBGMCU
|
|
938
|
+
// gates PPB access while the core sleeps unless DBGMCU_CR DBG_SLEEP/
|
|
939
|
+
// DBG_STOP/DBG_STANDBY are set — without them openocd cannot examine or
|
|
940
|
+
// halt the running target ("Failed to read memory at 0xe000ed04", "AP
|
|
941
|
+
// write error, reset will not halt"), and with no RST pad on boards like
|
|
942
|
+
// the Black Pill the only recovery is the BOOT0 bootloader. Zephyr's
|
|
943
|
+
// CONFIG_STM32_ENABLE_DEBUG_SLEEP_STOP sets only DBG_STOP on F4 (the
|
|
944
|
+
// soc_config.c F1/L1 branch is the one that sets DBG_SLEEP), so the bits
|
|
945
|
+
// are set here at boot, unconditionally for dev boards.
|
|
946
|
+
// (soc is empty on board-resolved chips — the SoC name rides in the
|
|
947
|
+
// qualified id's variant segment, e.g. 'blackpill_f411ce/stm32f411xe'.)
|
|
948
|
+
if (chip.soc.startsWith('stm32f4') || /stm32f4\d*/.test(chip.id)) {
|
|
949
|
+
guardBody.push(...stm32f4DbgmcuLines());
|
|
950
|
+
}
|
|
504
951
|
const lines = [];
|
|
505
952
|
if (guardBody.length > 0) {
|
|
506
953
|
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');
|
|
@@ -541,18 +988,19 @@ export class ZephyrStrategy {
|
|
|
541
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]; }',
|
|
542
989
|
// Console input: poll the UART console device. DEVICE_DT_GET(DT_CHOSEN(zephyr_console))
|
|
543
990
|
// resolves to the board's console (UART0 USB-CDC on the XIAO nRF52840).
|
|
544
|
-
|
|
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', '');
|
|
545
996
|
}
|
|
546
|
-
// --- Zephyr entrypoint
|
|
547
|
-
//
|
|
548
|
-
// entrypointFunctionName()="
|
|
549
|
-
//
|
|
550
|
-
//
|
|
551
|
-
//
|
|
552
|
-
//
|
|
553
|
-
// separate translation unit when generateHeaderFile() splits them into the
|
|
554
|
-
// header.
|
|
555
|
-
lines.push('extern void setup(void);', 'extern void loop(void);', '', 'int main(void) {', ' setup();', ' for (;;) {', ' loop();', ' k_msleep(1);', ' }', ' return 0;', '}');
|
|
997
|
+
// --- Zephyr entrypoint ---------------------------------------------------
|
|
998
|
+
// No bridge here: the synthesizer emits main() itself (it keys off
|
|
999
|
+
// entrypointFunctionName()="main" and requiresLoopFunction()=false), with
|
|
1000
|
+
// the top-level statements inlined. Per-frame driving (ui_tick, microtask
|
|
1001
|
+
// pumping, async task .run()) is appended to main() by the driver
|
|
1002
|
+
// machinery via asyncDriverFunctionName()="main" + hostEventLoop() — see
|
|
1003
|
+
// asyncLoopInjection for the scheduler loop it closes over.
|
|
556
1004
|
// GPIO read shim: emitted only when something actually reads a pin at
|
|
557
1005
|
// runtime — user digitalRead() calls, the @typecad/safety voter (calls
|
|
558
1006
|
// __tc_gpio_read directly), or the UI runtime header's digitalRead() poll
|
|
@@ -571,8 +1019,10 @@ export class ZephyrStrategy {
|
|
|
571
1019
|
// dispatcher that resolves the owning controller's device per pin;
|
|
572
1020
|
// single-controller SoCs collapse it to a one-liner. Each DT_NODELABEL is
|
|
573
1021
|
// still compile-time-resolved per branch, so it is always statically valid.
|
|
1022
|
+
// (The dispatcher itself now lives at the top of the guard block — it must
|
|
1023
|
+
// precede the wiring-ambient shims and the PinGroup factory there; the
|
|
1024
|
+
// __tc_gpio_read body below is what still needs emitting here.)
|
|
574
1025
|
if (this.needsGpioReadShim(program, ctx)) {
|
|
575
|
-
lines.push(...emitGpioDevDispatcher(chip));
|
|
576
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))); }');
|
|
577
1027
|
}
|
|
578
1028
|
// __tc_gpio_write / __tc_delay_us are only referenced via @typecad/safety
|
|
@@ -591,11 +1041,16 @@ export class ZephyrStrategy {
|
|
|
591
1041
|
// Collect the pins the program uses for output config, ADC reads, and
|
|
592
1042
|
// interrupt attaches — deep-walking the IR the same way framework-esp32
|
|
593
1043
|
// does (its profileDiagnostics walks program to find gpio.set_mode /
|
|
594
|
-
//
|
|
1044
|
+
// adc.read nodes).
|
|
595
1045
|
const outputPins = new Set();
|
|
596
1046
|
const adcReadPins = new Set();
|
|
1047
|
+
const adcOverriddenPins = new Set();
|
|
1048
|
+
const pwmOverriddenPins = new Set();
|
|
597
1049
|
const interruptPins = new Set();
|
|
1050
|
+
const wdtOps = new Set();
|
|
598
1051
|
const dacPins = new Set();
|
|
1052
|
+
const pwmPins = new Set();
|
|
1053
|
+
const busInstances = { i2c: new Set(), spi: new Set(), uart: new Set() };
|
|
599
1054
|
const hwtimerInstances = new Set();
|
|
600
1055
|
let usesWifiOps = false;
|
|
601
1056
|
let usesHttpOps = false;
|
|
@@ -610,16 +1065,51 @@ export class ZephyrStrategy {
|
|
|
610
1065
|
&& typeof op.pin === 'number') {
|
|
611
1066
|
outputPins.add(op.pin);
|
|
612
1067
|
}
|
|
613
|
-
if ((op.operation === 'adc.read' || op.operation === 'adc.read_voltage'
|
|
1068
|
+
if ((op.operation === 'adc.read' || op.operation === 'adc.read_voltage'
|
|
1069
|
+
|| op.operation === 'adc.read_raw' || op.operation === 'adc.read_mv')
|
|
614
1070
|
&& typeof op.pin === 'number') {
|
|
615
1071
|
adcReadPins.add(op.pin);
|
|
1072
|
+
// Inline routing overrides (construction opts): the user vouches
|
|
1073
|
+
// for the pin — the unavailable-pin diagnostic does not apply.
|
|
1074
|
+
if ((typeof op.channelOverride === 'number' && op.channelOverride >= 0)
|
|
1075
|
+
|| (typeof op.deviceOverride === 'string' && op.deviceOverride !== '')) {
|
|
1076
|
+
adcOverriddenPins.add(op.pin);
|
|
1077
|
+
}
|
|
616
1078
|
}
|
|
617
|
-
if (op.operation === 'interrupt.
|
|
1079
|
+
if (op.operation === 'interrupt.attach_flags' && typeof op.pin === 'number') {
|
|
618
1080
|
interruptPins.add(op.pin);
|
|
619
1081
|
}
|
|
620
|
-
if (op.operation === '
|
|
1082
|
+
if (typeof op.operation === 'string' && op.operation.startsWith('wdt.')) {
|
|
1083
|
+
wdtOps.add(op.operation);
|
|
1084
|
+
}
|
|
1085
|
+
if ((op.operation === 'dac.write' || op.operation === 'dac.write_value')
|
|
1086
|
+
&& typeof op.pin === 'number') {
|
|
621
1087
|
dacPins.add(op.pin);
|
|
622
1088
|
}
|
|
1089
|
+
if ((op.operation === 'pwm.set_pulse' || op.operation === 'pwm.set_duty'
|
|
1090
|
+
|| op.operation === 'pwm.set_period')
|
|
1091
|
+
&& typeof op.pin === 'number') {
|
|
1092
|
+
pwmPins.add(op.pin);
|
|
1093
|
+
if ((typeof op.controllerOverride === 'string' && op.controllerOverride !== '')
|
|
1094
|
+
|| (typeof op.channelOverride === 'number' && op.channelOverride >= 0)) {
|
|
1095
|
+
pwmOverriddenPins.add(op.pin);
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
// Bus instance usage: which I2C/SPI/UART controller indexes the
|
|
1099
|
+
// program drives (the lowering resolves index N against
|
|
1100
|
+
// chip.<bus>.controllers[N]; an undeclared index emits references
|
|
1101
|
+
// to a state block that is never declared → link error).
|
|
1102
|
+
if (typeof op.operation === 'string') {
|
|
1103
|
+
const busMatch = op.operation.match(/^(i2c|spi|uart)\./);
|
|
1104
|
+
if (busMatch) {
|
|
1105
|
+
const bus = busMatch[1];
|
|
1106
|
+
const id = typeof op.bus === 'string' ? op.bus
|
|
1107
|
+
: typeof op.port === 'string' ? op.port : undefined;
|
|
1108
|
+
const m = id?.match(/(\d+)$/);
|
|
1109
|
+
if (m)
|
|
1110
|
+
busInstances[bus].add(parseInt(m[1], 10));
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
623
1113
|
if (typeof op.operation === 'string' && op.operation.startsWith('hwtimer.')) {
|
|
624
1114
|
const inst = typeof op.instance === 'number'
|
|
625
1115
|
? op.instance
|
|
@@ -648,41 +1138,54 @@ export class ZephyrStrategy {
|
|
|
648
1138
|
};
|
|
649
1139
|
visit(program);
|
|
650
1140
|
// ── ADC pin validity ────────────────────────────────────────────────────
|
|
651
|
-
// The SAADC lowering resolves
|
|
652
|
-
// descriptor's adc.channels map
|
|
653
|
-
//
|
|
654
|
-
//
|
|
1141
|
+
// The SAADC lowering resolves the adc.read argument to a channel via the
|
|
1142
|
+
// chip descriptor's adc.channels map (pin-first, then channel-number
|
|
1143
|
+
// fallback for the Arduino-compat ADC.read(n) form — see
|
|
1144
|
+
// adcChannelForPin). A number resolving to neither emits
|
|
1145
|
+
// __tc_adc-1_setup() — an undefined symbol → link error. Flag it at
|
|
1146
|
+
// compile time with a clear message instead of an opaque link failure.
|
|
655
1147
|
const adcPins = new Set((chip.adc?.channels ?? []).map((c) => c.pin));
|
|
656
1148
|
for (const pin of adcReadPins) {
|
|
657
|
-
if (
|
|
1149
|
+
if (adcOverriddenPins.has(pin))
|
|
1150
|
+
continue;
|
|
1151
|
+
if (adcChannelForPin(chip, pin) < 0) {
|
|
658
1152
|
const valid = [...adcPins].sort((x, y) => x - y).join(', ');
|
|
1153
|
+
// Cross-peripheral suggestion: the facts know what this pin IS wired
|
|
1154
|
+
// to — a PWM-capable pin misread as analog is the classic mix-up.
|
|
1155
|
+
const pwmOnPin = (chip.pwm?.specs ?? []).find((s) => s.pin === pin);
|
|
1156
|
+
const mixup = pwmOnPin
|
|
1157
|
+
? ` GPIO ${pin} carries PWM on this board (${pwmOnPin.controller} ch ${pwmOnPin.channel}) — did you mean new PWM(${pin}, …)?`
|
|
1158
|
+
: '';
|
|
659
1159
|
diags.push({
|
|
660
1160
|
severity: 'error',
|
|
661
1161
|
code: 'zephyr-adc-pin-unavailable',
|
|
662
1162
|
message: `GPIO ${pin} is not a SAADC channel on ${chip.id} and cannot be read with adc.read.`,
|
|
663
1163
|
hint: valid
|
|
664
|
-
? `Use an analog-capable pin. On ${chip.id} (SAADC): ${valid}
|
|
665
|
-
: `This target has no ADC channels mapped in its chip descriptor
|
|
1164
|
+
? `Use an analog-capable pin. On ${chip.id} (SAADC): ${valid}.${mixup}`
|
|
1165
|
+
: `This target has no ADC channels mapped in its chip descriptor.${mixup}`,
|
|
666
1166
|
source: program.fileName,
|
|
667
1167
|
});
|
|
668
1168
|
}
|
|
669
1169
|
}
|
|
670
1170
|
// ── Interrupt pin validity ──────────────────────────────────────────────
|
|
671
|
-
//
|
|
672
|
-
// descriptor's gpio.interruptPins
|
|
673
|
-
//
|
|
674
|
-
//
|
|
675
|
-
//
|
|
1171
|
+
// GPIO.onInterrupt works on every REAL GPIO: pins listed in the chip
|
|
1172
|
+
// descriptor's gpio.interruptPins wire through the DT-spec chain, any
|
|
1173
|
+
// other in-range pin through the raw-controller chain. What cannot work
|
|
1174
|
+
// is a pin number no declared controller range covers (e.g. 99 on a
|
|
1175
|
+
// 34-pin SoC) — that lowering is a comment (silent no-op). Flag only
|
|
1176
|
+
// that case.
|
|
676
1177
|
const intPins = new Set((chip.gpio.interruptPins ?? []).map((p) => p.pin));
|
|
677
1178
|
for (const pin of interruptPins) {
|
|
678
|
-
if (
|
|
1179
|
+
if (intPins.has(pin))
|
|
1180
|
+
continue;
|
|
1181
|
+
const inRange = !chip.gpioControllers || chip.gpioControllers.length === 0
|
|
1182
|
+
|| chip.gpioControllers.some((r) => pin >= r.minPin && pin <= r.maxPin);
|
|
1183
|
+
if (!inRange) {
|
|
679
1184
|
diags.push({
|
|
680
1185
|
severity: 'error',
|
|
681
1186
|
code: 'zephyr-interrupt-pin-unavailable',
|
|
682
|
-
message: `GPIO ${pin}
|
|
683
|
-
hint:
|
|
684
|
-
? `Add the pin to the chip descriptor's gpio.interruptPins, or use an interrupt-capable pin: ${[...intPins].sort((x, y) => x - y).join(', ')}.`
|
|
685
|
-
: `This target declares no interrupt pins in its chip descriptor; interrupts are not available.`,
|
|
1187
|
+
message: `GPIO ${pin} does not exist on ${chip.id}; onInterrupt is a no-op.`,
|
|
1188
|
+
hint: `Use a real GPIO on this board (controller ranges: ${chip.gpioControllers.map((r) => `${r.nodelabel} ${r.minPin}-${r.maxPin}`).join(', ')}).`,
|
|
686
1189
|
source: program.fileName,
|
|
687
1190
|
});
|
|
688
1191
|
}
|
|
@@ -697,13 +1200,82 @@ export class ZephyrStrategy {
|
|
|
697
1200
|
const dacChannels = new Set((chip.dac?.channels ?? []).map((c) => c.pin));
|
|
698
1201
|
for (const pin of dacPins) {
|
|
699
1202
|
if (!dacChannels.has(pin)) {
|
|
1203
|
+
const adcCh = (chip.adc?.channels ?? []).find((c) => c.pin === pin);
|
|
1204
|
+
const mixup = adcCh
|
|
1205
|
+
? ` GPIO ${pin} is an ADC channel (${adcCh.controller ?? chip.adc?.nodeLabel ?? 'adc'} ch ${adcCh.channel}) — did you mean new ADC(${pin})?`
|
|
1206
|
+
: '';
|
|
700
1207
|
diags.push({
|
|
701
1208
|
severity: 'error',
|
|
702
1209
|
code: 'zephyr-dac-pin-unavailable',
|
|
703
1210
|
message: `GPIO ${pin} is not a DAC channel on ${chip.id} and cannot be driven with dac.write.`,
|
|
704
1211
|
hint: dacChannels.size > 0
|
|
705
|
-
? `Use a DAC-capable pin. On ${chip.id}: ${[...dacChannels].sort((x, y) => x - y).join(', ')}
|
|
706
|
-
: `${chip.id} has no DAC. Use an esp32_devkitc target (ESP32 DAC on GPIO25/26)
|
|
1212
|
+
? `Use a DAC-capable pin. On ${chip.id}: ${[...dacChannels].sort((x, y) => x - y).join(', ')}.${mixup}`
|
|
1213
|
+
: `${chip.id} has no DAC. Use an esp32_devkitc target (ESP32 DAC on GPIO25/26).${mixup}`,
|
|
1214
|
+
source: program.fileName,
|
|
1215
|
+
});
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
// ── PWM pin validity ────────────────────────────────────────────────────
|
|
1220
|
+
// pwm.write resolves a HAL pin to a DT spec via the chip descriptor's
|
|
1221
|
+
// pwm.specs (static) or pwm.matrix (ESP32 LEDC: any matrix pin synthesizes
|
|
1222
|
+
// a spec at build time). A pin on neither lowers to a comment — the pin
|
|
1223
|
+
// silently never toggles. Flag it so the user knows (warning, not error:
|
|
1224
|
+
// boards legitimately ship partial PWM coverage, e.g. only the aliased LED
|
|
1225
|
+
// channel, and the rest of the program still works).
|
|
1226
|
+
const pwmMatrix = chip.pwm?.matrix;
|
|
1227
|
+
const pwmSpecPins = new Set([
|
|
1228
|
+
...(chip.pwm?.specs ?? []).map((s) => s.pin),
|
|
1229
|
+
...(pwmMatrix?.pins ?? []),
|
|
1230
|
+
]);
|
|
1231
|
+
const pwmValid = [...pwmSpecPins].sort((x, y) => x - y).join(', ');
|
|
1232
|
+
for (const pin of pwmPins) {
|
|
1233
|
+
if (pwmOverriddenPins.has(pin))
|
|
1234
|
+
continue;
|
|
1235
|
+
if (!pwmSpecPins.has(pin)) {
|
|
1236
|
+
// Cross-peripheral suggestion: an analog pin driven as PWM is the
|
|
1237
|
+
// other classic mix-up.
|
|
1238
|
+
const adcCh = (chip.adc?.channels ?? []).find((c) => c.pin === pin);
|
|
1239
|
+
const mixup = adcCh
|
|
1240
|
+
? ` GPIO ${pin} is an ADC channel (${adcCh.controller ?? chip.adc?.nodeLabel ?? 'adc'} ch ${adcCh.channel}) — did you mean new ADC(${pin})?`
|
|
1241
|
+
: '';
|
|
1242
|
+
diags.push({
|
|
1243
|
+
severity: 'warning',
|
|
1244
|
+
code: 'zephyr-pwm-pin-unavailable',
|
|
1245
|
+
message: pwmMatrix
|
|
1246
|
+
? `pwm on GPIO ${pin} lowers to a no-op: the pin is outside ${chip.id}'s PWM-capable set (USB, flash/PSRAM, strapping, and console pads are excluded), so nothing is driven.`
|
|
1247
|
+
: `pwm on GPIO ${pin} lowers to a no-op: the pin has no PWM spec in ${chip.id}'s chip descriptor, so nothing is driven.`,
|
|
1248
|
+
hint: pwmValid
|
|
1249
|
+
? pwmMatrix
|
|
1250
|
+
? `PWM-capable pins on ${chip.id} (first ${pwmMatrix.channelCount} driven get channels): ${pwmValid}.${mixup}`
|
|
1251
|
+
: `PWM-capable pins on ${chip.id}: ${pwmValid}.${mixup}`
|
|
1252
|
+
: `${chip.id} maps no PWM channels in its chip descriptor — pwm.* are no-ops on this target.${mixup}`,
|
|
1253
|
+
source: program.fileName,
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
// ── Bus instance validity ───────────────────────────────────────────────
|
|
1258
|
+
// The bus lowerings resolve instance N against chip.<bus>.controllers[N]
|
|
1259
|
+
// and emit `__tc_<bus>N_dev` references; the state block is only declared
|
|
1260
|
+
// for declared controllers, so an out-of-range instance is an undefined
|
|
1261
|
+
// symbol at link time. Flag it here with the board's real controller set
|
|
1262
|
+
// instead (mirrors how the ADC gate replaces an opaque link error).
|
|
1263
|
+
const busLabels = {
|
|
1264
|
+
i2c: { name: 'I2C', label: 'Wire' },
|
|
1265
|
+
spi: { name: 'SPI', label: 'SPI' },
|
|
1266
|
+
uart: { name: 'UART', label: 'Serial' },
|
|
1267
|
+
};
|
|
1268
|
+
for (const bus of ['i2c', 'spi', 'uart']) {
|
|
1269
|
+
const controllers = chip[bus]?.controllers ?? [];
|
|
1270
|
+
for (const inst of busInstances[bus]) {
|
|
1271
|
+
if (inst < 0 || inst >= controllers.length) {
|
|
1272
|
+
diags.push({
|
|
1273
|
+
severity: 'error',
|
|
1274
|
+
code: 'zephyr-bus-instance-unavailable',
|
|
1275
|
+
message: `${busLabels[bus].name}${inst} is used but ${chip.id} declares only ${controllers.length} ${busLabels[bus].name} controller(s) in its Zephyr chip data — the generated code would not link.`,
|
|
1276
|
+
hint: controllers.length > 0
|
|
1277
|
+
? `Use ${busLabels[bus].name}0 (the board's default-enabled controller: ${controllers.map((c) => c.nodeLabel).join(', ')}).`
|
|
1278
|
+
: `${chip.id} declares no ${busLabels[bus].name} controllers for Zephyr.`,
|
|
707
1279
|
source: program.fileName,
|
|
708
1280
|
});
|
|
709
1281
|
}
|
|
@@ -736,12 +1308,34 @@ export class ZephyrStrategy {
|
|
|
736
1308
|
}
|
|
737
1309
|
}
|
|
738
1310
|
}
|
|
1311
|
+
// ── Watchdog availability ───────────────────────────────────────────────
|
|
1312
|
+
// wdt.* resolves the device via the chip descriptor's wdt.nodeLabel. A
|
|
1313
|
+
// chip without that entry (e.g. SAM D21 — Zephyr's samd21 dtsi exposes
|
|
1314
|
+
// no watchdog node) lowers to a comment — flag it so the user knows the
|
|
1315
|
+
// watchdog never arms.
|
|
1316
|
+
if (wdtOps.size > 0 && !chip.wdt) {
|
|
1317
|
+
diags.push({
|
|
1318
|
+
severity: 'error',
|
|
1319
|
+
code: 'zephyr-wdt-unavailable',
|
|
1320
|
+
message: `Watchdog ops are used but ${chip.id} exposes no watchdog device.`,
|
|
1321
|
+
hint: `${chip.id} declares no wdt.nodeLabel (Zephyr's devicetree for this SoC has no watchdog node). Use a target with a watchdog, or drop the wdt.* calls.`,
|
|
1322
|
+
source: program.fileName,
|
|
1323
|
+
});
|
|
1324
|
+
}
|
|
1325
|
+
// ── Radio presence: family-derived, not board-curated ───────────────────
|
|
1326
|
+
// Espressif ESP32 variants carry a 2.4GHz WiFi radio; every other Zephyr
|
|
1327
|
+
// family in the catalog is radioless (until an Ethernet/board-wifi fact
|
|
1328
|
+
// exists). Derived from the soc name — or, when no board module was
|
|
1329
|
+
// generated, from the raw build target the config carries.
|
|
1330
|
+
const familyTarget = `${chip.soc || ''} ${ctx?.frameworkData?.target ?? ''} ${ctx?.frameworkData?.buildTarget ?? ''}`.toLowerCase();
|
|
1331
|
+
const wifiSupported = chip.wifi?.supported
|
|
1332
|
+
?? familyTarget.split(/[^a-z0-9]+/).some((t) => t.startsWith('esp32'));
|
|
739
1333
|
// ── WiFi target validity ────────────────────────────────────────────────
|
|
740
1334
|
// WiFi ops require a chip with a WiFi radio. The ESP32-S3 descriptor sets
|
|
741
1335
|
// wifi.supported; the XIAO nRF52840 omits it (no radio). Flag wifi usage on
|
|
742
1336
|
// a radioless chip so the user gets a clear "use an ESP32 target" message
|
|
743
1337
|
// instead of an opaque link/DT failure.
|
|
744
|
-
if (usesWifiOps && !
|
|
1338
|
+
if (usesWifiOps && !wifiSupported) {
|
|
745
1339
|
diags.push({
|
|
746
1340
|
severity: 'error',
|
|
747
1341
|
code: 'zephyr-wifi-unavailable-on-target',
|
|
@@ -758,7 +1352,7 @@ export class ZephyrStrategy {
|
|
|
758
1352
|
// gets a clear "use an ESP32 target" message instead of an opaque link or
|
|
759
1353
|
// runtime failure. (HTTP rides over WiFi here; an Ethernet target would
|
|
760
1354
|
// set wifi.supported via a different transport flag if/when added.)
|
|
761
|
-
if (usesHttpOps && !
|
|
1355
|
+
if (usesHttpOps && !wifiSupported) {
|
|
762
1356
|
diags.push({
|
|
763
1357
|
severity: 'error',
|
|
764
1358
|
code: 'zephyr-http-unavailable-on-target',
|
|
@@ -770,7 +1364,7 @@ export class ZephyrStrategy {
|
|
|
770
1364
|
// ── MQTT target validity ─────────────────────────────────────────────
|
|
771
1365
|
// Same constraint as HTTP: MQTT needs a network transport to reach a broker.
|
|
772
1366
|
// Flag mqtt usage on a radioless chip so the user picks a networked target.
|
|
773
|
-
if (usesMqttOps && !
|
|
1367
|
+
if (usesMqttOps && !wifiSupported) {
|
|
774
1368
|
diags.push({
|
|
775
1369
|
severity: 'error',
|
|
776
1370
|
code: 'zephyr-mqtt-unavailable-on-target',
|
|
@@ -786,32 +1380,45 @@ export class ZephyrStrategy {
|
|
|
786
1380
|
return diags;
|
|
787
1381
|
}
|
|
788
1382
|
/**
|
|
789
|
-
*
|
|
790
|
-
* (
|
|
791
|
-
*
|
|
792
|
-
*
|
|
793
|
-
*
|
|
794
|
-
*
|
|
1383
|
+
* Strip Arduino library headers that @typecad/hal's class surfaces register
|
|
1384
|
+
* (I2CBus → <Wire.h>, SPIBus → <SPI.h>). The Zephyr
|
|
1385
|
+
* equivalents are usage-gated forcedIncludes (<zephyr/drivers/i2c.h>, …),
|
|
1386
|
+
* so an unstripped Wire.h reaches the west build and gcc rejects it
|
|
1387
|
+
* ("Wire.h: No such file or directory") — this hook is why I2C/SPI class
|
|
1388
|
+
* usage on Zephyr used to fail at compile time.
|
|
1389
|
+
*/
|
|
1390
|
+
filterRequiredIncludes(includes) {
|
|
1391
|
+
// Arduino-core / AVR-only headers the HAL classes declare via __includes.
|
|
1392
|
+
// On Zephyr the watchdog lowers to the Zephyr driver (<zephyr/drivers/
|
|
1393
|
+
// watchdog.h>, forced under usesWDT) — <avr/wdt.h> does not exist there.
|
|
1394
|
+
const arduinoOnlyHeaders = new Set(['<Wire.h>', '<SPI.h>', '<avr/wdt.h>', '<avr/io.h>', '<avr/interrupt.h>', '<avr/sleep.h>', '<avr/power.h>']);
|
|
1395
|
+
return includes.filter((i) => !arduinoOnlyHeaders.has(i));
|
|
1396
|
+
}
|
|
1397
|
+
/**
|
|
1398
|
+
* Nothing to exclude: no shim or Zephyr core header forward-declares a user
|
|
1399
|
+
* function extern (the old setup()/loop() bridge was the only consumer of
|
|
1400
|
+
* this hook, excluding `loop`). The entrypoint itself (`main`) is already
|
|
1401
|
+
* excluded from the static forward-declaration path because it equals
|
|
795
1402
|
* entrypointFunctionName().
|
|
796
1403
|
*/
|
|
797
1404
|
forwardDeclarationExclusions() {
|
|
798
|
-
return [
|
|
1405
|
+
return [];
|
|
799
1406
|
}
|
|
800
1407
|
// ── File shape ──────────────────────────────────────────────────────────
|
|
801
1408
|
sourceExtension() {
|
|
802
1409
|
return 'cpp';
|
|
803
1410
|
}
|
|
804
1411
|
entrypointFunctionName() {
|
|
805
|
-
return '
|
|
1412
|
+
return 'main';
|
|
806
1413
|
}
|
|
807
1414
|
requiresLoopFunction() {
|
|
808
|
-
return
|
|
1415
|
+
return false;
|
|
809
1416
|
}
|
|
810
1417
|
overrideBaseName(originalBaseName, outDirBaseName, isEntryFile, isNpmPackage) {
|
|
811
1418
|
// npm packages are library-style — don't rename. Entry files (non-npm) take
|
|
812
|
-
// the out-dir name (mirrors
|
|
813
|
-
// else passes through. (The manifest's entrypoint.overrideBaseName
|
|
814
|
-
// dead — never read in src/ — so this method is the sole name source.)
|
|
1419
|
+
// the out-dir name (mirrors the directory-name-must-match-entry rule).
|
|
1420
|
+
// Everything else passes through. (The manifest's entrypoint.overrideBaseName
|
|
1421
|
+
// field is dead — never read in src/ — so this method is the sole name source.)
|
|
815
1422
|
if (isNpmPackage)
|
|
816
1423
|
return originalBaseName;
|
|
817
1424
|
if (isEntryFile)
|
|
@@ -819,8 +1426,8 @@ export class ZephyrStrategy {
|
|
|
819
1426
|
return originalBaseName;
|
|
820
1427
|
}
|
|
821
1428
|
effectiveEmitMode(requestedMode, _isNpmPackage) {
|
|
822
|
-
// Zephyr always emits .cpp (no
|
|
823
|
-
// is passthrough regardless of npm/app. The 2-param shape
|
|
1429
|
+
// Zephyr always emits .cpp (no single-file entry equivalent to force away
|
|
1430
|
+
// from), so this is passthrough regardless of npm/app. The 2-param shape
|
|
824
1431
|
// interface and Arduino; behavior is identical across branches.
|
|
825
1432
|
return requestedMode;
|
|
826
1433
|
}
|
|
@@ -840,8 +1447,6 @@ export class ZephyrStrategy {
|
|
|
840
1447
|
mapReturnType(functionName, returnType) {
|
|
841
1448
|
if (functionName === 'main')
|
|
842
1449
|
return 'int';
|
|
843
|
-
if (functionName === 'setup' || functionName === 'loop')
|
|
844
|
-
return 'void';
|
|
845
1450
|
return this.normalizeCppType(returnType);
|
|
846
1451
|
}
|
|
847
1452
|
isStringLikeType(cppType) {
|
|
@@ -929,26 +1534,6 @@ export class ZephyrStrategy {
|
|
|
929
1534
|
// A throw becomes an infinite halt loop instead of a real throw.
|
|
930
1535
|
return 'for (;;) { k_msleep(1000); }';
|
|
931
1536
|
}
|
|
932
|
-
isConsoleCall(callee) {
|
|
933
|
-
return callee.startsWith('console.');
|
|
934
|
-
}
|
|
935
|
-
transformConsoleCall(method, renderedArgs, forHeader) {
|
|
936
|
-
const semi = forHeader ? '' : ';';
|
|
937
|
-
const empty = !renderedArgs || renderedArgs.trim() === '';
|
|
938
|
-
const tag = method === 'error' ? '[ERROR] ' : method === 'warn' ? '[WARN] ' : '';
|
|
939
|
-
if (empty)
|
|
940
|
-
return `printk("%s\\n", "${tag}")${semi}`;
|
|
941
|
-
const parts = renderedArgs.split(' << ');
|
|
942
|
-
if (parts.length === 1) {
|
|
943
|
-
return `printk("%s%s\\n", "${tag}", (${renderedArgs}))${semi}`;
|
|
944
|
-
}
|
|
945
|
-
const fmt = '%s' + '%s'.repeat(parts.length) + '\\n';
|
|
946
|
-
const args = [`"${tag}"`, ...parts].join(', ');
|
|
947
|
-
return `printk("${fmt}", ${args})${semi}`;
|
|
948
|
-
}
|
|
949
|
-
transformConsoleExpression(_method, _renderedArgs) {
|
|
950
|
-
return undefined;
|
|
951
|
-
}
|
|
952
1537
|
objectFieldInitializer() {
|
|
953
1538
|
return undefined;
|
|
954
1539
|
}
|
|
@@ -985,18 +1570,6 @@ export class ZephyrStrategy {
|
|
|
985
1570
|
reason: 'delayMicroseconds() busy-waits the CPU for the full delay, stalling every lower-priority interrupt and the scheduler for its duration',
|
|
986
1571
|
severity: 'warning',
|
|
987
1572
|
}],
|
|
988
|
-
['console.log', {
|
|
989
|
-
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',
|
|
990
|
-
severity: 'info',
|
|
991
|
-
}],
|
|
992
|
-
['console.error', {
|
|
993
|
-
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',
|
|
994
|
-
severity: 'info',
|
|
995
|
-
}],
|
|
996
|
-
['console.warn', {
|
|
997
|
-
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',
|
|
998
|
-
severity: 'info',
|
|
999
|
-
}],
|
|
1000
1573
|
['I2C0', {
|
|
1001
1574
|
reason: 'I2C transactions may sleep (driver locking + clock stretching) and are not callable from Zephyr interrupt context',
|
|
1002
1575
|
severity: 'warning',
|
|
@@ -1076,17 +1649,16 @@ export class ZephyrStrategy {
|
|
|
1076
1649
|
return false;
|
|
1077
1650
|
}
|
|
1078
1651
|
mathHeader() {
|
|
1079
|
-
// <math.h
|
|
1080
|
-
//
|
|
1081
|
-
// names
|
|
1082
|
-
|
|
1652
|
+
// <cmath>, not <math.h>: the shared lowering emits Math.<fn>() as
|
|
1653
|
+
// std::<fn>() (expression-to-ir), and picolibc's <math.h> declares the
|
|
1654
|
+
// C names in the global namespace only — std::round et al. fail to
|
|
1655
|
+
// compile. Builds run with CONFIG_REQUIRES_FULL_LIBCPP (real libstdc++),
|
|
1656
|
+
// whose <cmath> provides the std:: names.
|
|
1657
|
+
return '<cmath>';
|
|
1083
1658
|
}
|
|
1084
1659
|
cstringHeader() {
|
|
1085
1660
|
return '<cstring>';
|
|
1086
1661
|
}
|
|
1087
|
-
needsVectorOverload() {
|
|
1088
|
-
return true;
|
|
1089
|
-
}
|
|
1090
1662
|
needsLargeEnumUnderlying() {
|
|
1091
1663
|
return false;
|
|
1092
1664
|
}
|
|
@@ -1098,43 +1670,109 @@ export class ZephyrStrategy {
|
|
|
1098
1670
|
return undefined;
|
|
1099
1671
|
}
|
|
1100
1672
|
// ── Async ───────────────────────────────────────────────────────────────
|
|
1101
|
-
//
|
|
1102
|
-
//
|
|
1103
|
-
//
|
|
1104
|
-
//
|
|
1673
|
+
// Promises use the heap-free static runtime (generateStaticAsyncRuntime),
|
|
1674
|
+
// pumped cooperatively via cuttlefish_pump_microtasks(). There is no
|
|
1675
|
+
// __tc_timer_runtime.run() poll — periodic work is a Thread (k_thread) or a
|
|
1676
|
+
// Counter (hardware timer), never a cooperative timer queue.
|
|
1105
1677
|
getAsyncRuntimeConfig() {
|
|
1106
1678
|
return {
|
|
1107
1679
|
queueCapacity: 64,
|
|
1108
1680
|
scheduler: 'microtask',
|
|
1109
1681
|
waitForPinEdge: 'stub',
|
|
1110
1682
|
hasPromiseRuntime: true,
|
|
1111
|
-
hasTimers:
|
|
1683
|
+
hasTimers: false,
|
|
1112
1684
|
// Static (heap-free) runtime — no STL headers required.
|
|
1113
1685
|
requiredIncludes: [],
|
|
1114
1686
|
};
|
|
1115
1687
|
}
|
|
1688
|
+
/**
|
|
1689
|
+
* Board module generation: join the Zephyr board data pack (extracted
|
|
1690
|
+
* from the pinned tree) with the curated soc descriptors. See
|
|
1691
|
+
* src/boardgen.ts.
|
|
1692
|
+
*/
|
|
1693
|
+
generateBoardModule(target, opts) {
|
|
1694
|
+
try {
|
|
1695
|
+
const g = generateBoard(target, { factsJson: opts?.factsJson, asBuiltJson: opts?.asBuiltJson });
|
|
1696
|
+
return { boardTs: g.boardTs, boardJson: g.boardJson, ...(g.warnings ? { warnings: g.warnings } : {}) };
|
|
1697
|
+
}
|
|
1698
|
+
catch (err) {
|
|
1699
|
+
// A malformed facts/as-built file is the USER's error — surface it
|
|
1700
|
+
// verbatim instead of the generic "cannot generate" below.
|
|
1701
|
+
if (err instanceof Error && (err.message.includes('cuttlefish.facts.json') || err.message.includes('as-built.json')))
|
|
1702
|
+
throw err;
|
|
1703
|
+
return undefined;
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
/**
|
|
1707
|
+
* Regenerate the board catalog overlay from the user's own Zephyr tree —
|
|
1708
|
+
* `cuttlefish board sync`. After a `west update`, this is how new/changed/
|
|
1709
|
+
* removed boards reach projects without a cuttlefish release. See
|
|
1710
|
+
* src/sdk/board-catalog-sync.ts.
|
|
1711
|
+
*/
|
|
1712
|
+
syncBoardCatalog(zephyrBase) {
|
|
1713
|
+
return syncBoardCatalog(zephyrBase ? { zephyrBase } : {});
|
|
1714
|
+
}
|
|
1715
|
+
/**
|
|
1716
|
+
* Refresh the overlay only when it is stale (provenance no longer matches
|
|
1717
|
+
* the tree) — the pre-step `cuttlefish board regen` runs so a regen after
|
|
1718
|
+
* `west update` picks up the tree's boards automatically. Tree walk only
|
|
1719
|
+
* happens when there is actual refreshing to do.
|
|
1720
|
+
*/
|
|
1721
|
+
ensureFreshBoardCatalog() {
|
|
1722
|
+
return ensureFreshBoardCatalog();
|
|
1723
|
+
}
|
|
1116
1724
|
asyncLoopInjection(taskVarNames, configOrBool, _hasTimers) {
|
|
1117
1725
|
const cfg = typeof configOrBool === 'boolean'
|
|
1118
1726
|
? { hasPromiseRuntime: configOrBool, hasTimers: _hasTimers ?? false }
|
|
1119
1727
|
: configOrBool;
|
|
1120
|
-
// Drive every async state-machine task once per
|
|
1121
|
-
// globals auto-start on their first .run() (constructor sets STATE_0,
|
|
1122
|
-
// runs unconditionally), so this is both the start and the per-frame
|
|
1123
|
-
// The state machine no-ops in its terminal/cyclic state, so
|
|
1124
|
-
// .run() is correct (mirrors framework-arduino). No
|
|
1125
|
-
|
|
1728
|
+
// Drive every async state-machine task once per scheduler iteration. The
|
|
1729
|
+
// task globals auto-start on their first .run() (constructor sets STATE_0,
|
|
1730
|
+
// which runs unconditionally), so this is both the start and the per-frame
|
|
1731
|
+
// advance. The state machine no-ops in its terminal/cyclic state, so
|
|
1732
|
+
// unconditional .run() is correct (mirrors framework-arduino). No
|
|
1733
|
+
// isComplete() gating.
|
|
1734
|
+
const work = [];
|
|
1126
1735
|
if (cfg.hasPromiseRuntime) {
|
|
1127
|
-
|
|
1736
|
+
work.push('cuttlefish_pump_microtasks();');
|
|
1128
1737
|
}
|
|
1129
1738
|
for (const n of taskVarNames) {
|
|
1130
|
-
|
|
1739
|
+
work.push(`${n}.run();`);
|
|
1131
1740
|
}
|
|
1132
|
-
// NOTE: no
|
|
1133
|
-
// (timer
|
|
1134
|
-
|
|
1741
|
+
// NOTE: no timer-queue poll — periodic work is a Thread (k_thread) or a
|
|
1742
|
+
// Counter (hardware timer), never a cooperative poll.
|
|
1743
|
+
//
|
|
1744
|
+
// main() runs once, so the per-frame work must close over its own loop.
|
|
1745
|
+
// Under a mounted UI the emitter's hostEventLoop() already wraps ui_tick +
|
|
1746
|
+
// these lines in `while (...) { ...; k_msleep(1); }` — return flat lines
|
|
1747
|
+
// there. Otherwise (async/timer programs with no UI) self-wrap the same
|
|
1748
|
+
// scheduler loop here. For a program whose top-level `while (true)` never
|
|
1749
|
+
// exits, the loop below it is unreachable — harmless, and identical to the
|
|
1750
|
+
// old bridge behavior (whose for(;;) around loop() was equally dead).
|
|
1751
|
+
if (entryHasUI()) {
|
|
1752
|
+
return work;
|
|
1753
|
+
}
|
|
1754
|
+
return ['for (;;) {', ...work.map((l) => ` ${l}`), ' k_msleep(1);', '}'];
|
|
1135
1755
|
}
|
|
1136
1756
|
asyncDriverFunctionName() {
|
|
1137
|
-
return '
|
|
1757
|
+
return 'main';
|
|
1758
|
+
}
|
|
1759
|
+
/**
|
|
1760
|
+
* Zephyr's scheduler loop for event-driven programs (UI mounted). The
|
|
1761
|
+
* emitter wraps the per-frame work — ui_tick plus the asyncLoopInjection
|
|
1762
|
+
* lines — in `while (__tc_zephyr_event_loop) { ...; k_msleep(1); }`: a
|
|
1763
|
+
* 1 ms cooperative yield to the kernel each frame, matching the cadence the
|
|
1764
|
+
* old setup()/loop() bridge gave loop(). The flag is never cleared — the
|
|
1765
|
+
* loop is the program's lifetime — but keeping the bool gives the condition
|
|
1766
|
+
* a used symbol (a bare `while (true)` would leave nothing to name, and an
|
|
1767
|
+
* unused flag would trip -Werror's -Wunused-variable).
|
|
1768
|
+
*/
|
|
1769
|
+
hostEventLoop() {
|
|
1770
|
+
return {
|
|
1771
|
+
flagName: '__tc_zephyr_event_loop',
|
|
1772
|
+
continueCondition: '__tc_zephyr_event_loop',
|
|
1773
|
+
preIteration: '',
|
|
1774
|
+
postIteration: 'k_msleep(1);',
|
|
1775
|
+
};
|
|
1138
1776
|
}
|
|
1139
1777
|
// ── Type aliases ────────────────────────────────────────────────────────
|
|
1140
1778
|
shouldSkipTypeAlias() {
|
|
@@ -1145,7 +1783,7 @@ export class ZephyrStrategy {
|
|
|
1145
1783
|
return [];
|
|
1146
1784
|
}
|
|
1147
1785
|
currentTimeMillis() {
|
|
1148
|
-
return '
|
|
1786
|
+
return '__tc_now_ms()';
|
|
1149
1787
|
}
|
|
1150
1788
|
// ── Build configuration ──────────────────────────────────────────────────
|
|
1151
1789
|
asyncQueueCapacity() {
|
|
@@ -1188,12 +1826,10 @@ export class ZephyrStrategy {
|
|
|
1188
1826
|
// cuttlefish_halt: always (the runtime header may reference it).
|
|
1189
1827
|
// string_methods / static_array: STL-free array + string helpers a no-STL
|
|
1190
1828
|
// target needs (mutated/struct array literals + any string method).
|
|
1191
|
-
// timer_methods: k_timer/k_work pool for setInterval/setTimeout (gated on
|
|
1192
|
-
// timerCallCount at emit time in generateNativePolyfills).
|
|
1193
1829
|
// async_runtime: heap-free static Promise/microtask runtime (no STL needed).
|
|
1194
1830
|
return new Set([
|
|
1195
1831
|
'cuttlefish_halt', 'wiring_compat', 'string_methods', 'static_array',
|
|
1196
|
-
'
|
|
1832
|
+
'async_runtime',
|
|
1197
1833
|
]);
|
|
1198
1834
|
}
|
|
1199
1835
|
generateNativePolyfills(program, ctx) {
|
|
@@ -1321,22 +1957,6 @@ struct __tc_StaticArray {
|
|
|
1321
1957
|
dependencies: [],
|
|
1322
1958
|
},
|
|
1323
1959
|
];
|
|
1324
|
-
// Worker-offload runtime (Phase 1). Emitted only when the program uses
|
|
1325
|
-
// worker.* ops, backed by the Zephyr primitives in worker-backing.ts
|
|
1326
|
-
// (k_work system workqueue + k_sem for the completion barrier).
|
|
1327
|
-
const usesWorker = !!(ctx?.analysis?.usesWorker);
|
|
1328
|
-
if (program && usesWorker) {
|
|
1329
|
-
const workerPoly = buildWorkerRuntimePolyfill(program, this, buildZephyrWorkerBacking(), { poolSize: 4 });
|
|
1330
|
-
if (workerPoly)
|
|
1331
|
-
polyfills.push(workerPoly);
|
|
1332
|
-
}
|
|
1333
|
-
// timer_methods — k_timer/k_work pool. Gated on observed timer call count;
|
|
1334
|
-
// a program with no setInterval/setTimeout emits nothing.
|
|
1335
|
-
const analysis = ctx?.analysis;
|
|
1336
|
-
const timerCallCount = analysis?.timerCallCount ?? 0;
|
|
1337
|
-
if (timerCallCount > 0) {
|
|
1338
|
-
polyfills.push(buildTimerPolyfill(timerCallCount));
|
|
1339
|
-
}
|
|
1340
1960
|
// async_runtime — heap-free static Promise/microtask runtime. Emitted when
|
|
1341
1961
|
// the program declares an async function OR references an async-runtime
|
|
1342
1962
|
// symbol (Async.sleep/.then from a non-async fn). The static path requires
|
|
@@ -1350,10 +1970,10 @@ struct __tc_StaticArray {
|
|
|
1350
1970
|
domain: 'embedded',
|
|
1351
1971
|
requiredIncludes: [],
|
|
1352
1972
|
// Polyfill definitions emit before shimLines, but the runtime's
|
|
1353
|
-
// timer bodies call
|
|
1354
|
-
// first so the polyfill compiles even for programs whose source
|
|
1973
|
+
// timer bodies call __tc_now_ms() (defined in shimLines) — declare
|
|
1974
|
+
// it first so the polyfill compiles even for programs whose source
|
|
1355
1975
|
// has no explicit timing call.
|
|
1356
|
-
forwardDeclarations: ['
|
|
1976
|
+
forwardDeclarations: ['uint32_t __tc_now_ms(void);'],
|
|
1357
1977
|
helperStructs: [generateStaticAsyncRuntime(8, this.getAsyncRuntimeConfig().waitForPinEdge)],
|
|
1358
1978
|
helperFunctions: [],
|
|
1359
1979
|
shimMacros: [],
|
|
@@ -1413,19 +2033,10 @@ struct __tc_StaticArray {
|
|
|
1413
2033
|
}
|
|
1414
2034
|
// ── RTOS ─────────────────────────────────────────────────────────────────
|
|
1415
2035
|
isRtosTarget() {
|
|
1416
|
-
// Zephyr is a preemptive RTOS — delay()/k_msleep inside
|
|
2036
|
+
// Zephyr is a preemptive RTOS — delay()/k_msleep inside main() is the
|
|
1417
2037
|
// expected cooperative yield, not an anti-pattern to warn about.
|
|
1418
2038
|
return true;
|
|
1419
2039
|
}
|
|
1420
|
-
workerSpawnLines(handleId, trampolineName, waiterExpr) {
|
|
1421
|
-
return this._workerBacking.spawnLines(handleId, trampolineName, waiterExpr);
|
|
1422
|
-
}
|
|
1423
|
-
workerSignalDoneExpr(handleId) {
|
|
1424
|
-
return this._workerBacking.signalDoneExpr(handleId);
|
|
1425
|
-
}
|
|
1426
|
-
workerIsDoneExpr(handleId) {
|
|
1427
|
-
return this._workerBacking.isDoneExpr(handleId);
|
|
1428
|
-
}
|
|
1429
2040
|
// ── Graphics ──────────────────────────────────────────────────────────────
|
|
1430
2041
|
// Generic <zephyr/drivers/display.h> + ported GFX primitives (see src/display/).
|
|
1431
2042
|
// resolveDisplayOp delegates to resolveZephyrDisplayOp with the per-build
|
|
@@ -1493,6 +2104,12 @@ struct __tc_StaticArray {
|
|
|
1493
2104
|
if (boardId === 'esp32s3_devkitc' || boardId.startsWith('esp32s3')) {
|
|
1494
2105
|
return 'gdb';
|
|
1495
2106
|
}
|
|
2107
|
+
// The STM32 Black Pill ships a verified ST-Link probe method in its board
|
|
2108
|
+
// package (openocd runner over SWD, with the reset_config quirk for the
|
|
2109
|
+
// unwired SRST line), so F5 attaches natively out of the box.
|
|
2110
|
+
if (boardId.startsWith('blackpill_')) {
|
|
2111
|
+
return 'gdb';
|
|
2112
|
+
}
|
|
1496
2113
|
// The plain ESP32 (esp32_devkitc) intentionally stays on 'printf': unlike
|
|
1497
2114
|
// the S3 it has NO built-in USB-JTAG, so gdb needs an external ESP-PROG
|
|
1498
2115
|
// probe + a different OpenOCD cfg/toolchain dir (deferred). Falls through.
|