@typecad/framework-zephyr 1.0.0-alpha.12 → 1.0.0-alpha.14
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 +13 -1
- package/dist/chips/controllers.d.ts +28 -8
- package/dist/chips/controllers.js +49 -12
- package/dist/chips/resolve.js +32 -6
- package/dist/chips/types.d.ts +63 -12
- package/dist/chips/xiao-ble.js +12 -0
- package/dist/display/index.d.ts +1 -1
- package/dist/display/index.js +1 -1
- package/dist/display/profiles.d.ts +8 -0
- package/dist/display/profiles.js +19 -0
- package/dist/display/ui-adapter.d.ts +4 -0
- package/dist/display/ui-adapter.js +46 -0
- package/dist/dt-config/kconfig.d.ts +11 -0
- package/dist/dt-config/kconfig.js +1 -1
- package/dist/dt-config/overlay.d.ts +8 -1
- package/dist/dt-config/overlay.js +104 -1
- package/dist/framework.manifest.d.ts +20 -30
- package/dist/framework.manifest.js +12 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -0
- package/dist/lowering/adc.d.ts +7 -4
- package/dist/lowering/adc.js +25 -11
- package/dist/lowering/gpio.js +15 -8
- package/dist/lowering/mqtt.js +9 -1
- package/dist/lowering/pulse.js +7 -7
- package/dist/lowering/pwm.d.ts +21 -3
- package/dist/lowering/pwm.js +28 -4
- package/dist/lowering/spi.js +2 -2
- package/dist/lowering/tone.js +3 -2
- package/dist/lowering/wifi.js +28 -5
- package/dist/strategy.d.ts +20 -0
- package/dist/strategy.js +295 -119
- package/dist/toolchain/debug-config.d.ts +43 -2
- package/dist/toolchain/debug-config.js +129 -17
- package/dist/toolchain/index.d.ts +14 -1
- package/dist/toolchain/index.js +146 -20
- package/dist/toolchain/scaffold.d.ts +9 -0
- package/dist/toolchain/scaffold.js +84 -19
- package/dist/toolchain/west-discover.d.ts +4 -1
- package/dist/toolchain/west-discover.js +2 -0
- package/dist/toolchain/west-spawn.js +17 -5
- package/package.json +5 -5
- package/src/chips/controllers.ts +61 -12
- package/src/chips/resolve.ts +32 -5
- package/src/chips/types.ts +63 -12
- package/src/chips/xiao-ble.ts +82 -70
- package/src/display/index.ts +1 -1
- package/src/display/profiles.ts +23 -0
- package/src/display/ui-adapter.ts +51 -0
- package/src/dt-config/kconfig.ts +12 -1
- package/src/dt-config/overlay.ts +123 -0
- package/src/framework.manifest.ts +12 -3
- package/src/index.ts +6 -0
- package/src/lowering/adc.ts +28 -12
- package/src/lowering/gpio.ts +15 -8
- package/src/lowering/mqtt.ts +9 -1
- package/src/lowering/pulse.ts +7 -7
- package/src/lowering/pwm.ts +29 -4
- package/src/lowering/spi.ts +2 -2
- package/src/lowering/tone.ts +3 -3
- package/src/lowering/wifi.ts +29 -5
- package/src/strategy.ts +320 -123
- package/src/toolchain/debug-config.ts +137 -14
- package/src/toolchain/index.ts +645 -513
- package/src/toolchain/scaffold.ts +81 -17
- package/src/toolchain/west-discover.ts +321 -316
- package/src/toolchain/west-spawn.ts +17 -5
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { writeFileSync, existsSync, mkdirSync, readFileSync, readdirSync } from 'node:fs';
|
|
10
10
|
import { join } from 'node:path';
|
|
11
11
|
import { resolveKconfigFragments } from '../dt-config/kconfig.js';
|
|
12
|
+
import { readCuttlefishLibrarySidecar } from '@typecad/cuttlefish/library-packages';
|
|
12
13
|
/** Write a file only if the content differs from the existing file.
|
|
13
14
|
* Returns true when the file was written (content changed or file was new). */
|
|
14
15
|
export function writeIfChanged(filePath, content) {
|
|
@@ -24,27 +25,61 @@ export function writeIfChanged(filePath, content) {
|
|
|
24
25
|
writeFileSync(filePath, content);
|
|
25
26
|
return true;
|
|
26
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Names of the cuttlefish-emitted C/C++ sources under src/ (top level only,
|
|
30
|
+
* matching the old `src/*.cpp src/*.c` glob; sorted so the generated
|
|
31
|
+
* CMakeLists.txt is stable across readdir orderings). Empty when no sources
|
|
32
|
+
* exist yet (first prepare call).
|
|
33
|
+
*/
|
|
34
|
+
function listEmittedSources(srcDir) {
|
|
35
|
+
if (!existsSync(srcDir))
|
|
36
|
+
return [];
|
|
37
|
+
const names = readdirSync(srcDir).filter((name) => name.endsWith('.cpp') || name.endsWith('.c'));
|
|
38
|
+
names.sort();
|
|
39
|
+
return names;
|
|
40
|
+
}
|
|
27
41
|
/**
|
|
28
42
|
* Concatenate all emitted source under src/ so the scaffold can detect which
|
|
29
43
|
* peripherals the program actually uses. The cuttlefish lowering emits
|
|
30
44
|
* well-known driver API tokens (adc_read, spi_transceive, bt_*, …), so scanning
|
|
31
|
-
* the post-transpile source is an authoritative usage signal — and it keeps
|
|
32
|
-
* scaffold self-contained (no need to thread analysis through the toolchain
|
|
45
|
+
* the post-transpile source is an authoritative usage signal — and it keeps
|
|
46
|
+
* the scaffold self-contained (no need to thread analysis through the toolchain
|
|
33
47
|
* contract). Returns '' when no sources exist yet (first prepare call).
|
|
34
48
|
*/
|
|
35
49
|
function readEmittedSources(srcDir) {
|
|
36
|
-
if (!existsSync(srcDir))
|
|
37
|
-
return '';
|
|
38
50
|
let out = '';
|
|
39
|
-
for (const name of
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
51
|
+
for (const name of listEmittedSources(srcDir)) {
|
|
52
|
+
try {
|
|
53
|
+
out += readFileSync(join(srcDir, name), 'utf8');
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// ignore unreadable files
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Append cuttlefish library packages' devicetree overlay fragments to the
|
|
63
|
+
* generated overlay. Library entries come from the transpiler's libraries.json
|
|
64
|
+
* sidecar (next to the emitted sources) and are already gated on the
|
|
65
|
+
* library's include token appearing in the emitted sources — no re-detection.
|
|
66
|
+
* Fragments merge after the framework overlay so library nodes (e.g.
|
|
67
|
+
* @typecad/zephyr-esp32s3-rgb's WS2812 node on I2S0) layer over it.
|
|
68
|
+
*/
|
|
69
|
+
export function appendLibraryOverlayFragments(overlay, projectRoot) {
|
|
70
|
+
let out = overlay;
|
|
71
|
+
for (const entry of readCuttlefishLibrarySidecar(join(projectRoot, 'src'))) {
|
|
72
|
+
if (!entry.overlay)
|
|
73
|
+
continue;
|
|
74
|
+
try {
|
|
75
|
+
const fragment = readFileSync(entry.overlay, 'utf8').trim();
|
|
76
|
+
if (fragment.length > 0) {
|
|
77
|
+
out += (out.endsWith('\n') ? '' : '\n') + '\n' + fragment + '\n';
|
|
46
78
|
}
|
|
47
79
|
}
|
|
80
|
+
catch {
|
|
81
|
+
// best-effort; a missing fragment surfaces as a DT error
|
|
82
|
+
}
|
|
48
83
|
}
|
|
49
84
|
return out;
|
|
50
85
|
}
|
|
@@ -118,8 +153,13 @@ export function scaffoldZephyrProject(projectRoot, debug = false, userKconfig, p
|
|
|
118
153
|
};
|
|
119
154
|
let changed = false;
|
|
120
155
|
// ── Root CMakeLists.txt ─────────────────────────────────────────────────
|
|
121
|
-
// The canonical Zephyr CMake application.
|
|
122
|
-
//
|
|
156
|
+
// The canonical Zephyr CMake application. The emitted source list is
|
|
157
|
+
// explicit (no file(GLOB CONFIGURE_DEPENDS ...)): CONFIGURE_DEPENDS puts a
|
|
158
|
+
// cmake.verify_globs step in the ninja graph that spawns CMake to re-check
|
|
159
|
+
// the glob on every build, and the scaffold already rewrites this file via
|
|
160
|
+
// writeIfChanged whenever the emitted file set changes — which flips
|
|
161
|
+
// configChanged and reconfigures with the new list baked in.
|
|
162
|
+
const sourceFiles = listEmittedSources(srcDir);
|
|
123
163
|
const cmakeLists = [
|
|
124
164
|
'# Auto-generated by @typecad/framework-zephyr from cuttlefish.config.ts.',
|
|
125
165
|
'cmake_minimum_required(VERSION 3.20.0)',
|
|
@@ -128,12 +168,18 @@ export function scaffoldZephyrProject(projectRoot, debug = false, userKconfig, p
|
|
|
128
168
|
'',
|
|
129
169
|
'project(zephyr_app)',
|
|
130
170
|
'',
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
171
|
+
...(sourceFiles.length > 0
|
|
172
|
+
? [
|
|
173
|
+
'# Cuttlefish-emitted sources. This list is regenerated whenever the',
|
|
174
|
+
'# emitted file set changes (the scaffold rewrites CMakeLists.txt).',
|
|
175
|
+
'target_sources(app PRIVATE',
|
|
176
|
+
...sourceFiles.map((name) => ` src/${name}`),
|
|
177
|
+
')',
|
|
178
|
+
]
|
|
179
|
+
: [
|
|
180
|
+
'# No emitted sources yet — the scaffold regenerates this list on the',
|
|
181
|
+
'# next compile once src/ contains .cpp/.c files.',
|
|
182
|
+
]),
|
|
137
183
|
// When PSRAM is configured, define BOARD_HAS_PSRAM so the UI runtime's
|
|
138
184
|
// PSRAM canvas allocator (ui_create_canvas_best) is compiled in.
|
|
139
185
|
...(psram ? ['', '# PSRAM enabled: activate the runtime PSRAM canvas paths.', 'target_compile_definitions(app PRIVATE BOARD_HAS_PSRAM)', ''] : ['']),
|
|
@@ -182,6 +228,25 @@ export function scaffoldZephyrProject(projectRoot, debug = false, userKconfig, p
|
|
|
182
228
|
continue;
|
|
183
229
|
prjConf.push(`${sym}=${val}`);
|
|
184
230
|
}
|
|
231
|
+
// ── Cuttlefish library packages ─────────────────────────────────────────
|
|
232
|
+
// Libraries the program imports (recorded in the transpiler's libraries.json
|
|
233
|
+
// sidecar, next to the emitted sources) contribute their manifest's kconfig
|
|
234
|
+
// lines — e.g. @typecad/zephyr-esp32s3-rgb contributes CONFIG_LED_STRIP.
|
|
235
|
+
// Sidecar entries are already gated on the library's include token
|
|
236
|
+
// appearing in the emitted sources, so no re-detection here. User
|
|
237
|
+
// zephyr.kconfig overrides still win.
|
|
238
|
+
const libraryEntries = readCuttlefishLibrarySidecar(srcDir);
|
|
239
|
+
if (libraryEntries.length > 0) {
|
|
240
|
+
prjConf.push('', '# Library packages (cuttlefish.library.json contributions).');
|
|
241
|
+
for (const entry of libraryEntries) {
|
|
242
|
+
for (const line of entry.kconfig) {
|
|
243
|
+
const sym = line.split('=')[0];
|
|
244
|
+
if (userKconfig && sym !== undefined && userKconfig.hasOwnProperty(sym))
|
|
245
|
+
continue;
|
|
246
|
+
prjConf.push(line);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
185
250
|
// Emit user-specified Kconfig from cuttlefish.config.ts zephyr.kconfig.
|
|
186
251
|
// These override any matching auto-detected symbol (skipped above).
|
|
187
252
|
if (userKconfig) {
|
|
@@ -9,8 +9,11 @@ export interface WestInstall {
|
|
|
9
9
|
westExecutable?: string;
|
|
10
10
|
/** Absolute path to a Python interpreter with west installed (mode 'module'). */
|
|
11
11
|
pythonExecutable?: string;
|
|
12
|
-
/** Absolute path to the Zephyr
|
|
12
|
+
/** Absolute path to the Zephyr workspace (for $ZEPHYR_BASE), if found. */
|
|
13
13
|
zephyrBase?: string;
|
|
14
|
+
/** Absolute path to the Zephyr SDK install dir (hosttools/openocd lives
|
|
15
|
+
* there), if found — used to put the SDK's openocd on the flash PATH. */
|
|
16
|
+
sdkInstallDir?: string;
|
|
14
17
|
/** mode 'micromamba': path to the micromamba binary (for `micromamba run -n …`). */
|
|
15
18
|
micromambaExe?: string;
|
|
16
19
|
/** mode 'micromamba': the conda env name (default 'zephyr'). */
|
|
@@ -172,12 +172,14 @@ export function discoverFromMicromamba(envName = process.env.TYPECAD_ZEPHYR_ENV
|
|
|
172
172
|
// anything else in the cuttlefish process) can detect the Zephyr version
|
|
173
173
|
// WITHOUT activation — micromamba run sets it only inside the west subprocess.
|
|
174
174
|
const zb = readMicromambaEnvVar(envDir, 'TYPECAD_ZEPHYR_BASE');
|
|
175
|
+
const sdk = readMicromambaEnvVar(envDir, 'TYPECAD_ZEPHYR_SDK_INSTALL_DIR');
|
|
175
176
|
return {
|
|
176
177
|
mode: 'micromamba',
|
|
177
178
|
micromambaExe: mm.exe,
|
|
178
179
|
envName,
|
|
179
180
|
mambaRootPrefix: mm.rootPrefix,
|
|
180
181
|
zephyrBase: zb && isZephyrBase(zb) ? zb : undefined,
|
|
182
|
+
sdkInstallDir: sdk && existsSync(sdk) ? sdk : undefined,
|
|
181
183
|
source: 'micromamba',
|
|
182
184
|
};
|
|
183
185
|
}
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
// (find_package(Zephyr) needs it). west's prj.conf/CMakeLists are found via
|
|
15
15
|
// the project dir regardless.
|
|
16
16
|
// ---------------------------------------------------------------------------
|
|
17
|
-
import { dirname } from 'node:path';
|
|
17
|
+
import { dirname, join } from 'node:path';
|
|
18
|
+
import { existsSync } from 'node:fs';
|
|
18
19
|
import { discoverWest } from './west-discover.js';
|
|
19
20
|
/**
|
|
20
21
|
* The Scripts/ (Windows) or bin/ (POSIX) directory of the venv the discovered
|
|
@@ -41,8 +42,19 @@ export function buildEnv(install) {
|
|
|
41
42
|
if (install.zephyrBase && !env.ZEPHYR_BASE) {
|
|
42
43
|
env.ZEPHYR_BASE = install.zephyrBase;
|
|
43
44
|
}
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
// SWD flashing (`zephyr.runner: 'openocd'`): west's openocd runner resolves
|
|
46
|
+
// a bare `openocd` from PATH. The Zephyr SDK ships it under
|
|
47
|
+
// hosttools/openocd/bin (the SDK's own setup.cmd puts that dir on PATH for
|
|
48
|
+
// activated terminals) — do the same for spawned west processes so ST-Link
|
|
49
|
+
// flashing works without activation. $ZEPHYR_SDK_INSTALL_DIR (set by an
|
|
50
|
+
// activated env) wins over the discovered install dir.
|
|
51
|
+
const sdkRoot = env.ZEPHYR_SDK_INSTALL_DIR || install.sdkInstallDir;
|
|
52
|
+
const openocdBin = sdkRoot ? join(sdkRoot, 'hosttools', 'openocd', 'bin') : undefined;
|
|
53
|
+
const prepend = [
|
|
54
|
+
openocdBin !== undefined && existsSync(openocdBin) ? openocdBin : undefined,
|
|
55
|
+
venvBinDir(install),
|
|
56
|
+
].filter((d) => d !== undefined);
|
|
57
|
+
if (prepend.length > 0) {
|
|
46
58
|
const sep = process.platform === 'win32' ? ';' : ':';
|
|
47
59
|
// On Windows the PATH environment variable may be cased as `Path` (the
|
|
48
60
|
// registry-native form, the only one populated when node is launched from
|
|
@@ -50,9 +62,9 @@ export function buildEnv(install) {
|
|
|
50
62
|
// casing can leave the other stale/empty, which under PowerShell would drop
|
|
51
63
|
// the user's real PATH (cmake, ninja, …) — breaking `west` configure. Read
|
|
52
64
|
// whichever casing is populated and write that same casing back, preserving
|
|
53
|
-
// the full existing value with the
|
|
65
|
+
// the full existing value with the discovered dirs prepended.
|
|
54
66
|
const existing = env.Path ?? env.PATH ?? '';
|
|
55
|
-
const updated =
|
|
67
|
+
const updated = prepend.join(sep) + sep + existing;
|
|
56
68
|
if (env.Path !== undefined || (env.PATH === undefined && process.platform === 'win32')) {
|
|
57
69
|
env.Path = updated;
|
|
58
70
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@typecad/framework-zephyr",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.14",
|
|
4
4
|
"description": "TypeCAD framework package for the Zephyr RTOS — west/CMake build, devicetree-driven GPIO",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -38,11 +38,11 @@
|
|
|
38
38
|
"test:hw:mqtt": "cd ../../tests/hardware && npm exec -- cuttlefish-test mqtt-client.test.ts"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@typecad/cuttlefish": "1.0.0-alpha.
|
|
41
|
+
"@typecad/cuttlefish": "1.0.0-alpha.14"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@typecad/expect": "1.0.0-alpha.
|
|
45
|
-
"@typecad/board-xiao-nrf52840": "1.0.0-alpha.
|
|
44
|
+
"@typecad/expect": "1.0.0-alpha.14",
|
|
45
|
+
"@typecad/board-xiao-nrf52840": "1.0.0-alpha.14",
|
|
46
46
|
"typescript": "^5.7.3"
|
|
47
47
|
},
|
|
48
48
|
"license": "MIT",
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
"zephyr"
|
|
76
76
|
],
|
|
77
77
|
"engines": {
|
|
78
|
-
"node": ">=
|
|
78
|
+
"node": ">=22.11.0"
|
|
79
79
|
},
|
|
80
80
|
"author": "typecad0",
|
|
81
81
|
"sideEffects": false
|
package/src/chips/controllers.ts
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
// collapse to a one-liner.
|
|
20
20
|
// ---------------------------------------------------------------------------
|
|
21
21
|
|
|
22
|
-
import type { ZephyrChipDescriptor } from './types.js';
|
|
22
|
+
import type { ZephyrChipDescriptor, ZephyrGpioController } from './types.js';
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* Resolve the devicetree nodelabel of the GPIO controller that owns `pin`.
|
|
@@ -30,23 +30,53 @@ import type { ZephyrChipDescriptor } from './types.js';
|
|
|
30
30
|
* for out-of-range pins (e.g. the manifest probe's synthetic pin 0).
|
|
31
31
|
*/
|
|
32
32
|
export function controllerNodelabelForPin(chip: ZephyrChipDescriptor, pin: number): string {
|
|
33
|
+
const hit = controllerRangeForPin(chip, pin);
|
|
34
|
+
return hit ? hit.nodelabel : chip.gpioController;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The owning controller range for a HAL pin, if the chip declares a split.
|
|
39
|
+
*/
|
|
40
|
+
export function controllerRangeForPin(
|
|
41
|
+
chip: ZephyrChipDescriptor,
|
|
42
|
+
pin: number,
|
|
43
|
+
): ZephyrGpioController | undefined {
|
|
33
44
|
const ranges = chip.gpioControllers;
|
|
34
45
|
if (ranges && ranges.length > 0) {
|
|
35
|
-
|
|
36
|
-
if (hit) return hit.nodelabel;
|
|
46
|
+
return ranges.find((r) => pin >= r.minPin && pin <= r.maxPin);
|
|
37
47
|
}
|
|
38
|
-
return
|
|
48
|
+
return undefined;
|
|
39
49
|
}
|
|
40
50
|
|
|
41
51
|
/**
|
|
42
|
-
*
|
|
52
|
+
* The PORT-RELATIVE pin index for the raw gpio_pin_*_raw() API — the Zephyr
|
|
53
|
+
* raw calls address the index WITHIN the controller, not the global HAL pin
|
|
54
|
+
* number. For split SoCs each controller's `minPin` is its base (STM32:
|
|
55
|
+
* gpiob minPin 16, so PB12 = pin 28 → raw 12); single-controller SoCs have
|
|
56
|
+
* no offset. Emitting the global number against a port driver would address
|
|
57
|
+
* a nonexistent port bit (STM32 gpiob is 0–15) and fail at runtime.
|
|
58
|
+
*/
|
|
59
|
+
export function controllerRawPinForPin(chip: ZephyrChipDescriptor, pin: number): number {
|
|
60
|
+
const hit = controllerRangeForPin(chip, pin);
|
|
61
|
+
return pin - (hit?.minPin ?? 0);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Emit the C++ source for runtime pin → GPIO-device / port-relative-index
|
|
66
|
+
* dispatchers.
|
|
67
|
+
*
|
|
68
|
+
* Returns lines defining:
|
|
69
|
+
* - `static inline const struct device* __tc_gpio_dev(uint32_t pin)` — the
|
|
70
|
+
* owning controller's device, resolved via DEVICE_DT_GET(DT_NODELABEL(...))
|
|
71
|
+
* at compile time (the macro is evaluated per branch, so it is always
|
|
72
|
+
* statically valid); only `pin` is runtime.
|
|
73
|
+
* - `static inline gpio_pin_t __tc_gpio_pin(uint32_t pin)` — the
|
|
74
|
+
* port-relative index for gpio_pin_*_raw() (see controllerRawPinForPin).
|
|
75
|
+
* The runtime shim paths (UI pin-watch, safety voters) take a runtime pin,
|
|
76
|
+
* so they cannot bake the offset in at emit time.
|
|
43
77
|
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* compile time (the macro is evaluated per branch, so it is always statically
|
|
47
|
-
* valid); only `pin` is runtime. For a single-controller SoC this collapses to
|
|
48
|
-
* a one-liner returning that controller, so the existing XIAO nRF52840 behavior
|
|
49
|
-
* is byte-for-byte unchanged.
|
|
78
|
+
* For a single-controller SoC both collapse to one-liners, so the existing
|
|
79
|
+
* XIAO nRF52840 behavior is byte-for-byte unchanged.
|
|
50
80
|
*/
|
|
51
81
|
export function emitGpioDevDispatcher(chip: ZephyrChipDescriptor): string[] {
|
|
52
82
|
const ranges = chip.gpioControllers;
|
|
@@ -56,19 +86,38 @@ export function emitGpioDevDispatcher(chip: ZephyrChipDescriptor): string[] {
|
|
|
56
86
|
` (void)pin;`,
|
|
57
87
|
` return DEVICE_DT_GET(DT_NODELABEL(${chip.gpioController}));`,
|
|
58
88
|
'}',
|
|
89
|
+
'static inline gpio_pin_t __tc_gpio_pin(uint32_t pin) {',
|
|
90
|
+
` return (gpio_pin_t)pin;`,
|
|
91
|
+
'}',
|
|
59
92
|
];
|
|
60
93
|
}
|
|
61
94
|
const lines: string[] = [
|
|
62
95
|
'static inline const struct device* __tc_gpio_dev(uint32_t pin) {',
|
|
63
96
|
];
|
|
97
|
+
const pinLines: string[] = [
|
|
98
|
+
'static inline gpio_pin_t __tc_gpio_pin(uint32_t pin) {',
|
|
99
|
+
];
|
|
64
100
|
for (const r of ranges) {
|
|
65
101
|
lines.push(
|
|
66
102
|
` if (pin >= ${r.minPin} && pin <= ${r.maxPin}) { return DEVICE_DT_GET(DT_NODELABEL(${r.nodelabel})); }`,
|
|
67
103
|
);
|
|
104
|
+
if (r.minPin === 0) {
|
|
105
|
+
pinLines.push(
|
|
106
|
+
` if (pin >= ${r.minPin} && pin <= ${r.maxPin}) { return (gpio_pin_t)pin; }`,
|
|
107
|
+
);
|
|
108
|
+
} else {
|
|
109
|
+
pinLines.push(
|
|
110
|
+
` if (pin >= ${r.minPin} && pin <= ${r.maxPin}) { return (gpio_pin_t)(pin - ${r.minPin}); }`,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
68
113
|
}
|
|
69
114
|
lines.push(
|
|
70
115
|
` return DEVICE_DT_GET(DT_NODELABEL(${chip.gpioController}));`,
|
|
71
116
|
'}',
|
|
72
117
|
);
|
|
73
|
-
|
|
118
|
+
pinLines.push(
|
|
119
|
+
` return (gpio_pin_t)pin;`,
|
|
120
|
+
'}',
|
|
121
|
+
);
|
|
122
|
+
return [...lines, ...pinLines];
|
|
74
123
|
}
|
package/src/chips/resolve.ts
CHANGED
|
@@ -102,18 +102,36 @@ export function resolveChipFromBoard(
|
|
|
102
102
|
|
|
103
103
|
const pwmSpecs = collectIndexed<ZephyrPwmSpec>(bc, 'zephyr.pwm.specs', (m, i) => {
|
|
104
104
|
const pin = m.get(`zephyr.pwm.specs.${i}.pin`) as number;
|
|
105
|
-
const dtSpec = m.get(`zephyr.pwm.specs.${i}.dtSpec`) as string;
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
const dtSpec = m.get(`zephyr.pwm.specs.${i}.dtSpec`) as string | undefined;
|
|
106
|
+
const controller = m.get(`zephyr.pwm.specs.${i}.controller`) as string | undefined;
|
|
107
|
+
const channel = m.get(`zephyr.pwm.specs.${i}.channel`) as number | undefined;
|
|
108
|
+
const periodNs = m.get(`zephyr.pwm.specs.${i}.periodNs`) as number | undefined;
|
|
109
|
+
const polarity = m.get(`zephyr.pwm.specs.${i}.polarity`) as string | undefined;
|
|
110
|
+
// Board-shipped alias form (dtSpec) or synthesized form (controller +
|
|
111
|
+
// channel → overlay-generated alias) — at least one, else drop the entry.
|
|
112
|
+
if (pin == null || !(dtSpec || (controller && channel != null))) return null;
|
|
113
|
+
return {
|
|
114
|
+
pin,
|
|
115
|
+
...(dtSpec ? { dtSpec } : {}),
|
|
116
|
+
...(controller ? { controller } : {}),
|
|
117
|
+
...(channel != null ? { channel } : {}),
|
|
118
|
+
...(periodNs != null ? { periodNs } : {}),
|
|
119
|
+
...(polarity ? { polarity } : {}),
|
|
120
|
+
};
|
|
108
121
|
});
|
|
109
122
|
|
|
110
123
|
const adcNodeLabel = bc.get('zephyr.adc.nodeLabel') as string | undefined;
|
|
111
124
|
const adcResolution = bc.get('zephyr.adc.resolution') as number | undefined;
|
|
112
125
|
const adcVref = bc.get('zephyr.adc.vrefMv') as number | undefined;
|
|
126
|
+
const adcGain = bc.get('zephyr.adc.gain') as string | undefined;
|
|
127
|
+
const adcReference = bc.get('zephyr.adc.reference') as string | undefined;
|
|
113
128
|
const adcChannels = collectIndexed<ZephyrAdcChannel>(bc, 'zephyr.adc.channels', (m, i) => {
|
|
114
129
|
const pin = m.get(`zephyr.adc.channels.${i}.pin`) as number;
|
|
115
130
|
const channel = m.get(`zephyr.adc.channels.${i}.channel`) as number;
|
|
116
|
-
|
|
131
|
+
const pinctrl = m.get(`zephyr.adc.channels.${i}.pinctrl`) as string | undefined;
|
|
132
|
+
if (pin != null && channel != null) {
|
|
133
|
+
return { pin, channel, ...(pinctrl ? { pinctrl } : {}) };
|
|
134
|
+
}
|
|
117
135
|
return null;
|
|
118
136
|
});
|
|
119
137
|
|
|
@@ -138,7 +156,16 @@ export function resolveChipFromBoard(
|
|
|
138
156
|
...(uartControllers.length > 0 ? { uart: { controllers: uartControllers } } : {}),
|
|
139
157
|
...(pwmSpecs.length > 0 ? { pwm: { specs: pwmSpecs } } : {}),
|
|
140
158
|
...(adcNodeLabel || adcResolution != null || adcVref != null || adcChannels.length > 0
|
|
141
|
-
? {
|
|
159
|
+
? {
|
|
160
|
+
adc: {
|
|
161
|
+
nodeLabel: adcNodeLabel ?? 'adc',
|
|
162
|
+
resolution: adcResolution ?? 12,
|
|
163
|
+
vrefMv: adcVref ?? 3000,
|
|
164
|
+
channels: adcChannels,
|
|
165
|
+
...(adcGain ? { gain: adcGain } : {}),
|
|
166
|
+
...(adcReference ? { reference: adcReference } : {}),
|
|
167
|
+
},
|
|
168
|
+
}
|
|
142
169
|
: {}),
|
|
143
170
|
...(wdtNodeLabel ? { wdt: { nodeLabel: wdtNodeLabel } } : {}),
|
|
144
171
|
...(wifiSupported ? { wifi: { supported: true as const } } : {}),
|
package/src/chips/types.ts
CHANGED
|
@@ -64,14 +64,37 @@ export interface ZephyrBusController {
|
|
|
64
64
|
/**
|
|
65
65
|
* A PWM channel described as a devicetree spec.
|
|
66
66
|
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
67
|
+
* Two forms, mutually exclusive:
|
|
68
|
+
* - **Board-shipped alias:** `dtSpec` names a DT alias the board's own DTS
|
|
69
|
+
* already defines (e.g. the XIAO's `pwm-led0`). Emitted as
|
|
70
|
+
* `PWM_DT_SPEC_GET(DT_ALIAS(<dtSpec>))`.
|
|
71
|
+
* - **Synthesized** (controller + channel): the board DTS enables a PWM
|
|
72
|
+
* controller node (e.g. `pwm4`) but defines no alias for it. The overlay
|
|
73
|
+
* generator synthesizes a `pwm-leds` consumer node + a `tc-pwm<pin>` alias
|
|
74
|
+
* in `<board>.overlay`; the lowering emits
|
|
75
|
+
* `PWM_DT_SPEC_GET(DT_ALIAS(tc-pwm<pin>))`. Both sides derive the alias
|
|
76
|
+
* name from the pin, so they always agree.
|
|
77
|
+
*
|
|
78
|
+
* `pwm_set_pulse_dt(&spec, pulse_ns)` honors the spec's period/polarity.
|
|
69
79
|
*/
|
|
70
80
|
export interface ZephyrPwmSpec {
|
|
71
81
|
/** GPIO number (matches the HAL op `pin` field). */
|
|
72
82
|
readonly pin: number;
|
|
73
|
-
/**
|
|
74
|
-
|
|
83
|
+
/** Board-shipped DT alias, e.g. 'pwm-led0'. Omit when using the
|
|
84
|
+
* synthesized form (controller + channel). */
|
|
85
|
+
readonly dtSpec?: string;
|
|
86
|
+
/** Synthesized form: PWM controller DT nodelabel, e.g. 'pwm4' (the STM32
|
|
87
|
+
* timer's pwm child node). The overlay's pwm-leds node consumes it. */
|
|
88
|
+
readonly controller?: string;
|
|
89
|
+
/** Synthesized form: channel index within the controller (1-based timer
|
|
90
|
+
* channel, matching the `pwms` binding's channel cell). */
|
|
91
|
+
readonly channel?: number;
|
|
92
|
+
/** Synthesized form: period in nanoseconds, baked into the DT spec. The
|
|
93
|
+
* lowering scales duty against `spec.period`. Default 20 000 000 (20 ms /
|
|
94
|
+
* 50 Hz — the servo convention; harmless for LED dimming). */
|
|
95
|
+
readonly periodNs?: number;
|
|
96
|
+
/** Synthesized form: PWM polarity flag. Default PWM_POLARITY_NORMAL. */
|
|
97
|
+
readonly polarity?: string;
|
|
75
98
|
}
|
|
76
99
|
|
|
77
100
|
/**
|
|
@@ -88,7 +111,7 @@ export interface ZephyrInterruptPin {
|
|
|
88
111
|
}
|
|
89
112
|
|
|
90
113
|
/**
|
|
91
|
-
* An ADC channel: which
|
|
114
|
+
* An ADC channel: which ADC input a given HAL pin maps to.
|
|
92
115
|
*
|
|
93
116
|
* The XIAO nRF52840 has no pre-declared ADC channel nodes in devicetree, so the
|
|
94
117
|
* lowering emits `adc_channel_setup` against `DEVICE_DT_GET(DT_NODELABEL(adc))`
|
|
@@ -97,8 +120,16 @@ export interface ZephyrInterruptPin {
|
|
|
97
120
|
export interface ZephyrAdcChannel {
|
|
98
121
|
/** GPIO number (matches the HAL op `pin` field). */
|
|
99
122
|
readonly pin: number;
|
|
100
|
-
/**
|
|
123
|
+
/** ADC channel index (nRF SAADC AIN0–AIN7; STM32 ADC1_IN0–IN9). */
|
|
101
124
|
readonly channel: number;
|
|
125
|
+
/**
|
|
126
|
+
* Pinctrl node label that muxes this pin to analog mode, e.g.
|
|
127
|
+
* 'adc1_in0_pa0' (STM32). When present, the overlay generator rewrites the
|
|
128
|
+
* ADC node's pinctrl-0 to the channels the program actually reads — SoCs
|
|
129
|
+
* like STM32 leave the pad in GPIO mode otherwise and reads float.
|
|
130
|
+
* Omit on SoCs whose ADC needs no pad muxing (nRF SAADC, RP2040).
|
|
131
|
+
*/
|
|
132
|
+
readonly pinctrl?: string;
|
|
102
133
|
}
|
|
103
134
|
|
|
104
135
|
/**
|
|
@@ -134,11 +165,18 @@ export interface ZephyrChipDescriptor {
|
|
|
134
165
|
readonly gpioController: string;
|
|
135
166
|
/**
|
|
136
167
|
* Per-range GPIO controllers for SoCs that split GPIO across multiple
|
|
137
|
-
* devicetree nodes (ESP32-S3: `gpio0` 0–31, `gpio1` 32–48
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
168
|
+
* devicetree nodes (ESP32-S3: `gpio0` 0–31, `gpio1` 32–48; STM32: one
|
|
169
|
+
* controller per port — `gpioa` 0–15, `gpiob` 16–31, `gpioc` 32–47).
|
|
170
|
+
* When present, the lowering routes a HAL pin to its owning controller at
|
|
171
|
+
* runtime via the emitted `__tc_gpio_dev(pin)` dispatcher; `gpioController`
|
|
172
|
+
* is the fallback. Omit on single-controller SoCs (RP2040, …) — every pin
|
|
173
|
+
* is on the one controller described by `gpioController`.
|
|
174
|
+
*
|
|
175
|
+
* NUMBERING RULE (load-bearing): `minPin` must equal the controller's port
|
|
176
|
+
* base so the port-relative raw index is `pin - minPin` (STM32 PB12 = pin
|
|
177
|
+
* 28 → raw 12 — the Zephyr raw API addresses the index WITHIN the
|
|
178
|
+
* controller). Number pins by port blocks and never contiguously across
|
|
179
|
+
* unbonded pins.
|
|
142
180
|
*/
|
|
143
181
|
readonly gpioControllers?: readonly ZephyrGpioController[];
|
|
144
182
|
/** GPIO pins with devicetree specs (LEDs, buttons, board-defined pins). */
|
|
@@ -155,7 +193,7 @@ export interface ZephyrChipDescriptor {
|
|
|
155
193
|
readonly uart?: { readonly controllers: readonly ZephyrBusController[] };
|
|
156
194
|
/** PWM channels with DT specs. */
|
|
157
195
|
readonly pwm?: { readonly specs: readonly ZephyrPwmSpec[] };
|
|
158
|
-
/** ADC: the
|
|
196
|
+
/** ADC: the ADC device node label + the pin→channel map. */
|
|
159
197
|
readonly adc?: {
|
|
160
198
|
readonly nodeLabel: string;
|
|
161
199
|
readonly channels: readonly ZephyrAdcChannel[];
|
|
@@ -163,6 +201,19 @@ export interface ZephyrChipDescriptor {
|
|
|
163
201
|
readonly vrefMv: number;
|
|
164
202
|
/** ADC resolution in bits. */
|
|
165
203
|
readonly resolution: number;
|
|
204
|
+
/**
|
|
205
|
+
* Zephyr `enum adc_gain` macro for the channel setup, e.g.
|
|
206
|
+
* 'ADC_GAIN_1_4' (nRF SAADC default) or 'ADC_GAIN_1' (STM32 driver
|
|
207
|
+
* requires exactly this). Defaults to 'ADC_GAIN_1_4'.
|
|
208
|
+
*/
|
|
209
|
+
readonly gain?: string;
|
|
210
|
+
/**
|
|
211
|
+
* Zephyr `enum adc_reference` macro, e.g. 'ADC_REF_INTERNAL'. Defaults to
|
|
212
|
+
* 'ADC_REF_INTERNAL' — on nRF that is the 0.6 V internal ref measured
|
|
213
|
+
* through the gain divider; the STM32 driver ALSO requires
|
|
214
|
+
* ADC_REF_INTERNAL (Zephyr maps it to the VREF+ pad) with vrefMv = VDDA.
|
|
215
|
+
*/
|
|
216
|
+
readonly reference?: string;
|
|
166
217
|
};
|
|
167
218
|
/**
|
|
168
219
|
* DAC: the DAC device node label + the pin→channel map. Present only on chips
|