@typecad/framework-zephyr 1.0.0-alpha.10 → 1.0.0-alpha.12

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.
Files changed (67) hide show
  1. package/dist/chips/esp32.js +12 -0
  2. package/dist/chips/types.d.ts +30 -0
  3. package/dist/chips/xiao-ble.js +6 -0
  4. package/dist/display/gfx.d.ts +12 -3
  5. package/dist/display/gfx.js +130 -17
  6. package/dist/display/profiles.d.ts +25 -0
  7. package/dist/display/profiles.js +30 -0
  8. package/dist/display/touch-adapter.d.ts +3 -4
  9. package/dist/display/touch-adapter.js +119 -16
  10. package/dist/display/ui-adapter.js +308 -139
  11. package/dist/doctor.d.ts +3 -3
  12. package/dist/doctor.js +56 -29
  13. package/dist/dt-config/kconfig.d.ts +8 -1
  14. package/dist/dt-config/kconfig.js +40 -5
  15. package/dist/dt-config/overlay.d.ts +18 -1
  16. package/dist/dt-config/overlay.js +124 -26
  17. package/dist/framework.manifest.d.ts +29 -28
  18. package/dist/framework.manifest.js +46 -17
  19. package/dist/index.d.ts +1 -0
  20. package/dist/index.js +5 -0
  21. package/dist/licenses.d.ts +59 -0
  22. package/dist/licenses.js +347 -0
  23. package/dist/lowering/ble.js +3 -1
  24. package/dist/lowering/dac.d.ts +15 -0
  25. package/dist/lowering/dac.js +69 -0
  26. package/dist/lowering/fs.d.ts +16 -0
  27. package/dist/lowering/fs.js +121 -0
  28. package/dist/lowering/hwtimer.d.ts +15 -0
  29. package/dist/lowering/hwtimer.js +84 -0
  30. package/dist/lowering/index.d.ts +4 -1
  31. package/dist/lowering/index.js +12 -3
  32. package/dist/strategy.d.ts +4 -0
  33. package/dist/strategy.js +275 -35
  34. package/dist/toolchain/compat.js +10 -1
  35. package/dist/toolchain/env-check.d.ts +93 -0
  36. package/dist/toolchain/env-check.js +190 -0
  37. package/dist/toolchain/index.js +39 -9
  38. package/dist/toolchain/scaffold.js +7 -2
  39. package/dist/toolchain/west-discover.d.ts +11 -3
  40. package/dist/toolchain/west-discover.js +84 -7
  41. package/dist/toolchain/west-spawn.js +15 -0
  42. package/package.json +4 -4
  43. package/src/chips/esp32.ts +12 -0
  44. package/src/chips/types.ts +29 -0
  45. package/src/chips/xiao-ble.ts +6 -0
  46. package/src/display/gfx.ts +135 -19
  47. package/src/display/profiles.ts +53 -0
  48. package/src/display/touch-adapter.ts +119 -15
  49. package/src/display/ui-adapter.ts +311 -139
  50. package/src/doctor.ts +77 -56
  51. package/src/dt-config/kconfig.ts +45 -6
  52. package/src/dt-config/overlay.ts +159 -29
  53. package/src/framework.manifest.ts +47 -17
  54. package/src/index.ts +6 -0
  55. package/src/licenses.ts +425 -0
  56. package/src/lowering/ble.ts +3 -1
  57. package/src/lowering/dac.ts +82 -0
  58. package/src/lowering/fs.ts +127 -0
  59. package/src/lowering/hwtimer.ts +101 -0
  60. package/src/lowering/index.ts +9 -2
  61. package/src/strategy.ts +271 -35
  62. package/src/toolchain/compat.ts +154 -145
  63. package/src/toolchain/env-check.ts +285 -0
  64. package/src/toolchain/index.ts +40 -9
  65. package/src/toolchain/scaffold.ts +7 -2
  66. package/src/toolchain/west-discover.ts +92 -9
  67. package/src/toolchain/west-spawn.ts +15 -0
@@ -56,4 +56,16 @@ export const ESP32_DEVKITC = {
56
56
  // the ESP32 is AMP (dual-image procpu/appcpu), not SMP, by default — so the
57
57
  // dependency is satisfied. Omitted on radioless targets.
58
58
  wifi: { supported: true },
59
+ // DAC: the ESP32 has two 8-bit DAC channels on GPIO25 (channel 1) and GPIO26
60
+ // (channel 2). The Zephyr esp32 DAC driver (drivers/dac/dac_esp32.c) exposes
61
+ // them via the `dac0` node; the lowering emits dac_channel_setup +
62
+ // dac_write_value against DEVICE_DT_GET(DT_NODELABEL(dac0)). The overlay
63
+ // enables the node when the program uses dac.*. ESP32-S3 has no DAC.
64
+ dac: {
65
+ device: 'dac0',
66
+ channels: [
67
+ { pin: 25, channel: 1, resolution: 8 },
68
+ { pin: 26, channel: 2, resolution: 8 },
69
+ ],
70
+ },
59
71
  };
@@ -81,6 +81,18 @@ export interface ZephyrAdcChannel {
81
81
  /** SAADC channel index (AIN0–AIN7). */
82
82
  readonly channel: number;
83
83
  }
84
+ /**
85
+ * A DAC channel: which DAC output a given HAL pin maps to. The lowering emits
86
+ * `dac_channel_setup` + `dac_write_value` against the DAC device node.
87
+ */
88
+ export interface ZephyrDacChannel {
89
+ /** GPIO number (matches the HAL op `pin` field). */
90
+ readonly pin: number;
91
+ /** DAC channel index (ESP32: GPIO25 → 1, GPIO26 → 2). */
92
+ readonly channel: number;
93
+ /** DAC resolution in bits (ESP32 DAC is 8-bit). */
94
+ readonly resolution: number;
95
+ }
84
96
  /**
85
97
  * Pure-data descriptor for a Zephyr board + its SoC's peripheral layout.
86
98
  */
@@ -139,10 +151,28 @@ export interface ZephyrChipDescriptor {
139
151
  /** ADC resolution in bits. */
140
152
  readonly resolution: number;
141
153
  };
154
+ /**
155
+ * DAC: the DAC device node label + the pin→channel map. Present only on chips
156
+ * with a DAC (ESP32 has 2 channels on GPIO25/26; ESP32-S3 and nRF52840 have
157
+ * none). Read by profileDiagnostics to flag dac.* usage on chips without it.
158
+ */
159
+ readonly dac?: {
160
+ readonly device: string;
161
+ readonly channels: readonly ZephyrDacChannel[];
162
+ };
142
163
  /** Watchdog node label, e.g. 'wdt0'. */
143
164
  readonly wdt?: {
144
165
  readonly nodeLabel: string;
145
166
  };
167
+ /**
168
+ * Hardware timers exposed as Zephyr counter devices. `instance` (the HAL
169
+ * hwtimer.* op's instance index) maps to `controllers[instance].nodeLabel`.
170
+ * Omit on chips whose counter nodes are kernel-owned or unavailable; the
171
+ * lowering then lowers to a comment and profileDiagnostics flags usage.
172
+ */
173
+ readonly hwtimer?: {
174
+ readonly controllers: readonly ZephyrBusController[];
175
+ };
146
176
  /**
147
177
  * WiFi capability marker. Present only on chips with a WiFi radio (ESP32-S3).
148
178
  * Read by profileDiagnostics to flag wifi.* usage on chips without a radio.
@@ -58,4 +58,10 @@ export const XIAO_BLE = {
58
58
  ],
59
59
  },
60
60
  wdt: { nodeLabel: 'wdt0' },
61
+ // Hardware timer: nRF RTC1 is the free counter (RTC0 is kernel-owned by the
62
+ // softdevice/clock driver). The hwtimer lowering drives it as a Zephyr
63
+ // counter device (counter_start/stop + a top-value alarm for set_frequency).
64
+ // Verified against the nRF52840 SoC dtsi (rtc0/rtc1 nodes). The kernel uses
65
+ // RTC0 for the system tick; RTC1 is available for application use.
66
+ hwtimer: { controllers: [{ nodeLabel: 'rtc1' }] },
61
67
  };
@@ -10,8 +10,17 @@ export interface DisplayRuntimeResult {
10
10
  readonly fontTable: string;
11
11
  }
12
12
  /**
13
- * Build the display runtime C++ for a profile. The line buffer is one row
14
- * (width pixels × 2 bytes rgb565); fill_rect/draw_rect/draw_text compute into
15
- * it row-by-row and display_write each row. No full framebuffer.
13
+ * Build the display runtime C++ for a profile.
14
+ *
15
+ * Two rendering models, selected by `profile.colorFormat`:
16
+ * - `'rgb565'` (TFT): a one-row line buffer (no full framebuffer — see
17
+ * AGENTS.md rendering guardrails); fill_rect/draw_rect/draw_text stream
18
+ * each row via display_write.
19
+ * - `'mono'` (OLED, e.g. SSD1306): a full framebuffer — the standard model
20
+ * for page-buffered monochrome panels (the AGENTS.md "no full framebuffer"
21
+ * guardrail targets RGB SPI TFTs, not mono OLEDs). draw ops set bits;
22
+ * display_flush pushes the whole framebuffer. The MONO01 packing is
23
+ * horizontal, MSB-first (Zephyr convention): byte = (y*rowBytes)+(x>>3),
24
+ * bit = 0x80>>(x&7).
16
25
  */
17
26
  export declare function buildDisplayRuntime(profile: ZephyrDisplayProfile): DisplayRuntimeResult;
@@ -71,18 +71,23 @@ function fontTableCpp() {
71
71
  return lines.join('\n');
72
72
  }
73
73
  /**
74
- * Build the display runtime C++ for a profile. The line buffer is one row
75
- * (width pixels × 2 bytes rgb565); fill_rect/draw_rect/draw_text compute into
76
- * it row-by-row and display_write each row. No full framebuffer.
74
+ * Build the display runtime C++ for a profile.
75
+ *
76
+ * Two rendering models, selected by `profile.colorFormat`:
77
+ * - `'rgb565'` (TFT): a one-row line buffer (no full framebuffer — see
78
+ * AGENTS.md rendering guardrails); fill_rect/draw_rect/draw_text stream
79
+ * each row via display_write.
80
+ * - `'mono'` (OLED, e.g. SSD1306): a full framebuffer — the standard model
81
+ * for page-buffered monochrome panels (the AGENTS.md "no full framebuffer"
82
+ * guardrail targets RGB SPI TFTs, not mono OLEDs). draw ops set bits;
83
+ * display_flush pushes the whole framebuffer. The MONO01 packing is
84
+ * horizontal, MSB-first (Zephyr convention): byte = (y*rowBytes)+(x>>3),
85
+ * bit = 0x80>>(x&7).
77
86
  */
78
87
  export function buildDisplayRuntime(profile) {
79
88
  const w = profile.width;
80
- const stateLines = [
81
- '// CUTTLEFISH_DISPLAY_BEGIN',
82
- `static const struct device* __tc_display = DEVICE_DT_GET(DT_NODELABEL(${profile.dtLabel}));`,
83
- `static uint16_t __tc_display_line[${w}]; // one-row line buffer (rgb565)`,
84
- '// CUTTLEFISH_DISPLAY_END',
85
- ];
89
+ const h = profile.height;
90
+ const isMono = profile.colorFormat === 'mono';
86
91
  // Backlight is optional: the overlay emits the DT alias only when a backlight
87
92
  // GPIO is configured. Guard with DT_HAS_ALIAS so this compiles whether or not
88
93
  // the alias exists (DT_NODE_HAS_STATUS(DT_ALIAS(...)) is version-dependent
@@ -90,7 +95,122 @@ export function buildDisplayRuntime(profile) {
90
95
  const blInit = profile.backlight
91
96
  ? `#if DT_HAS_ALIAS(${profile.backlight})\n const struct device* __bl = DEVICE_DT_GET(DT_ALIAS(${profile.backlight}));\n gpio_pin_configure(__bl, 0, GPIO_OUTPUT); gpio_pin_set(__bl, 0, 1);\n#endif`
92
97
  : '';
93
- const helpers = `
98
+ const stateLines = isMono
99
+ ? [
100
+ '// CUTTLEFISH_DISPLAY_BEGIN',
101
+ `static const struct device* __tc_display = DEVICE_DT_GET(DT_NODELABEL(${profile.dtLabel}));`,
102
+ `// Mono framebuffer (Zephyr MONO01: horizontal, MSB-first). ${(w + 7) >> 3} bytes/row x ${h} rows.`,
103
+ `static uint8_t __tc_display_fb[((${w} * ${h}) + 7) / 8];`,
104
+ '// CUTTLEFISH_DISPLAY_END',
105
+ ]
106
+ : [
107
+ '// CUTTLEFISH_DISPLAY_BEGIN',
108
+ `static const struct device* __tc_display = DEVICE_DT_GET(DT_NODELABEL(${profile.dtLabel}));`,
109
+ `static uint16_t __tc_display_line[${w}]; // one-row line buffer (rgb565)`,
110
+ '// CUTTLEFISH_DISPLAY_END',
111
+ ];
112
+ const helpers = isMono
113
+ ? monoHelpers(w, h, blInit)
114
+ : rgb565Helpers(w, blInit);
115
+ const fontTable = fontTableCpp();
116
+ return {
117
+ includes: ['<zephyr/drivers/display.h>'],
118
+ stateLines,
119
+ helpers,
120
+ fontTable,
121
+ };
122
+ }
123
+ /**
124
+ * Mono (OLED) helpers: a full framebuffer + bit-packing. SSD1306-class panels
125
+ * are page-buffered, so draw ops set bits in the framebuffer and display_flush
126
+ * pushes the whole buffer. color != 0 ⇒ lit.
127
+ */
128
+ function monoHelpers(w, h, blInit) {
129
+ const rowBytes = (w + 7) >> 3; // bytes per row (w is byte-aligned for 128-wide panels)
130
+ return `
131
+ // MONO01 pixel packing: byte = (y * ${rowBytes}) + (x >> 3), bit = 0x80 >> (x & 7).
132
+ static inline void __tc_set_pixel(uint16_t x, uint16_t y, uint8_t on) {
133
+ if ((x >= ${w}U) || (y >= ${h}U)) { return; }
134
+ uint16_t idx = static_cast<uint16_t>((static_cast<uint32_t>(y) * ${rowBytes}U) + (x >> 3));
135
+ uint8_t mask = static_cast<uint8_t>(0x80U >> (x & 7U));
136
+ if (on != 0U) {
137
+ __tc_display_fb[idx] = static_cast<uint8_t>(__tc_display_fb[idx] | mask);
138
+ } else {
139
+ __tc_display_fb[idx] = static_cast<uint8_t>(__tc_display_fb[idx] & static_cast<uint8_t>(~mask));
140
+ }
141
+ }
142
+
143
+ static inline void display_init(void) {
144
+ if (!device_is_ready(__tc_display)) { for (;;) { k_msleep(1000); } }
145
+ ${blInit}
146
+ for (uint16_t i = 0; i < static_cast<uint16_t>(sizeof(__tc_display_fb)); i++) { __tc_display_fb[i] = 0U; }
147
+ display_blanking_off(__tc_display);
148
+ }
149
+
150
+ static inline void display_fill_rect(uint16_t x, uint16_t y, uint16_t rw, uint16_t rh, uint16_t color) {
151
+ uint8_t on = (color != 0U) ? 1U : 0U;
152
+ for (uint16_t row = 0; row < rh; row++) {
153
+ for (uint16_t i = 0; i < rw; i++) {
154
+ __tc_set_pixel(static_cast<uint16_t>(x + i), static_cast<uint16_t>(y + row), on);
155
+ }
156
+ }
157
+ }
158
+
159
+ static inline void display_draw_rect(uint16_t x, uint16_t y, uint16_t rw, uint16_t rh, uint16_t color) {
160
+ uint8_t on = (color != 0U) ? 1U : 0U;
161
+ for (uint16_t i = 0; i < rw; i++) {
162
+ __tc_set_pixel(static_cast<uint16_t>(x + i), y, on);
163
+ __tc_set_pixel(static_cast<uint16_t>(x + i), static_cast<uint16_t>(y + rh - 1U), on);
164
+ }
165
+ for (uint16_t row = 1U; row < rh - 1U; row++) {
166
+ __tc_set_pixel(x, static_cast<uint16_t>(y + row), on);
167
+ __tc_set_pixel(static_cast<uint16_t>(x + rw - 1U), static_cast<uint16_t>(y + row), on);
168
+ }
169
+ }
170
+
171
+ static inline void display_draw_text(uint16_t x, uint16_t y, const char* text, uint16_t color) {
172
+ uint8_t on = (color != 0U) ? 1U : 0U;
173
+ uint16_t cx = x;
174
+ for (const char* p = text; *p != 0; p++) {
175
+ uint8_t uc = static_cast<uint8_t>(*p);
176
+ if (uc >= 128U) { uc = static_cast<uint8_t>(' '); }
177
+ const uint8_t* glyph = &__tc_font5x7[uc][0];
178
+ if (uc >= static_cast<uint8_t>('a') && uc <= static_cast<uint8_t>('z')) {
179
+ glyph = &__tc_font5x7[uc - 32U][0];
180
+ } else if (uc < static_cast<uint8_t>('0')
181
+ || (uc > static_cast<uint8_t>('9') && uc < static_cast<uint8_t>('A'))
182
+ || uc > static_cast<uint8_t>('Z')) {
183
+ glyph = &__tc_font5x7[static_cast<uint8_t>(' ')][0];
184
+ }
185
+ for (uint16_t col = 0; col < 5U; col++) {
186
+ uint8_t bits = glyph[col];
187
+ for (uint16_t row = 0; row < 7U; row++) {
188
+ if ((bits & static_cast<uint8_t>(1U << row)) != 0U) {
189
+ __tc_set_pixel(static_cast<uint16_t>(cx + col), static_cast<uint16_t>(y + row), on);
190
+ }
191
+ }
192
+ }
193
+ cx = static_cast<uint16_t>(cx + 6U);
194
+ }
195
+ }
196
+
197
+ static inline void display_flush(void) {
198
+ struct display_buffer_descriptor __desc;
199
+ __desc.buf_size = sizeof(__tc_display_fb); // ${rowBytes} * ${h} bytes
200
+ __desc.width = ${w}U;
201
+ __desc.height = ${h}U;
202
+ __desc.pitch = ${rowBytes}U; // bytes per row
203
+ __desc.frame_incomplete = false;
204
+ (void)display_write(__tc_display, 0, 0, &__desc, __tc_display_fb);
205
+ }
206
+ `;
207
+ }
208
+ /**
209
+ * RGB565 (TFT) helpers: a one-row line buffer; each op streams rows via
210
+ * display_write. No full framebuffer (AGENTS.md rendering guardrails).
211
+ */
212
+ function rgb565Helpers(w, blInit) {
213
+ return `
94
214
  // Write a single row of \`rw\` rgb565 pixels at (x,y). Builds the
95
215
  // display_buffer_descriptor the Zephyr display_write API requires (rgb565 =
96
216
  // 2 bytes/pixel) and pushes the one-row line buffer.
@@ -161,11 +281,4 @@ static inline void display_flush(void) {
161
281
  // No-op: writes are immediate via display_write; there is no framebuffer to push.
162
282
  }
163
283
  `;
164
- const fontTable = fontTableCpp();
165
- return {
166
- includes: ['<zephyr/drivers/display.h>'],
167
- stateLines,
168
- helpers,
169
- fontTable,
170
- };
171
284
  }
@@ -15,7 +15,32 @@ export interface ZephyrDisplayProfile {
15
15
  readonly rotation?: number;
16
16
  /** DT alias for the backlight GPIO (set high at init), if any. */
17
17
  readonly backlight?: string;
18
+ /** Panel controller the direct-drive UI adapter targets. Selects the init
19
+ * sequence + pixel wire format (ST7796S: 18-bit; ILI9341: 16-bit RGB565).
20
+ * Required for rgb565 profiles; mono profiles use the direct-op GFX
21
+ * runtime and ignore it. */
22
+ readonly controller?: ZephyrPanelController;
23
+ /** DT compatible string for the display@0 node. Defaults per controller
24
+ * (see PANEL_CONTROLLER_DEFAULTS) — override only for a panel whose DT
25
+ * binding differs from its controller family. */
26
+ readonly dtCompatible?: string;
27
+ /** SPI controller nodelabel the panel hangs off (and SPI touch, if any).
28
+ * Default 'spi2' — the ESP32-S3 first general-purpose SPI controller. */
29
+ readonly busLabel?: string;
30
+ /** Nodelabel of the MIPI DBI bridge node carrying the dc/reset GPIOs.
31
+ * Default 'mipi_dbi' (the overlay emits the bridge under that label). */
32
+ readonly bridgeLabel?: string;
18
33
  }
34
+ /** Panel controllers the direct-drive UI adapter knows how to init. */
35
+ export type ZephyrPanelController = 'st7796s' | 'ili9341';
36
+ /** Per-controller DT + transport defaults, shared by the overlay generator
37
+ * (DT node props) and the UI adapter (init sequence + wire format). */
38
+ export declare const PANEL_CONTROLLER_DEFAULTS: Record<ZephyrPanelController, {
39
+ dtCompatible: string;
40
+ }>;
41
+ /** Resolve a profile's panel controller, inferring it from the driver id when
42
+ * the profile doesn't declare one (the '<controller>-zephyr' naming scheme). */
43
+ export declare function panelControllerFor(profile: Pick<ZephyrDisplayProfile, 'driver' | 'controller'>): ZephyrPanelController;
19
44
  /**
20
45
  * Built-in profile registry. Looked up by driver id. Add a profile here when a
21
46
  * new board's display node is wired into its devicetree.
@@ -6,6 +6,21 @@
6
6
  // pin wiring. The GFX runtime (gfx.ts) reads width/height/colorFormat from the
7
7
  // active profile to size its line buffer.
8
8
  // ---------------------------------------------------------------------------
9
+ /** Per-controller DT + transport defaults, shared by the overlay generator
10
+ * (DT node props) and the UI adapter (init sequence + wire format). */
11
+ export const PANEL_CONTROLLER_DEFAULTS = {
12
+ st7796s: { dtCompatible: 'sitronix,st7796s' },
13
+ ili9341: { dtCompatible: 'ilitek,ili9341' },
14
+ };
15
+ /** Resolve a profile's panel controller, inferring it from the driver id when
16
+ * the profile doesn't declare one (the '<controller>-zephyr' naming scheme). */
17
+ export function panelControllerFor(profile) {
18
+ if (profile.controller)
19
+ return profile.controller;
20
+ if (profile.driver.startsWith('ili9341'))
21
+ return 'ili9341';
22
+ return 'st7796s';
23
+ }
9
24
  /**
10
25
  * Built-in profile registry. Looked up by driver id. Add a profile here when a
11
26
  * new board's display node is wired into its devicetree.
@@ -17,6 +32,7 @@ export const ZEPHYR_DISPLAY_PROFILES = {
17
32
  width: 320,
18
33
  height: 240,
19
34
  colorFormat: 'rgb565',
35
+ controller: 'ili9341',
20
36
  rotation: 90,
21
37
  backlight: 'backlight',
22
38
  },
@@ -33,9 +49,23 @@ export const ZEPHYR_DISPLAY_PROFILES = {
33
49
  nativeWidth: 320,
34
50
  nativeHeight: 480,
35
51
  colorFormat: 'rgb565',
52
+ controller: 'st7796s',
36
53
  rotation: 1,
37
54
  backlight: 'backlight',
38
55
  },
56
+ 'ssd1306-zephyr': {
57
+ // Monochrome OLED (SSD1306-class, 128x64, 1bpp). Driven through Zephyr's
58
+ // generic display API (the ssd1306 driver + a DT display node). The GFX
59
+ // runtime (gfx.ts mono branch) keeps a full page-framebuffer and pushes it
60
+ // on display_flush — the standard model for page-buffered OLEDs. Direct
61
+ // display.* ops only (no @typecad/ui CuttlefishGFX rendering on mono).
62
+ driver: 'ssd1306-zephyr',
63
+ dtLabel: 'display0',
64
+ width: 128,
65
+ height: 64,
66
+ colorFormat: 'mono',
67
+ rotation: 0,
68
+ },
39
69
  };
40
70
  /** The default profile used when resolveDisplayOp is probed without a display.init. */
41
71
  export const DEFAULT_ZEPHYR_DISPLAY_PROFILE = ZEPHYR_DISPLAY_PROFILES['ili9341-zephyr'];
@@ -1,9 +1,8 @@
1
1
  import type { TouchAdapterCodegen } from "@typecad/cuttlefish/api/shared";
2
2
  import type { TouchProfile } from "@typecad/cuttlefish/api/shared";
3
3
  /**
4
- * Generate the FT6336U touch adapter for Zephyr. Emits touch_init /
5
- * touch_isTouched / touch_readRaw the three symbols the runtime's
6
- * ui_poll_touch body calls. Returns undefined for other libraries so the
7
- * strategy can decline.
4
+ * Generate a Zephyr touch adapter for the profile's library. Returns
5
+ * undefined for libraries this framework doesn't handle so the strategy can
6
+ * decline and cuttlefish surfaces a clear error.
8
7
  */
9
8
  export declare function zephyrTouchAdapter(touch: TouchProfile): TouchAdapterCodegen | undefined;
@@ -1,29 +1,132 @@
1
1
  // ---------------------------------------------------------------------------
2
- // Zephyr touch adapter for the Cuttlefish UI rendering pipeline.
2
+ // Zephyr touch adapters for the Cuttlefish UI rendering pipeline.
3
3
  //
4
- // FT6336U capacitive touch controller over I2C, driven via Zephyr's
5
- // device-tree-bound I2C API (i2c_write_read_dt). This is the Zephyr analog of
6
- // the Arduino FT6336U adapter in framework-arduino (touch-adapters-codegen.ts)
7
- // and the stale native ESP32 reference (demo-display/.../main.cc).
4
+ // Two controllers, both driven directly (polling, no in-tree driver):
5
+ // - FT6336U capacitive over I2C (i2c_write_read_dt)
6
+ // - XPT2046 resistive over SPI (spi_transceive_dt) the Zephyr analog of
7
+ // Arduino's XPT2046_Touchscreen adapter. The DT node uses the in-tree
8
+ // xptek,xpt2046 binding; CONFIG_INPUT stays off, so the in-tree input
9
+ // driver does not build and this adapter owns the chip.
8
10
  //
9
- // The touch node is resolved via DEVICE_DT_GET(DT_NODELABEL(ft6336u)). The
10
- // board's devicetree must carry an `ft6336u` nodelabel with the I2C spec +
11
- // the `focaltech,ft6336u` (or compatible) binding so the device is ready.
11
+ // The adapters emit touch_init / touch_isTouched / touch_readRaw — the three
12
+ // symbols the runtime's ui_poll_touch body calls.
12
13
  //
13
14
  // EMIT BOUNDARY: emitted bytes land in user firmware. Covered by the TypeCAD
14
15
  // Runtime Exception (RUNTIME_EXCEPTION.md at the repo root).
15
16
  // ---------------------------------------------------------------------------
16
17
  /**
17
- * Generate the FT6336U touch adapter for Zephyr. Emits touch_init /
18
- * touch_isTouched / touch_readRaw the three symbols the runtime's
19
- * ui_poll_touch body calls. Returns undefined for other libraries so the
20
- * strategy can decline.
18
+ * Generate a Zephyr touch adapter for the profile's library. Returns
19
+ * undefined for libraries this framework doesn't handle so the strategy can
20
+ * decline and cuttlefish surfaces a clear error.
21
21
  */
22
22
  export function zephyrTouchAdapter(touch) {
23
- if (touch.library !== "FT6336U")
24
- return undefined;
25
- const addr = touch.i2cAddress ?? 0x38;
26
- const addrHex = "0x" + addr.toString(16).toUpperCase();
23
+ if (touch.library === "XPT2046_Touchscreen")
24
+ return xpt2046Adapter(touch);
25
+ if (touch.library === "FT6336U")
26
+ return ft6336uAdapter(touch);
27
+ return undefined;
28
+ }
29
+ /** XPT2046 control bytes (12-bit differential mode, auto power-down). */
30
+ const XPT2046_CMD_X = 0x90;
31
+ const XPT2046_CMD_Y = 0xd0;
32
+ const XPT2046_CMD_Z1 = 0xb0;
33
+ const hex = (v) => `0x${v.toString(16).toUpperCase()}`;
34
+ function xpt2046Adapter(touch) {
35
+ // Pen-detect Z1 threshold (raw 12-bit counts). Resistive panels need a few
36
+ // hundred counts above noise; the TouchProfile default (10) is far too low
37
+ // for raw Z1, so the adapter defaults to 400 (the Arduino XPT2046
38
+ // library's Z_THRESHOLD) unless the config sets one explicitly.
39
+ const zThreshold = touch.minPressure ?? 400;
40
+ return {
41
+ includes: [
42
+ `#include <zephyr/kernel.h>`,
43
+ `#include <zephyr/drivers/spi.h>`,
44
+ `#include <zephyr/drivers/gpio.h>`,
45
+ ],
46
+ declaration: [
47
+ `// Zephyr XPT2046 resistive touch — SPI device resolved via devicetree.`,
48
+ `// The overlay defines the 'xpt2046' nodelabel on the panel's SPI bus`,
49
+ `// (CS index 1) with the xptek,xpt2046 binding; the bus + CS + 2.5MHz`,
50
+ `// ceiling come from that node. Raw X/Y/Z1 values are reported — the`,
51
+ `// runtime applies the profile calibration + rotation.`,
52
+ `static const struct spi_dt_spec __tc_touch =`,
53
+ ` SPI_DT_SPEC_GET(DT_NODELABEL(xpt2046), SPI_OP_MODE_MASTER | SPI_WORD_SET(8), 0U);`,
54
+ `static int16_t __tc_touch_cached_x = 0;`,
55
+ `static int16_t __tc_touch_cached_y = 0;`,
56
+ `static int16_t __tc_touch_cached_z = 0;`,
57
+ `static uint8_t __tc_touch_cached_valid = 0;`,
58
+ `#if DT_NODE_HAS_PROP(DT_NODELABEL(xpt2046), int_gpios)`,
59
+ `static const struct gpio_dt_spec __tc_touch_irq = GPIO_DT_SPEC_GET(DT_NODELABEL(xpt2046), int_gpios);`,
60
+ `static uint8_t __tc_touch_irq_ready = 0;`,
61
+ `#endif`,
62
+ ].join("\n"),
63
+ functions: [
64
+ `// One XPT2046 conversion: clock out a control byte, read the 12-bit`,
65
+ `// result. The rx byte during the command phase is a dummy; the value is`,
66
+ `// MSB-aligned across the following 16 bits ((b1<<8 | b2) >> 3). The`,
67
+ `// control byte's PD bits are 00 (auto power-down between transactions),`,
68
+ `// so the bus stays shareable with the panel. Touch is polled every frame`,
69
+ `// from ui_poll_touch, so a failed transfer is non-fatal — returns 0 and`,
70
+ `// touch_isTouched() reports false.`,
71
+ `static uint16_t __tc_xpt_read(uint8_t cmd) {`,
72
+ ` if (!device_is_ready(__tc_touch.bus)) return 0U;`,
73
+ ` uint8_t __tx[3] = { cmd, 0U, 0U };`,
74
+ ` uint8_t __rx[3] = { 0U, 0U, 0U };`,
75
+ ` struct spi_buf __bt = { __tx, sizeof(__tx) };`,
76
+ ` struct spi_buf __br = { __rx, sizeof(__rx) };`,
77
+ ` struct spi_buf_set __st = { &__bt, 1 };`,
78
+ ` struct spi_buf_set __sr = { &__br, 1 };`,
79
+ ` if (spi_transceive_dt(&__tc_touch, &__st, &__sr) != 0) return 0U;`,
80
+ ` return static_cast<uint16_t>(((static_cast<uint16_t>(__rx[1]) << 8) | __rx[2]) >> 3);`,
81
+ `}`,
82
+ ``,
83
+ `static inline void touch_init() {`,
84
+ ` // The SPI bus + CS are configured by devicetree. Configure the pen IRQ`,
85
+ ` // input (active low) when the overlay wired it — the cheap detect path.`,
86
+ `#if DT_NODE_HAS_PROP(DT_NODELABEL(xpt2046), int_gpios)`,
87
+ ` if (device_is_ready(__tc_touch_irq.port)) {`,
88
+ ` gpio_pin_configure_dt(&__tc_touch_irq, GPIO_INPUT);`,
89
+ ` __tc_touch_irq_ready = 1U;`,
90
+ ` }`,
91
+ `#endif`,
92
+ ` (void)device_is_ready(__tc_touch.bus);`,
93
+ `}`,
94
+ ``,
95
+ `static inline bool touch_isTouched() {`,
96
+ ` __tc_touch_cached_valid = 0;`,
97
+ ` __tc_touch_cached_z = 0;`,
98
+ `#if DT_NODE_HAS_PROP(DT_NODELABEL(xpt2046), int_gpios)`,
99
+ ` if (__tc_touch_irq_ready != 0U) {`,
100
+ ` // PENIRQ asserts (active low) while the panel is pressed — no SPI`,
101
+ ` // traffic needed to answer the per-frame poll.`,
102
+ ` if (gpio_pin_get_dt(&__tc_touch_irq) <= 0) return false;`,
103
+ ` }`,
104
+ `#endif`,
105
+ ` // No IRQ (or it fired): confirm via Z1 pressure before reading coords.`,
106
+ ` uint16_t __z1 = __tc_xpt_read(${hex(XPT2046_CMD_Z1)});`,
107
+ ` if (__z1 <= ${zThreshold}) return false;`,
108
+ ` __tc_touch_cached_x = static_cast<int16_t>(__tc_xpt_read(${hex(XPT2046_CMD_X)}));`,
109
+ ` __tc_touch_cached_y = static_cast<int16_t>(__tc_xpt_read(${hex(XPT2046_CMD_Y)}));`,
110
+ ` __tc_touch_cached_z = static_cast<int16_t>(__z1);`,
111
+ ` __tc_touch_cached_valid = 1;`,
112
+ ` return true;`,
113
+ `}`,
114
+ ``,
115
+ `static inline void touch_readRaw(int16_t* x, int16_t* y, int16_t* z) {`,
116
+ ` // touch_isTouched() is polled each frame by ui_poll_touch; the cached`,
117
+ ` // values are fresh. If something calls readRaw before isTouched, probe now.`,
118
+ ` if (!__tc_touch_cached_valid) {`,
119
+ ` (void)touch_isTouched();`,
120
+ ` }`,
121
+ ` if (x) *x = __tc_touch_cached_x;`,
122
+ ` if (y) *y = __tc_touch_cached_y;`,
123
+ ` if (z) *z = __tc_touch_cached_z;`,
124
+ ` __tc_touch_cached_valid = 0;`,
125
+ `}`,
126
+ ].join("\n"),
127
+ };
128
+ }
129
+ function ft6336uAdapter(_touch) {
27
130
  return {
28
131
  includes: [
29
132
  `#include <zephyr/drivers/i2c.h>`,