@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
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Power lowering — Zephyr power management
|
|
3
|
+
//
|
|
4
|
+
// Zephyr enters low-power states from the idle thread per policy. The app-facing
|
|
5
|
+
// controls are pm_state_force (override policy to force a state on next idle)
|
|
6
|
+
// and k_sleep (block the calling thread, allowing the system to idle into a
|
|
7
|
+
// low-power state). "Deep sleep" maps to k_sleep (the SoC picks its deepest
|
|
8
|
+
// allowed state); "light sleep" best-effort forces SUSPEND_TO_IDLE.
|
|
9
|
+
//
|
|
10
|
+
// deep_sleep_pin configures the wake GPIO as an interrupt source then k_sleeps
|
|
11
|
+
// (the interrupt wakes the SoC). CPU frequency is SoC-specific (nRF52 has a
|
|
12
|
+
// fixed 64 MHz HFXO with no portable dynamic scaling) and stays a documented
|
|
13
|
+
// no-op. deep_sleep_pin targets gpio0 (single-controller nRF52840 case);
|
|
14
|
+
// multi-controller SoC routing is a documented follow-on.
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
/**
|
|
17
|
+
* Resolve a HAL power.* op to Zephyr C++.
|
|
18
|
+
* Returns `{ code }` for statement ops, `{ expression }` for value-returning ops.
|
|
19
|
+
*/
|
|
20
|
+
export function lowerPower(op) {
|
|
21
|
+
const o = op;
|
|
22
|
+
switch (op.operation) {
|
|
23
|
+
case 'power.deep_sleep':
|
|
24
|
+
// Block for the requested duration; the idle thread + PM policy will enter
|
|
25
|
+
// the deepest state the SoC allows during the sleep.
|
|
26
|
+
return { code: `k_sleep(K_MSEC(${o.ms}));` };
|
|
27
|
+
case 'power.light_sleep':
|
|
28
|
+
// Best-effort: force a shallow suspend on the next idle. pm_state_force
|
|
29
|
+
// requires a pm_state_info; SUSPEND_TO_IDLE is a common shallow state.
|
|
30
|
+
return { code: `pm_state_force(0, &(const struct pm_state_info){ .state = PM_STATE_SUSPEND_TO_IDLE, .substate_id = 0 });` };
|
|
31
|
+
case 'power.set_cpu_frequency':
|
|
32
|
+
// nRF52 has a fixed 64 MHz HFXO; no portable dynamic scaling from C++.
|
|
33
|
+
// Documented no-op (best-effort), matching Arduino's approach.
|
|
34
|
+
return { code: `/* power.set_cpu_frequency(${o.mhz}): nRF52 fixed 64 MHz HFXO — no portable scaling */` };
|
|
35
|
+
case 'power.deep_sleep_pin': {
|
|
36
|
+
// Configure the pin as a GPIO wake source (level interrupt), then k_sleep
|
|
37
|
+
// until the interrupt fires. A static gpio_callback + a no-op handler (the
|
|
38
|
+
// wake just needs the interrupt enabled; the handler does nothing). level:
|
|
39
|
+
// 1=high, 0=low. Targets gpio0 (single-controller nRF52840); multi-
|
|
40
|
+
// controller SoC routing is a documented follow-on.
|
|
41
|
+
const pin = o.pin;
|
|
42
|
+
const level = o.level;
|
|
43
|
+
const flags = level ? 'GPIO_INT_LEVEL_HIGH' : 'GPIO_INT_LEVEL_LOW';
|
|
44
|
+
return {
|
|
45
|
+
code: [
|
|
46
|
+
'{',
|
|
47
|
+
` static struct gpio_callback __tc_wake_cb;`,
|
|
48
|
+
` const struct device* __dev = DEVICE_DT_GET(DT_NODELABEL(gpio0));`,
|
|
49
|
+
` gpio_pin_configure(__dev, ${pin}, GPIO_INPUT);`,
|
|
50
|
+
` gpio_init_callback(&__tc_wake_cb, [](const struct device*, struct gpio_callback*, gpio_port_pins_t) { (void)0; }, BIT(${pin}));`,
|
|
51
|
+
` gpio_add_callback(__dev, &__tc_wake_cb);`,
|
|
52
|
+
` gpio_pin_interrupt_configure(__dev, ${pin}, ${flags});`,
|
|
53
|
+
` k_sleep(K_FOREVER);`,
|
|
54
|
+
` gpio_pin_interrupt_configure(__dev, ${pin}, GPIO_INT_DISABLE);`,
|
|
55
|
+
` gpio_remove_callback(__dev, &__tc_wake_cb);`,
|
|
56
|
+
'}',
|
|
57
|
+
].join(' '),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
default:
|
|
61
|
+
throw new Error(`framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
|
|
62
|
+
`Open an issue or use rawCpp() to emit it manually.`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
|
|
2
|
+
/**
|
|
3
|
+
* The preferences runtime shim. All helpers are `static`/`inline` so unused
|
|
4
|
+
* ones don't trip -Wunused-function in the single generated TU (mirrors
|
|
5
|
+
* wifi.ts / http.ts / mqtt.ts).
|
|
6
|
+
*
|
|
7
|
+
* The cache is the source of truth at runtime: settings_load() repopulates it
|
|
8
|
+
* once at boot via h_set, and every put / get / remove reads or writes it
|
|
9
|
+
* directly. settings_save_one and settings_delete keep flash in sync on
|
|
10
|
+
* writes; reads never touch flash. This is the same split the ESP32
|
|
11
|
+
* Preferences library uses (NVS is the cache, just in flash-backed RAM pages).
|
|
12
|
+
*/
|
|
13
|
+
export declare function preferencesInitLines(): string[];
|
|
14
|
+
/**
|
|
15
|
+
* Resolve a HAL preferences.* op to Zephyr C++ via the __tc_prefs_* shim.
|
|
16
|
+
* Returns `{ code }` for statement ops, `{ expression }` for value-returning
|
|
17
|
+
* ops (the get_* family). The `default` arm throws the standard unsupported-op
|
|
18
|
+
* error so the manifest validator's per-op probe stays honest.
|
|
19
|
+
*/
|
|
20
|
+
export declare function lowerPreferences(op: HALOpIR): {
|
|
21
|
+
code?: string;
|
|
22
|
+
expression?: string;
|
|
23
|
+
};
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Preferences lowering — Zephyr settings subsystem (ZMS backend)
|
|
3
|
+
//
|
|
4
|
+
// The HAL Preferences surface is the ESP32-NVS session model: begin(ns) →
|
|
5
|
+
// typed put_*/get_* → end(). Zephyr's settings subsystem has no session — it
|
|
6
|
+
// is a flat dotted key-space plus a one-time settings_load() at boot that
|
|
7
|
+
// fills an in-RAM cache via the h_set callback. We model the session on top:
|
|
8
|
+
//
|
|
9
|
+
// begin("app") → records the active subtree prefix "tc/app/"
|
|
10
|
+
// putInt("k",v) → writes the cache + settings_save_one("tc/app/k", bytes, 4)
|
|
11
|
+
// getInt("k",d) → cache lookup, returns d if absent
|
|
12
|
+
// remove("k") → settings_delete("tc/app/k") + drop cache slot
|
|
13
|
+
// clear() → settings_delete each known tc/<ns>/* key + clear cache
|
|
14
|
+
// end() → clears the prefix (NOT the cache — end() is not a wipe on
|
|
15
|
+
// ESP32 either; it only closes the NVS handle)
|
|
16
|
+
//
|
|
17
|
+
// begin/end are intentionally lightweight on Zephyr. They stay in the IR so a
|
|
18
|
+
// future backend that genuinely needs a session/namespace (NVS, a crypto-
|
|
19
|
+
// sealed store) has the hook without lowering every read through setup. On
|
|
20
|
+
// Zephyr they only carry the key prefix.
|
|
21
|
+
//
|
|
22
|
+
// The settings backend is ZMS (Zephyr Memory Storage), selected by
|
|
23
|
+
// CONFIG_SETTINGS + CONFIG_ZMS + CONFIG_SETTINGS_ZMS. The backend locates the
|
|
24
|
+
// storage partition automatically: the fixed-partition labeled
|
|
25
|
+
// `storage_partition`, or whatever /chosen `zephyr,settings-partition` points
|
|
26
|
+
// at (see dt-config/overlay.ts). No partition macro is referenced here.
|
|
27
|
+
//
|
|
28
|
+
// EMIT BOUNDARY: emitted bytes land in user firmware. Covered by the TypeCAD
|
|
29
|
+
// Runtime Exception (RUNTIME_EXCEPTION.md at the repo root).
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
/** Render a HAL field: pass through (already rendered by the resolver). */
|
|
32
|
+
function s(v) {
|
|
33
|
+
return String(v);
|
|
34
|
+
}
|
|
35
|
+
// Type tags for the cache slot payload. Scoped enum (AUTOSAR: enum class).
|
|
36
|
+
// The on-metal order matters: these are stored in the `type` byte and matched
|
|
37
|
+
// at h_set time, so do not renumber.
|
|
38
|
+
const TAG = {
|
|
39
|
+
INT: 1,
|
|
40
|
+
UINT: 2,
|
|
41
|
+
BOOL: 3,
|
|
42
|
+
FLOAT: 4,
|
|
43
|
+
STRING: 5,
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* The preferences runtime shim. All helpers are `static`/`inline` so unused
|
|
47
|
+
* ones don't trip -Wunused-function in the single generated TU (mirrors
|
|
48
|
+
* wifi.ts / http.ts / mqtt.ts).
|
|
49
|
+
*
|
|
50
|
+
* The cache is the source of truth at runtime: settings_load() repopulates it
|
|
51
|
+
* once at boot via h_set, and every put / get / remove reads or writes it
|
|
52
|
+
* directly. settings_save_one and settings_delete keep flash in sync on
|
|
53
|
+
* writes; reads never touch flash. This is the same split the ESP32
|
|
54
|
+
* Preferences library uses (NVS is the cache, just in flash-backed RAM pages).
|
|
55
|
+
*/
|
|
56
|
+
export function preferencesInitLines() {
|
|
57
|
+
return [
|
|
58
|
+
`// CUTTLEFISH_PREFS_BEGIN`,
|
|
59
|
+
`#define __TC_PREFS_SLOT_COUNT 32`,
|
|
60
|
+
`#define __TC_PREFS_KEY_LEN 48`,
|
|
61
|
+
`#define __TC_PREFS_VAL_LEN 32`,
|
|
62
|
+
`#define __TC_PREFS_STR_LEN 31`,
|
|
63
|
+
``,
|
|
64
|
+
`// One cache slot. The full settings key (tc/<ns>/<key>) lives in key[] so`,
|
|
65
|
+
`// remove()/clear() can reconstruct the exact name to settings_delete. The`,
|
|
66
|
+
`// value is stored in its native wire form (little-endian for the numeric`,
|
|
67
|
+
`// types) so h_set can memcpy straight from the settings read callback.`,
|
|
68
|
+
`struct __tc_prefs_slot final {`,
|
|
69
|
+
` char key[__TC_PREFS_KEY_LEN];`,
|
|
70
|
+
` uint8_t type;`,
|
|
71
|
+
` uint8_t val[__TC_PREFS_VAL_LEN];`,
|
|
72
|
+
` uint8_t len;`,
|
|
73
|
+
` bool used;`,
|
|
74
|
+
`};`,
|
|
75
|
+
``,
|
|
76
|
+
`// Active namespace prefix ("tc/<ns>/"). begin() overwrites it; end() clears.`,
|
|
77
|
+
`// prefs_ns is deliberately sized to hold "tc/" + a 16-char ns + "/" + NUL.`,
|
|
78
|
+
`static struct {`,
|
|
79
|
+
` char ns[24];`,
|
|
80
|
+
` bool loaded;`,
|
|
81
|
+
` __tc_prefs_slot slots[__TC_PREFS_SLOT_COUNT];`,
|
|
82
|
+
`} __tc_prefs;`,
|
|
83
|
+
``,
|
|
84
|
+
`// ── settings_load() handler (the only mandatory callback) ───────────────`,
|
|
85
|
+
`// settings_load() calls h_set once per stored key it finds, handing us the`,
|
|
86
|
+
`// value via read_cb. We copy it into a cache slot keyed by the full dotted`,
|
|
87
|
+
`// path. h_get/h_commit are unused (the cache is the post-load source of`,
|
|
88
|
+
`// truth) so they return -ENOENT / 0 to satisfy the handler contract.`,
|
|
89
|
+
`static int __tc_prefs_h_set(const char* name, size_t len, settings_read_cb read_cb, void* cb_arg) {`,
|
|
90
|
+
` (void)len;`,
|
|
91
|
+
` for (int i = 0; i < __TC_PREFS_SLOT_COUNT; i++) {`,
|
|
92
|
+
` if (!__tc_prefs.slots[i].used) {`,
|
|
93
|
+
` // Stash the type byte (first val byte) by reading into the slot.`,
|
|
94
|
+
` ssize_t got = read_cb(cb_arg, __tc_prefs.slots[i].val, __TC_PREFS_VAL_LEN);`,
|
|
95
|
+
` if (got <= 0) { return static_cast<int>(got); }`,
|
|
96
|
+
` __tc_prefs.slots[i].len = static_cast<uint8_t>(got);`,
|
|
97
|
+
` // Type is encoded as the first byte of the stored value (see put_*).`,
|
|
98
|
+
` __tc_prefs.slots[i].type = __tc_prefs.slots[i].val[0];`,
|
|
99
|
+
` (void)snprintf(__tc_prefs.slots[i].key, __TC_PREFS_KEY_LEN, "%s", name);`,
|
|
100
|
+
` __tc_prefs.slots[i].used = true;`,
|
|
101
|
+
` return 0;`,
|
|
102
|
+
` }`,
|
|
103
|
+
` }`,
|
|
104
|
+
` return 0; // cache full — silently drop (matches ESP32 NVS-full behavior)`,
|
|
105
|
+
`}`,
|
|
106
|
+
`static int __tc_prefs_h_get(const char* key, char* val, int val_len_max) {`,
|
|
107
|
+
` (void)key; (void)val; (void)val_len_max;`,
|
|
108
|
+
` return -ENOENT;`,
|
|
109
|
+
`}`,
|
|
110
|
+
`static int __tc_prefs_h_commit(void) { return 0; }`,
|
|
111
|
+
`SETTINGS_STATIC_HANDLER_DEFINE(tc_prefs, "tc", __tc_prefs_h_get, __tc_prefs_h_set,`,
|
|
112
|
+
` __tc_prefs_h_commit, NULL);`,
|
|
113
|
+
``,
|
|
114
|
+
`// One-time subsystem init + load. Idempotent — the HAL may call begin()`,
|
|
115
|
+
`// repeatedly. settings_subsys_init() is itself idempotent in Zephyr, but the`,
|
|
116
|
+
`// loaded flag short-circuits the (relatively expensive) settings_load() walk.`,
|
|
117
|
+
`static inline void __tc_prefs_ensure_loaded(void) {`,
|
|
118
|
+
` if (!__tc_prefs.loaded) {`,
|
|
119
|
+
` (void)settings_subsys_init();`,
|
|
120
|
+
` (void)settings_load();`,
|
|
121
|
+
` __tc_prefs.loaded = true;`,
|
|
122
|
+
` }`,
|
|
123
|
+
`}`,
|
|
124
|
+
``,
|
|
125
|
+
`// Build the full settings name "tc/<ns>/<key>" into out. ns is the active`,
|
|
126
|
+
`// prefix set by begin(); the slot's key[] stores this full path so remove()`,
|
|
127
|
+
`// and clear() can settings_delete the exact stored name.`,
|
|
128
|
+
`static void __tc_prefs_full(char* out, size_t outsz, const char* key) {`,
|
|
129
|
+
` (void)snprintf(out, outsz, "%s%s", __tc_prefs.ns, key);`,
|
|
130
|
+
`}`,
|
|
131
|
+
``,
|
|
132
|
+
`// Slot lookup by full settings name. Returns nullptr when absent (the get_*`,
|
|
133
|
+
`// helpers then fall back to the caller's default value, matching the ESP32`,
|
|
134
|
+
`// getInt(key, default) contract).`,
|
|
135
|
+
`static __tc_prefs_slot* __tc_prefs_find(const char* full) {`,
|
|
136
|
+
` for (int i = 0; i < __TC_PREFS_SLOT_COUNT; i++) {`,
|
|
137
|
+
` if (__tc_prefs.slots[i].used &&`,
|
|
138
|
+
` strncmp(__tc_prefs.slots[i].key, full, __TC_PREFS_KEY_LEN) == 0) {`,
|
|
139
|
+
` return &__tc_prefs.slots[i];`,
|
|
140
|
+
` }`,
|
|
141
|
+
` }`,
|
|
142
|
+
` return nullptr;`,
|
|
143
|
+
`}`,
|
|
144
|
+
``,
|
|
145
|
+
`// Allocate (or reuse) a slot for a full name. Eviction is last-write on a`,
|
|
146
|
+
`// free slot; a full cache returns nullptr and the put silently no-ops (the`,
|
|
147
|
+
`// in-RAM value is still returned to the program on get, just not persisted).`,
|
|
148
|
+
`static __tc_prefs_slot* __tc_prefs_slot_for(const char* full) {`,
|
|
149
|
+
` __tc_prefs_slot* existing = __tc_prefs_find(full);`,
|
|
150
|
+
` if (existing != nullptr) { return existing; }`,
|
|
151
|
+
` for (int i = 0; i < __TC_PREFS_SLOT_COUNT; i++) {`,
|
|
152
|
+
` if (!__tc_prefs.slots[i].used) { return &__tc_prefs.slots[i]; }`,
|
|
153
|
+
` }`,
|
|
154
|
+
` return nullptr;`,
|
|
155
|
+
`}`,
|
|
156
|
+
``,
|
|
157
|
+
`// Generic typed put: stamp the type byte, stash the value, persist via`,
|
|
158
|
+
`// settings_save_one, and mirror into the cache. The type byte rides along`,
|
|
159
|
+
`// as val[0] so h_set can recover it on the next boot without a separate`,
|
|
160
|
+
`// name→type map.`,
|
|
161
|
+
`template <typename T>`,
|
|
162
|
+
`static inline void __tc_prefs_put(const char* key, uint8_t tag, const T& value) {`,
|
|
163
|
+
` char full[__TC_PREFS_KEY_LEN];`,
|
|
164
|
+
` __tc_prefs_full(full, sizeof(full), key);`,
|
|
165
|
+
` uint8_t buf[1U + sizeof(T)];`,
|
|
166
|
+
` buf[0] = tag;`,
|
|
167
|
+
` (void)memcpy(&buf[1], &value, sizeof(T));`,
|
|
168
|
+
` (void)settings_save_one(full, buf, sizeof(buf));`,
|
|
169
|
+
` __tc_prefs_slot* slot = __tc_prefs_slot_for(full);`,
|
|
170
|
+
` if (slot != nullptr) {`,
|
|
171
|
+
` (void)snprintf(slot->key, __TC_PREFS_KEY_LEN, "%s", full);`,
|
|
172
|
+
` slot->type = tag;`,
|
|
173
|
+
` slot->len = static_cast<uint8_t>(sizeof(buf));`,
|
|
174
|
+
` (void)memcpy(slot->val, buf, sizeof(buf));`,
|
|
175
|
+
` slot->used = true;`,
|
|
176
|
+
` }`,
|
|
177
|
+
`}`,
|
|
178
|
+
``,
|
|
179
|
+
`template <typename T>`,
|
|
180
|
+
`static inline T __tc_prefs_get(const char* key, uint8_t tag, T def) {`,
|
|
181
|
+
` char full[__TC_PREFS_KEY_LEN];`,
|
|
182
|
+
` __tc_prefs_full(full, sizeof(full), key);`,
|
|
183
|
+
` __tc_prefs_slot* slot = __tc_prefs_find(full);`,
|
|
184
|
+
` if (slot == nullptr || slot->type != tag || slot->len < (1U + sizeof(T))) {`,
|
|
185
|
+
` return def;`,
|
|
186
|
+
` }`,
|
|
187
|
+
` T out;`,
|
|
188
|
+
` (void)memcpy(&out, &slot->val[1], sizeof(T));`,
|
|
189
|
+
` return out;`,
|
|
190
|
+
`}`,
|
|
191
|
+
``,
|
|
192
|
+
`// ── Lifecycle (session model on top of the flat key-space) ──────────────`,
|
|
193
|
+
`// begin records "tc/<ns>/" as the prefix every subsequent key is built under.`,
|
|
194
|
+
`// end clears the prefix (NOT the cache). Both are cheap on Zephyr: the real`,
|
|
195
|
+
`// work is in settings_load() at boot. They exist for portability with the`,
|
|
196
|
+
`// ESP32 NVS session model and so a future session-needing backend has a hook.`,
|
|
197
|
+
`static inline void __tc_prefs_begin(const char* ns, bool readOnly) {`,
|
|
198
|
+
` (void)readOnly;`,
|
|
199
|
+
` __tc_prefs_ensure_loaded();`,
|
|
200
|
+
` (void)snprintf(__tc_prefs.ns, sizeof(__tc_prefs.ns), "tc/%s/", ns);`,
|
|
201
|
+
`}`,
|
|
202
|
+
`static inline void __tc_prefs_end(void) {`,
|
|
203
|
+
` __tc_prefs.ns[0] = '\\0';`,
|
|
204
|
+
`}`,
|
|
205
|
+
``,
|
|
206
|
+
`// remove: drop one key from flash and the cache. Falls back to no-op if the`,
|
|
207
|
+
`// slot isn't cached (settings_delete on a missing key returns -ENOENT, which`,
|
|
208
|
+
`// we swallow).`,
|
|
209
|
+
`static inline void __tc_prefs_remove(const char* key) {`,
|
|
210
|
+
` char full[__TC_PREFS_KEY_LEN];`,
|
|
211
|
+
` __tc_prefs_full(full, sizeof(full), key);`,
|
|
212
|
+
` (void)settings_delete(full);`,
|
|
213
|
+
` __tc_prefs_slot* slot = __tc_prefs_find(full);`,
|
|
214
|
+
` if (slot != nullptr) { slot->used = false; }`,
|
|
215
|
+
`}`,
|
|
216
|
+
``,
|
|
217
|
+
`// clear: drop every key under the active namespace. We walk the cache (the`,
|
|
218
|
+
`// authoritative list of keys we have written) and settings_delete each whose`,
|
|
219
|
+
`// full name starts with the active ns prefix, then mark the slot free. Only`,
|
|
220
|
+
`// the active namespace is cleared — keys under other namespaces survive.`,
|
|
221
|
+
`static inline void __tc_prefs_clear(void) {`,
|
|
222
|
+
` const size_t plen = strlen(__tc_prefs.ns);`,
|
|
223
|
+
` for (int i = 0; i < __TC_PREFS_SLOT_COUNT; i++) {`,
|
|
224
|
+
` if (__tc_prefs.slots[i].used &&`,
|
|
225
|
+
` strncmp(__tc_prefs.slots[i].key, __tc_prefs.ns, plen) == 0) {`,
|
|
226
|
+
` (void)settings_delete(__tc_prefs.slots[i].key);`,
|
|
227
|
+
` __tc_prefs.slots[i].used = false;`,
|
|
228
|
+
` }`,
|
|
229
|
+
` }`,
|
|
230
|
+
`}`,
|
|
231
|
+
``,
|
|
232
|
+
`// ── Typed accessors ─────────────────────────────────────────────────────`,
|
|
233
|
+
`// Each put_* stamps its tag byte and persists; each get_* reads the cache and`,
|
|
234
|
+
`// returns the default on miss/type-mismatch (mirrors ESP32 Preferences).`,
|
|
235
|
+
`static inline void __tc_prefs_put_int(const char* key, int32_t v) {`,
|
|
236
|
+
` __tc_prefs_put<int32_t>(key, ${TAG.INT}, v);`,
|
|
237
|
+
`}`,
|
|
238
|
+
`static inline int32_t __tc_prefs_get_int(const char* key, int32_t def) {`,
|
|
239
|
+
` return __tc_prefs_get<int32_t>(key, ${TAG.INT}, def);`,
|
|
240
|
+
`}`,
|
|
241
|
+
`static inline void __tc_prefs_put_uint(const char* key, uint32_t v) {`,
|
|
242
|
+
` __tc_prefs_put<uint32_t>(key, ${TAG.UINT}, v);`,
|
|
243
|
+
`}`,
|
|
244
|
+
`static inline uint32_t __tc_prefs_get_uint(const char* key, uint32_t def) {`,
|
|
245
|
+
` return __tc_prefs_get<uint32_t>(key, ${TAG.UINT}, def);`,
|
|
246
|
+
`}`,
|
|
247
|
+
`static inline void __tc_prefs_put_bool(const char* key, bool v) {`,
|
|
248
|
+
` uint8_t b = v ? 1U : 0U;`,
|
|
249
|
+
` __tc_prefs_put<uint8_t>(key, ${TAG.BOOL}, b);`,
|
|
250
|
+
`}`,
|
|
251
|
+
`static inline bool __tc_prefs_get_bool(const char* key, bool def) {`,
|
|
252
|
+
` uint8_t b = __tc_prefs_get<uint8_t>(key, ${TAG.BOOL}, def ? 1U : 0U);`,
|
|
253
|
+
` return b != 0U;`,
|
|
254
|
+
`}`,
|
|
255
|
+
`static inline void __tc_prefs_put_float(const char* key, float v) {`,
|
|
256
|
+
` __tc_prefs_put<float>(key, ${TAG.FLOAT}, v);`,
|
|
257
|
+
`}`,
|
|
258
|
+
`static inline float __tc_prefs_get_float(const char* key, float def) {`,
|
|
259
|
+
` return __tc_prefs_get<float>(key, ${TAG.FLOAT}, def);`,
|
|
260
|
+
`}`,
|
|
261
|
+
``,
|
|
262
|
+
`// Strings are length-prefixed into the value buffer (capped at STR_LEN so the`,
|
|
263
|
+
`// whole slot still fits in __TC_PREFS_VAL_LEN). get copies out with a NUL and`,
|
|
264
|
+
`// returns def when the slot is absent or the wrong type.`,
|
|
265
|
+
`static inline void __tc_prefs_put_string(const char* key, const char* v) {`,
|
|
266
|
+
` char full[__TC_PREFS_KEY_LEN];`,
|
|
267
|
+
` __tc_prefs_full(full, sizeof(full), key);`,
|
|
268
|
+
` uint8_t buf[1U + __TC_PREFS_STR_LEN];`,
|
|
269
|
+
` buf[0] = ${TAG.STRING};`,
|
|
270
|
+
` size_t n = strlen(v);`,
|
|
271
|
+
` if (n > __TC_PREFS_STR_LEN) { n = __TC_PREFS_STR_LEN; }`,
|
|
272
|
+
` (void)memcpy(&buf[1], v, n);`,
|
|
273
|
+
` (void)settings_save_one(full, buf, 1U + n);`,
|
|
274
|
+
` __tc_prefs_slot* slot = __tc_prefs_slot_for(full);`,
|
|
275
|
+
` if (slot != nullptr) {`,
|
|
276
|
+
` (void)snprintf(slot->key, __TC_PREFS_KEY_LEN, "%s", full);`,
|
|
277
|
+
` slot->type = ${TAG.STRING};`,
|
|
278
|
+
` slot->len = static_cast<uint8_t>(1U + static_cast<uint8_t>(n));`,
|
|
279
|
+
` (void)memcpy(slot->val, buf, 1U + n);`,
|
|
280
|
+
` slot->used = true;`,
|
|
281
|
+
` }`,
|
|
282
|
+
`}`,
|
|
283
|
+
`static inline const char* __tc_prefs_get_string(const char* key, const char* def) {`,
|
|
284
|
+
` static char out[__TC_PREFS_STR_LEN + 1U];`,
|
|
285
|
+
` char full[__TC_PREFS_KEY_LEN];`,
|
|
286
|
+
` __tc_prefs_full(full, sizeof(full), key);`,
|
|
287
|
+
` __tc_prefs_slot* slot = __tc_prefs_find(full);`,
|
|
288
|
+
` if (slot == nullptr || slot->type != ${TAG.STRING} || slot->len < 1U) {`,
|
|
289
|
+
` return def;`,
|
|
290
|
+
` }`,
|
|
291
|
+
` size_t n = slot->len - 1U;`,
|
|
292
|
+
` if (n > __TC_PREFS_STR_LEN) { n = __TC_PREFS_STR_LEN; }`,
|
|
293
|
+
` (void)memcpy(out, &slot->val[1], n);`,
|
|
294
|
+
` out[n] = '\\0';`,
|
|
295
|
+
` return out;`,
|
|
296
|
+
`}`,
|
|
297
|
+
`// CUTTLEFISH_PREFS_END`,
|
|
298
|
+
``,
|
|
299
|
+
];
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Resolve a HAL preferences.* op to Zephyr C++ via the __tc_prefs_* shim.
|
|
303
|
+
* Returns `{ code }` for statement ops, `{ expression }` for value-returning
|
|
304
|
+
* ops (the get_* family). The `default` arm throws the standard unsupported-op
|
|
305
|
+
* error so the manifest validator's per-op probe stays honest.
|
|
306
|
+
*/
|
|
307
|
+
export function lowerPreferences(op) {
|
|
308
|
+
const o = op;
|
|
309
|
+
switch (op.operation) {
|
|
310
|
+
case 'preferences.begin':
|
|
311
|
+
// readOnly is honored as a no-op: Zephyr settings has no read-only mount,
|
|
312
|
+
// and silently ignoring it matches the ESP32 Preferences behavior when
|
|
313
|
+
// an RO handle is written (the put returns an error the app doesn't see).
|
|
314
|
+
return { code: `__tc_prefs_begin(${s(o.namespace)}, ${o.readOnly ? 'true' : 'false'});` };
|
|
315
|
+
case 'preferences.end':
|
|
316
|
+
return { code: `__tc_prefs_end();` };
|
|
317
|
+
case 'preferences.clear':
|
|
318
|
+
return { code: `__tc_prefs_clear();` };
|
|
319
|
+
case 'preferences.remove':
|
|
320
|
+
return { code: `__tc_prefs_remove(${s(o.key)});` };
|
|
321
|
+
case 'preferences.put_int':
|
|
322
|
+
return { code: `__tc_prefs_put_int(${s(o.key)}, ${s(o.value)});` };
|
|
323
|
+
case 'preferences.get_int':
|
|
324
|
+
return { expression: `__tc_prefs_get_int(${s(o.key)}, ${s(o.defaultValue)})` };
|
|
325
|
+
case 'preferences.put_uint':
|
|
326
|
+
return { code: `__tc_prefs_put_uint(${s(o.key)}, ${s(o.value)});` };
|
|
327
|
+
case 'preferences.get_uint':
|
|
328
|
+
return { expression: `__tc_prefs_get_uint(${s(o.key)}, ${s(o.defaultValue)})` };
|
|
329
|
+
case 'preferences.put_bool':
|
|
330
|
+
return { code: `__tc_prefs_put_bool(${s(o.key)}, ${o.value ? 'true' : 'false'});` };
|
|
331
|
+
case 'preferences.get_bool':
|
|
332
|
+
return { expression: `__tc_prefs_get_bool(${s(o.key)}, ${o.defaultValue ? 'true' : 'false'})` };
|
|
333
|
+
case 'preferences.put_float':
|
|
334
|
+
return { code: `__tc_prefs_put_float(${s(o.key)}, ${s(o.value)});` };
|
|
335
|
+
case 'preferences.get_float':
|
|
336
|
+
return { expression: `__tc_prefs_get_float(${s(o.key)}, ${s(o.defaultValue)})` };
|
|
337
|
+
case 'preferences.put_string':
|
|
338
|
+
return { code: `__tc_prefs_put_string(${s(o.key)}, ${s(o.value)});` };
|
|
339
|
+
case 'preferences.get_string':
|
|
340
|
+
return { expression: `__tc_prefs_get_string(${s(o.key)}, ${s(o.defaultValue)})` };
|
|
341
|
+
default:
|
|
342
|
+
throw new Error(`framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
|
|
343
|
+
`Open an issue or use rawCpp() to emit it manually.`);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
|
|
2
|
+
import type { ZephyrChipDescriptor } from '../chips/types.js';
|
|
3
|
+
/** Resolve a HAL pulse.* or shift.* op to Zephyr C++ (bit-bang). */
|
|
4
|
+
export declare function lowerPulseOrShift(op: HALOpIR, chip: ZephyrChipDescriptor): {
|
|
5
|
+
code?: string;
|
|
6
|
+
expression?: string;
|
|
7
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Pulse + shift lowering — bit-bang via GPIO + timing
|
|
3
|
+
//
|
|
4
|
+
// pulse.in measures the duration of a pulse (HIGH or LOW) on a GPIO pin by
|
|
5
|
+
// edge-polling with k_uptime_get. shift.out/in bit-bang a byte through a data
|
|
6
|
+
// + clock pin pair using gpio_pin_set + k_busy_wait. These are software
|
|
7
|
+
// implementations (no hardware pulse capture on nRF for the HAL surface); they
|
|
8
|
+
// are correct but not high-precision.
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
import { controllerNodelabelForPin } from '../chips/controllers.js';
|
|
11
|
+
/** `DEVICE_DT_GET(DT_NODELABEL(<owning-controller>))` for a HAL pin. */
|
|
12
|
+
function devForPin(chip, pin) {
|
|
13
|
+
return `DEVICE_DT_GET(DT_NODELABEL(${controllerNodelabelForPin(chip, pin)}))`;
|
|
14
|
+
}
|
|
15
|
+
/** Resolve a HAL pulse.* or shift.* op to Zephyr C++ (bit-bang). */
|
|
16
|
+
export function lowerPulseOrShift(op, chip) {
|
|
17
|
+
const o = op;
|
|
18
|
+
switch (op.operation) {
|
|
19
|
+
case 'pulse.in': {
|
|
20
|
+
// Wait for the pin to reach `value`, then measure until it changes.
|
|
21
|
+
// Edge-poll with k_uptime_get() (ms granularity — coarse but correct,
|
|
22
|
+
// and 64-bit so it cannot wrap mid-measurement over ~49 days). The wait
|
|
23
|
+
// for the start edge is bounded by the timeout; the measurement of the
|
|
24
|
+
// pulse itself is intentionally unbounded (that IS the pulse length).
|
|
25
|
+
// Returns -1 (0) if the start edge never arrives within the timeout.
|
|
26
|
+
const pin = o.pin;
|
|
27
|
+
const want = o.value;
|
|
28
|
+
const dev = devForPin(chip, pin);
|
|
29
|
+
const timeout = o.timeout ?? 1_000_000; // default 1s in us
|
|
30
|
+
return {
|
|
31
|
+
expression: `({ int64_t __max = static_cast<int64_t>(${timeout} / 1000); int64_t __t0 = k_uptime_get(); bool __ok = true; while (gpio_pin_get_raw(${dev}, ${pin}) != ${want}) { if ((k_uptime_get() - __t0) > __max) { __ok = false; break; } } int32_t __ret = 0; if (__ok) { int64_t __start = k_uptime_get(); while (gpio_pin_get_raw(${dev}, ${pin}) == ${want}) { } __ret = static_cast<int32_t>((k_uptime_get() - __start) * 1000); } __ret; })`,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
case 'pulse.in_long': {
|
|
35
|
+
// Same as pulse.in but for long pulses. Previously this spun forever
|
|
36
|
+
// with NO timeout (the comment claimed "no overflow concern" but the
|
|
37
|
+
// real risk was hanging the thread on a stuck pin). Apply the same
|
|
38
|
+
// timeout-bounded start-edge wait as pulse.in (bug Q5).
|
|
39
|
+
const pin = o.pin;
|
|
40
|
+
const want = o.value;
|
|
41
|
+
const dev = devForPin(chip, pin);
|
|
42
|
+
const timeout = o.timeout ?? 3_000_000; // default 3s in us (long pulses)
|
|
43
|
+
return {
|
|
44
|
+
expression: `({ int64_t __max = static_cast<int64_t>(${timeout} / 1000); int64_t __t0 = k_uptime_get(); bool __ok = true; while (gpio_pin_get_raw(${dev}, ${pin}) != ${want}) { if ((k_uptime_get() - __t0) > __max) { __ok = false; break; } } int32_t __ret = 0; if (__ok) { int64_t __start = k_uptime_get(); while (gpio_pin_get_raw(${dev}, ${pin}) == ${want}) { } __ret = static_cast<int32_t>((k_uptime_get() - __start) * 1000); } __ret; })`,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
case 'shift.out': {
|
|
48
|
+
// Bit-bang `value` (MSB or LSB first) via dataPin + clockPin. The two
|
|
49
|
+
// pins may sit on different GPIO controllers on multi-controller SoCs
|
|
50
|
+
// (ESP32-S3), so each is resolved to its own owning controller.
|
|
51
|
+
const dataPin = o.dataPin;
|
|
52
|
+
const clockPin = o.clockPin;
|
|
53
|
+
const dataDev = devForPin(chip, dataPin);
|
|
54
|
+
const clockDev = devForPin(chip, clockPin);
|
|
55
|
+
const msbFirst = o.bitOrder === 'msb';
|
|
56
|
+
const init = msbFirst ? '7' : '0';
|
|
57
|
+
const test = msbFirst ? '(__i >= 0)' : '(__i < 8)';
|
|
58
|
+
const step = msbFirst ? '__i--' : '__i++';
|
|
59
|
+
return {
|
|
60
|
+
code: `for (int __i = ${init}; ${test}; ${step}) { gpio_pin_set_raw(${dataDev}, ${dataPin}, (${o.value} >> __i) & 1); gpio_pin_set_raw(${clockDev}, ${clockPin}, 1); k_busy_wait(1); gpio_pin_set_raw(${clockDev}, ${clockPin}, 0); }`,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
case 'shift.in': {
|
|
64
|
+
// Read 8 bits (MSB or LSB first) from dataPin, clocking on clockPin.
|
|
65
|
+
const dataPin = o.dataPin;
|
|
66
|
+
const clockPin = o.clockPin;
|
|
67
|
+
const dataDev = devForPin(chip, dataPin);
|
|
68
|
+
const clockDev = devForPin(chip, clockPin);
|
|
69
|
+
const msbFirst = o.bitOrder === 'msb';
|
|
70
|
+
const init = msbFirst ? '7' : '0';
|
|
71
|
+
const test = msbFirst ? '(__i >= 0)' : '(__i < 8)';
|
|
72
|
+
const step = msbFirst ? '__i--' : '__i++';
|
|
73
|
+
const accum = msbFirst ? '__v = (__v << 1)' : '__v |= (bit << __i)';
|
|
74
|
+
return {
|
|
75
|
+
expression: `({ uint8_t __v = 0; for (int __i = ${init}; ${test}; ${step}) { gpio_pin_set_raw(${clockDev}, ${clockPin}, 1); k_busy_wait(1); int bit = gpio_pin_get_raw(${dataDev}, ${dataPin}); gpio_pin_set_raw(${clockDev}, ${clockPin}, 0); ${accum}; } __v; })`,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
default:
|
|
79
|
+
throw new Error(`framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
|
|
80
|
+
`Open an issue or use rawCpp() to emit it manually.`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
|
|
2
|
+
import type { ZephyrChipDescriptor } from '../chips/types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Emit the per-channel PWM spec declarations. One per spec in the chip
|
|
5
|
+
* descriptor. Called from shimLines when the program uses PWM.
|
|
6
|
+
*/
|
|
7
|
+
export declare function pwmInitLines(chip: ZephyrChipDescriptor): string[];
|
|
8
|
+
/**
|
|
9
|
+
* Resolve a HAL pwm.* op to Zephyr C++.
|
|
10
|
+
* Returns `{ code }` for statement ops, `{ expression }` for value-returning ops.
|
|
11
|
+
*/
|
|
12
|
+
export declare function lowerPwm(op: HALOpIR, chip: ZephyrChipDescriptor): {
|
|
13
|
+
code?: string;
|
|
14
|
+
expression?: string;
|
|
15
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
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
|
+
/** Look up a PWM spec by HAL pin number. */
|
|
10
|
+
function findPwmSpec(chip, pin) {
|
|
11
|
+
return chip.pwm?.specs.find((s) => s.pin === pin);
|
|
12
|
+
}
|
|
13
|
+
/** The C variable name emitted for a PWM channel's spec. */
|
|
14
|
+
function pwmVarName(spec) {
|
|
15
|
+
return `__tc_pwm_${spec.dtSpec.replace(/-/g, '_')}`;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Emit the per-channel PWM spec declarations. One per spec in the chip
|
|
19
|
+
* descriptor. Called from shimLines when the program uses PWM.
|
|
20
|
+
*/
|
|
21
|
+
export function pwmInitLines(chip) {
|
|
22
|
+
const lines = ['// CUTTLEFISH_PWM_BEGIN'];
|
|
23
|
+
for (const spec of chip.pwm?.specs ?? []) {
|
|
24
|
+
lines.push(`static const struct pwm_dt_spec ${pwmVarName(spec)} = PWM_DT_SPEC_GET(DT_ALIAS(${spec.dtSpec}));`);
|
|
25
|
+
}
|
|
26
|
+
lines.push('// CUTTLEFISH_PWM_END');
|
|
27
|
+
return lines;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Resolve a HAL pwm.* op to Zephyr C++.
|
|
31
|
+
* Returns `{ code }` for statement ops, `{ expression }` for value-returning ops.
|
|
32
|
+
*/
|
|
33
|
+
export function lowerPwm(op, chip) {
|
|
34
|
+
const o = op;
|
|
35
|
+
const spec = findPwmSpec(chip, o.pin);
|
|
36
|
+
if (!spec) {
|
|
37
|
+
// Probe / unlisted pin: return a comment so the resolver reports non-
|
|
38
|
+
// undefined (the manifest validator's probe sends pin:0 with no spec).
|
|
39
|
+
// A real program pins the descriptor's pwm.specs entry.
|
|
40
|
+
return { code: `/* pwm on pin ${o.pin}: no PWM spec in chip descriptor */` };
|
|
41
|
+
}
|
|
42
|
+
const v = pwmVarName(spec);
|
|
43
|
+
switch (op.operation) {
|
|
44
|
+
case 'pwm.write': {
|
|
45
|
+
// Duty is 0–255 (Arduino analogWrite). Scale to ns against the period.
|
|
46
|
+
return { code: `pwm_set_pulse_dt(&${v}, (static_cast<uint32_t>(${o.duty}) * ${v}.period) / 255);` };
|
|
47
|
+
}
|
|
48
|
+
case 'pwm.get_frequency': {
|
|
49
|
+
// period is in ns; frequency = 1e9 / period (Hz).
|
|
50
|
+
return { expression: `(${v}.period ? (1000000000ULL / ${v}.period) : 0)` };
|
|
51
|
+
}
|
|
52
|
+
case 'pwm.get_resolution':
|
|
53
|
+
// Arduino-compatible 8-bit duty range.
|
|
54
|
+
return { expression: '8' };
|
|
55
|
+
default:
|
|
56
|
+
throw new Error(`framework-zephyr does not yet support HAL op \`${op.operation}\`. ` +
|
|
57
|
+
`Open an issue or use rawCpp() to emit it manually.`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { HALOpIR } from '@typecad/cuttlefish/api/shared';
|
|
2
|
+
/**
|
|
3
|
+
* The random runtime shim. The xorshift32 state is a 32-bit word seeded from
|
|
4
|
+
* sys_rand_get on first use; random.seed overwrites it. Helpers are
|
|
5
|
+
* `static`/`inline` so the single generated TU does not trip -Wunused-function
|
|
6
|
+
* when only a subset of the ops is used (mirrors preferences.ts / wifi.ts).
|
|
7
|
+
*/
|
|
8
|
+
export declare function randomInitLines(): string[];
|
|
9
|
+
/**
|
|
10
|
+
* Resolve a HAL random.* op to Zephyr C++ via the __tc_rand_* shim.
|
|
11
|
+
* Returns `{ code }` for the statement op (seed), `{ expression }` for the
|
|
12
|
+
* value-returning ops (int / range). The `default` arm throws the standard
|
|
13
|
+
* unsupported-op error so the manifest validator's per-op probe stays honest.
|
|
14
|
+
*/
|
|
15
|
+
export declare function lowerRandom(op: HALOpIR): {
|
|
16
|
+
code?: string;
|
|
17
|
+
expression?: string;
|
|
18
|
+
};
|