@typecad/framework-zephyr 1.0.0-alpha.8
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 +21 -0
- package/README.md +100 -0
- package/dist/async/timer-polyfill.d.ts +10 -0
- package/dist/async/timer-polyfill.js +95 -0
- package/dist/chips/controllers.d.ts +21 -0
- package/dist/chips/controllers.js +66 -0
- package/dist/chips/esp32.d.ts +2 -0
- package/dist/chips/esp32.js +59 -0
- package/dist/chips/esp32s3.d.ts +2 -0
- package/dist/chips/esp32s3.js +54 -0
- package/dist/chips/index.d.ts +15 -0
- package/dist/chips/index.js +45 -0
- package/dist/chips/resolve.d.ts +9 -0
- package/dist/chips/resolve.js +121 -0
- package/dist/chips/types.d.ts +154 -0
- package/dist/chips/types.js +14 -0
- package/dist/chips/xiao-ble.d.ts +2 -0
- package/dist/chips/xiao-ble.js +61 -0
- package/dist/debug-codegen.d.ts +33 -0
- package/dist/debug-codegen.js +162 -0
- package/dist/display/gfx.d.ts +17 -0
- package/dist/display/gfx.js +171 -0
- package/dist/display/index.d.ts +20 -0
- package/dist/display/index.js +53 -0
- package/dist/display/profiles.d.ts +25 -0
- package/dist/display/profiles.js +41 -0
- package/dist/display/touch-adapter.d.ts +9 -0
- package/dist/display/touch-adapter.js +95 -0
- package/dist/display/ui-adapter.d.ts +21 -0
- package/dist/display/ui-adapter.js +534 -0
- package/dist/dt-config/kconfig.d.ts +27 -0
- package/dist/dt-config/kconfig.js +283 -0
- package/dist/dt-config/overlay.d.ts +35 -0
- package/dist/dt-config/overlay.js +246 -0
- package/dist/framework.manifest.d.ts +98 -0
- package/dist/framework.manifest.js +525 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +12 -0
- package/dist/lowering/adc.d.ts +19 -0
- package/dist/lowering/adc.js +76 -0
- package/dist/lowering/ble.d.ts +20 -0
- package/dist/lowering/ble.js +419 -0
- package/dist/lowering/board.d.ts +9 -0
- package/dist/lowering/board.js +33 -0
- package/dist/lowering/gpio.d.ts +12 -0
- package/dist/lowering/gpio.js +123 -0
- package/dist/lowering/http.d.ts +23 -0
- package/dist/lowering/http.js +482 -0
- package/dist/lowering/i2c.d.ts +15 -0
- package/dist/lowering/i2c.js +114 -0
- package/dist/lowering/index.d.ts +29 -0
- package/dist/lowering/index.js +93 -0
- package/dist/lowering/interrupts.d.ts +21 -0
- package/dist/lowering/interrupts.js +91 -0
- package/dist/lowering/mqtt.d.ts +19 -0
- package/dist/lowering/mqtt.js +366 -0
- package/dist/lowering/power.d.ts +9 -0
- package/dist/lowering/power.js +64 -0
- package/dist/lowering/preferences.d.ts +23 -0
- package/dist/lowering/preferences.js +345 -0
- package/dist/lowering/pulse.d.ts +7 -0
- package/dist/lowering/pulse.js +82 -0
- package/dist/lowering/pwm.d.ts +15 -0
- package/dist/lowering/pwm.js +59 -0
- package/dist/lowering/random.d.ts +18 -0
- package/dist/lowering/random.js +129 -0
- package/dist/lowering/spi.d.ts +17 -0
- package/dist/lowering/spi.js +113 -0
- package/dist/lowering/timing.d.ts +9 -0
- package/dist/lowering/timing.js +63 -0
- package/dist/lowering/tone.d.ts +10 -0
- package/dist/lowering/tone.js +50 -0
- package/dist/lowering/uart.d.ts +15 -0
- package/dist/lowering/uart.js +102 -0
- package/dist/lowering/util.d.ts +4 -0
- package/dist/lowering/util.js +12 -0
- package/dist/lowering/wdt.d.ts +15 -0
- package/dist/lowering/wdt.js +80 -0
- package/dist/lowering/wifi.d.ts +21 -0
- package/dist/lowering/wifi.js +447 -0
- package/dist/lowering/worker-backing.d.ts +14 -0
- package/dist/lowering/worker-backing.js +79 -0
- package/dist/lowering/worker.d.ts +6 -0
- package/dist/lowering/worker.js +14 -0
- package/dist/strategy.d.ts +168 -0
- package/dist/strategy.js +1094 -0
- package/dist/toolchain/debug-config.d.ts +82 -0
- package/dist/toolchain/debug-config.js +359 -0
- package/dist/toolchain/index.d.ts +73 -0
- package/dist/toolchain/index.js +427 -0
- package/dist/toolchain/scaffold.d.ts +15 -0
- package/dist/toolchain/scaffold.js +196 -0
- package/dist/toolchain/west-discover.d.ts +40 -0
- package/dist/toolchain/west-discover.js +203 -0
- package/dist/toolchain/west-spawn.d.ts +39 -0
- package/dist/toolchain/west-spawn.js +117 -0
- package/package.json +82 -0
- package/src/async/timer-polyfill.ts +107 -0
- package/src/chips/controllers.ts +74 -0
- package/src/chips/esp32.ts +62 -0
- package/src/chips/esp32s3.ts +57 -0
- package/src/chips/index.ts +54 -0
- package/src/chips/resolve.ts +146 -0
- package/src/chips/types.ts +163 -0
- package/src/chips/xiao-ble.ts +64 -0
- package/src/debug-codegen.ts +207 -0
- package/src/display/gfx.ts +190 -0
- package/src/display/index.ts +70 -0
- package/src/display/profiles.ts +63 -0
- package/src/display/touch-adapter.ts +100 -0
- package/src/display/ui-adapter.ts +558 -0
- package/src/dt-config/kconfig.ts +308 -0
- package/src/dt-config/overlay.ts +293 -0
- package/src/framework.manifest.ts +545 -0
- package/src/index.ts +23 -0
- package/src/lowering/adc.ts +102 -0
- package/src/lowering/ble.ts +426 -0
- package/src/lowering/board.ts +38 -0
- package/src/lowering/gpio.ts +148 -0
- package/src/lowering/http.ts +488 -0
- package/src/lowering/i2c.ts +126 -0
- package/src/lowering/index.ts +86 -0
- package/src/lowering/interrupts.ts +114 -0
- package/src/lowering/mqtt.ts +371 -0
- package/src/lowering/power.ts +70 -0
- package/src/lowering/preferences.ts +354 -0
- package/src/lowering/pulse.ts +93 -0
- package/src/lowering/pwm.ts +74 -0
- package/src/lowering/random.ts +135 -0
- package/src/lowering/spi.ts +124 -0
- package/src/lowering/timing.ts +72 -0
- package/src/lowering/tone.ts +60 -0
- package/src/lowering/uart.ts +111 -0
- package/src/lowering/util.ts +12 -0
- package/src/lowering/wdt.ts +84 -0
- package/src/lowering/wifi.ts +452 -0
- package/src/lowering/worker-backing.ts +94 -0
- package/src/lowering/worker.ts +17 -0
- package/src/strategy.ts +1265 -0
- package/src/toolchain/debug-config.ts +399 -0
- package/src/toolchain/index.ts +459 -0
- package/src/toolchain/scaffold.ts +196 -0
- package/src/toolchain/west-discover.ts +233 -0
- package/src/toolchain/west-spawn.ts +141 -0
package/dist/strategy.js
ADDED
|
@@ -0,0 +1,1094 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// ZephyrStrategy — Zephyr RTOS target
|
|
3
|
+
//
|
|
4
|
+
// Outputs C++ built with `west` (the Zephyr build tool). Programs use the
|
|
5
|
+
// Arduino-style setup()/loop() pair; a generated main() bridges them into
|
|
6
|
+
// Zephyr's standard C entry point and yields to the scheduler between loops.
|
|
7
|
+
//
|
|
8
|
+
// GPIO is lowered through devicetree specs (gpio_pin_*_dt) so an active-low
|
|
9
|
+
// LED's polarity is honored by the DT flags, not by the generated code. See
|
|
10
|
+
// src/lowering/gpio.ts.
|
|
11
|
+
//
|
|
12
|
+
// EMIT BOUNDARY: This file is a canonical entry point of the framework strategy
|
|
13
|
+
// surface (B) — its main()/shim bytes land in user firmware. The emitted bytes
|
|
14
|
+
// are covered by the TypeCAD Runtime Exception (see RUNTIME_EXCEPTION.md at the
|
|
15
|
+
// repository root) and are not subject to the license of this tool source.
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
import { buildWorkerRuntimePolyfill } from '@typecad/cuttlefish/api/shared';
|
|
18
|
+
import { programUsesSafety } from '@typecad/cuttlefish/api';
|
|
19
|
+
import { chipForTarget, setActiveChip } from './chips/index.js';
|
|
20
|
+
import { resolveChipFromBoard } from './chips/resolve.js';
|
|
21
|
+
import { emitGpioDevDispatcher } from './chips/controllers.js';
|
|
22
|
+
import { lowerHalOp } from './lowering/index.js';
|
|
23
|
+
import { buildZephyrWorkerBacking } from './lowering/worker-backing.js';
|
|
24
|
+
import { adcInitLines } from './lowering/adc.js';
|
|
25
|
+
import { pwmInitLines } from './lowering/pwm.js';
|
|
26
|
+
import { i2cInitLines } from './lowering/i2c.js';
|
|
27
|
+
import { spiInitLines } from './lowering/spi.js';
|
|
28
|
+
import { uartInitLines } from './lowering/uart.js';
|
|
29
|
+
import { interruptInitLines } from './lowering/interrupts.js';
|
|
30
|
+
import { wdtInitLines } from './lowering/wdt.js';
|
|
31
|
+
import { bleInitLines } from './lowering/ble.js';
|
|
32
|
+
import { wifiInitLines } from './lowering/wifi.js';
|
|
33
|
+
import { httpInitLines } from './lowering/http.js';
|
|
34
|
+
import { mqttInitLines } from './lowering/mqtt.js';
|
|
35
|
+
import { preferencesInitLines } from './lowering/preferences.js';
|
|
36
|
+
import { randomInitLines } from './lowering/random.js';
|
|
37
|
+
import { generateZephyrInitCode, generateZephyrBreakpointCode, generateZephyrLogpointCode } from './debug-codegen.js';
|
|
38
|
+
import { generateStaticAsyncRuntime } from '@typecad/cuttlefish/api/shared';
|
|
39
|
+
import { buildTimerPolyfill } from './async/timer-polyfill.js';
|
|
40
|
+
import { resolveZephyrDisplayOp, newDisplayState } from './display/index.js';
|
|
41
|
+
import { buildDisplayRuntime } from './display/gfx.js';
|
|
42
|
+
import { ZEPHYR_DISPLAY_PROFILES } from './display/profiles.js';
|
|
43
|
+
import { zephyrDisplayAdapterGenerator } from './display/ui-adapter.js';
|
|
44
|
+
import { zephyrTouchAdapter } from './display/touch-adapter.js';
|
|
45
|
+
export class ZephyrStrategy {
|
|
46
|
+
constructor() {
|
|
47
|
+
this.id = 'zephyr';
|
|
48
|
+
this._largeEnumNames = new Set();
|
|
49
|
+
// ── Worker offload backing (Phase 1) ─────────────────────────────────────
|
|
50
|
+
// Delegates to the Zephyr backing (worker-backing.ts): k_work system
|
|
51
|
+
// workqueue + k_sem for completion. k_sem provides the kernel memory barrier
|
|
52
|
+
// the dual-core contract requires (the worker runs on a workqueue thread).
|
|
53
|
+
this._workerBacking = buildZephyrWorkerBacking();
|
|
54
|
+
// Display state (mirrors Arduino's _displayCtx). Seeded on display.init; the
|
|
55
|
+
// validator-probe path seeds the default profile lazily.
|
|
56
|
+
this._displayState = newDisplayState();
|
|
57
|
+
}
|
|
58
|
+
// ── Profile resolution ──────────────────────────────────────────────────
|
|
59
|
+
/**
|
|
60
|
+
* Resolve + cache the active chip from the platform context. Called lazily
|
|
61
|
+
* by the methods that need the descriptor (shimLines, resolveHALOperation
|
|
62
|
+
* via lowerHalOp).
|
|
63
|
+
*
|
|
64
|
+
* Tries to derive the chip descriptor from the board/MCU package's zephyr
|
|
65
|
+
* fields (via boardConstants) first. Falls back to the hardcoded
|
|
66
|
+
* chipForTarget registry for boards that haven't shipped zephyr config yet.
|
|
67
|
+
*/
|
|
68
|
+
resolveChip(ctx, program) {
|
|
69
|
+
// 1. Try board/MCU package constants (new path)
|
|
70
|
+
const fromBoard = resolveChipFromBoard(program?.boardConstants);
|
|
71
|
+
if (fromBoard) {
|
|
72
|
+
setActiveChip(fromBoard);
|
|
73
|
+
return fromBoard;
|
|
74
|
+
}
|
|
75
|
+
// 2. Fall back to frameworkData.buildTarget → hardcoded registry
|
|
76
|
+
const fd = ctx?.frameworkData;
|
|
77
|
+
const target = fd?.target ??
|
|
78
|
+
fd?.buildTarget;
|
|
79
|
+
const chip = chipForTarget(target);
|
|
80
|
+
setActiveChip(chip);
|
|
81
|
+
return chip;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Resolve the debug mode for the active target from the platform context.
|
|
85
|
+
* Mirrors resolveChip's target extraction so shimLines/forcedIncludes can
|
|
86
|
+
* gate the printf halt shim + console UART include to printf builds only
|
|
87
|
+
* (gdb builds use VS Code native breakpoints + #line markers, so the
|
|
88
|
+
* __tc_debug_wait_for_continue shim and its <zephyr/drivers/uart.h> include
|
|
89
|
+
* are dead code there).
|
|
90
|
+
*/
|
|
91
|
+
resolveDebugMode(ctx) {
|
|
92
|
+
const fd = ctx?.frameworkData;
|
|
93
|
+
const target = fd?.target ??
|
|
94
|
+
fd?.buildTarget;
|
|
95
|
+
return this.debugMode(target);
|
|
96
|
+
}
|
|
97
|
+
forcedIncludes(_program, ctx) {
|
|
98
|
+
const isPrintf = this.resolveDebugMode(ctx) === 'printf';
|
|
99
|
+
// <zephyr/kernel.h> for k_msleep / k_uptime_get_32 / k_busy_wait / printk.
|
|
100
|
+
// <zephyr/drivers/gpio.h> for the gpio_pin_*_dt / gpio_dt_spec API.
|
|
101
|
+
// <cstdint> because DIRECT_CPP_TYPE_MAP passes int32_t/uint8_t through
|
|
102
|
+
// verbatim and Zephyr's minimal C++ lib provides it.
|
|
103
|
+
//
|
|
104
|
+
// Driver headers are usage-gated on ctx.analysis.usesX (same flags
|
|
105
|
+
// shimLines uses to emit the per-peripheral bus state), so an unused
|
|
106
|
+
// peripheral doesn't pull in its header. When analysis is absent (e.g. a
|
|
107
|
+
// capability query before a real build), the uses() helper defaults to
|
|
108
|
+
// true so nothing is stripped — mirrors framework-esp32's forcedIncludes.
|
|
109
|
+
const a = ctx?.analysis;
|
|
110
|
+
const uses = (f) => (a ? !!a[f] : true);
|
|
111
|
+
const inc = ['<zephyr/kernel.h>', '<zephyr/drivers/gpio.h>', '<cstdio>', '<cstdint>'];
|
|
112
|
+
if (uses('usesI2C'))
|
|
113
|
+
inc.push('<zephyr/drivers/i2c.h>');
|
|
114
|
+
if (uses('usesSPI'))
|
|
115
|
+
inc.push('<zephyr/drivers/spi.h>');
|
|
116
|
+
if (uses('usesUart'))
|
|
117
|
+
inc.push('<zephyr/drivers/uart.h>');
|
|
118
|
+
// uart.h is also needed by the printf-mode debug halt shim
|
|
119
|
+
// (__tc_debug_wait_for_continue polls the console UART) even when the
|
|
120
|
+
// program itself does not use the UART HAL. In gdb mode the shim is not
|
|
121
|
+
// emitted, so skip the include there to avoid pulling in an unused header.
|
|
122
|
+
if (isPrintf && !inc.includes('<zephyr/drivers/uart.h>'))
|
|
123
|
+
inc.push('<zephyr/drivers/uart.h>');
|
|
124
|
+
if (uses('usesADC'))
|
|
125
|
+
inc.push('<zephyr/drivers/adc.h>');
|
|
126
|
+
if (uses('usesPWM'))
|
|
127
|
+
inc.push('<zephyr/drivers/pwm.h>');
|
|
128
|
+
if (uses('usesWDT'))
|
|
129
|
+
inc.push('<zephyr/drivers/watchdog.h>');
|
|
130
|
+
if (uses('usesPower'))
|
|
131
|
+
inc.push('<zephyr/pm/pm.h>', '<zephyr/pm/state.h>', '<zephyr/pm/policy.h>');
|
|
132
|
+
// BLE: the bt_* GATT API + the flat-string headers the shim uses. <string>
|
|
133
|
+
// is needed because a Utf8 (BleValueType.Utf8) read handler lowers to a
|
|
134
|
+
// std::string-returning function (the string literal return type), and the
|
|
135
|
+
// program-analysis usesStdString detector doesn't see types generated by
|
|
136
|
+
// the BLE lowering layer — so without forcing <string> here, any BLE server
|
|
137
|
+
// with a Utf8 characteristic fails to compile ('std::string does not name a
|
|
138
|
+
// type'). Uses <string>, not <string.h>: the latter is the C flat-string
|
|
139
|
+
// header (already included for the shim's strncpy/strcmp).
|
|
140
|
+
if (uses('usesBle'))
|
|
141
|
+
inc.push('<stdlib.h>', '<string.h>', '<string>', '<zephyr/bluetooth/bluetooth.h>', '<zephyr/bluetooth/conn.h>', '<zephyr/bluetooth/gatt.h>', '<zephyr/bluetooth/uuid.h>');
|
|
142
|
+
// Display: the analyzer's usesDisplay flag (set by display.* hal-ops) drives
|
|
143
|
+
// this include. When ctx.analysis is absent (capability query), uses()
|
|
144
|
+
// defaults to true so a real build never strips it.
|
|
145
|
+
if (uses('usesDisplay'))
|
|
146
|
+
inc.push('<zephyr/drivers/display.h>');
|
|
147
|
+
// Random: <zephyr/random/random.h> for sys_rand_get (the entropy tap that
|
|
148
|
+
// seeds the __tc_rand_* xorshift32 PRNG). The shim block re-includes it, but
|
|
149
|
+
// force it here too so a split-TU emit still has the symbol available.
|
|
150
|
+
if (uses('usesRandom'))
|
|
151
|
+
inc.push('<zephyr/random/random.h>');
|
|
152
|
+
if (uses('usesWifi'))
|
|
153
|
+
inc.push('<zephyr/net/net_mgmt.h>', '<zephyr/net/wifi_mgmt.h>', '<zephyr/net/net_if.h>', '<zephyr/net/net_ip.h>', '<zephyr/net/conn_mgr_connectivity.h>', '<zephyr/net/conn_mgr_monitor.h>');
|
|
154
|
+
// HTTP/S client: Zephyr's http_client_req runs over a pre-connected socket,
|
|
155
|
+
// so the shim pulls in the BSD socket + POSIX DNS surfaces alongside the
|
|
156
|
+
// http client/parser headers. TLS sec tags need tls_credentials; <cstring>
|
|
157
|
+
// /<cstdlib> back the shim's memcpy/strlen/new-nothrow usage (the core
|
|
158
|
+
// shim only includes <cstdio>/<cstdint>).
|
|
159
|
+
if (uses('usesHttp'))
|
|
160
|
+
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>');
|
|
161
|
+
// MQTT client: <zephyr/net/mqtt.h> for mqtt_connect/publish/subscribe, plus
|
|
162
|
+
// <zephyr/net/socket.h> for the zsock_* poll/getaddrinfo API the shim's poll
|
|
163
|
+
// thread uses, and <zephyr/net/tls_credentials.h> for the mqtts:// path
|
|
164
|
+
// (mqtt_sec_config). <cstring> backs the shim's memcpy/strlen.
|
|
165
|
+
if (uses('usesMqtt'))
|
|
166
|
+
inc.push('<zephyr/net/mqtt.h>', '<zephyr/net/socket.h>', '<zephyr/net/tls_credentials.h>', '<cstring>');
|
|
167
|
+
// Preferences: Zephyr settings subsystem (ZMS backend) — settings_load/
|
|
168
|
+
// settings_save_one/settings_delete + the SETTINGS_STATIC_HANDLER_DEFINE
|
|
169
|
+
// macro. <cstring> backs the shim's memcpy/memmove/strncpy/strncmp (the
|
|
170
|
+
// core shim only includes <cstdio>/<cstdint>). <errno.h> for ENOENT in h_get.
|
|
171
|
+
if (uses('usesPreferences'))
|
|
172
|
+
inc.push('<zephyr/settings/settings.h>', '<cstring>', '<errno.h>');
|
|
173
|
+
// std::string — Zephyr has no umbrella header that transitively pulls in
|
|
174
|
+
// <string> (unlike framework-arduino's <Arduino.h>), so a program that
|
|
175
|
+
// lowers a std::string parameter/variable must request it explicitly. Uses
|
|
176
|
+
// <string>, not <string.h>: the latter is the C flat-string header.
|
|
177
|
+
if (uses('usesStdString'))
|
|
178
|
+
inc.push('<string>');
|
|
179
|
+
return inc;
|
|
180
|
+
}
|
|
181
|
+
symbolAliases() {
|
|
182
|
+
return {};
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Detect async-runtime usage: a program needs the Promise/microtask runtime
|
|
186
|
+
* if it declares an async function OR references an async-runtime symbol
|
|
187
|
+
* (`__cuttlefish_async_`) — e.g. Async.sleep()/.then() called from a non-async
|
|
188
|
+
* function. Mirrors Arduino's programUsesAsyncRuntime walk (that helper is
|
|
189
|
+
* private to framework-arduino and not exported from cuttlefish, so we walk
|
|
190
|
+
* here). The token appears in `raw` expr nodes and in `hal-expr`/`hal-op`
|
|
191
|
+
* nodes whose resolved `raw` code references it.
|
|
192
|
+
*/
|
|
193
|
+
programUsesAsyncRuntime(program) {
|
|
194
|
+
if (!program)
|
|
195
|
+
return false;
|
|
196
|
+
const TOKEN = '__cuttlefish_async_';
|
|
197
|
+
let found = false;
|
|
198
|
+
const visit = (node) => {
|
|
199
|
+
if (found || !node || typeof node !== 'object')
|
|
200
|
+
return;
|
|
201
|
+
// raw expression node
|
|
202
|
+
if (node.kind === 'raw' && typeof node.value === 'string' && node.value.includes(TOKEN)) {
|
|
203
|
+
found = true;
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
// hal-expr / hal-op node whose operation is a raw op carrying code
|
|
207
|
+
if (node.operation && typeof node.operation === 'object'
|
|
208
|
+
&& node.operation.operation === 'raw'
|
|
209
|
+
&& typeof node.operation.code === 'string'
|
|
210
|
+
&& node.operation.code.includes(TOKEN)) {
|
|
211
|
+
found = true;
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
for (const v of Object.values(node)) {
|
|
215
|
+
if (Array.isArray(v)) {
|
|
216
|
+
for (const item of v)
|
|
217
|
+
visit(item);
|
|
218
|
+
}
|
|
219
|
+
else if (v && typeof v === 'object')
|
|
220
|
+
visit(v);
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
visit(program);
|
|
224
|
+
return found;
|
|
225
|
+
}
|
|
226
|
+
shimLines(program, ctx) {
|
|
227
|
+
const chip = this.resolveChip(ctx, program);
|
|
228
|
+
const isPrintf = this.resolveDebugMode(ctx) === 'printf';
|
|
229
|
+
const lines = [
|
|
230
|
+
'// cuttlefish runtime shim. Wrapped in a single include guard so the',
|
|
231
|
+
'// block is safe to emit into multiple headers and .cpp files within',
|
|
232
|
+
'// one translation unit (a .cpp may #include several headers that each',
|
|
233
|
+
'// carry the shim). The guard ensures the definitions are seen exactly',
|
|
234
|
+
'// once per TU.',
|
|
235
|
+
'#ifndef CUTTLEFISH_SHIM_DEFINED',
|
|
236
|
+
'#define CUTTLEFISH_SHIM_DEFINED',
|
|
237
|
+
'#ifndef CUTTLEFISH_UNDEFINED',
|
|
238
|
+
'#define CUTTLEFISH_UNDEFINED 0',
|
|
239
|
+
'#endif',
|
|
240
|
+
'template<typename T> inline bool cuttlefish_is_nullish(const T& v) { return false; }',
|
|
241
|
+
'inline bool cuttlefish_is_nullish(long long v) { return v == CUTTLEFISH_UNDEFINED; }',
|
|
242
|
+
'inline bool cuttlefish_is_nullish(int v) { return v == CUTTLEFISH_UNDEFINED; }',
|
|
243
|
+
'inline bool cuttlefish_is_nullish(double v) { return v == static_cast<double>(CUTTLEFISH_UNDEFINED); }',
|
|
244
|
+
'inline bool cuttlefish_is_nullish(bool v) { return v == false; }',
|
|
245
|
+
'template<typename T> inline bool cuttlefish_is_nullish(T* v) { return v == nullptr; }',
|
|
246
|
+
'template<typename T> inline bool cuttlefish_exists(const T& v) { return !cuttlefish_is_nullish(v); }',
|
|
247
|
+
'template<typename T, typename U> inline T cuttlefish_nullish(const T& a, U b) { return !cuttlefish_is_nullish(a) ? a : (T)b; }',
|
|
248
|
+
// millis() backed by the Zephyr uptime counter. uint32_t return matches
|
|
249
|
+
// the Arduino API the shared runtime expects (wraps every ~49.7 days).
|
|
250
|
+
'inline unsigned long millis() { return static_cast<unsigned long>(k_uptime_get_32()); }',
|
|
251
|
+
// Arduino-compat defines referenced by the shared runtime polyfills.
|
|
252
|
+
'#ifndef HIGH', '#define HIGH 1', '#endif',
|
|
253
|
+
'#ifndef LOW', '#define LOW 0', '#endif',
|
|
254
|
+
'#ifndef PROGMEM', '#define PROGMEM', '#endif',
|
|
255
|
+
'inline long map(long x, long in_min, long in_max, long out_min, long out_max) { return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; }',
|
|
256
|
+
'inline long constrain(long x, long a, long b) { return x < a ? a : (x > b ? b : x); }',
|
|
257
|
+
// Test-runner console helpers: @typecad/expect's Zephyr shim calls these
|
|
258
|
+
// for protocol output. Overloaded for string (const char*) and numeric
|
|
259
|
+
// (double) so the same call site works for markers and test values.
|
|
260
|
+
'inline void __tc_print(const char* s) { printf("%s", s); }',
|
|
261
|
+
'inline void __tc_print(double v) { printf("%g", v); }',
|
|
262
|
+
'inline void __tc_println(const char* s) { printf("%s\\n", s); }',
|
|
263
|
+
'inline void __tc_println(double v) { printf("%g\\n", v); }',
|
|
264
|
+
];
|
|
265
|
+
// Devicetree specs for every board-defined GPIO pin. Emitted unconditionally
|
|
266
|
+
// (guarded by the include guard) so any of them is available whether or not
|
|
267
|
+
// a given program uses it. Safe because every spec references a node that
|
|
268
|
+
// exists in the active board's devicetree.
|
|
269
|
+
for (const spec of chip.gpio.dtSpecs) {
|
|
270
|
+
lines.push(`static const struct gpio_dt_spec __tc_dt_${spec.dtSpec} = GPIO_DT_SPEC_GET(DT_ALIAS(${spec.dtSpec}), gpios);`);
|
|
271
|
+
}
|
|
272
|
+
// Per-peripheral bus state — gated on the same ctx.analysis.usesX flags as
|
|
273
|
+
// forcedIncludes, so an unused peripheral emits no state (and its header is
|
|
274
|
+
// not included). Mirrors framework-esp32's shimLines espInit block.
|
|
275
|
+
const a = ctx?.analysis;
|
|
276
|
+
const uses = (f) => (a ? !!a[f] : true);
|
|
277
|
+
if (uses('usesI2C') && chip.i2c) {
|
|
278
|
+
for (let i = 0; i < chip.i2c.controllers.length; i++)
|
|
279
|
+
lines.push(...i2cInitLines(chip, i));
|
|
280
|
+
}
|
|
281
|
+
if (uses('usesSPI') && chip.spi) {
|
|
282
|
+
for (let i = 0; i < chip.spi.controllers.length; i++)
|
|
283
|
+
lines.push(...spiInitLines(chip, i));
|
|
284
|
+
}
|
|
285
|
+
if (uses('usesUart') && chip.uart) {
|
|
286
|
+
for (let i = 0; i < chip.uart.controllers.length; i++)
|
|
287
|
+
lines.push(...uartInitLines(chip, i));
|
|
288
|
+
}
|
|
289
|
+
if (uses('usesADC') && chip.adc)
|
|
290
|
+
lines.push(...adcInitLines(chip));
|
|
291
|
+
if (uses('usesPWM') && chip.pwm)
|
|
292
|
+
lines.push(...pwmInitLines(chip));
|
|
293
|
+
if (uses('usesInterrupts'))
|
|
294
|
+
lines.push(...interruptInitLines(chip));
|
|
295
|
+
if (uses('usesWDT') && chip.wdt)
|
|
296
|
+
lines.push(...wdtInitLines(chip));
|
|
297
|
+
if (uses('usesBle'))
|
|
298
|
+
lines.push(...bleInitLines());
|
|
299
|
+
// Display runtime (minimal rect/text renderer): gated on the analyzer's
|
|
300
|
+
// usesDisplay flag (set by display.* hal-ops). This is the DIRECT-call
|
|
301
|
+
// display path (user code calling screen.display.fillRect etc.). It is
|
|
302
|
+
// SUPPRESSED when the UI display adapter is active — the adapter seam
|
|
303
|
+
// (providesDisplayAdapter/resolveDisplayAdapter) emits its own display_*
|
|
304
|
+
// runtime that drives the UI rendering pipeline via CuttlefishGFX, and the
|
|
305
|
+
// two define the same symbols (display_init, __tc_display_line) so emitting
|
|
306
|
+
// both causes redefinition errors. The adapter is active for the
|
|
307
|
+
// strategy-owned drivers (ili9341-zephyr, st7796-zephyr).
|
|
308
|
+
if (uses('usesDisplay') && !this.providesDisplayAdapter()) {
|
|
309
|
+
const rt = buildDisplayRuntime(this._displayState.profile);
|
|
310
|
+
lines.push(...rt.stateLines);
|
|
311
|
+
lines.push(rt.fontTable);
|
|
312
|
+
lines.push(rt.helpers);
|
|
313
|
+
}
|
|
314
|
+
if (uses('usesWifi'))
|
|
315
|
+
lines.push(...wifiInitLines());
|
|
316
|
+
if (uses('usesHttp'))
|
|
317
|
+
lines.push(...httpInitLines());
|
|
318
|
+
if (uses('usesMqtt'))
|
|
319
|
+
lines.push(...mqttInitLines());
|
|
320
|
+
if (uses('usesPreferences'))
|
|
321
|
+
lines.push(...preferencesInitLines());
|
|
322
|
+
if (uses('usesRandom'))
|
|
323
|
+
lines.push(...randomInitLines());
|
|
324
|
+
lines.push('#endif // CUTTLEFISH_SHIM_DEFINED');
|
|
325
|
+
// --- Debug-mode halt + per-breakpoint disable registry ---
|
|
326
|
+
//
|
|
327
|
+
// Printf mode only. In gdb mode the cuttlefish debug preprocessor is
|
|
328
|
+
// skipped (core emits #line markers + VS Code native breakpoints instead),
|
|
329
|
+
// so __tc_debug_wait_for_continue is never called — skip the shim and its
|
|
330
|
+
// <zephyr/drivers/uart.h> dependency entirely (forcedIncludes mirrors this).
|
|
331
|
+
//
|
|
332
|
+
// The cuttlefish debug preprocessor injects __tc_debug_wait_for_continue(id)
|
|
333
|
+
// calls at each breakpoint; without these definitions the emitted code
|
|
334
|
+
// would not link.
|
|
335
|
+
//
|
|
336
|
+
// Zephyr's minimal libc has no getchar()/EOF, so the halt polls the console
|
|
337
|
+
// UART directly via uart_poll_in on the system console device, yielding to
|
|
338
|
+
// the scheduler with k_msleep between polls so an unattended breakpoint
|
|
339
|
+
// does not starve the system. ENTER (or any non-'s' byte) = continue;
|
|
340
|
+
// 's'/'S' = skip this breakpoint for the rest of the run (records the id).
|
|
341
|
+
if (isPrintf) {
|
|
342
|
+
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]; }',
|
|
343
|
+
// Console input: poll the UART console device. DEVICE_DT_GET(DT_CHOSEN(zephyr_console))
|
|
344
|
+
// resolves to the board's console (UART0 USB-CDC on the XIAO nRF52840).
|
|
345
|
+
'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);', '}', '#endif // __TC_BP_DISABLED_DEFINED', '');
|
|
346
|
+
}
|
|
347
|
+
// --- Zephyr entrypoint: main() runs setup()/loop() directly ---
|
|
348
|
+
// The cuttlefish synthesizer emits setup() and loop() (it keys off
|
|
349
|
+
// entrypointFunctionName()="setup" and requiresLoopFunction()=true). Zephyr
|
|
350
|
+
// is a standard C main()-based RTOS, so main() bridges the two: it calls
|
|
351
|
+
// setup() once, then loops loop() forever, yielding to the scheduler with
|
|
352
|
+
// k_msleep(1) each iteration (cheap cooperative yield — matches the esp32
|
|
353
|
+
// app_main pattern). Declared extern here because setup/loop live in a
|
|
354
|
+
// separate translation unit when generateHeaderFile() splits them into the
|
|
355
|
+
// header.
|
|
356
|
+
lines.push('extern void setup(void);', 'extern void loop(void);', '', 'int main(void) {', ' setup();', ' for (;;) {', ' loop();', ' k_msleep(1);', ' }', ' return 0;', '}');
|
|
357
|
+
// Safety shims: when the program uses @typecad/safety, provide __tc_gpio_read
|
|
358
|
+
// / __tc_gpio_write backed by the raw controller (a best-effort read that
|
|
359
|
+
// does not depend on a pin having a DT spec). __tc_delay_us uses k_busy_wait.
|
|
360
|
+
//
|
|
361
|
+
// The pin is a RUNTIME value here (safety's voter passes whatever pin it
|
|
362
|
+
// was handed), so the controller cannot be baked in as a single DT_NODELABEL
|
|
363
|
+
// on a multi-controller SoC (ESP32-S3: pins 0–31 → gpio0, 32–48 → gpio1).
|
|
364
|
+
// Emit a tiny __tc_gpio_dev(pin) dispatcher that resolves the owning
|
|
365
|
+
// controller's device per pin; single-controller SoCs collapse it to a
|
|
366
|
+
// one-liner. Each DT_NODELABEL is still compile-time-resolved per branch, so
|
|
367
|
+
// it is always statically valid.
|
|
368
|
+
if (program && programUsesSafety(program)) {
|
|
369
|
+
lines.push(...emitGpioDevDispatcher(chip));
|
|
370
|
+
lines.push('inline int __tc_gpio_read(uint32_t pin) { return gpio_pin_get_raw(__tc_gpio_dev(pin), pin); }', 'inline void __tc_gpio_write(uint32_t pin, uint32_t value) { gpio_pin_set_raw(__tc_gpio_dev(pin), pin, value); }', '#ifndef __TC_DELAY_US_DEFINED', '#define __TC_DELAY_US_DEFINED', 'inline void __tc_delay_us(uint32_t us) { k_busy_wait(us); }', '#endif');
|
|
371
|
+
}
|
|
372
|
+
return lines;
|
|
373
|
+
}
|
|
374
|
+
profileDiagnostics(program, ctx) {
|
|
375
|
+
if (!program)
|
|
376
|
+
return [];
|
|
377
|
+
const chip = this.resolveChip(ctx, program);
|
|
378
|
+
const a = ctx?.analysis ?? {};
|
|
379
|
+
const diags = [];
|
|
380
|
+
// Collect the pins the program uses for output config, ADC reads, and
|
|
381
|
+
// interrupt attaches — deep-walking the IR the same way framework-esp32
|
|
382
|
+
// does (its profileDiagnostics walks program to find gpio.set_mode /
|
|
383
|
+
// power.deep_sleep_pin / adc.read nodes).
|
|
384
|
+
const outputPins = new Set();
|
|
385
|
+
const adcReadPins = new Set();
|
|
386
|
+
const interruptPins = new Set();
|
|
387
|
+
let usesWifiOps = false;
|
|
388
|
+
let usesHttpOps = false;
|
|
389
|
+
let usesMqttOps = false;
|
|
390
|
+
const visit = (node) => {
|
|
391
|
+
if (node && typeof node === 'object') {
|
|
392
|
+
if (node.operation && typeof node.operation === 'object') {
|
|
393
|
+
const op = node.operation;
|
|
394
|
+
if (op.operation === 'gpio.set_mode'
|
|
395
|
+
&& typeof op.mode === 'string'
|
|
396
|
+
&& op.mode.toLowerCase() === 'output'
|
|
397
|
+
&& typeof op.pin === 'number') {
|
|
398
|
+
outputPins.add(op.pin);
|
|
399
|
+
}
|
|
400
|
+
if ((op.operation === 'adc.read' || op.operation === 'adc.read_voltage')
|
|
401
|
+
&& typeof op.pin === 'number') {
|
|
402
|
+
adcReadPins.add(op.pin);
|
|
403
|
+
}
|
|
404
|
+
if (op.operation === 'interrupt.attach' && typeof op.pin === 'number') {
|
|
405
|
+
interruptPins.add(op.pin);
|
|
406
|
+
}
|
|
407
|
+
if (typeof op.operation === 'string' && op.operation.startsWith('wifi.')) {
|
|
408
|
+
usesWifiOps = true;
|
|
409
|
+
}
|
|
410
|
+
if (typeof op.operation === 'string' && op.operation.startsWith('http.')) {
|
|
411
|
+
usesHttpOps = true;
|
|
412
|
+
}
|
|
413
|
+
if (typeof op.operation === 'string' && op.operation.startsWith('mqtt.')) {
|
|
414
|
+
usesMqttOps = true;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
for (const k of Object.keys(node)) {
|
|
418
|
+
const v = node[k];
|
|
419
|
+
if (Array.isArray(v))
|
|
420
|
+
v.forEach(visit);
|
|
421
|
+
else if (typeof v === 'object' && v !== null)
|
|
422
|
+
visit(v);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
visit(program);
|
|
427
|
+
// ── ADC pin validity ────────────────────────────────────────────────────
|
|
428
|
+
// The SAADC lowering resolves a HAL pin to a channel via the chip
|
|
429
|
+
// descriptor's adc.channels map. A pin not in that map resolves to -1,
|
|
430
|
+
// which emits __tc_adc-1_setup() — an undefined symbol → link error. Flag
|
|
431
|
+
// it at compile time with a clear message instead of an opaque link failure.
|
|
432
|
+
const adcPins = new Set((chip.adc?.channels ?? []).map((c) => c.pin));
|
|
433
|
+
for (const pin of adcReadPins) {
|
|
434
|
+
if (!adcPins.has(pin)) {
|
|
435
|
+
const valid = [...adcPins].sort((x, y) => x - y).join(', ');
|
|
436
|
+
diags.push({
|
|
437
|
+
severity: 'error',
|
|
438
|
+
code: 'zephyr-adc-pin-unavailable',
|
|
439
|
+
message: `GPIO ${pin} is not a SAADC channel on ${chip.id} and cannot be read with adc.read.`,
|
|
440
|
+
hint: valid
|
|
441
|
+
? `Use an analog-capable pin. On ${chip.id} (SAADC): ${valid}.`
|
|
442
|
+
: `This target has no ADC channels mapped in its chip descriptor.`,
|
|
443
|
+
source: program.fileName,
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
// ── Interrupt pin validity ──────────────────────────────────────────────
|
|
448
|
+
// interrupt.attach only wires a real callback for pins listed in the chip
|
|
449
|
+
// descriptor's gpio.interruptPins (the lowering needs a DT spec to build
|
|
450
|
+
// the gpio_callback struct at init). An attach on an unlisted pin emits
|
|
451
|
+
// only a comment — silent no-op. Flag it so the user knows the handler
|
|
452
|
+
// will never fire.
|
|
453
|
+
const intPins = new Set((chip.gpio.interruptPins ?? []).map((p) => p.pin));
|
|
454
|
+
for (const pin of interruptPins) {
|
|
455
|
+
if (!intPins.has(pin)) {
|
|
456
|
+
diags.push({
|
|
457
|
+
severity: 'error',
|
|
458
|
+
code: 'zephyr-interrupt-pin-unavailable',
|
|
459
|
+
message: `GPIO ${pin} has no interrupt DT spec on ${chip.id}; interrupt.attach is a no-op.`,
|
|
460
|
+
hint: intPins.size > 0
|
|
461
|
+
? `Add the pin to the chip descriptor's gpio.interruptPins, or use an interrupt-capable pin: ${[...intPins].sort((x, y) => x - y).join(', ')}.`
|
|
462
|
+
: `This target declares no interrupt pins in its chip descriptor; interrupts are not available.`,
|
|
463
|
+
source: program.fileName,
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
// ── WiFi target validity ────────────────────────────────────────────────
|
|
468
|
+
// WiFi ops require a chip with a WiFi radio. The ESP32-S3 descriptor sets
|
|
469
|
+
// wifi.supported; the XIAO nRF52840 omits it (no radio). Flag wifi usage on
|
|
470
|
+
// a radioless chip so the user gets a clear "use an ESP32 target" message
|
|
471
|
+
// instead of an opaque link/DT failure.
|
|
472
|
+
if (usesWifiOps && !chip.wifi?.supported) {
|
|
473
|
+
diags.push({
|
|
474
|
+
severity: 'error',
|
|
475
|
+
code: 'zephyr-wifi-unavailable-on-target',
|
|
476
|
+
message: `WiFi ops are used but ${chip.id} has no WiFi radio.`,
|
|
477
|
+
hint: `Use an esp32s3_devkitc or esp32_devkitc target (Espressif ESP32 variants have a 2.4GHz WiFi radio).`,
|
|
478
|
+
source: program.fileName,
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
// ── HTTP target validity ──────────────────────────────────────────────
|
|
482
|
+
// HTTP needs a network transport. On Zephyr the only networked target is
|
|
483
|
+
// the ESP32 (WiFi) — the nRF52840 has neither WiFi nor Ethernet wired in
|
|
484
|
+
// its chip descriptor, so the shim's socket/connect calls would fail at
|
|
485
|
+
// runtime. Flag http usage on a chip without a network radio so the user
|
|
486
|
+
// gets a clear "use an ESP32 target" message instead of an opaque link or
|
|
487
|
+
// runtime failure. (HTTP rides over WiFi here; an Ethernet target would
|
|
488
|
+
// set wifi.supported via a different transport flag if/when added.)
|
|
489
|
+
if (usesHttpOps && !chip.wifi?.supported) {
|
|
490
|
+
diags.push({
|
|
491
|
+
severity: 'error',
|
|
492
|
+
code: 'zephyr-http-unavailable-on-target',
|
|
493
|
+
message: `HTTP ops are used but ${chip.id} has no network stack available.`,
|
|
494
|
+
hint: `Use an esp32s3_devkitc or esp32_devkitc target (HTTP needs a network transport; the ESP32 WiFi radio provides it).`,
|
|
495
|
+
source: program.fileName,
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
// ── MQTT target validity ─────────────────────────────────────────────
|
|
499
|
+
// Same constraint as HTTP: MQTT needs a network transport to reach a broker.
|
|
500
|
+
// Flag mqtt usage on a radioless chip so the user picks a networked target.
|
|
501
|
+
if (usesMqttOps && !chip.wifi?.supported) {
|
|
502
|
+
diags.push({
|
|
503
|
+
severity: 'error',
|
|
504
|
+
code: 'zephyr-mqtt-unavailable-on-target',
|
|
505
|
+
message: `MQTT ops are used but ${chip.id} has no network stack available.`,
|
|
506
|
+
hint: `Use an esp32s3_devkitc or esp32_devkitc target (MQTT needs a network transport; the ESP32 WiFi radio provides it).`,
|
|
507
|
+
source: program.fileName,
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
// ── Unused-analysis: surface a hint that this is a no-analysis probe ────
|
|
511
|
+
// (intentionally minimal — esp32 has richer rules around strapping/RTC pins;
|
|
512
|
+
// nRF52840 has fewer silicon foot-guns, so the rules above are the material
|
|
513
|
+
// ones. Extend as constraints are identified.)
|
|
514
|
+
return diags;
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* `loop` is forward-declared `extern` by the main() bridge shim
|
|
518
|
+
* (extern void loop(void); → int main(void) {...}), so emitting a
|
|
519
|
+
* `static void loop()` definition redeclares it with conflicting linkage,
|
|
520
|
+
* which GCC rejects. Exclude it from the static forward-declaration path
|
|
521
|
+
* — mirrors how ArduinoStrategy excludes `loop` (the Arduino core forward-
|
|
522
|
+
* declares it extern). `setup` is already handled because it equals
|
|
523
|
+
* entrypointFunctionName().
|
|
524
|
+
*/
|
|
525
|
+
forwardDeclarationExclusions() {
|
|
526
|
+
return ['loop'];
|
|
527
|
+
}
|
|
528
|
+
// ── File shape ──────────────────────────────────────────────────────────
|
|
529
|
+
sourceExtension() {
|
|
530
|
+
return 'cpp';
|
|
531
|
+
}
|
|
532
|
+
entrypointFunctionName() {
|
|
533
|
+
return 'setup';
|
|
534
|
+
}
|
|
535
|
+
requiresLoopFunction() {
|
|
536
|
+
return true;
|
|
537
|
+
}
|
|
538
|
+
overrideBaseName(originalBaseName, outDirBaseName, isEntryFile, isNpmPackage) {
|
|
539
|
+
// npm packages are library-style — don't rename. Entry files (non-npm) take
|
|
540
|
+
// the out-dir name (mirrors Arduino's .ino-must-match-dir rule). Everything
|
|
541
|
+
// else passes through. (The manifest's entrypoint.overrideBaseName field is
|
|
542
|
+
// dead — never read in src/ — so this method is the sole name source.)
|
|
543
|
+
if (isNpmPackage)
|
|
544
|
+
return originalBaseName;
|
|
545
|
+
if (isEntryFile)
|
|
546
|
+
return outDirBaseName;
|
|
547
|
+
return originalBaseName;
|
|
548
|
+
}
|
|
549
|
+
effectiveEmitMode(requestedMode, _isNpmPackage) {
|
|
550
|
+
// Zephyr always emits .cpp (no .ino equivalent to force away from), so this
|
|
551
|
+
// is passthrough regardless of npm/app. The 2-param shape matches the
|
|
552
|
+
// interface and Arduino; behavior is identical across branches.
|
|
553
|
+
return requestedMode;
|
|
554
|
+
}
|
|
555
|
+
// ── Type normalisation ──────────────────────────────────────────────────
|
|
556
|
+
normalizeCppType(typeName) {
|
|
557
|
+
if (typeName === 'auto')
|
|
558
|
+
return 'auto';
|
|
559
|
+
if (typeName === 'std::string')
|
|
560
|
+
return 'const char*';
|
|
561
|
+
return typeName;
|
|
562
|
+
}
|
|
563
|
+
defaultNumericType(compliance) {
|
|
564
|
+
// Zephyr favors fixed-width types. Under AUTOSAR compliance (A3-9-1) emit
|
|
565
|
+
// int32_t; otherwise the standard 'int'.
|
|
566
|
+
return compliance?.isBanned('A3-9-1') ? 'int32_t' : 'int';
|
|
567
|
+
}
|
|
568
|
+
mapReturnType(functionName, returnType) {
|
|
569
|
+
if (functionName === 'main')
|
|
570
|
+
return 'int';
|
|
571
|
+
if (functionName === 'setup' || functionName === 'loop')
|
|
572
|
+
return 'void';
|
|
573
|
+
return this.normalizeCppType(returnType);
|
|
574
|
+
}
|
|
575
|
+
isStringLikeType(cppType) {
|
|
576
|
+
return cppType === 'std::string' || cppType === 'const char*' || cppType === 'char*';
|
|
577
|
+
}
|
|
578
|
+
isPointerType(cppType) {
|
|
579
|
+
return cppType.endsWith('*');
|
|
580
|
+
}
|
|
581
|
+
mapFunctionName(originalName) {
|
|
582
|
+
if (originalName === '__cuttlefish_entrypoint__')
|
|
583
|
+
return 'main';
|
|
584
|
+
return originalName;
|
|
585
|
+
}
|
|
586
|
+
// ── Expression rendering ────────────────────────────────────────────────
|
|
587
|
+
normalizeRawExpression(value) {
|
|
588
|
+
let prev = '';
|
|
589
|
+
let v = value;
|
|
590
|
+
while (prev !== v) {
|
|
591
|
+
prev = v;
|
|
592
|
+
v = v.replace(/\bundefined\b/g, 'CUTTLEFISH_UNDEFINED');
|
|
593
|
+
v = v.replace(/\bnull\b/g, 'CUTTLEFISH_UNDEFINED');
|
|
594
|
+
}
|
|
595
|
+
return v;
|
|
596
|
+
}
|
|
597
|
+
nullValue() {
|
|
598
|
+
return 'CUTTLEFISH_UNDEFINED';
|
|
599
|
+
}
|
|
600
|
+
wrapStringConcat() {
|
|
601
|
+
return undefined;
|
|
602
|
+
}
|
|
603
|
+
wrapStringObject(value) {
|
|
604
|
+
return `std::to_string(${value})`;
|
|
605
|
+
}
|
|
606
|
+
useSnprintfForStrings() {
|
|
607
|
+
return true;
|
|
608
|
+
}
|
|
609
|
+
renameEnumMember(_enumName, memberName) {
|
|
610
|
+
return memberName;
|
|
611
|
+
}
|
|
612
|
+
setLargeEnumNames(names) {
|
|
613
|
+
this._largeEnumNames = new Set(names);
|
|
614
|
+
}
|
|
615
|
+
enumCastType(_enumName) {
|
|
616
|
+
return undefined;
|
|
617
|
+
}
|
|
618
|
+
renderBoardDefinitionAccess(chain, boardConstants) {
|
|
619
|
+
// Fold Board.definition.<path> / Pins.definition.<path> into the literal
|
|
620
|
+
// board-constant value, mirroring framework-arduino. The renderer calls
|
|
621
|
+
// this (expression-renderer.ts) with the populated boardConstants from the
|
|
622
|
+
// loaded board/MCU package, so a known path resolves to its scalar value.
|
|
623
|
+
// `board.resolve` HAL ops, by contrast, are constant-folded earlier at
|
|
624
|
+
// IR-build time and never reach here; see lowering/board.ts.
|
|
625
|
+
if (chain.length < 3)
|
|
626
|
+
return undefined;
|
|
627
|
+
if (chain[0] !== 'Board' && chain[0] !== 'Pins')
|
|
628
|
+
return undefined;
|
|
629
|
+
if (chain[1] !== 'definition')
|
|
630
|
+
return undefined;
|
|
631
|
+
if (!boardConstants)
|
|
632
|
+
return undefined;
|
|
633
|
+
const path = chain.slice(2).join('.');
|
|
634
|
+
const val = boardConstants.get(path);
|
|
635
|
+
return val !== undefined ? String(val) : undefined;
|
|
636
|
+
}
|
|
637
|
+
// ── Statement rendering ─────────────────────────────────────────────────
|
|
638
|
+
promotesArrayLiteralsToStaticArray() {
|
|
639
|
+
// No std::vector in the minimal C++ lib — promote array literals to the
|
|
640
|
+
// StaticArray wrapper (the embedded/generic default).
|
|
641
|
+
return true;
|
|
642
|
+
}
|
|
643
|
+
renderThrow(_valueExpr) {
|
|
644
|
+
// Zephyr minimal config disables C++ exceptions (CONFIG_CPP_EXCEPTIONS=n).
|
|
645
|
+
// A throw becomes an infinite halt loop instead of a real throw.
|
|
646
|
+
return 'for (;;) { k_msleep(1000); }';
|
|
647
|
+
}
|
|
648
|
+
isConsoleCall(callee) {
|
|
649
|
+
return callee.startsWith('console.');
|
|
650
|
+
}
|
|
651
|
+
transformConsoleCall(method, renderedArgs, forHeader) {
|
|
652
|
+
const semi = forHeader ? '' : ';';
|
|
653
|
+
const empty = !renderedArgs || renderedArgs.trim() === '';
|
|
654
|
+
const tag = method === 'error' ? '[ERROR] ' : method === 'warn' ? '[WARN] ' : '';
|
|
655
|
+
if (empty)
|
|
656
|
+
return `printk("%s\\n", "${tag}")${semi}`;
|
|
657
|
+
const parts = renderedArgs.split(' << ');
|
|
658
|
+
if (parts.length === 1) {
|
|
659
|
+
return `printk("%s%s\\n", "${tag}", (${renderedArgs}))${semi}`;
|
|
660
|
+
}
|
|
661
|
+
const fmt = '%s' + '%s'.repeat(parts.length) + '\\n';
|
|
662
|
+
const args = [`"${tag}"`, ...parts].join(', ');
|
|
663
|
+
return `printk("${fmt}", ${args})${semi}`;
|
|
664
|
+
}
|
|
665
|
+
transformConsoleExpression(_method, _renderedArgs) {
|
|
666
|
+
return undefined;
|
|
667
|
+
}
|
|
668
|
+
objectFieldInitializer() {
|
|
669
|
+
return undefined;
|
|
670
|
+
}
|
|
671
|
+
overrideClassFieldType(_fieldName, normalizedType) {
|
|
672
|
+
return normalizedType;
|
|
673
|
+
}
|
|
674
|
+
// ── Name guards ─────────────────────────────────────────────────────────
|
|
675
|
+
reservedNames() {
|
|
676
|
+
return new Set();
|
|
677
|
+
}
|
|
678
|
+
passthroughMacroNames() {
|
|
679
|
+
return new Set();
|
|
680
|
+
}
|
|
681
|
+
apiReservedEnumNames() {
|
|
682
|
+
return new Set();
|
|
683
|
+
}
|
|
684
|
+
apiReservedEnumGuard() {
|
|
685
|
+
return '';
|
|
686
|
+
}
|
|
687
|
+
ambientTypeDeclarations() {
|
|
688
|
+
// Preferences is the only HAL surface the framework lowers that is used as
|
|
689
|
+
// a bare global (the HAL Preferences class is exported, but the canonical
|
|
690
|
+
// usage — and the hal/tests/14-preferences hardware suite — references it
|
|
691
|
+
// as an unqualified `Preferences.*`). Declaring it ambient lets those
|
|
692
|
+
// programs type-check and resolve to the preferences.* ops the lowering
|
|
693
|
+
// in src/lowering/preferences.ts handles (ZMS-backed settings). Mirrors
|
|
694
|
+
// framework-arduino's ambient Preferences declaration.
|
|
695
|
+
return [
|
|
696
|
+
"",
|
|
697
|
+
" // Persistent key/value store (ZMS-backed Zephyr settings — see",
|
|
698
|
+
" // src/lowering/preferences.ts). begin/end carry the namespace prefix;",
|
|
699
|
+
" // typed put/get round-trip through an in-RAM cache + settings_save_one.",
|
|
700
|
+
" const Preferences: {",
|
|
701
|
+
" begin(name: string, readOnly?: boolean): void;",
|
|
702
|
+
" end(): void;",
|
|
703
|
+
" clear(): void;",
|
|
704
|
+
" putInt(key: string, value: number): void;",
|
|
705
|
+
" getInt(key: string, defaultValue: number): number;",
|
|
706
|
+
" putUInt(key: string, value: number): void;",
|
|
707
|
+
" getUInt(key: string, defaultValue: number): number;",
|
|
708
|
+
" putBool(key: string, value: boolean): void;",
|
|
709
|
+
" getBool(key: string, defaultValue: boolean): boolean;",
|
|
710
|
+
" putFloat(key: string, value: number): void;",
|
|
711
|
+
" getFloat(key: string, defaultValue: number): number;",
|
|
712
|
+
" putString(key: string, value: string): void;",
|
|
713
|
+
" getString(key: string, defaultValue: string): string;",
|
|
714
|
+
" remove(key: string): void;",
|
|
715
|
+
" };",
|
|
716
|
+
];
|
|
717
|
+
}
|
|
718
|
+
// ── Includes ────────────────────────────────────────────────────────────
|
|
719
|
+
needsIostream() {
|
|
720
|
+
return false;
|
|
721
|
+
}
|
|
722
|
+
needsStdString() {
|
|
723
|
+
// Zephyr's minimal C++ lib has no <string>. Set false so the transpiler
|
|
724
|
+
// does not emit std::string-dependent code (e.g. the string-method
|
|
725
|
+
// polyfills). A program that needs it must enable a full STL.
|
|
726
|
+
return false;
|
|
727
|
+
}
|
|
728
|
+
needsStdVector() {
|
|
729
|
+
// No <vector> in the minimal C++ lib.
|
|
730
|
+
return false;
|
|
731
|
+
}
|
|
732
|
+
needsStdExcept() {
|
|
733
|
+
return false;
|
|
734
|
+
}
|
|
735
|
+
needsStdFunction() {
|
|
736
|
+
// No <functional> in the minimal C++ lib.
|
|
737
|
+
return false;
|
|
738
|
+
}
|
|
739
|
+
mathHeader() {
|
|
740
|
+
// <math.h> is the schema-permitted value (the manifest enum allows none |
|
|
741
|
+
// <math.h> | <Arduino.h>). Zephyr's toolchain provides it; the C++ <cmath>
|
|
742
|
+
// names are available via it as well.
|
|
743
|
+
return '<math.h>';
|
|
744
|
+
}
|
|
745
|
+
cstringHeader() {
|
|
746
|
+
return '<cstring>';
|
|
747
|
+
}
|
|
748
|
+
needsVectorOverload() {
|
|
749
|
+
return true;
|
|
750
|
+
}
|
|
751
|
+
needsLargeEnumUnderlying() {
|
|
752
|
+
return false;
|
|
753
|
+
}
|
|
754
|
+
// ── Struct field handling ───────────────────────────────────────────────
|
|
755
|
+
renameStructField(fieldName) {
|
|
756
|
+
return fieldName;
|
|
757
|
+
}
|
|
758
|
+
structFieldInitializer() {
|
|
759
|
+
return undefined;
|
|
760
|
+
}
|
|
761
|
+
// ── Async ───────────────────────────────────────────────────────────────
|
|
762
|
+
// Hybrid: timers are native (k_timer + k_work, see src/async/timer-polyfill.ts);
|
|
763
|
+
// Promises use the heap-free static runtime (generateStaticAsyncRuntime), pumped
|
|
764
|
+
// cooperatively in loop() via cuttlefish_pump_microtasks(). There is no
|
|
765
|
+
// __tc_timer_runtime.run() poll — native timers fire from their own expiry path.
|
|
766
|
+
getAsyncRuntimeConfig() {
|
|
767
|
+
return {
|
|
768
|
+
queueCapacity: 64,
|
|
769
|
+
scheduler: 'microtask',
|
|
770
|
+
waitForPinEdge: 'stub',
|
|
771
|
+
hasPromiseRuntime: true,
|
|
772
|
+
hasTimers: true,
|
|
773
|
+
// Static (heap-free) runtime — no STL headers required.
|
|
774
|
+
requiredIncludes: [],
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
asyncLoopInjection(taskVarNames, configOrBool, _hasTimers) {
|
|
778
|
+
const cfg = typeof configOrBool === 'boolean'
|
|
779
|
+
? { hasPromiseRuntime: configOrBool, hasTimers: _hasTimers ?? false }
|
|
780
|
+
: configOrBool;
|
|
781
|
+
// Drive every async state-machine task once per loop() iteration. The task
|
|
782
|
+
// globals auto-start on their first .run() (constructor sets STATE_0, which
|
|
783
|
+
// runs unconditionally), so this is both the start and the per-frame advance.
|
|
784
|
+
// The state machine no-ops in its terminal/cyclic state, so unconditional
|
|
785
|
+
// .run() is correct (mirrors framework-arduino). No isComplete() gating.
|
|
786
|
+
const lines = [];
|
|
787
|
+
if (cfg.hasPromiseRuntime) {
|
|
788
|
+
lines.push('cuttlefish_pump_microtasks();');
|
|
789
|
+
}
|
|
790
|
+
for (const n of taskVarNames) {
|
|
791
|
+
lines.push(`${n}.run();`);
|
|
792
|
+
}
|
|
793
|
+
// NOTE: no __tc_timer_runtime.run() — Zephyr timers are native k_timer
|
|
794
|
+
// (timer-polyfill.ts), not a cooperative poll.
|
|
795
|
+
return lines;
|
|
796
|
+
}
|
|
797
|
+
asyncDriverFunctionName() {
|
|
798
|
+
return 'loop';
|
|
799
|
+
}
|
|
800
|
+
// ── Type aliases ────────────────────────────────────────────────────────
|
|
801
|
+
shouldSkipTypeAlias() {
|
|
802
|
+
return false;
|
|
803
|
+
}
|
|
804
|
+
// ── Diagnostics ─────────────────────────────────────────────────────────
|
|
805
|
+
emitDiagnostics() {
|
|
806
|
+
return [];
|
|
807
|
+
}
|
|
808
|
+
currentTimeMillis() {
|
|
809
|
+
return 'millis()';
|
|
810
|
+
}
|
|
811
|
+
// ── Build configuration ──────────────────────────────────────────────────
|
|
812
|
+
asyncQueueCapacity() {
|
|
813
|
+
return 64;
|
|
814
|
+
}
|
|
815
|
+
outputSubdirectory(_baseName) {
|
|
816
|
+
// Zephyr convention: application sources live under src/.
|
|
817
|
+
return 'src';
|
|
818
|
+
}
|
|
819
|
+
generateHeaderFile() {
|
|
820
|
+
return true;
|
|
821
|
+
}
|
|
822
|
+
enumApiGuard(_enumName) {
|
|
823
|
+
return undefined;
|
|
824
|
+
}
|
|
825
|
+
getStdLibSupport(_architecture) {
|
|
826
|
+
// Zephyr's minimal C++ support (lib/cpp/minimal) provides only <cstddef>,
|
|
827
|
+
// <cstdint>, <new>. No <vector>, <string>, <iostream>, <functional>, no
|
|
828
|
+
// exceptions, no RTTI. The blink MVP uses only GPIO + kernel timing, so
|
|
829
|
+
// none of those are needed. Array/string literals are not promoted to the
|
|
830
|
+
// STL containers; a future program needing them must enable a full STL
|
|
831
|
+
// and update these flags.
|
|
832
|
+
return {
|
|
833
|
+
hasVector: false,
|
|
834
|
+
hasString: false,
|
|
835
|
+
hasIostream: false,
|
|
836
|
+
hasExceptions: false,
|
|
837
|
+
hasRTTI: false,
|
|
838
|
+
recommendedArrayImpl: 'static_array',
|
|
839
|
+
recommendedStringImpl: 'static_string',
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
// ── Polyfills ───────────────────────────────────────────────────────────
|
|
843
|
+
// No native polyfills for the MVP — the shared runtime's string/array
|
|
844
|
+
// polyfills are pulled in when a program uses them. cuttlefish_halt is the
|
|
845
|
+
// one symbol the runtime header may reference; supply it as a halt loop.
|
|
846
|
+
nativePolyfills() {
|
|
847
|
+
// cuttlefish_halt: always (the runtime header may reference it).
|
|
848
|
+
// timer_methods: k_timer/k_work pool for setInterval/setTimeout (gated on
|
|
849
|
+
// timerCallCount at emit time in generateNativePolyfills).
|
|
850
|
+
// async_runtime: heap-free static Promise/microtask runtime (no STL needed).
|
|
851
|
+
return new Set(['cuttlefish_halt', 'wiring_compat', 'timer_methods', 'async_runtime']);
|
|
852
|
+
}
|
|
853
|
+
generateNativePolyfills(program, ctx) {
|
|
854
|
+
const polyfills = [
|
|
855
|
+
{
|
|
856
|
+
kind: 'polyfill',
|
|
857
|
+
id: 'cuttlefish_halt',
|
|
858
|
+
domain: 'standard',
|
|
859
|
+
requiredIncludes: [],
|
|
860
|
+
forwardDeclarations: [],
|
|
861
|
+
helperStructs: [],
|
|
862
|
+
helperFunctions: [
|
|
863
|
+
'[[noreturn]] inline void cuttlefish_halt() { for (;;) { k_msleep(1000); } }',
|
|
864
|
+
],
|
|
865
|
+
shimMacros: [],
|
|
866
|
+
dependencies: [],
|
|
867
|
+
},
|
|
868
|
+
{
|
|
869
|
+
// Wiring-compatibility shims for symbols the UI runtime header
|
|
870
|
+
// references unconditionally (e.g. init-press-input.ts polls pin
|
|
871
|
+
// watchers via digitalRead/HIGH/LOW even when none are configured —
|
|
872
|
+
// the loop body is dead but must compile). Zephyr lowers GPIO through
|
|
873
|
+
// its __tc_gpio_* helpers (defined in shimLines); these macros route
|
|
874
|
+
// the Wiring tokens to them.
|
|
875
|
+
kind: 'polyfill',
|
|
876
|
+
id: 'wiring_compat',
|
|
877
|
+
domain: 'standard',
|
|
878
|
+
requiredIncludes: [],
|
|
879
|
+
forwardDeclarations: [
|
|
880
|
+
// Forward-declared so the digitalRead macro (below) can reference it
|
|
881
|
+
// before the shim block defines the body. The shim emits the full
|
|
882
|
+
// definition via gpio_pin_get_raw.
|
|
883
|
+
'int __tc_gpio_read(int pin);',
|
|
884
|
+
],
|
|
885
|
+
helperStructs: [],
|
|
886
|
+
helperFunctions: [],
|
|
887
|
+
shimMacros: [
|
|
888
|
+
'#ifndef HIGH',
|
|
889
|
+
'#define HIGH 1',
|
|
890
|
+
'#endif',
|
|
891
|
+
'#ifndef LOW',
|
|
892
|
+
'#define LOW 0',
|
|
893
|
+
'#endif',
|
|
894
|
+
'#ifndef digitalRead',
|
|
895
|
+
'#define digitalRead(pin) __tc_gpio_read(pin)',
|
|
896
|
+
'#endif',
|
|
897
|
+
],
|
|
898
|
+
dependencies: [],
|
|
899
|
+
},
|
|
900
|
+
];
|
|
901
|
+
// Worker-offload runtime (Phase 1). Emitted only when the program uses
|
|
902
|
+
// worker.* ops, backed by the Zephyr primitives in worker-backing.ts
|
|
903
|
+
// (k_work system workqueue + k_sem for the completion barrier).
|
|
904
|
+
const usesWorker = !!(ctx?.analysis?.usesWorker);
|
|
905
|
+
if (program && usesWorker) {
|
|
906
|
+
const workerPoly = buildWorkerRuntimePolyfill(program, this, buildZephyrWorkerBacking(), { poolSize: 4 });
|
|
907
|
+
if (workerPoly)
|
|
908
|
+
polyfills.push(workerPoly);
|
|
909
|
+
}
|
|
910
|
+
// timer_methods — k_timer/k_work pool. Gated on observed timer call count;
|
|
911
|
+
// a program with no setInterval/setTimeout emits nothing.
|
|
912
|
+
const analysis = ctx?.analysis;
|
|
913
|
+
const timerCallCount = analysis?.timerCallCount ?? 0;
|
|
914
|
+
if (timerCallCount > 0) {
|
|
915
|
+
polyfills.push(buildTimerPolyfill(timerCallCount));
|
|
916
|
+
}
|
|
917
|
+
// async_runtime — heap-free static Promise/microtask runtime. Emitted when
|
|
918
|
+
// the program declares an async function OR references an async-runtime
|
|
919
|
+
// symbol (Async.sleep/.then from a non-async fn). The static path requires
|
|
920
|
+
// no STL headers, so it is safe under Zephyr's minimal C++ lib.
|
|
921
|
+
const usesAsync = !!program
|
|
922
|
+
&& (program.functions.some((fn) => fn && fn.isAsync) || this.programUsesAsyncRuntime(program));
|
|
923
|
+
if (usesAsync) {
|
|
924
|
+
polyfills.push({
|
|
925
|
+
kind: 'polyfill',
|
|
926
|
+
id: 'async_runtime',
|
|
927
|
+
domain: 'embedded',
|
|
928
|
+
requiredIncludes: [],
|
|
929
|
+
forwardDeclarations: [],
|
|
930
|
+
helperStructs: [generateStaticAsyncRuntime(8, this.getAsyncRuntimeConfig().waitForPinEdge)],
|
|
931
|
+
helperFunctions: [],
|
|
932
|
+
shimMacros: [],
|
|
933
|
+
dependencies: [],
|
|
934
|
+
hasPromiseRuntime: true,
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
return polyfills;
|
|
938
|
+
}
|
|
939
|
+
// ── HAL ──────────────────────────────────────────────────────────────────
|
|
940
|
+
resolveHALOperation(op) {
|
|
941
|
+
return lowerHalOp(op);
|
|
942
|
+
}
|
|
943
|
+
modelsGpio() {
|
|
944
|
+
return true;
|
|
945
|
+
}
|
|
946
|
+
// ── Atomic HAL primitives ─────────────────────────────────────────────────
|
|
947
|
+
// Zephyr lowers GPIO through devicetree specs and its own __tc_gpio_* helpers
|
|
948
|
+
// (defined in shimLines via gpio_pin_get_raw / gpio_pin_set_raw). Cuttlefish
|
|
949
|
+
// asks these instead of emitting Wiring tokens by name. The async polling
|
|
950
|
+
// path is gated to 'stub' on Zephyr (waitForPinEdge), so delayMs is unlikely
|
|
951
|
+
// to be called here, but a busy-wait form is provided for completeness.
|
|
952
|
+
readDigitalPin(pin) {
|
|
953
|
+
return `__tc_gpio_read(${pin})`;
|
|
954
|
+
}
|
|
955
|
+
readAnalogPin(pin) {
|
|
956
|
+
// Zephyr ADC is lowered through its own shim; this stub keeps cuttlefish
|
|
957
|
+
// from emitting a Wiring analogRead token. Update if a __tc_adc_read helper
|
|
958
|
+
// is introduced.
|
|
959
|
+
return `/* adc lowering via zephyr shim */ 0`;
|
|
960
|
+
}
|
|
961
|
+
writeDigitalPin(pin, val) {
|
|
962
|
+
return `__tc_gpio_write(${pin}, ${val})`;
|
|
963
|
+
}
|
|
964
|
+
setPinMode(_pin, _mode) {
|
|
965
|
+
// Zephyr configures pin direction via devicetree, not a runtime pinMode.
|
|
966
|
+
return `/* pin mode configured via devicetree */`;
|
|
967
|
+
}
|
|
968
|
+
delayMs(ms) {
|
|
969
|
+
return `k_msleep(${ms})`;
|
|
970
|
+
}
|
|
971
|
+
delayMicroseconds(us) {
|
|
972
|
+
return `__tc_delay_us(${us})`;
|
|
973
|
+
}
|
|
974
|
+
halCallNames() {
|
|
975
|
+
// Zephyr's HAL surface uses __tc_ prefixed helpers + the Zephyr API.
|
|
976
|
+
return new Set([
|
|
977
|
+
"__tc_gpio_read", "__tc_gpio_write", "__tc_delay_us",
|
|
978
|
+
"gpio_pin_get_raw", "gpio_pin_set_raw", "k_msleep", "k_busy_wait",
|
|
979
|
+
]);
|
|
980
|
+
}
|
|
981
|
+
isHalCall(name) {
|
|
982
|
+
return this.halCallNames().has(name);
|
|
983
|
+
}
|
|
984
|
+
analogReadCallNames() {
|
|
985
|
+
return new Set();
|
|
986
|
+
}
|
|
987
|
+
// ── RTOS ─────────────────────────────────────────────────────────────────
|
|
988
|
+
isRtosTarget() {
|
|
989
|
+
// Zephyr is a preemptive RTOS — delay()/k_msleep inside loop() is the
|
|
990
|
+
// expected cooperative yield, not an anti-pattern to warn about.
|
|
991
|
+
return true;
|
|
992
|
+
}
|
|
993
|
+
workerSpawnLines(handleId, trampolineName, waiterExpr) {
|
|
994
|
+
return this._workerBacking.spawnLines(handleId, trampolineName, waiterExpr);
|
|
995
|
+
}
|
|
996
|
+
workerSignalDoneExpr(handleId) {
|
|
997
|
+
return this._workerBacking.signalDoneExpr(handleId);
|
|
998
|
+
}
|
|
999
|
+
workerIsDoneExpr(handleId) {
|
|
1000
|
+
return this._workerBacking.isDoneExpr(handleId);
|
|
1001
|
+
}
|
|
1002
|
+
// ── Graphics ──────────────────────────────────────────────────────────────
|
|
1003
|
+
// Generic <zephyr/drivers/display.h> + ported GFX primitives (see src/display/).
|
|
1004
|
+
// resolveDisplayOp delegates to resolveZephyrDisplayOp with the per-build
|
|
1005
|
+
// DisplayState; the GFX runtime (device handle + line buffer + helpers) is
|
|
1006
|
+
// emitted into shimLines when usesDisplay.
|
|
1007
|
+
resolveDisplayOp(op) {
|
|
1008
|
+
return resolveZephyrDisplayOp(op, this._displayState);
|
|
1009
|
+
}
|
|
1010
|
+
supportedDisplayDrivers() {
|
|
1011
|
+
return new Set(Object.keys(ZEPHYR_DISPLAY_PROFILES));
|
|
1012
|
+
}
|
|
1013
|
+
// ── Strategy-owned display/touch adapter seam ────────────────────────────
|
|
1014
|
+
// Zephyr owns its display + touch adapters: the UI display adapter bridges
|
|
1015
|
+
// the in-tree CuttlefishGFX class to Zephyr's display_write() API (see
|
|
1016
|
+
// src/display/ui-adapter.ts), and the FT6336U touch adapter drives the I2C
|
|
1017
|
+
// controller via Zephyr's i2c API (src/display/touch-adapter.ts). Both live
|
|
1018
|
+
// in this package so cuttlefish carries no Zephyr/Wiring-specific display or
|
|
1019
|
+
// touch knowledge. Mirrors ArduinoStrategy's provides*/resolve* pattern.
|
|
1020
|
+
providesDisplayAdapter() { return true; }
|
|
1021
|
+
resolveDisplayAdapter(display) {
|
|
1022
|
+
const code = zephyrDisplayAdapterGenerator(display);
|
|
1023
|
+
return code ?? undefined;
|
|
1024
|
+
}
|
|
1025
|
+
providesTouchAdapter() { return true; }
|
|
1026
|
+
resolveTouchAdapter(touch) {
|
|
1027
|
+
return zephyrTouchAdapter(touch);
|
|
1028
|
+
}
|
|
1029
|
+
// Named display-profile registry: maps config `profile` values (e.g.
|
|
1030
|
+
// "st7796-zephyr") to the shared DisplayProfile shape so transpile.ts can
|
|
1031
|
+
// resolve them per-framework. The Zephyr profiles are DT-binding descriptors;
|
|
1032
|
+
// they're mapped to the shared shape (driver/width/height/colorFormat/
|
|
1033
|
+
// rotation) the profile resolver expects.
|
|
1034
|
+
getProfileRegistry() {
|
|
1035
|
+
const m = new Map();
|
|
1036
|
+
for (const [name, p] of Object.entries(ZEPHYR_DISPLAY_PROFILES)) {
|
|
1037
|
+
m.set(name, {
|
|
1038
|
+
driver: p.driver,
|
|
1039
|
+
width: p.width,
|
|
1040
|
+
height: p.height,
|
|
1041
|
+
nativeWidth: p.nativeWidth,
|
|
1042
|
+
nativeHeight: p.nativeHeight,
|
|
1043
|
+
colorFormat: p.colorFormat,
|
|
1044
|
+
rotation: p.rotation ?? 1,
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1047
|
+
return m;
|
|
1048
|
+
}
|
|
1049
|
+
colorFormat() {
|
|
1050
|
+
return 'rgb565';
|
|
1051
|
+
}
|
|
1052
|
+
graphicsCapacity() {
|
|
1053
|
+
return {
|
|
1054
|
+
maxNodes: 256,
|
|
1055
|
+
maxBindings: 64,
|
|
1056
|
+
maxActiveTransitions: 32,
|
|
1057
|
+
nodeStorage: 'flash',
|
|
1058
|
+
};
|
|
1059
|
+
}
|
|
1060
|
+
// ── Debug ─────────────────────────────────────────────────────────────────
|
|
1061
|
+
// Zephyr's minimal C++ config has no <iostream>, so the GenericStrategy
|
|
1062
|
+
// std::cout fallback the debug preprocessor uses by default would NOT
|
|
1063
|
+
// compile. Override the debug surface to route through printk (always
|
|
1064
|
+
// available, no CONFIG_CONSOLE dependency) and the __tc_debug_wait_for_continue
|
|
1065
|
+
// halt emitted in shimLines. See src/debug-codegen.ts.
|
|
1066
|
+
//
|
|
1067
|
+
// Target-selective: targets with a debug probe get native GDB source-level
|
|
1068
|
+
// debugging (core emits #line markers + skips printf instrumentation); the
|
|
1069
|
+
// rest fall back to the printk instrumentation path. The ESP32-S3 has a
|
|
1070
|
+
// built-in USB-JTAG (single-cable GDB via OpenOCD) so it selects 'gdb'.
|
|
1071
|
+
// The XIAO nRF52840 needs its J-Link wired up; its GDB path is a follow-on,
|
|
1072
|
+
// so it stays on printf for now.
|
|
1073
|
+
debugMode(target) {
|
|
1074
|
+
// `target` is the Zephyr board id (optionally with a /qualifier suffix,
|
|
1075
|
+
// e.g. 'esp32s3_devkitc/esp32s3/procpu'). Match on the bare board id.
|
|
1076
|
+
const boardId = (target ?? '').split('/')[0];
|
|
1077
|
+
if (boardId === 'esp32s3_devkitc' || boardId.startsWith('esp32s3')) {
|
|
1078
|
+
return 'gdb';
|
|
1079
|
+
}
|
|
1080
|
+
// The plain ESP32 (esp32_devkitc) intentionally stays on 'printf': unlike
|
|
1081
|
+
// the S3 it has NO built-in USB-JTAG, so gdb needs an external ESP-PROG
|
|
1082
|
+
// probe + a different OpenOCD cfg/toolchain dir (deferred). Falls through.
|
|
1083
|
+
return 'printf';
|
|
1084
|
+
}
|
|
1085
|
+
generateDebugInitCode() {
|
|
1086
|
+
return generateZephyrInitCode();
|
|
1087
|
+
}
|
|
1088
|
+
generateDebugBreakpointCode(params) {
|
|
1089
|
+
return generateZephyrBreakpointCode(params.fileName, params.lineNum, params.originalLine, params.variables, params.normalizedCondition, params.breakpointId);
|
|
1090
|
+
}
|
|
1091
|
+
generateDebugLogpointCode(params) {
|
|
1092
|
+
return generateZephyrLogpointCode(params.fileName, params.lineNum, params.parts, params.variables);
|
|
1093
|
+
}
|
|
1094
|
+
}
|