@typecad/framework-zephyr 1.0.0-alpha.18 → 1.0.0-alpha.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audit.d.ts +111 -0
- package/dist/audit.js +416 -0
- package/dist/boardgen.js +63 -5
- package/dist/chips/resolve.js +20 -0
- package/dist/chips/types.d.ts +21 -0
- package/dist/display/bindings.d.ts +55 -0
- package/dist/display/bindings.js +316 -0
- package/dist/display/gfx.d.ts +2 -3
- package/dist/display/gfx.js +166 -154
- package/dist/display/index.js +20 -1
- package/dist/display/mipi-dbi-host.d.ts +9 -0
- package/dist/display/mipi-dbi-host.js +174 -0
- package/dist/display/profiles.d.ts +109 -4
- package/dist/display/profiles.js +270 -7
- package/dist/display/touch-adapter.js +118 -48
- package/dist/display/ui-adapter-eink.d.ts +2 -0
- package/dist/display/ui-adapter-eink.js +4 -0
- package/dist/display/ui-adapter-gray.d.ts +8 -0
- package/dist/display/ui-adapter-gray.js +170 -0
- package/dist/display/ui-adapter-mono.d.ts +13 -0
- package/dist/display/ui-adapter-mono.js +230 -0
- package/dist/display/ui-adapter-native.d.ts +10 -0
- package/dist/display/ui-adapter-native.js +295 -0
- package/dist/display/ui-adapter-shared.d.ts +11 -0
- package/dist/display/ui-adapter-shared.js +122 -0
- package/dist/display/ui-adapter.js +53 -101
- package/dist/dt-config/kconfig.d.ts +62 -1
- package/dist/dt-config/kconfig.js +123 -26
- package/dist/dt-config/overlay.d.ts +13 -0
- package/dist/dt-config/overlay.js +431 -16
- package/dist/framework.manifest.d.ts +9 -3
- package/dist/framework.manifest.js +123 -10
- package/dist/index.d.ts +2 -0
- package/dist/index.js +9 -1
- package/dist/licenses.js +5 -84
- package/dist/lowering/can.d.ts +25 -0
- package/dist/lowering/can.js +97 -0
- package/dist/lowering/clock.d.ts +17 -0
- package/dist/lowering/clock.js +58 -0
- package/dist/lowering/hid.d.ts +27 -0
- package/dist/lowering/hid.js +244 -0
- package/dist/lowering/i2c.d.ts +8 -0
- package/dist/lowering/i2c.js +140 -0
- package/dist/lowering/i2s.d.ts +27 -0
- package/dist/lowering/i2s.js +98 -0
- package/dist/lowering/index.d.ts +9 -1
- package/dist/lowering/index.js +25 -1
- package/dist/lowering/matrix.d.ts +15 -0
- package/dist/lowering/matrix.js +63 -0
- package/dist/lowering/power.d.ts +10 -0
- package/dist/lowering/power.js +35 -0
- package/dist/lowering/pwm.js +25 -0
- package/dist/lowering/sensor.d.ts +2 -2
- package/dist/lowering/sensor.js +8 -4
- package/dist/lowering/strip.d.ts +16 -0
- package/dist/lowering/strip.js +70 -0
- package/dist/lowering/thread.js +5 -1
- package/dist/lowering/trace.d.ts +44 -0
- package/dist/lowering/trace.js +239 -0
- package/dist/lowering/uart.js +6 -1
- package/dist/lowering/usb.d.ts +3 -1
- package/dist/lowering/usb.js +6 -3
- package/dist/sbom.d.ts +181 -0
- package/dist/sbom.js +901 -0
- package/dist/strategy.d.ts +60 -2
- package/dist/strategy.js +485 -25
- package/dist/toolchain/index.d.ts +12 -1
- package/dist/toolchain/index.js +115 -27
- package/dist/toolchain/scaffold.d.ts +4 -1
- package/dist/toolchain/scaffold.js +67 -3
- package/dist/west-inventory.d.ts +25 -0
- package/dist/west-inventory.js +97 -0
- package/package.json +6 -6
- package/src/audit.ts +529 -0
- package/src/boardgen.ts +54 -5
- package/src/chips/resolve.ts +21 -0
- package/src/chips/types.ts +576 -567
- package/src/display/bindings.ts +347 -0
- package/src/display/gfx.ts +318 -306
- package/src/display/index.ts +87 -70
- package/src/display/mipi-dbi-host.ts +183 -0
- package/src/display/profiles.ts +458 -139
- package/src/display/touch-adapter.ts +274 -204
- package/src/display/ui-adapter-eink.ts +13 -0
- package/src/display/ui-adapter-gray.ts +178 -0
- package/src/display/ui-adapter-mono.ts +238 -0
- package/src/display/ui-adapter-native.ts +304 -0
- package/src/display/ui-adapter-shared.ts +125 -0
- package/src/display/ui-adapter.ts +51 -100
- package/src/dt-config/kconfig.ts +647 -511
- package/src/dt-config/overlay.ts +433 -16
- package/src/framework.manifest.ts +131 -10
- package/src/index.ts +11 -1
- package/src/licenses.ts +5 -84
- package/src/lowering/can.ts +140 -0
- package/src/lowering/clock.ts +91 -0
- package/src/lowering/hid.ts +261 -0
- package/src/lowering/i2c.ts +146 -0
- package/src/lowering/i2s.ts +143 -0
- package/src/lowering/index.ts +18 -1
- package/src/lowering/matrix.ts +70 -0
- package/src/lowering/power.ts +41 -0
- package/src/lowering/pwm.ts +192 -167
- package/src/lowering/sensor.ts +159 -155
- package/src/lowering/strip.ts +81 -0
- package/src/lowering/thread.ts +5 -1
- package/src/lowering/trace.ts +270 -0
- package/src/lowering/uart.ts +6 -1
- package/src/lowering/usb.ts +224 -221
- package/src/sbom.ts +1117 -0
- package/src/strategy.ts +440 -25
- package/src/toolchain/index.ts +117 -28
- package/src/toolchain/scaffold.ts +62 -3
- package/src/west-inventory.ts +102 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Matrix lowering — gpio-kbd-matrix node + input-event trampoline
|
|
3
|
+
//
|
|
4
|
+
// The gpio-kbd-matrix driver (Zephyr input subsystem) scans the grid and
|
|
5
|
+
// reports each key as ABS_X (column) + ABS_Y (row) + BTN_TOUCH (press).
|
|
6
|
+
// The shim decodes the triple and calls the user's lowered callback as
|
|
7
|
+
// (row, col, pressed). The overlay generator synthesizes the DT node with
|
|
8
|
+
// row-gpios/col-gpios phandle arrays from the construction pad lists —
|
|
9
|
+
// the lowering's comment marker carries rows/cols to the toolchain scan
|
|
10
|
+
// (the pwm user-facts marker precedent).
|
|
11
|
+
// ----------------------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Emit the matrix trampoline state. Called from shimLines when the program
|
|
17
|
+
* uses matrix.* — the listener targets the synthesized tc_matrix node, the
|
|
18
|
+
* handler pointer is assigned by the on_key lowering.
|
|
19
|
+
*/
|
|
20
|
+
export function matrixInitLines(): string[] {
|
|
21
|
+
return [
|
|
22
|
+
'// CUTTLEFISH_MATRIX_BEGIN',
|
|
23
|
+
'static void (*__tc_matrix_on_key)(double, double, bool) = NULL;',
|
|
24
|
+
'static int32_t __tc_matrix_row = -1;',
|
|
25
|
+
'static int32_t __tc_matrix_col = -1;',
|
|
26
|
+
'static void __tc_matrix_cb(struct input_event* evt, void* user_data) {',
|
|
27
|
+
' (void)user_data;',
|
|
28
|
+
' switch (evt->code) {',
|
|
29
|
+
' case INPUT_ABS_X:',
|
|
30
|
+
' __tc_matrix_col = evt->value;',
|
|
31
|
+
' break;',
|
|
32
|
+
' case INPUT_ABS_Y:',
|
|
33
|
+
' __tc_matrix_row = evt->value;',
|
|
34
|
+
' break;',
|
|
35
|
+
' case INPUT_BTN_TOUCH:',
|
|
36
|
+
' if (__tc_matrix_on_key != NULL && __tc_matrix_row >= 0 && __tc_matrix_col >= 0) {',
|
|
37
|
+
' __tc_matrix_on_key(static_cast<double>(__tc_matrix_row), static_cast<double>(__tc_matrix_col), evt->value != 0);',
|
|
38
|
+
' }',
|
|
39
|
+
' break;',
|
|
40
|
+
' default:',
|
|
41
|
+
' break;',
|
|
42
|
+
' }',
|
|
43
|
+
'}',
|
|
44
|
+
'INPUT_CALLBACK_DEFINE(DEVICE_DT_GET(DT_NODELABEL(tc_matrix)), __tc_matrix_cb, NULL);',
|
|
45
|
+
'// CUTTLEFISH_MATRIX_END',
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Resolve a HAL matrix.* op to Zephyr C++.
|
|
51
|
+
* Returns `{ code }` for statement ops.
|
|
52
|
+
*/
|
|
53
|
+
export function lowerMatrix(op: HALOpIR): { code?: string; expression?: string } {
|
|
54
|
+
const o = op as any;
|
|
55
|
+
|
|
56
|
+
switch (op.operation) {
|
|
57
|
+
case 'matrix.on_key': {
|
|
58
|
+
// The comment marker carries the construction pad lists to the
|
|
59
|
+
// toolchain's overlay scan (the pwm user-facts precedent).
|
|
60
|
+
return {
|
|
61
|
+
code: `/* cuttlefish-matrix: rows=${o.rows} cols=${o.cols} */ __tc_matrix_on_key = ${o.handler};`,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
default:
|
|
65
|
+
throw new Error(
|
|
66
|
+
`framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
|
|
67
|
+
`Open an issue or use rawCpp() to emit it manually.`,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Power lowering — sys_poweroff() for explicit soft-off entry
|
|
3
|
+
//
|
|
4
|
+
// Both SoC families implement the kernel's sys_poweroff(): ESP32 parks the
|
|
5
|
+
// RTC domain and calls esp_deep_sleep_start(); STM32 enters standby-class
|
|
6
|
+
// deep sleep. The call never returns — wake is a reset or a wake source
|
|
7
|
+
// (board-specific: the Black Pill's WKUP pin is PA0; ESP32 GPIOs armed as
|
|
8
|
+
// wake triggers). Statement emission continues after the call site, but
|
|
9
|
+
// nothing there runs — the transpiler treats it as any other void call.
|
|
10
|
+
// ----------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
|
|
13
|
+
import type { ZephyrChipDescriptor } from '../chips/types.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Resolve a HAL power.* op to Zephyr C++.
|
|
17
|
+
* Returns `{ code }` for statement ops.
|
|
18
|
+
*/
|
|
19
|
+
export function lowerPower(op: HALOpIR, chip?: ZephyrChipDescriptor): { code?: string; expression?: string } {
|
|
20
|
+
const o = op as any;
|
|
21
|
+
switch (op.operation) {
|
|
22
|
+
case 'power.off':
|
|
23
|
+
return { code: 'sys_poweroff();' };
|
|
24
|
+
case 'power.off_for': {
|
|
25
|
+
// DAC discipline: without the RTC wake-timer fact, a timed off would
|
|
26
|
+
// sleep with no wake source — lower to a comment naming it.
|
|
27
|
+
if (!chip?.powerWakeTimer) {
|
|
28
|
+
return { code: `/* Power.offFor(${o.ms} ms): this board's SoC declares no RTC wake timer — soft-off here has no armed wake; use Power.off() */` };
|
|
29
|
+
}
|
|
30
|
+
// Arm the RTC timer (µs), then enter soft-off; wake is a reboot.
|
|
31
|
+
return {
|
|
32
|
+
code: `{ esp_sleep_enable_timer_wakeup(static_cast<uint64_t>(${o.ms}) * 1000ULL); sys_poweroff(); }`,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
default:
|
|
36
|
+
throw new Error(
|
|
37
|
+
`framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
|
|
38
|
+
`Open an issue or use rawCpp() to emit it manually.`,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
}
|
package/src/lowering/pwm.ts
CHANGED
|
@@ -1,167 +1,192 @@
|
|
|
1
|
-
// ---------------------------------------------------------------------------
|
|
2
|
-
// PWM lowering — pwm_dt_spec via pwm-led0 alias
|
|
3
|
-
//
|
|
4
|
-
// The XIAO nRF52840 exposes PWM via the `pwm-led0` devicetree alias (PWM_OUT0
|
|
5
|
-
// on P0.17, inverted). The lowering emits a `pwm_dt_spec` per channel and uses
|
|
6
|
-
// `pwm_set_pulse_dt` / `pwm_set_dt`. Duty is scaled from the Arduino-style
|
|
7
|
-
// 0–255 (or 0–1023) range to nanoseconds against the spec's period.
|
|
8
|
-
// ---------------------------------------------------------------------------
|
|
9
|
-
|
|
10
|
-
import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
|
|
11
|
-
import type { ZephyrChipDescriptor, ZephyrPwmSpec } from '../chips/types.js';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* The DT alias a PWM spec is addressed by. Board-shipped specs carry their
|
|
15
|
-
* alias in `dtSpec`; synthesized specs (controller + channel) get a
|
|
16
|
-
* `tc-pwm<pin>` alias that the overlay generator creates in
|
|
17
|
-
* <board>.overlay — both sides derive the name from the pin so they agree.
|
|
18
|
-
*/
|
|
19
|
-
export function pwmDtAlias(spec: ZephyrPwmSpec): string {
|
|
20
|
-
return spec.dtSpec ?? `tc-pwm${spec.pin}`;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* The C macro token for a spec's alias. Zephyr's devicetree macros replace
|
|
25
|
-
* dashes in alias names with underscores (`pwm-led0` in DTS is
|
|
26
|
-
* DT_ALIAS(pwm_led0) in C) — the dashed spelling is a subtraction
|
|
27
|
-
* expression and fails to compile (caught by the blackpill E2E west build).
|
|
28
|
-
*/
|
|
29
|
-
export function pwmDtAliasToken(spec: ZephyrPwmSpec): string {
|
|
30
|
-
return pwmDtAlias(spec).replace(/-/g, '_');
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Look up a PWM spec by HAL pin number. Matrix pins (ESP32 LEDC) synthesize
|
|
35
|
-
* a spec on the fly: the channel is inert here — the emitted C++ addresses
|
|
36
|
-
* the pin only via its `tc-pwm<pin>` alias (the DT pwms cell carries the
|
|
37
|
-
* real channel, assigned by the overlay generator over the driven pins).
|
|
38
|
-
*/
|
|
39
|
-
export function findPwmSpec(chip: ZephyrChipDescriptor, pin: number): ZephyrPwmSpec | undefined {
|
|
40
|
-
const spec = chip.pwm?.specs.find((s) => s.pin === pin);
|
|
41
|
-
if (spec) return spec;
|
|
42
|
-
const m = chip.pwm?.matrix;
|
|
43
|
-
if (m && m.pins.includes(pin)) return { pin, controller: m.controller, channel: 0 };
|
|
44
|
-
return undefined;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/** The C variable name emitted for a PWM channel's spec. */
|
|
48
|
-
function pwmVarName(spec: ZephyrPwmSpec): string {
|
|
49
|
-
return `__tc_pwm_${pwmDtAliasToken(spec)}`;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Emit the per-channel PWM spec declarations. One per spec in the chip
|
|
54
|
-
* descriptor that the PROGRAM ACTUALLY DRIVES (`usedPins`) — a spec for an
|
|
55
|
-
* untouched pin is unused code in the emitted TU (and would need a dead DT
|
|
56
|
-
* alias in the overlay). When `usedPins` is omitted (probe paths with no
|
|
57
|
-
* program), every spec is emitted. Called from shimLines when the program
|
|
58
|
-
* uses PWM.
|
|
59
|
-
*/
|
|
60
|
-
export function pwmInitLines(
|
|
61
|
-
chip: ZephyrChipDescriptor,
|
|
62
|
-
usedPins?: ReadonlySet<number>,
|
|
63
|
-
userSpecs?: readonly { pin: number; controller: string; channel: number }[],
|
|
64
|
-
): string[] {
|
|
65
|
-
const lines: string[] = ['// CUTTLEFISH_PWM_BEGIN'];
|
|
66
|
-
for (const spec of chip.pwm?.specs ?? []) {
|
|
67
|
-
if (usedPins && !usedPins.has(spec.pin)) continue;
|
|
68
|
-
lines.push(
|
|
69
|
-
`static const struct pwm_dt_spec ${pwmVarName(spec)} = PWM_DT_SPEC_GET(DT_ALIAS(${pwmDtAliasToken(spec)}));`,
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
// Inline-override pins (the escape hatch): the construction opts vouch for
|
|
73
|
-
// controller+channel on a pin the manifest does not map — the alias var
|
|
74
|
-
// the lowered calls reference (the DT node itself comes from the overlay
|
|
75
|
-
// regen's marker merge).
|
|
76
|
-
for (const spec of userSpecs ?? []) {
|
|
77
|
-
if (chip.pwm?.specs.some((s) => s.pin === spec.pin)) continue;
|
|
78
|
-
if (usedPins && !usedPins.has(spec.pin)) continue;
|
|
79
|
-
lines.push(
|
|
80
|
-
`static const struct pwm_dt_spec ${pwmVarName(spec)} = PWM_DT_SPEC_GET(DT_ALIAS(${pwmDtAliasToken(spec)}));`,
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
// Matrix pins (ESP32 LEDC): one alias per driven pin, ascending — the same
|
|
84
|
-
// order the overlay generator assigns channels in, though the C++ never
|
|
85
|
-
// needs the channel (the DT pwms cell carries it). An omitted usage set
|
|
86
|
-
// (probe paths) emits every matrix pin, mirroring the static behavior.
|
|
87
|
-
const m = chip.pwm?.matrix;
|
|
88
|
-
if (m) {
|
|
89
|
-
const pins = (usedPins ? [...usedPins].filter((p) => m.pins.includes(p)) : [...m.pins])
|
|
90
|
-
.sort((a, b) => a - b);
|
|
91
|
-
for (const pin of pins) {
|
|
92
|
-
const spec: ZephyrPwmSpec = { pin, controller: m.controller, channel: 0 };
|
|
93
|
-
lines.push(
|
|
94
|
-
`static const struct pwm_dt_spec ${pwmVarName(spec)} = PWM_DT_SPEC_GET(DT_ALIAS(${pwmDtAliasToken(spec)}));`,
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
lines.push('// CUTTLEFISH_PWM_END');
|
|
99
|
-
return lines;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Resolve a HAL pwm.* op to Zephyr C++.
|
|
104
|
-
* Returns `{ code }` for statement ops, `{ expression }` for value-returning ops.
|
|
105
|
-
*/
|
|
106
|
-
export function lowerPwm(
|
|
107
|
-
op: HALOpIR,
|
|
108
|
-
chip: ZephyrChipDescriptor,
|
|
109
|
-
): { code?: string; expression?: string } {
|
|
110
|
-
const o = op as any;
|
|
111
|
-
// Construction-time controller/channel overrides (hal/pwm-pin.ts opts):
|
|
112
|
-
// the user vouches for the routing. The spec synthesizes from the
|
|
113
|
-
// override (addressing rides the tc-pwm<pin> alias like a matrix pin) and
|
|
114
|
-
// a marker comment carries controller+channel to the overlay regen, which
|
|
115
|
-
// synthesizes the DT node — the transpiler cannot.
|
|
116
|
-
const hasOverride = (typeof o.controllerOverride === 'string' && o.controllerOverride !== '')
|
|
117
|
-
|| (typeof o.channelOverride === 'number' && o.channelOverride >= 0);
|
|
118
|
-
const spec = hasOverride
|
|
119
|
-
? {
|
|
120
|
-
pin: o.pin as number,
|
|
121
|
-
controller: (o.controllerOverride as string | undefined) ?? 'pwm0',
|
|
122
|
-
channel: (o.channelOverride as number | undefined) ?? 0,
|
|
123
|
-
}
|
|
124
|
-
: findPwmSpec(chip, o.pin);
|
|
125
|
-
if (!spec) {
|
|
126
|
-
// Probe / unlisted pin: return a comment so the resolver reports non-
|
|
127
|
-
// undefined (the manifest validator's probe sends pin:0 with no spec).
|
|
128
|
-
// A real program pins the descriptor's pwm.specs entry.
|
|
129
|
-
return { code: `/* pwm on pin ${o.pin}: no PWM spec in chip descriptor */` };
|
|
130
|
-
}
|
|
131
|
-
const v = pwmVarName(spec);
|
|
132
|
-
// The overlay regen (toolchain) parses this into a synthesized pwm-leds
|
|
133
|
-
// spec: pin → controller/channel. Comment placement inside the block
|
|
134
|
-
// braces is legal C.
|
|
135
|
-
const marker = hasOverride
|
|
136
|
-
? `/* cuttlefish-user-facts: pwm pin=${spec.pin} controller=${spec.controller} channel=${spec.channel} */ `
|
|
137
|
-
: '';
|
|
138
|
-
|
|
139
|
-
switch (op.operation) {
|
|
140
|
-
// ── Thin PWM (hal/pwm-pin.ts) — ns-true verbs ──────────────────────────
|
|
141
|
-
// Zephyr 4.4 has pwm_set_dt (period + pulse) and pwm_set_pulse_dt (pulse
|
|
142
|
-
// only) — no period-only setter. The construction period is established
|
|
143
|
-
// once via pwm_set_dt(period, pulse 0 = line idle), then every set is one
|
|
144
|
-
// pwm_set_pulse_dt. No 0–255 scaling anywhere.
|
|
145
|
-
case 'pwm.set_pulse': {
|
|
146
|
-
return {
|
|
147
|
-
code: `{ ${marker}static bool __tc_pwm_p${o.pin}_prd = false; if (!__tc_pwm_p${o.pin}_prd) { (void)pwm_set_dt(&${v}, ${o.periodNs}, 0); __tc_pwm_p${o.pin}_prd = true; } (void)pwm_set_pulse_dt(&${v}, ${o.pulseNs}); }`,
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
case 'pwm.set_duty': {
|
|
151
|
-
// duty is 0.0–1.0; pulse = duty × the construction period.
|
|
152
|
-
return {
|
|
153
|
-
code: `{ ${marker}static bool __tc_pwm_p${o.pin}_prd = false; if (!__tc_pwm_p${o.pin}_prd) { (void)pwm_set_dt(&${v}, ${o.periodNs}, 0); __tc_pwm_p${o.pin}_prd = true; } (void)pwm_set_pulse_dt(&${v}, static_cast<uint32_t>(static_cast<double>(${o.duty}) * static_cast<double>(${o.periodNs}))); }`,
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
case 'pwm.set_period': {
|
|
157
|
-
// No period-only API: pwm_set_dt applies the new period and resets the
|
|
158
|
-
// pulse to idle — follow with setPulse/setDuty to drive the line.
|
|
159
|
-
return { code: `${marker}(void)pwm_set_dt(&${v}, ${o.periodNs}, 0);` };
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// PWM lowering — pwm_dt_spec via pwm-led0 alias
|
|
3
|
+
//
|
|
4
|
+
// The XIAO nRF52840 exposes PWM via the `pwm-led0` devicetree alias (PWM_OUT0
|
|
5
|
+
// on P0.17, inverted). The lowering emits a `pwm_dt_spec` per channel and uses
|
|
6
|
+
// `pwm_set_pulse_dt` / `pwm_set_dt`. Duty is scaled from the Arduino-style
|
|
7
|
+
// 0–255 (or 0–1023) range to nanoseconds against the spec's period.
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
|
|
11
|
+
import type { ZephyrChipDescriptor, ZephyrPwmSpec } from '../chips/types.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The DT alias a PWM spec is addressed by. Board-shipped specs carry their
|
|
15
|
+
* alias in `dtSpec`; synthesized specs (controller + channel) get a
|
|
16
|
+
* `tc-pwm<pin>` alias that the overlay generator creates in
|
|
17
|
+
* <board>.overlay — both sides derive the name from the pin so they agree.
|
|
18
|
+
*/
|
|
19
|
+
export function pwmDtAlias(spec: ZephyrPwmSpec): string {
|
|
20
|
+
return spec.dtSpec ?? `tc-pwm${spec.pin}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The C macro token for a spec's alias. Zephyr's devicetree macros replace
|
|
25
|
+
* dashes in alias names with underscores (`pwm-led0` in DTS is
|
|
26
|
+
* DT_ALIAS(pwm_led0) in C) — the dashed spelling is a subtraction
|
|
27
|
+
* expression and fails to compile (caught by the blackpill E2E west build).
|
|
28
|
+
*/
|
|
29
|
+
export function pwmDtAliasToken(spec: ZephyrPwmSpec): string {
|
|
30
|
+
return pwmDtAlias(spec).replace(/-/g, '_');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Look up a PWM spec by HAL pin number. Matrix pins (ESP32 LEDC) synthesize
|
|
35
|
+
* a spec on the fly: the channel is inert here — the emitted C++ addresses
|
|
36
|
+
* the pin only via its `tc-pwm<pin>` alias (the DT pwms cell carries the
|
|
37
|
+
* real channel, assigned by the overlay generator over the driven pins).
|
|
38
|
+
*/
|
|
39
|
+
export function findPwmSpec(chip: ZephyrChipDescriptor, pin: number): ZephyrPwmSpec | undefined {
|
|
40
|
+
const spec = chip.pwm?.specs.find((s) => s.pin === pin);
|
|
41
|
+
if (spec) return spec;
|
|
42
|
+
const m = chip.pwm?.matrix;
|
|
43
|
+
if (m && m.pins.includes(pin)) return { pin, controller: m.controller, channel: 0 };
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** The C variable name emitted for a PWM channel's spec. */
|
|
48
|
+
function pwmVarName(spec: ZephyrPwmSpec): string {
|
|
49
|
+
return `__tc_pwm_${pwmDtAliasToken(spec)}`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Emit the per-channel PWM spec declarations. One per spec in the chip
|
|
54
|
+
* descriptor that the PROGRAM ACTUALLY DRIVES (`usedPins`) — a spec for an
|
|
55
|
+
* untouched pin is unused code in the emitted TU (and would need a dead DT
|
|
56
|
+
* alias in the overlay). When `usedPins` is omitted (probe paths with no
|
|
57
|
+
* program), every spec is emitted. Called from shimLines when the program
|
|
58
|
+
* uses PWM.
|
|
59
|
+
*/
|
|
60
|
+
export function pwmInitLines(
|
|
61
|
+
chip: ZephyrChipDescriptor,
|
|
62
|
+
usedPins?: ReadonlySet<number>,
|
|
63
|
+
userSpecs?: readonly { pin: number; controller: string; channel: number }[],
|
|
64
|
+
): string[] {
|
|
65
|
+
const lines: string[] = ['// CUTTLEFISH_PWM_BEGIN'];
|
|
66
|
+
for (const spec of chip.pwm?.specs ?? []) {
|
|
67
|
+
if (usedPins && !usedPins.has(spec.pin)) continue;
|
|
68
|
+
lines.push(
|
|
69
|
+
`static const struct pwm_dt_spec ${pwmVarName(spec)} = PWM_DT_SPEC_GET(DT_ALIAS(${pwmDtAliasToken(spec)}));`,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
// Inline-override pins (the escape hatch): the construction opts vouch for
|
|
73
|
+
// controller+channel on a pin the manifest does not map — the alias var
|
|
74
|
+
// the lowered calls reference (the DT node itself comes from the overlay
|
|
75
|
+
// regen's marker merge).
|
|
76
|
+
for (const spec of userSpecs ?? []) {
|
|
77
|
+
if (chip.pwm?.specs.some((s) => s.pin === spec.pin)) continue;
|
|
78
|
+
if (usedPins && !usedPins.has(spec.pin)) continue;
|
|
79
|
+
lines.push(
|
|
80
|
+
`static const struct pwm_dt_spec ${pwmVarName(spec)} = PWM_DT_SPEC_GET(DT_ALIAS(${pwmDtAliasToken(spec)}));`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
// Matrix pins (ESP32 LEDC): one alias per driven pin, ascending — the same
|
|
84
|
+
// order the overlay generator assigns channels in, though the C++ never
|
|
85
|
+
// needs the channel (the DT pwms cell carries it). An omitted usage set
|
|
86
|
+
// (probe paths) emits every matrix pin, mirroring the static behavior.
|
|
87
|
+
const m = chip.pwm?.matrix;
|
|
88
|
+
if (m) {
|
|
89
|
+
const pins = (usedPins ? [...usedPins].filter((p) => m.pins.includes(p)) : [...m.pins])
|
|
90
|
+
.sort((a, b) => a - b);
|
|
91
|
+
for (const pin of pins) {
|
|
92
|
+
const spec: ZephyrPwmSpec = { pin, controller: m.controller, channel: 0 };
|
|
93
|
+
lines.push(
|
|
94
|
+
`static const struct pwm_dt_spec ${pwmVarName(spec)} = PWM_DT_SPEC_GET(DT_ALIAS(${pwmDtAliasToken(spec)}));`,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
lines.push('// CUTTLEFISH_PWM_END');
|
|
99
|
+
return lines;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Resolve a HAL pwm.* op to Zephyr C++.
|
|
104
|
+
* Returns `{ code }` for statement ops, `{ expression }` for value-returning ops.
|
|
105
|
+
*/
|
|
106
|
+
export function lowerPwm(
|
|
107
|
+
op: HALOpIR,
|
|
108
|
+
chip: ZephyrChipDescriptor,
|
|
109
|
+
): { code?: string; expression?: string } {
|
|
110
|
+
const o = op as any;
|
|
111
|
+
// Construction-time controller/channel overrides (hal/pwm-pin.ts opts):
|
|
112
|
+
// the user vouches for the routing. The spec synthesizes from the
|
|
113
|
+
// override (addressing rides the tc-pwm<pin> alias like a matrix pin) and
|
|
114
|
+
// a marker comment carries controller+channel to the overlay regen, which
|
|
115
|
+
// synthesizes the DT node — the transpiler cannot.
|
|
116
|
+
const hasOverride = (typeof o.controllerOverride === 'string' && o.controllerOverride !== '')
|
|
117
|
+
|| (typeof o.channelOverride === 'number' && o.channelOverride >= 0);
|
|
118
|
+
const spec = hasOverride
|
|
119
|
+
? {
|
|
120
|
+
pin: o.pin as number,
|
|
121
|
+
controller: (o.controllerOverride as string | undefined) ?? 'pwm0',
|
|
122
|
+
channel: (o.channelOverride as number | undefined) ?? 0,
|
|
123
|
+
}
|
|
124
|
+
: findPwmSpec(chip, o.pin);
|
|
125
|
+
if (!spec) {
|
|
126
|
+
// Probe / unlisted pin: return a comment so the resolver reports non-
|
|
127
|
+
// undefined (the manifest validator's probe sends pin:0 with no spec).
|
|
128
|
+
// A real program pins the descriptor's pwm.specs entry.
|
|
129
|
+
return { code: `/* pwm on pin ${o.pin}: no PWM spec in chip descriptor */` };
|
|
130
|
+
}
|
|
131
|
+
const v = pwmVarName(spec);
|
|
132
|
+
// The overlay regen (toolchain) parses this into a synthesized pwm-leds
|
|
133
|
+
// spec: pin → controller/channel. Comment placement inside the block
|
|
134
|
+
// braces is legal C.
|
|
135
|
+
const marker = hasOverride
|
|
136
|
+
? `/* cuttlefish-user-facts: pwm pin=${spec.pin} controller=${spec.controller} channel=${spec.channel} */ `
|
|
137
|
+
: '';
|
|
138
|
+
|
|
139
|
+
switch (op.operation) {
|
|
140
|
+
// ── Thin PWM (hal/pwm-pin.ts) — ns-true verbs ──────────────────────────
|
|
141
|
+
// Zephyr 4.4 has pwm_set_dt (period + pulse) and pwm_set_pulse_dt (pulse
|
|
142
|
+
// only) — no period-only setter. The construction period is established
|
|
143
|
+
// once via pwm_set_dt(period, pulse 0 = line idle), then every set is one
|
|
144
|
+
// pwm_set_pulse_dt. No 0–255 scaling anywhere.
|
|
145
|
+
case 'pwm.set_pulse': {
|
|
146
|
+
return {
|
|
147
|
+
code: `{ ${marker}static bool __tc_pwm_p${o.pin}_prd = false; if (!__tc_pwm_p${o.pin}_prd) { (void)pwm_set_dt(&${v}, ${o.periodNs}, 0); __tc_pwm_p${o.pin}_prd = true; } (void)pwm_set_pulse_dt(&${v}, ${o.pulseNs}); }`,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
case 'pwm.set_duty': {
|
|
151
|
+
// duty is 0.0–1.0; pulse = duty × the construction period.
|
|
152
|
+
return {
|
|
153
|
+
code: `{ ${marker}static bool __tc_pwm_p${o.pin}_prd = false; if (!__tc_pwm_p${o.pin}_prd) { (void)pwm_set_dt(&${v}, ${o.periodNs}, 0); __tc_pwm_p${o.pin}_prd = true; } (void)pwm_set_pulse_dt(&${v}, static_cast<uint32_t>(static_cast<double>(${o.duty}) * static_cast<double>(${o.periodNs}))); }`,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
case 'pwm.set_period': {
|
|
157
|
+
// No period-only API: pwm_set_dt applies the new period and resets the
|
|
158
|
+
// pulse to idle — follow with setPulse/setDuty to drive the line.
|
|
159
|
+
return { code: `${marker}(void)pwm_set_dt(&${v}, ${o.periodNs}, 0);` };
|
|
160
|
+
}
|
|
161
|
+
// ── Servo (hal/servo.ts) — calibrated 50 Hz sugar on the same channel ──
|
|
162
|
+
// The commanded value is runtime text; the calibrated range and travel
|
|
163
|
+
// are construction literals carried on the op. Clamping and the angle→
|
|
164
|
+
// pulse mapping run in the emitted C++ — they cannot fold at transpile
|
|
165
|
+
// time. Pulse widths convert µs→ns here (×1000).
|
|
166
|
+
case 'pwm.servo_us': {
|
|
167
|
+
const minNs = Math.round(Number(o.minUs ?? 1000) * 1000);
|
|
168
|
+
const maxNs = Math.round(Number(o.maxUs ?? 2000) * 1000);
|
|
169
|
+
return {
|
|
170
|
+
code: `{ ${marker}static bool __tc_pwm_p${o.pin}_prd = false; if (!__tc_pwm_p${o.pin}_prd) { (void)pwm_set_dt(&${v}, ${o.periodNs}, 0); __tc_pwm_p${o.pin}_prd = true; } uint32_t __tc_sv_p${o.pin} = static_cast<uint32_t>(${o.us}) * 1000U; if (__tc_sv_p${o.pin} < ${minNs}U) { __tc_sv_p${o.pin} = ${minNs}U; } if (__tc_sv_p${o.pin} > ${maxNs}U) { __tc_sv_p${o.pin} = ${maxNs}U; } (void)pwm_set_pulse_dt(&${v}, __tc_sv_p${o.pin}); }`,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
case 'pwm.servo_angle': {
|
|
174
|
+
const minNs = Math.round(Number(o.minUs ?? 1000) * 1000);
|
|
175
|
+
const maxNs = Math.round(Number(o.maxUs ?? 2000) * 1000);
|
|
176
|
+
const maxAngle = Number(o.maxAngle ?? 180) || 180;
|
|
177
|
+
return {
|
|
178
|
+
code: `{ ${marker}static bool __tc_pwm_p${o.pin}_prd = false; if (!__tc_pwm_p${o.pin}_prd) { (void)pwm_set_dt(&${v}, ${o.periodNs}, 0); __tc_pwm_p${o.pin}_prd = true; } double __tc_sv_a${o.pin} = static_cast<double>(${o.angle}); if (__tc_sv_a${o.pin} < 0.0) { __tc_sv_a${o.pin} = 0.0; } if (__tc_sv_a${o.pin} > ${maxAngle}.0) { __tc_sv_a${o.pin} = ${maxAngle}.0; } (void)pwm_set_pulse_dt(&${v}, static_cast<uint32_t>(static_cast<double>(${minNs}) + (__tc_sv_a${o.pin} * ((static_cast<double>(${maxNs}) - static_cast<double>(${minNs})) / static_cast<double>(${maxAngle}.0))))); }`,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
case 'pwm.servo_idle': {
|
|
182
|
+
return {
|
|
183
|
+
code: `{ ${marker}static bool __tc_pwm_p${o.pin}_prd = false; if (!__tc_pwm_p${o.pin}_prd) { (void)pwm_set_dt(&${v}, ${o.periodNs}, 0); __tc_pwm_p${o.pin}_prd = true; } (void)pwm_set_pulse_dt(&${v}, 0U); }`,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
default:
|
|
187
|
+
throw new Error(
|
|
188
|
+
`framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
|
|
189
|
+
`Open an issue or use rawCpp() to emit it manually.`,
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
}
|