@typecad/framework-zephyr 1.0.0-alpha.16 → 1.0.0-alpha.19

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 (210) hide show
  1. package/LICENSE +202 -21
  2. package/README.md +49 -87
  3. package/dist/as-built.d.ts +2 -2
  4. package/dist/as-built.js +2 -2
  5. package/dist/audit.d.ts +111 -0
  6. package/dist/audit.js +416 -0
  7. package/dist/boardgen.d.ts +1 -9
  8. package/dist/boardgen.js +288 -47
  9. package/dist/chips/resolve.js +20 -0
  10. package/dist/chips/types.d.ts +22 -1
  11. package/dist/debug-codegen.js +1 -1
  12. package/dist/display/bindings.d.ts +55 -0
  13. package/dist/display/bindings.js +316 -0
  14. package/dist/display/gfx.d.ts +2 -3
  15. package/dist/display/gfx.js +166 -154
  16. package/dist/display/index.js +20 -1
  17. package/dist/display/mipi-dbi-host.d.ts +9 -0
  18. package/dist/display/mipi-dbi-host.js +174 -0
  19. package/dist/display/profiles.d.ts +109 -4
  20. package/dist/display/profiles.js +270 -7
  21. package/dist/display/touch-adapter.js +119 -49
  22. package/dist/display/ui-adapter-eink.d.ts +2 -0
  23. package/dist/display/ui-adapter-eink.js +4 -0
  24. package/dist/display/ui-adapter-gray.d.ts +8 -0
  25. package/dist/display/ui-adapter-gray.js +170 -0
  26. package/dist/display/ui-adapter-mono.d.ts +13 -0
  27. package/dist/display/ui-adapter-mono.js +230 -0
  28. package/dist/display/ui-adapter-native.d.ts +10 -0
  29. package/dist/display/ui-adapter-native.js +295 -0
  30. package/dist/display/ui-adapter-shared.d.ts +11 -0
  31. package/dist/display/ui-adapter-shared.js +122 -0
  32. package/dist/display/ui-adapter.js +510 -558
  33. package/dist/doctor.js +4 -4
  34. package/dist/dt-config/custom-board.js +2 -2
  35. package/dist/dt-config/kconfig.d.ts +62 -1
  36. package/dist/dt-config/kconfig.js +141 -38
  37. package/dist/dt-config/overlay.d.ts +15 -2
  38. package/dist/dt-config/overlay.js +433 -18
  39. package/dist/framework.manifest.d.ts +10 -4
  40. package/dist/framework.manifest.js +133 -17
  41. package/dist/index.d.ts +2 -0
  42. package/dist/index.js +14 -6
  43. package/dist/licenses.d.ts +2 -2
  44. package/dist/licenses.js +13 -92
  45. package/dist/lowering/can.d.ts +25 -0
  46. package/dist/lowering/can.js +97 -0
  47. package/dist/lowering/clock.d.ts +17 -0
  48. package/dist/lowering/clock.js +58 -0
  49. package/dist/lowering/fs.js +1 -1
  50. package/dist/lowering/gpio.js +0 -32
  51. package/dist/lowering/hid.d.ts +27 -0
  52. package/dist/lowering/hid.js +244 -0
  53. package/dist/lowering/http.js +264 -32
  54. package/dist/lowering/i2c.d.ts +8 -0
  55. package/dist/lowering/i2c.js +137 -5
  56. package/dist/lowering/i2s.d.ts +27 -0
  57. package/dist/lowering/i2s.js +98 -0
  58. package/dist/lowering/index.d.ts +9 -1
  59. package/dist/lowering/index.js +25 -1
  60. package/dist/lowering/interrupts.js +6 -0
  61. package/dist/lowering/matrix.d.ts +15 -0
  62. package/dist/lowering/matrix.js +63 -0
  63. package/dist/lowering/mqtt.js +110 -8
  64. package/dist/lowering/power.d.ts +3 -2
  65. package/dist/lowering/power.js +20 -45
  66. package/dist/lowering/pwm.js +25 -0
  67. package/dist/lowering/sensor.d.ts +2 -2
  68. package/dist/lowering/sensor.js +8 -4
  69. package/dist/lowering/strip.d.ts +16 -0
  70. package/dist/lowering/strip.js +70 -0
  71. package/dist/lowering/thread.js +5 -1
  72. package/dist/lowering/trace.d.ts +44 -0
  73. package/dist/lowering/trace.js +239 -0
  74. package/dist/lowering/uart.js +6 -1
  75. package/dist/lowering/usb.d.ts +3 -1
  76. package/dist/lowering/usb.js +16 -13
  77. package/dist/lowering/wdt.js +2 -29
  78. package/dist/sbom.d.ts +181 -0
  79. package/dist/sbom.js +901 -0
  80. package/dist/sdk/board-catalog-sync.d.ts +1 -3
  81. package/dist/sdk/board-catalog-sync.js +4 -10
  82. package/dist/strategy.d.ts +82 -46
  83. package/dist/strategy.js +639 -182
  84. package/dist/tmp-probe.d.ts +2 -0
  85. package/dist/tmp-probe.js +9 -0
  86. package/dist/toolchain/debug-config.d.ts +50 -90
  87. package/dist/toolchain/debug-config.js +239 -510
  88. package/dist/toolchain/env-check.d.ts +1 -3
  89. package/dist/toolchain/env-check.js +2 -7
  90. package/dist/toolchain/index.d.ts +28 -3
  91. package/dist/toolchain/index.js +498 -62
  92. package/dist/toolchain/runners.d.ts +16 -0
  93. package/dist/toolchain/runners.js +75 -0
  94. package/dist/toolchain/scaffold.d.ts +5 -2
  95. package/dist/toolchain/scaffold.js +79 -15
  96. package/dist/toolchain/west-discover.d.ts +6 -0
  97. package/dist/toolchain/west-discover.js +36 -13
  98. package/dist/toolchain/west-spawn.js +8 -2
  99. package/dist/west-inventory.d.ts +25 -0
  100. package/dist/west-inventory.js +97 -0
  101. package/installer/README.md +328 -328
  102. package/installer/install.sh +2 -2
  103. package/installer/templates/project/.typecad/activate-zephyr.ps1 +1 -1
  104. package/installer/templates/project/.typecad/activate-zephyr.sh +1 -1
  105. package/installer/templates/project/.vscode/settings.json +1 -1
  106. package/installer/templates/project/README.md +2 -2
  107. package/package.json +5 -5
  108. package/src/as-built.ts +206 -206
  109. package/src/audit.ts +529 -0
  110. package/src/boardgen.ts +265 -50
  111. package/src/chips/resolve.ts +21 -0
  112. package/src/chips/types.ts +10 -1
  113. package/src/display/bindings.ts +347 -0
  114. package/src/display/gfx.ts +318 -306
  115. package/src/display/index.ts +87 -70
  116. package/src/display/mipi-dbi-host.ts +183 -0
  117. package/src/display/profiles.ts +458 -139
  118. package/src/display/touch-adapter.ts +119 -49
  119. package/src/display/ui-adapter-eink.ts +13 -0
  120. package/src/display/ui-adapter-gray.ts +178 -0
  121. package/src/display/ui-adapter-mono.ts +238 -0
  122. package/src/display/ui-adapter-native.ts +304 -0
  123. package/src/display/ui-adapter-shared.ts +125 -0
  124. package/src/display/ui-adapter.ts +732 -781
  125. package/src/doctor.ts +4 -4
  126. package/src/dt-config/custom-board.ts +2 -2
  127. package/src/dt-config/kconfig.ts +647 -505
  128. package/src/dt-config/overlay.ts +1475 -1058
  129. package/src/framework.manifest.ts +659 -535
  130. package/src/index.ts +16 -6
  131. package/src/licenses.ts +346 -425
  132. package/src/lowering/can.ts +140 -0
  133. package/src/lowering/clock.ts +91 -0
  134. package/src/lowering/fs.ts +135 -135
  135. package/src/lowering/gpio.ts +0 -33
  136. package/src/lowering/hid.ts +261 -0
  137. package/src/lowering/http.ts +264 -32
  138. package/src/lowering/i2c.ts +142 -5
  139. package/src/lowering/i2s.ts +143 -0
  140. package/src/lowering/index.ts +18 -1
  141. package/src/lowering/interrupts.ts +6 -0
  142. package/src/lowering/matrix.ts +70 -0
  143. package/src/lowering/mqtt.ts +109 -8
  144. package/src/lowering/power.ts +41 -0
  145. package/src/lowering/pwm.ts +192 -167
  146. package/src/lowering/sensor.ts +159 -155
  147. package/src/lowering/strip.ts +81 -0
  148. package/src/lowering/thread.ts +5 -1
  149. package/src/lowering/trace.ts +270 -0
  150. package/src/lowering/uart.ts +6 -1
  151. package/src/lowering/usb.ts +16 -13
  152. package/src/lowering/wdt.ts +2 -25
  153. package/src/sbom.ts +1117 -0
  154. package/src/sdk/board-catalog-sync.ts +4 -25
  155. package/src/strategy.ts +2680 -2309
  156. package/src/toolchain/debug-config.ts +262 -522
  157. package/src/toolchain/env-check.ts +279 -285
  158. package/src/toolchain/index.ts +1792 -1359
  159. package/src/toolchain/runners.ts +80 -0
  160. package/src/toolchain/scaffold.ts +355 -296
  161. package/src/toolchain/west-discover.ts +35 -13
  162. package/src/toolchain/west-spawn.ts +174 -168
  163. package/src/west-inventory.ts +102 -0
  164. package/dist/async/timer-polyfill.d.ts +0 -10
  165. package/dist/async/timer-polyfill.js +0 -95
  166. package/dist/chips/board-overrides.d.ts +0 -7
  167. package/dist/chips/board-overrides.js +0 -11
  168. package/dist/chips/esp32.d.ts +0 -2
  169. package/dist/chips/esp32.js +0 -71
  170. package/dist/chips/esp32s3.d.ts +0 -2
  171. package/dist/chips/esp32s3.js +0 -103
  172. package/dist/chips/soc/.d.ts +0 -2
  173. package/dist/chips/soc/.js +0 -129
  174. package/dist/chips/soc/esp32.d.ts +0 -2
  175. package/dist/chips/soc/esp32.js +0 -120
  176. package/dist/chips/soc/esp32c3.d.ts +0 -2
  177. package/dist/chips/soc/esp32c3.js +0 -90
  178. package/dist/chips/soc/esp32c6.d.ts +0 -2
  179. package/dist/chips/soc/esp32c6.js +0 -109
  180. package/dist/chips/soc/esp32s3.d.ts +0 -2
  181. package/dist/chips/soc/esp32s3.js +0 -189
  182. package/dist/chips/soc/index.d.ts +0 -2
  183. package/dist/chips/soc/index.js +0 -23
  184. package/dist/chips/soc/nrf52840.d.ts +0 -2
  185. package/dist/chips/soc/nrf52840.js +0 -130
  186. package/dist/chips/soc/rp2040.d.ts +0 -2
  187. package/dist/chips/soc/rp2040.js +0 -141
  188. package/dist/chips/soc/rp2350a.d.ts +0 -2
  189. package/dist/chips/soc/rp2350a.js +0 -145
  190. package/dist/chips/soc/samd21g18a.d.ts +0 -2
  191. package/dist/chips/soc/samd21g18a.js +0 -143
  192. package/dist/chips/soc/stm32f411xe.d.ts +0 -2
  193. package/dist/chips/soc/stm32f411xe.js +0 -251
  194. package/dist/chips/xiao-ble.d.ts +0 -2
  195. package/dist/chips/xiao-ble.js +0 -100
  196. package/dist/lowering/pulse.d.ts +0 -7
  197. package/dist/lowering/pulse.js +0 -51
  198. package/dist/lowering/tone.d.ts +0 -10
  199. package/dist/lowering/tone.js +0 -63
  200. package/dist/lowering/worker-backing.d.ts +0 -14
  201. package/dist/lowering/worker-backing.js +0 -79
  202. package/dist/lowering/worker.d.ts +0 -6
  203. package/dist/lowering/worker.js +0 -14
  204. package/dist/sdk/board-data.generated.d.ts +0 -2
  205. package/dist/sdk/board-data.generated.js +0 -4
  206. package/dist/sdk/catalog-walker.d.ts +0 -90
  207. package/dist/sdk/catalog-walker.js +0 -682
  208. package/dist/sdk/dts-reader.d.ts +0 -83
  209. package/dist/sdk/dts-reader.js +0 -596
  210. package/src/debug-codegen.ts +0 -207
@@ -1,306 +1,318 @@
1
- // ---------------------------------------------------------------------------
2
- // Zephyr display GFX runtime — generic display_write + ported GFX primitives
3
- //
4
- // Emits C++ that resolves a DT-bound display device, keeps a one-row line
5
- // buffer (no full framebuffer — see AGENTS.md rendering guardrails), and
6
- // implements fill_rect / draw_rect / draw_text / flush on top of display_write.
7
- // Text uses a minimal 5x7 bitmap font covering digits 0-9 and uppercase A-Z
8
- // only; unknown characters render as a space.
9
- //
10
- // EMIT BOUNDARY: emitted bytes land in user firmware. Covered by the TypeCAD
11
- // Runtime Exception (RUNTIME_EXCEPTION.md at the repo root).
12
- // ---------------------------------------------------------------------------
13
-
14
- import type { ZephyrDisplayProfile } from './profiles.js';
15
-
16
- export interface DisplayRuntimeResult {
17
- /** #include lines to force when the display is used. */
18
- readonly includes: string[];
19
- /** File-scope C++ declarations (device handle + line buffer). */
20
- readonly stateLines: string[];
21
- /** The C++ helper functions (display_init/fill_rect/draw_rect/draw_text/flush). */
22
- readonly helpers: string;
23
- /** The C++ font-table source (exposed for testing). */
24
- readonly fontTable: string;
25
- }
26
-
27
- // 5x7 bitmap glyphs for 0-9 and A-Z. Each glyph is 5 bytes (columns), MSB=top.
28
- // Unknown characters fall back to the space (all-zero columns).
29
- // (Compact hand-authored bitmaps; the test asserts presence of '0'..'9','A'..'Z'.)
30
- function fontTableCpp(): string {
31
- // A flat map: char -> 5 bytes. Built so the test sees '0'..'9' and 'A'..'Z'.
32
- const glyphs: Record<string, number[]> = {
33
- '0': [0x3e, 0x51, 0x49, 0x45, 0x3e],
34
- '1': [0x00, 0x42, 0x7f, 0x40, 0x00],
35
- '2': [0x42, 0x61, 0x51, 0x49, 0x46],
36
- '3': [0x21, 0x41, 0x45, 0x4b, 0x31],
37
- '4': [0x18, 0x14, 0x12, 0x7f, 0x10],
38
- '5': [0x27, 0x45, 0x45, 0x45, 0x39],
39
- '6': [0x3c, 0x4a, 0x49, 0x49, 0x30],
40
- '7': [0x01, 0x71, 0x09, 0x05, 0x03],
41
- '8': [0x36, 0x49, 0x49, 0x49, 0x36],
42
- '9': [0x06, 0x49, 0x49, 0x29, 0x1e],
43
- 'A': [0x7e, 0x11, 0x11, 0x11, 0x7e],
44
- 'B': [0x7f, 0x49, 0x49, 0x49, 0x36],
45
- 'C': [0x3e, 0x41, 0x41, 0x41, 0x22],
46
- 'D': [0x7f, 0x41, 0x41, 0x22, 0x1c],
47
- 'E': [0x7f, 0x49, 0x49, 0x49, 0x41],
48
- 'F': [0x7f, 0x09, 0x09, 0x09, 0x01],
49
- 'G': [0x3e, 0x41, 0x49, 0x49, 0x7a],
50
- 'H': [0x7f, 0x08, 0x08, 0x08, 0x7f],
51
- 'I': [0x00, 0x41, 0x7f, 0x41, 0x00],
52
- 'J': [0x20, 0x40, 0x41, 0x3f, 0x01],
53
- 'K': [0x7f, 0x08, 0x14, 0x22, 0x41],
54
- 'L': [0x7f, 0x40, 0x40, 0x40, 0x40],
55
- 'M': [0x7f, 0x02, 0x0c, 0x02, 0x7f],
56
- 'N': [0x7f, 0x04, 0x08, 0x10, 0x7f],
57
- 'O': [0x3e, 0x41, 0x41, 0x41, 0x3e],
58
- 'P': [0x7f, 0x09, 0x09, 0x09, 0x06],
59
- 'Q': [0x3e, 0x41, 0x51, 0x21, 0x5e],
60
- 'R': [0x7f, 0x09, 0x19, 0x29, 0x46],
61
- 'S': [0x46, 0x49, 0x49, 0x49, 0x31],
62
- 'T': [0x01, 0x01, 0x7f, 0x01, 0x01],
63
- 'U': [0x3f, 0x40, 0x40, 0x40, 0x3f],
64
- 'V': [0x1f, 0x20, 0x40, 0x20, 0x1f],
65
- 'W': [0x3f, 0x40, 0x38, 0x40, 0x3f],
66
- 'X': [0x63, 0x14, 0x08, 0x14, 0x63],
67
- 'Y': [0x07, 0x08, 0x70, 0x08, 0x07],
68
- 'Z': [0x61, 0x51, 0x49, 0x45, 0x43],
69
- };
70
- const lines: string[] = [
71
- '// Minimal 5x7 font: digits 0-9 and uppercase A-Z. Unknown chars → space.',
72
- '// (Rows for other codes are all-zero — the table is indexed by char code.)',
73
- 'static const uint8_t __tc_font5x7[128][5] = {',
74
- ];
75
- for (let c = 0; c < 128; c++) {
76
- const ch = String.fromCharCode(c);
77
- const known = Object.prototype.hasOwnProperty.call(glyphs, ch);
78
- const g = glyphs[ch] ?? [0x00, 0x00, 0x00, 0x00, 0x00];
79
- // Annotate known glyphs (0-9, A-Z) with their char literal; other rows use
80
- // only the code point so the table carries no stray char literals.
81
- const label = known ? ` '${ch}'` : ` ${c}`;
82
- lines.push(` {0x${g[0].toString(16).padStart(2, '0')}, 0x${g[1].toString(16).padStart(2, '0')}, 0x${g[2].toString(16).padStart(2, '0')}, 0x${g[3].toString(16).padStart(2, '0')}, 0x${g[4].toString(16).padStart(2, '0')}}, //${label}`);
83
- }
84
- lines.push('};');
85
- return lines.join('\n');
86
- }
87
-
88
- /**
89
- * Build the display runtime C++ for a profile.
90
- *
91
- * Two rendering models, selected by `profile.colorFormat`:
92
- * - `'rgb565'` (TFT): a one-row line buffer (no full framebuffer — see
93
- * AGENTS.md rendering guardrails); fill_rect/draw_rect/draw_text stream
94
- * each row via display_write.
95
- * - `'mono'` (OLED, e.g. SSD1306): a full framebuffer — the standard model
96
- * for page-buffered monochrome panels (the AGENTS.md "no full framebuffer"
97
- * guardrail targets RGB SPI TFTs, not mono OLEDs). draw ops set bits;
98
- * display_flush pushes the whole framebuffer. The MONO01 packing is
99
- * horizontal, MSB-first (Zephyr convention): byte = (y*rowBytes)+(x>>3),
100
- * bit = 0x80>>(x&7).
101
- */
102
- export function buildDisplayRuntime(profile: ZephyrDisplayProfile): DisplayRuntimeResult {
103
- const w = profile.width;
104
- const h = profile.height;
105
- const isMono = profile.colorFormat === 'mono';
106
-
107
- // Backlight is optional: the overlay emits the DT alias only when a backlight
108
- // GPIO is configured. Guard with DT_HAS_ALIAS so this compiles whether or not
109
- // the alias exists (DT_NODE_HAS_STATUS(DT_ALIAS(...)) is version-dependent
110
- // when the alias is missing).
111
- const blInit = profile.backlight
112
- ? `#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`
113
- : '';
114
-
115
- const stateLines = isMono
116
- ? [
117
- '// CUTTLEFISH_DISPLAY_BEGIN',
118
- `static const struct device* __tc_display = DEVICE_DT_GET(DT_NODELABEL(${profile.dtLabel}));`,
119
- `// Mono framebuffer (Zephyr MONO01: horizontal, MSB-first). ${(w + 7) >> 3} bytes/row x ${h} rows.`,
120
- `static uint8_t __tc_display_fb[((${w} * ${h}) + 7) / 8];`,
121
- '// CUTTLEFISH_DISPLAY_END',
122
- ]
123
- : [
124
- '// CUTTLEFISH_DISPLAY_BEGIN',
125
- `static const struct device* __tc_display = DEVICE_DT_GET(DT_NODELABEL(${profile.dtLabel}));`,
126
- `static uint16_t __tc_display_line[${w}]; // one-row line buffer (rgb565)`,
127
- '// CUTTLEFISH_DISPLAY_END',
128
- ];
129
-
130
- const helpers = isMono
131
- ? monoHelpers(w, h, blInit)
132
- : rgb565Helpers(w, blInit);
133
-
134
- const fontTable = fontTableCpp();
135
-
136
- return {
137
- includes: ['<zephyr/drivers/display.h>'],
138
- stateLines,
139
- helpers,
140
- fontTable,
141
- };
142
- }
143
-
144
- /**
145
- * Mono (OLED) helpers: a full framebuffer + bit-packing. SSD1306-class panels
146
- * are page-buffered, so draw ops set bits in the framebuffer and display_flush
147
- * pushes the whole buffer. color != 0 ⇒ lit.
148
- */
149
- function monoHelpers(w: number, h: number, blInit: string): string {
150
- const rowBytes = (w + 7) >> 3; // bytes per row (w is byte-aligned for 128-wide panels)
151
- return `
152
- // MONO01 pixel packing: byte = (y * ${rowBytes}) + (x >> 3), bit = 0x80 >> (x & 7).
153
- static inline void __tc_set_pixel(uint16_t x, uint16_t y, uint8_t on) {
154
- if ((x >= ${w}U) || (y >= ${h}U)) { return; }
155
- uint16_t idx = static_cast<uint16_t>((static_cast<uint32_t>(y) * ${rowBytes}U) + (x >> 3));
156
- uint8_t mask = static_cast<uint8_t>(0x80U >> (x & 7U));
157
- if (on != 0U) {
158
- __tc_display_fb[idx] = static_cast<uint8_t>(__tc_display_fb[idx] | mask);
159
- } else {
160
- __tc_display_fb[idx] = static_cast<uint8_t>(__tc_display_fb[idx] & static_cast<uint8_t>(~mask));
161
- }
162
- }
163
-
164
- static inline void display_init(void) {
165
- if (!device_is_ready(__tc_display)) { for (;;) { k_msleep(1000); } }
166
- ${blInit}
167
- for (uint16_t i = 0; i < static_cast<uint16_t>(sizeof(__tc_display_fb)); i++) { __tc_display_fb[i] = 0U; }
168
- display_blanking_off(__tc_display);
169
- }
170
-
171
- static inline void display_fill_rect(uint16_t x, uint16_t y, uint16_t rw, uint16_t rh, uint16_t color) {
172
- uint8_t on = (color != 0U) ? 1U : 0U;
173
- for (uint16_t row = 0; row < rh; row++) {
174
- for (uint16_t i = 0; i < rw; i++) {
175
- __tc_set_pixel(static_cast<uint16_t>(x + i), static_cast<uint16_t>(y + row), on);
176
- }
177
- }
178
- }
179
-
180
- static inline void display_draw_rect(uint16_t x, uint16_t y, uint16_t rw, uint16_t rh, uint16_t color) {
181
- uint8_t on = (color != 0U) ? 1U : 0U;
182
- for (uint16_t i = 0; i < rw; i++) {
183
- __tc_set_pixel(static_cast<uint16_t>(x + i), y, on);
184
- __tc_set_pixel(static_cast<uint16_t>(x + i), static_cast<uint16_t>(y + rh - 1U), on);
185
- }
186
- for (uint16_t row = 1U; row < rh - 1U; row++) {
187
- __tc_set_pixel(x, static_cast<uint16_t>(y + row), on);
188
- __tc_set_pixel(static_cast<uint16_t>(x + rw - 1U), static_cast<uint16_t>(y + row), on);
189
- }
190
- }
191
-
192
- static inline void display_draw_text(uint16_t x, uint16_t y, const char* text, uint16_t color) {
193
- uint8_t on = (color != 0U) ? 1U : 0U;
194
- uint16_t cx = x;
195
- for (const char* p = text; *p != 0; p++) {
196
- uint8_t uc = static_cast<uint8_t>(*p);
197
- if (uc >= 128U) { uc = static_cast<uint8_t>(' '); }
198
- const uint8_t* glyph = &__tc_font5x7[uc][0];
199
- if (uc >= static_cast<uint8_t>('a') && uc <= static_cast<uint8_t>('z')) {
200
- glyph = &__tc_font5x7[uc - 32U][0];
201
- } else if (uc < static_cast<uint8_t>('0')
202
- || (uc > static_cast<uint8_t>('9') && uc < static_cast<uint8_t>('A'))
203
- || uc > static_cast<uint8_t>('Z')) {
204
- glyph = &__tc_font5x7[static_cast<uint8_t>(' ')][0];
205
- }
206
- for (uint16_t col = 0; col < 5U; col++) {
207
- uint8_t bits = glyph[col];
208
- for (uint16_t row = 0; row < 7U; row++) {
209
- if ((bits & static_cast<uint8_t>(1U << row)) != 0U) {
210
- __tc_set_pixel(static_cast<uint16_t>(cx + col), static_cast<uint16_t>(y + row), on);
211
- }
212
- }
213
- }
214
- cx = static_cast<uint16_t>(cx + 6U);
215
- }
216
- }
217
-
218
- static inline void display_flush(void) {
219
- struct display_buffer_descriptor __desc;
220
- __desc.buf_size = sizeof(__tc_display_fb); // ${rowBytes} * ${h} bytes
221
- __desc.width = ${w}U;
222
- __desc.height = ${h}U;
223
- __desc.pitch = ${rowBytes}U; // bytes per row
224
- __desc.frame_incomplete = false;
225
- (void)display_write(__tc_display, 0, 0, &__desc, __tc_display_fb);
226
- }
227
- `;
228
- }
229
-
230
- /**
231
- * RGB565 (TFT) helpers: a one-row line buffer; each op streams rows via
232
- * display_write. No full framebuffer (AGENTS.md rendering guardrails).
233
- */
234
- function rgb565Helpers(w: number, blInit: string): string {
235
- return `
236
- // Write a single row of \`rw\` rgb565 pixels at (x,y). Builds the
237
- // display_buffer_descriptor the Zephyr display_write API requires (rgb565 =
238
- // 2 bytes/pixel) and pushes the one-row line buffer.
239
- static inline void __tc_display_write_row(uint16_t x, uint16_t y, uint16_t rw) {
240
- struct display_buffer_descriptor __desc;
241
- __desc.buf_size = static_cast<uint32_t>(rw) * 2U; // 2 bytes per rgb565 pixel
242
- __desc.width = rw;
243
- __desc.height = 1U;
244
- __desc.pitch = rw;
245
- __desc.frame_incomplete = false;
246
- (void)display_write(__tc_display, x, y, &__desc, __tc_display_line);
247
- }
248
-
249
- static inline void display_init(void) {
250
- if (!device_is_ready(__tc_display)) { for (;;) { k_msleep(1000); } }
251
- ${blInit}
252
- display_blanking_off(__tc_display);
253
- }
254
-
255
- static inline void display_fill_rect(uint16_t x, uint16_t y, uint16_t rw, uint16_t h, uint16_t color) {
256
- for (uint16_t row = 0; row < h; row++) {
257
- for (uint16_t i = 0; i < rw; i++) { __tc_display_line[i] = color; }
258
- __tc_display_write_row(x, static_cast<uint16_t>(y + row), rw);
259
- }
260
- }
261
-
262
- static inline void display_draw_rect(uint16_t x, uint16_t y, uint16_t rw, uint16_t h, uint16_t color) {
263
- // Top + bottom edges.
264
- for (uint16_t i = 0; i < rw; i++) { __tc_display_line[i] = color; }
265
- __tc_display_write_row(x, y, rw);
266
- __tc_display_write_row(x, static_cast<uint16_t>(y + h - 1U), rw);
267
- // Left + right edges.
268
- for (uint16_t row = 1U; row < h - 1U; row++) {
269
- __tc_display_line[0] = color;
270
- if (rw > 1U) { __tc_display_line[rw - 1U] = color; }
271
- __tc_display_write_row(x, static_cast<uint16_t>(y + row), rw);
272
- }
273
- }
274
-
275
- static inline void display_draw_text(uint16_t x, uint16_t y, const char* text, uint16_t color) {
276
- uint16_t cx = x;
277
- for (const char* p = text; *p != 0; p++) {
278
- // Read the byte UNSIGNED so bytes >= 0x80 don't index the 128-entry
279
- // font table negatively (signed char would make 0x80 == -128).
280
- uint8_t uc = static_cast<uint8_t>(*p);
281
- if (uc >= 128U) { uc = static_cast<uint8_t>(' '); } // unknown → space
282
- const uint8_t* glyph = &__tc_font5x7[uc][0];
283
- // Lowercase → uppercase; anything outside 0-9/A-Z → space glyph.
284
- if (uc >= static_cast<uint8_t>('a') && uc <= static_cast<uint8_t>('z')) {
285
- glyph = &__tc_font5x7[uc - 32U][0];
286
- } else if (uc < static_cast<uint8_t>('0')
287
- || (uc > static_cast<uint8_t>('9') && uc < static_cast<uint8_t>('A'))
288
- || uc > static_cast<uint8_t>('Z')) {
289
- glyph = &__tc_font5x7[static_cast<uint8_t>(' ')][0];
290
- }
291
- for (uint16_t col = 0; col < 5U; col++) {
292
- uint8_t bits = glyph[col];
293
- for (uint16_t row = 0; row < 7U; row++) {
294
- __tc_display_line[0] = ((bits & static_cast<uint8_t>(1U << row)) != 0U) ? color : 0U;
295
- __tc_display_write_row(static_cast<uint16_t>(cx + col), static_cast<uint16_t>(y + row), 1U);
296
- }
297
- }
298
- cx = static_cast<uint16_t>(cx + 6U); // 5 cols + 1 space
299
- }
300
- }
301
-
302
- static inline void display_flush(void) {
303
- // No-op: writes are immediate via display_write; there is no framebuffer to push.
304
- }
305
- `;
306
- }
1
+ // ---------------------------------------------------------------------------
2
+ // Zephyr display GFX runtime — generic display_write + ported GFX primitives
3
+ //
4
+ // Emits C++ that resolves a DT-bound display device, keeps a one-row line
5
+ // buffer (no full framebuffer — see AGENTS.md rendering guardrails), and
6
+ // implements fill_rect / draw_rect / draw_text / flush on top of display_write.
7
+ // Text uses a minimal 5x7 bitmap font covering digits 0-9 and uppercase A-Z
8
+ // only; unknown characters render as a space.
9
+ //
10
+ // EMIT BOUNDARY: emitted bytes land in user firmware. Covered by the TypeCAD
11
+ // Runtime Exception (RUNTIME_EXCEPTION.md at the repo root).
12
+ // ---------------------------------------------------------------------------
13
+
14
+ import type { ZephyrDisplayProfile } from './profiles.js';
15
+ import { displayFactsLine } from './profiles.js';
16
+
17
+ export interface DisplayRuntimeResult {
18
+ /** #include lines to force when the display is used. */
19
+ readonly includes: string[];
20
+ /** File-scope C++ declarations (device handle + line buffer). */
21
+ readonly stateLines: string[];
22
+ /** The C++ helper functions (display_init/fill_rect/draw_rect/draw_text/flush). */
23
+ readonly helpers: string;
24
+ /** The C++ font-table source (exposed for testing). */
25
+ readonly fontTable: string;
26
+ }
27
+
28
+ // 5x7 bitmap glyphs for 0-9 and A-Z. Each glyph is 5 bytes (columns), MSB=top.
29
+ // Unknown characters fall back to the space (all-zero columns).
30
+ // (Compact hand-authored bitmaps; the test asserts presence of '0'..'9','A'..'Z'.)
31
+ function fontTableCpp(): string {
32
+ // A flat map: char -> 5 bytes. Built so the test sees '0'..'9' and 'A'..'Z'.
33
+ const glyphs: Record<string, number[]> = {
34
+ '0': [0x3e, 0x51, 0x49, 0x45, 0x3e],
35
+ '1': [0x00, 0x42, 0x7f, 0x40, 0x00],
36
+ '2': [0x42, 0x61, 0x51, 0x49, 0x46],
37
+ '3': [0x21, 0x41, 0x45, 0x4b, 0x31],
38
+ '4': [0x18, 0x14, 0x12, 0x7f, 0x10],
39
+ '5': [0x27, 0x45, 0x45, 0x45, 0x39],
40
+ '6': [0x3c, 0x4a, 0x49, 0x49, 0x30],
41
+ '7': [0x01, 0x71, 0x09, 0x05, 0x03],
42
+ '8': [0x36, 0x49, 0x49, 0x49, 0x36],
43
+ '9': [0x06, 0x49, 0x49, 0x29, 0x1e],
44
+ 'A': [0x7e, 0x11, 0x11, 0x11, 0x7e],
45
+ 'B': [0x7f, 0x49, 0x49, 0x49, 0x36],
46
+ 'C': [0x3e, 0x41, 0x41, 0x41, 0x22],
47
+ 'D': [0x7f, 0x41, 0x41, 0x22, 0x1c],
48
+ 'E': [0x7f, 0x49, 0x49, 0x49, 0x41],
49
+ 'F': [0x7f, 0x09, 0x09, 0x09, 0x01],
50
+ 'G': [0x3e, 0x41, 0x49, 0x49, 0x7a],
51
+ 'H': [0x7f, 0x08, 0x08, 0x08, 0x7f],
52
+ 'I': [0x00, 0x41, 0x7f, 0x41, 0x00],
53
+ 'J': [0x20, 0x40, 0x41, 0x3f, 0x01],
54
+ 'K': [0x7f, 0x08, 0x14, 0x22, 0x41],
55
+ 'L': [0x7f, 0x40, 0x40, 0x40, 0x40],
56
+ 'M': [0x7f, 0x02, 0x0c, 0x02, 0x7f],
57
+ 'N': [0x7f, 0x04, 0x08, 0x10, 0x7f],
58
+ 'O': [0x3e, 0x41, 0x41, 0x41, 0x3e],
59
+ 'P': [0x7f, 0x09, 0x09, 0x09, 0x06],
60
+ 'Q': [0x3e, 0x41, 0x51, 0x21, 0x5e],
61
+ 'R': [0x7f, 0x09, 0x19, 0x29, 0x46],
62
+ 'S': [0x46, 0x49, 0x49, 0x49, 0x31],
63
+ 'T': [0x01, 0x01, 0x7f, 0x01, 0x01],
64
+ 'U': [0x3f, 0x40, 0x40, 0x40, 0x3f],
65
+ 'V': [0x1f, 0x20, 0x40, 0x20, 0x1f],
66
+ 'W': [0x3f, 0x40, 0x38, 0x40, 0x3f],
67
+ 'X': [0x63, 0x14, 0x08, 0x14, 0x63],
68
+ 'Y': [0x07, 0x08, 0x70, 0x08, 0x07],
69
+ 'Z': [0x61, 0x51, 0x49, 0x45, 0x43],
70
+ };
71
+ const lines: string[] = [
72
+ '// Minimal 5x7 font: digits 0-9 and uppercase A-Z. Unknown chars → space.',
73
+ '// (Rows for other codes are all-zero — the table is indexed by char code.)',
74
+ 'static const uint8_t __tc_font5x7[128][5] = {',
75
+ ];
76
+ for (let c = 0; c < 128; c++) {
77
+ const ch = String.fromCharCode(c);
78
+ const known = Object.prototype.hasOwnProperty.call(glyphs, ch);
79
+ const g = glyphs[ch] ?? [0x00, 0x00, 0x00, 0x00, 0x00];
80
+ // Annotate known glyphs (0-9, A-Z) with their char literal; other rows use
81
+ // only the code point so the table carries no stray char literals.
82
+ const label = known ? ` '${ch}'` : ` ${c}`;
83
+ lines.push(` {0x${g[0].toString(16).padStart(2, '0')}, 0x${g[1].toString(16).padStart(2, '0')}, 0x${g[2].toString(16).padStart(2, '0')}, 0x${g[3].toString(16).padStart(2, '0')}, 0x${g[4].toString(16).padStart(2, '0')}}, //${label}`);
84
+ }
85
+ lines.push('};');
86
+ return lines.join('\n');
87
+ }
88
+
89
+ /**
90
+ * Build the display runtime C++ for a profile.
91
+ *
92
+ * Two rendering models, selected by `profile.colorFormat`:
93
+ * - `'rgb565'` (TFT): a one-row line buffer (no full framebuffer — see
94
+ * AGENTS.md rendering guardrails); fill_rect/draw_rect/draw_text stream
95
+ * each row via display_write.
96
+ * - `'mono'` (OLED, e.g. SSD1306): a full framebuffer — the standard model
97
+ * for page-buffered monochrome panels (the AGENTS.md "no full framebuffer"
98
+ * guardrail targets RGB SPI TFTs, not mono OLEDs). draw ops set bits;
99
+ * display_flush pushes the whole framebuffer as VTILED MONO01 (the layout
100
+ * the driver consumes; see monoHelpers).
101
+ */
102
+ export function buildDisplayRuntime(profile: ZephyrDisplayProfile): DisplayRuntimeResult {
103
+ const w = profile.width;
104
+ const h = profile.height;
105
+ const isMono = profile.colorFormat === 'mono';
106
+
107
+ // Backlight is optional: the overlay emits the DT alias only when a backlight
108
+ // GPIO is configured. Guard with DT_HAS_ALIAS so this compiles whether or not
109
+ // the alias exists (DT_NODE_HAS_STATUS(DT_ALIAS(...)) is version-dependent
110
+ // when the alias is missing).
111
+ const blInit = profile.backlight
112
+ ? `#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`
113
+ : '';
114
+
115
+ const stateLines = isMono
116
+ ? [
117
+ '// CUTTLEFISH_DISPLAY_BEGIN',
118
+ `static const struct device* __tc_display = DEVICE_DT_GET(DT_NODELABEL(${profile.dtLabel}));`,
119
+ `// Mono framebuffer, vtiled MONO01 — the layout Zephyr's ssd1306-class`,
120
+ `// drivers expect from display_write (SCREEN_INFO_MONO_VTILED, matching`,
121
+ `// cfb's draw_point): byte = fb[(y>>3)*${w} + x], bit = 1<<(y&7).`,
122
+ `// pitch must equal width and (y, height) must be multiples of 8 — the`,
123
+ `// full-frame flush satisfies both by construction.`,
124
+ `static uint8_t __tc_display_fb[(${w} * ${h} + 7) / 8];`,
125
+ '// CUTTLEFISH_DISPLAY_END',
126
+ ]
127
+ : [
128
+ '// CUTTLEFISH_DISPLAY_BEGIN',
129
+ `static const struct device* __tc_display = DEVICE_DT_GET(DT_NODELABEL(${profile.dtLabel}));`,
130
+ `static uint16_t __tc_display_line[${w}]; // one-row line buffer (rgb565)`,
131
+ '// CUTTLEFISH_DISPLAY_END',
132
+ ];
133
+
134
+ const helpers = isMono
135
+ ? monoHelpers(w, h, blInit)
136
+ : rgb565Helpers(w, blInit);
137
+
138
+ const fontTable = fontTableCpp();
139
+
140
+ return {
141
+ includes: ['<zephyr/drivers/display.h>'],
142
+ // Profile marker: lets the toolchain recover which profile emitted this
143
+ // runtime (profileFromEmittedSource in profiles.ts) instead of re-deriving
144
+ // geometry from the emitted C.
145
+ stateLines: [`// typecad-display-profile: ${profile.driver}`, displayFactsLine(profile), ...stateLines],
146
+ helpers,
147
+ fontTable,
148
+ };
149
+ }
150
+
151
+ /**
152
+ * Mono (OLED) helpers: a full framebuffer + bit-packing. SSD1306-class panels
153
+ * are page-buffered, so draw ops set bits in the framebuffer and display_flush
154
+ * pushes the whole buffer. color != 0 ⇒ lit. The packing is VTILED MONO01
155
+ * (byte = 8 vertical pixels, fb[(y>>3)*W + x], bit = 1<<(y&7)) because that
156
+ * is what the driver's display_write consumes — a horizontal-packed buffer
157
+ * with pitch = rowBytes fails the driver's pitch==width check (-EINVAL) and
158
+ * nothing reaches the panel.
159
+ */
160
+ function monoHelpers(w: number, h: number, blInit: string): string {
161
+ return `
162
+ // VTILED MONO01 pixel packing: byte = fb[(y>>3)*${w}] + x, bit = 1<<(y&7).
163
+ static inline void __tc_set_pixel(uint16_t x, uint16_t y, uint8_t on) {
164
+ if ((x >= ${w}U) || (y >= ${h}U)) { return; }
165
+ uint16_t idx = static_cast<uint16_t>((static_cast<uint32_t>(y) >> 3) * ${w}U + x);
166
+ uint8_t mask = static_cast<uint8_t>(1U << (y & 7U));
167
+ if (on != 0U) {
168
+ __tc_display_fb[idx] = static_cast<uint8_t>(__tc_display_fb[idx] | mask);
169
+ } else {
170
+ __tc_display_fb[idx] = static_cast<uint8_t>(__tc_display_fb[idx] & static_cast<uint8_t>(~mask));
171
+ }
172
+ }
173
+
174
+ static inline void display_init(void) {
175
+ if (!device_is_ready(__tc_display)) { for (;;) { k_msleep(1000); } }
176
+ ${blInit}
177
+ for (uint16_t i = 0; i < static_cast<uint16_t>(sizeof(__tc_display_fb)); i++) { __tc_display_fb[i] = 0U; }
178
+ display_blanking_off(__tc_display);
179
+ }
180
+
181
+ static inline void display_fill_rect(uint16_t x, uint16_t y, uint16_t rw, uint16_t rh, uint16_t color) {
182
+ uint8_t on = (color != 0U) ? 1U : 0U;
183
+ for (uint16_t row = 0; row < rh; row++) {
184
+ for (uint16_t i = 0; i < rw; i++) {
185
+ __tc_set_pixel(static_cast<uint16_t>(x + i), static_cast<uint16_t>(y + row), on);
186
+ }
187
+ }
188
+ }
189
+
190
+ static inline void display_draw_rect(uint16_t x, uint16_t y, uint16_t rw, uint16_t rh, uint16_t color) {
191
+ uint8_t on = (color != 0U) ? 1U : 0U;
192
+ for (uint16_t i = 0; i < rw; i++) {
193
+ __tc_set_pixel(static_cast<uint16_t>(x + i), y, on);
194
+ __tc_set_pixel(static_cast<uint16_t>(x + i), static_cast<uint16_t>(y + rh - 1U), on);
195
+ }
196
+ for (uint16_t row = 1U; row < rh - 1U; row++) {
197
+ __tc_set_pixel(x, static_cast<uint16_t>(y + row), on);
198
+ __tc_set_pixel(static_cast<uint16_t>(x + rw - 1U), static_cast<uint16_t>(y + row), on);
199
+ }
200
+ }
201
+
202
+ static inline void display_draw_text(uint16_t x, uint16_t y, const char* text, uint16_t color) {
203
+ uint8_t on = (color != 0U) ? 1U : 0U;
204
+ uint16_t cx = x;
205
+ for (const char* p = text; *p != 0; p++) {
206
+ uint8_t uc = static_cast<uint8_t>(*p);
207
+ if (uc >= 128U) { uc = static_cast<uint8_t>(' '); }
208
+ const uint8_t* glyph = &__tc_font5x7[uc][0];
209
+ if (uc >= static_cast<uint8_t>('a') && uc <= static_cast<uint8_t>('z')) {
210
+ glyph = &__tc_font5x7[uc - 32U][0];
211
+ } else if (uc < static_cast<uint8_t>('0')
212
+ || (uc > static_cast<uint8_t>('9') && uc < static_cast<uint8_t>('A'))
213
+ || uc > static_cast<uint8_t>('Z')) {
214
+ glyph = &__tc_font5x7[static_cast<uint8_t>(' ')][0];
215
+ }
216
+ for (uint16_t col = 0; col < 5U; col++) {
217
+ uint8_t bits = glyph[col];
218
+ for (uint16_t row = 0; row < 7U; row++) {
219
+ if ((bits & static_cast<uint8_t>(1U << row)) != 0U) {
220
+ __tc_set_pixel(static_cast<uint16_t>(cx + col), static_cast<uint16_t>(y + row), on);
221
+ }
222
+ }
223
+ }
224
+ cx = static_cast<uint16_t>(cx + 6U);
225
+ }
226
+ }
227
+
228
+ static inline void display_flush(void) {
229
+ // Full-frame push. pitch = width and height a multiple of 8 are the
230
+ // ssd1306 driver's hard requirements — the full frame satisfies both.
231
+ struct display_buffer_descriptor __desc;
232
+ __desc.buf_size = sizeof(__tc_display_fb); // ${w} * ${h} / 8 bytes
233
+ __desc.width = ${w}U;
234
+ __desc.height = ${h}U;
235
+ __desc.pitch = ${w}U; // vtiled: bytes per 8-pixel page row == width
236
+ __desc.frame_incomplete = false;
237
+ (void)display_write(__tc_display, 0, 0, &__desc, __tc_display_fb);
238
+ }
239
+ `;
240
+ }
241
+
242
+ /**
243
+ * RGB565 (TFT) helpers: a one-row line buffer; each op streams rows via
244
+ * display_write. No full framebuffer (AGENTS.md rendering guardrails).
245
+ */
246
+ function rgb565Helpers(w: number, blInit: string): string {
247
+ return `
248
+ // Write a single row of \`rw\` rgb565 pixels at (x,y). Builds the
249
+ // display_buffer_descriptor the Zephyr display_write API requires (rgb565 =
250
+ // 2 bytes/pixel) and pushes the one-row line buffer.
251
+ static inline void __tc_display_write_row(uint16_t x, uint16_t y, uint16_t rw) {
252
+ struct display_buffer_descriptor __desc;
253
+ __desc.buf_size = static_cast<uint32_t>(rw) * 2U; // 2 bytes per rgb565 pixel
254
+ __desc.width = rw;
255
+ __desc.height = 1U;
256
+ __desc.pitch = rw;
257
+ __desc.frame_incomplete = false;
258
+ (void)display_write(__tc_display, x, y, &__desc, __tc_display_line);
259
+ }
260
+
261
+ static inline void display_init(void) {
262
+ if (!device_is_ready(__tc_display)) { for (;;) { k_msleep(1000); } }
263
+ ${blInit}
264
+ display_blanking_off(__tc_display);
265
+ }
266
+
267
+ static inline void display_fill_rect(uint16_t x, uint16_t y, uint16_t rw, uint16_t h, uint16_t color) {
268
+ for (uint16_t row = 0; row < h; row++) {
269
+ for (uint16_t i = 0; i < rw; i++) { __tc_display_line[i] = color; }
270
+ __tc_display_write_row(x, static_cast<uint16_t>(y + row), rw);
271
+ }
272
+ }
273
+
274
+ static inline void display_draw_rect(uint16_t x, uint16_t y, uint16_t rw, uint16_t h, uint16_t color) {
275
+ // Top + bottom edges.
276
+ for (uint16_t i = 0; i < rw; i++) { __tc_display_line[i] = color; }
277
+ __tc_display_write_row(x, y, rw);
278
+ __tc_display_write_row(x, static_cast<uint16_t>(y + h - 1U), rw);
279
+ // Left + right edges.
280
+ for (uint16_t row = 1U; row < h - 1U; row++) {
281
+ __tc_display_line[0] = color;
282
+ if (rw > 1U) { __tc_display_line[rw - 1U] = color; }
283
+ __tc_display_write_row(x, static_cast<uint16_t>(y + row), rw);
284
+ }
285
+ }
286
+
287
+ static inline void display_draw_text(uint16_t x, uint16_t y, const char* text, uint16_t color) {
288
+ uint16_t cx = x;
289
+ for (const char* p = text; *p != 0; p++) {
290
+ // Read the byte UNSIGNED so bytes >= 0x80 don't index the 128-entry
291
+ // font table negatively (signed char would make 0x80 == -128).
292
+ uint8_t uc = static_cast<uint8_t>(*p);
293
+ if (uc >= 128U) { uc = static_cast<uint8_t>(' '); } // unknown → space
294
+ const uint8_t* glyph = &__tc_font5x7[uc][0];
295
+ // Lowercase → uppercase; anything outside 0-9/A-Z → space glyph.
296
+ if (uc >= static_cast<uint8_t>('a') && uc <= static_cast<uint8_t>('z')) {
297
+ glyph = &__tc_font5x7[uc - 32U][0];
298
+ } else if (uc < static_cast<uint8_t>('0')
299
+ || (uc > static_cast<uint8_t>('9') && uc < static_cast<uint8_t>('A'))
300
+ || uc > static_cast<uint8_t>('Z')) {
301
+ glyph = &__tc_font5x7[static_cast<uint8_t>(' ')][0];
302
+ }
303
+ for (uint16_t col = 0; col < 5U; col++) {
304
+ uint8_t bits = glyph[col];
305
+ for (uint16_t row = 0; row < 7U; row++) {
306
+ __tc_display_line[0] = ((bits & static_cast<uint8_t>(1U << row)) != 0U) ? color : 0U;
307
+ __tc_display_write_row(static_cast<uint16_t>(cx + col), static_cast<uint16_t>(y + row), 1U);
308
+ }
309
+ }
310
+ cx = static_cast<uint16_t>(cx + 6U); // 5 cols + 1 space
311
+ }
312
+ }
313
+
314
+ static inline void display_flush(void) {
315
+ // No-op: writes are immediate via display_write; there is no framebuffer to push.
316
+ }
317
+ `;
318
+ }