├── .gitignore ├── .github └── FUNDING.yml ├── lvgl.zig ├── lvgltest.zig ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | zig-cache 3 | lvgltest-analysis.json 4 | lvgltest.bc 5 | lvgltest.ll 6 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [lupyuen] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ['paypal.me/lupyuen'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /lvgl.zig: -------------------------------------------------------------------------------- 1 | //! LVGL Module that wraps the LVGL API (incomplete) 2 | 3 | /// Import the Zig Standard Library 4 | const std = @import("std"); 5 | 6 | /// Import the LVGL Library from C 7 | const c = @cImport({ 8 | // NuttX Defines 9 | @cDefine("__NuttX__", ""); 10 | @cDefine("NDEBUG", ""); 11 | @cDefine("ARCH_RISCV", ""); 12 | @cDefine("LV_LVGL_H_INCLUDE_SIMPLE", ""); 13 | 14 | // Workaround for "Unable to translate macro: undefined identifier `LL`" 15 | @cDefine("LL", ""); 16 | @cDefine("__int_c_join(a, b)", "a"); // Bypass zig/lib/include/stdint.h 17 | 18 | // NuttX Header Files 19 | @cInclude("arch/types.h"); 20 | @cInclude("../../nuttx/include/limits.h"); 21 | 22 | // LVGL Header Files 23 | @cInclude("lvgl/lvgl.h"); 24 | }); 25 | 26 | /// Return the Active Screen 27 | pub fn getActiveScreen() !Object { 28 | 29 | // Get the Active Screen 30 | const screen = c.lv_scr_act(); 31 | 32 | // If successfully fetched... 33 | if (screen) |s| { 34 | // Wrap Active Screen as Object and return it 35 | return Object.init(s); 36 | } else { 37 | // Unable to get Active Screen 38 | std.log.err("lv_scr_act failed", .{}); 39 | return LvglError.UnknownError; 40 | } 41 | } 42 | 43 | /// LVGL Object 44 | pub const Object = struct { 45 | 46 | /// Pointer to LVGL Object 47 | obj: *c.lv_obj_t, 48 | 49 | /// Init the Object 50 | pub fn init(obj: *c.lv_obj_t) Object { 51 | return .{ .obj = obj }; 52 | } 53 | 54 | /// Create a Label as a child of the Object 55 | pub fn createLabel(self: *Object) !Label { 56 | 57 | // Assume we won't copy from another Object 58 | const copy: ?*const c.lv_obj_t = null; 59 | 60 | // Create the Label 61 | const label = c.lv_label_create(self.obj, copy); 62 | 63 | // If successfully created... 64 | if (label) |l| { 65 | // Wrap as Label and return it 66 | return Label.init(l); 67 | } else { 68 | // Unable to create Label 69 | std.log.err("lv_label_create failed", .{}); 70 | return LvglError.UnknownError; 71 | } 72 | } 73 | }; 74 | 75 | /// LVGL Label 76 | pub const Label = struct { 77 | 78 | /// Pointer to LVGL Label 79 | obj: *c.lv_obj_t, 80 | 81 | /// Init the Label 82 | pub fn init(obj: *c.lv_obj_t) Label { 83 | return .{ .obj = obj }; 84 | } 85 | 86 | /// Set the wrapping of long lines in the label text 87 | pub fn setLongMode(self: *Label, long_mode: c.lv_label_long_mode_t) void { 88 | c.lv_label_set_long_mode(self.obj, long_mode); 89 | } 90 | 91 | /// Set the label text alignment 92 | pub fn setAlign(self: *Label, alignment: c.lv_label_align_t) void { 93 | c.lv_label_set_align(self.obj, alignment); 94 | } 95 | 96 | /// Enable or disable color codes in the label text 97 | pub fn setRecolor(self: *Label, en: bool) void { 98 | c.lv_label_set_recolor(self.obj, en); 99 | } 100 | 101 | /// Set the label text and colors 102 | pub fn setText(self: *Label, text: [*c]const u8) void { 103 | c.lv_label_set_text(self.obj, text); 104 | } 105 | 106 | /// Set the object width 107 | pub fn setWidth(self: *Label, w: c.lv_coord_t) void { 108 | c.lv_obj_set_width(self.obj, w); 109 | } 110 | 111 | /// Set the object alignment 112 | pub fn alignObject(self: *Label, alignment: c.lv_align_t, x_ofs: c.lv_coord_t, y_ofs: c.lv_coord_t) void { 113 | const base: ?*const c.lv_obj_t = null; 114 | c.lv_obj_align(self.obj, base, alignment, x_ofs, y_ofs); 115 | } 116 | }; 117 | 118 | /// LVGL Errors 119 | pub const LvglError = error{ UnknownError }; 120 | -------------------------------------------------------------------------------- /lvgltest.zig: -------------------------------------------------------------------------------- 1 | //! LVGL Test App that renders an LVGL Screen and handles Touch Input 2 | 3 | /// Import the Zig Standard Library 4 | const std = @import("std"); 5 | 6 | /// Import the LVGL Module 7 | const lvgl = @import("lvgl.zig"); 8 | 9 | /// Import the LVGL Library from C 10 | const c = @cImport({ 11 | // NuttX Defines 12 | @cDefine("__NuttX__", ""); 13 | @cDefine("NDEBUG", ""); 14 | @cDefine("ARCH_RISCV", ""); 15 | @cDefine("LV_LVGL_H_INCLUDE_SIMPLE", ""); 16 | 17 | // Workaround for "Unable to translate macro: undefined identifier `LL`" 18 | @cDefine("LL", ""); 19 | @cDefine("__int_c_join(a, b)", "a"); // Bypass zig/lib/include/stdint.h 20 | 21 | // NuttX Header Files 22 | @cInclude("arch/types.h"); 23 | @cInclude("../../nuttx/include/limits.h"); 24 | @cInclude("stdio.h"); 25 | @cInclude("nuttx/config.h"); 26 | @cInclude("sys/boardctl.h"); 27 | @cInclude("unistd.h"); 28 | @cInclude("stddef.h"); 29 | @cInclude("stdlib.h"); 30 | 31 | // LVGL Header Files 32 | @cInclude("lvgl/lvgl.h"); 33 | 34 | // App Header Files 35 | @cInclude("fbdev.h"); 36 | @cInclude("lcddev.h"); 37 | @cInclude("tp.h"); 38 | @cInclude("tp_cal.h"); 39 | }); 40 | 41 | /////////////////////////////////////////////////////////////////////////////// 42 | // Main Function 43 | 44 | /// Main Function that will be called by NuttX. We render an LVGL Screen and 45 | /// handle Touch Input. 46 | pub export fn lvgltest_main( 47 | _argc: c_int, 48 | _argv: [*]const [*]const u8 49 | ) c_int { 50 | // Command-line args are not used 51 | _ = _argc; 52 | _ = _argv; 53 | debug("Zig LVGL Test", .{}); 54 | 55 | // Init LVGL Library 56 | c.lv_init(); 57 | 58 | // Init Display Buffer 59 | const disp_buf = c.get_disp_buf().?; 60 | c.init_disp_buf(disp_buf); 61 | 62 | // Init Display Driver 63 | const disp_drv = c.get_disp_drv().?; 64 | c.init_disp_drv(disp_drv, disp_buf, monitorCallback); 65 | 66 | // Init LCD Driver 67 | if (c.lcddev_init(disp_drv) != c.EXIT_SUCCESS) { 68 | // If failed, try Framebuffer Driver 69 | if (c.fbdev_init(disp_drv) != c.EXIT_SUCCESS) { 70 | // No possible drivers left, fail 71 | return c.EXIT_FAILURE; 72 | } 73 | } 74 | 75 | // Register Display Driver 76 | _ = c.lv_disp_drv_register(disp_drv); 77 | 78 | // Init Touch Panel 79 | _ = c.tp_init(); 80 | 81 | // Init Input Device. tp_read will be called periodically 82 | // to get the touched position and state 83 | const indev_drv = c.get_indev_drv().?; 84 | c.init_indev_drv(indev_drv, c.tp_read); 85 | 86 | // Create the widgets for display 87 | createWidgetsUnwrapped() 88 | catch |e| { 89 | // In case of error, quit 90 | std.log.err("createWidgets failed: {}", .{e}); 91 | return c.EXIT_FAILURE; 92 | }; 93 | 94 | // To call the LVGL API that's wrapped in Zig, change 95 | // `createWidgetsUnwrapped` above to `createWidgetsWrapped` 96 | 97 | // Start Touch Panel calibration 98 | c.tp_cal_create(); 99 | 100 | // Loop forever handing LVGL tasks 101 | while (true) { 102 | // Handle LVGL tasks 103 | _ = c.lv_task_handler(); 104 | 105 | // Sleep a while 106 | _ = c.usleep(10000); 107 | } 108 | return 0; 109 | } 110 | 111 | /////////////////////////////////////////////////////////////////////////////// 112 | // Create Widgets 113 | 114 | /// Create the LVGL Widgets that will be rendered on the display. Calls the 115 | /// LVGL API directly, without wrapping in Zig. Based on 116 | /// https://docs.lvgl.io/7.11/widgets/label.html#label-recoloring-and-scrolling 117 | fn createWidgetsUnwrapped() !void { 118 | debug("createWidgetsUnwrapped", .{}); 119 | 120 | // Get the Active Screen 121 | const screen = c.lv_scr_act().?; 122 | 123 | // Create a Label Widget 124 | const label = c.lv_label_create(screen, null).?; 125 | 126 | // Wrap long lines in the label text 127 | c.lv_label_set_long_mode(label, c.LV_LABEL_LONG_BREAK); 128 | 129 | // Interpret color codes in the label text 130 | c.lv_label_set_recolor(label, true); 131 | 132 | // Center align the label text 133 | c.lv_label_set_align(label, c.LV_LABEL_ALIGN_CENTER); 134 | 135 | // Set the label text and colors 136 | c.lv_label_set_text( 137 | label, 138 | "#ff0000 HELLO# " ++ // Red Text 139 | "#00aa00 PINEDIO# " ++ // Green Text 140 | "#0000ff STACK!# " // Blue Text 141 | ); 142 | 143 | // Set the label width 144 | c.lv_obj_set_width(label, 200); 145 | 146 | // Align the label to the center of the screen, shift 30 pixels up 147 | c.lv_obj_align(label, null, c.LV_ALIGN_CENTER, 0, -30); 148 | } 149 | 150 | /// Create the LVGL Widgets that will be rendered on the display. Calls the 151 | /// LVGL API that has been wrapped in Zig. Based on 152 | /// https://docs.lvgl.io/7.11/widgets/label.html#label-recoloring-and-scrolling 153 | fn createWidgetsWrapped() !void { 154 | debug("createWidgetsWrapped", .{}); 155 | 156 | // Get the Active Screen 157 | var screen = try lvgl.getActiveScreen(); 158 | 159 | // Create a Label Widget 160 | var label = try screen.createLabel(); 161 | 162 | // Wrap long lines in the label text 163 | label.setLongMode(c.LV_LABEL_LONG_BREAK); 164 | 165 | // Interpret color codes in the label text 166 | label.setRecolor(true); 167 | 168 | // Center align the label text 169 | label.setAlign(c.LV_LABEL_ALIGN_CENTER); 170 | 171 | // Set the label text and colors 172 | label.setText( 173 | "#ff0000 HELLO# " ++ // Red Text 174 | "#00aa00 PINEDIO# " ++ // Green Text 175 | "#0000ff STACK!# " // Blue Text 176 | ); 177 | 178 | // Set the label width 179 | label.setWidth(200); 180 | 181 | // Align the label to the center of the screen, shift 30 pixels up 182 | label.alignObject(c.LV_ALIGN_CENTER, 0, -30); 183 | } 184 | 185 | /////////////////////////////////////////////////////////////////////////////// 186 | // Callbacks 187 | 188 | /// Monitoring callback from LVGL every time the screen is flushed 189 | pub export fn monitorCallback( 190 | _disp_drv: ?*c.lv_disp_drv_t, 191 | _time: u32, 192 | _px: u32 193 | ) void { 194 | // Do nothing 195 | _ = _disp_drv; 196 | _ = _time; 197 | _ = _px; 198 | } 199 | 200 | /////////////////////////////////////////////////////////////////////////////// 201 | // Panic Handler 202 | 203 | /// Called by Zig when it hits a Panic. We print the Panic Message, Stack Trace and halt. See 204 | /// https://andrewkelley.me/post/zig-stack-traces-kernel-panic-bare-bones-os.html 205 | /// https://github.com/ziglang/zig/blob/master/lib/std/builtin.zig#L763-L847 206 | pub fn panic( 207 | message: []const u8, 208 | _stack_trace: ?*std.builtin.StackTrace 209 | ) noreturn { 210 | // Print the Panic Message 211 | _ = _stack_trace; 212 | _ = puts("\n!ZIG PANIC!"); 213 | _ = puts(@ptrCast([*c]const u8, message)); 214 | 215 | // Print the Stack Trace 216 | _ = puts("Stack Trace:"); 217 | var it = std.debug.StackIterator.init(@returnAddress(), null); 218 | while (it.next()) |return_address| { 219 | _ = printf("%p\n", return_address); 220 | } 221 | 222 | // Halt 223 | while(true) {} 224 | } 225 | 226 | /////////////////////////////////////////////////////////////////////////////// 227 | // Logging 228 | 229 | /// Called by Zig for `std.log.debug`, `std.log.info`, `std.log.err`, ... 230 | /// https://gist.github.com/leecannon/d6f5d7e5af5881c466161270347ce84d 231 | pub fn log( 232 | comptime _message_level: std.log.Level, 233 | comptime _scope: @Type(.EnumLiteral), 234 | comptime format: []const u8, 235 | args: anytype, 236 | ) void { 237 | _ = _message_level; 238 | _ = _scope; 239 | 240 | // Format the message 241 | var buf: [100]u8 = undefined; // Limit to 100 chars 242 | var slice = std.fmt.bufPrint(&buf, format, args) 243 | catch { _ = puts("*** log error: buf too small"); return; }; 244 | 245 | // Terminate the formatted message with a null 246 | var buf2: [buf.len + 1 : 0]u8 = undefined; 247 | std.mem.copy( 248 | u8, 249 | buf2[0..slice.len], 250 | slice[0..slice.len] 251 | ); 252 | buf2[slice.len] = 0; 253 | 254 | // Print the formatted message 255 | _ = puts(&buf2); 256 | } 257 | 258 | /////////////////////////////////////////////////////////////////////////////// 259 | // Imported Functions and Variables 260 | 261 | /// For safety, we import these functions ourselves to enforce Null-Terminated Strings. 262 | /// We changed `[*c]const u8` to `[*:0]const u8` 263 | extern fn printf(format: [*:0]const u8, ...) c_int; 264 | extern fn puts(str: [*:0]const u8) c_int; 265 | 266 | /// Aliases for Zig Standard Library 267 | const assert = std.debug.assert; 268 | const debug = std.log.debug; 269 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![LVGL Test App in C](https://lupyuen.github.io/images/lvgl-title.jpg) 2 | 3 | # Zig LVGL Touchscreen App on Apache NuttX RTOS 4 | 5 | Read the article... 6 | 7 | - ["Build an LVGL Touchscreen App with Zig"](https://lupyuen.github.io/articles/lvgl) 8 | 9 | Can we use Zig to code an LVGL Touchscreen App for Apache NuttX RTOS? 10 | 11 | Maybe make LVGL a little safer and friendlier... By wrapping the LVGL API in Zig? 12 | 13 | Or will we get blocked by something beyond our control? (Like Bit Fields in LVGL Structs) 14 | 15 | Let's find out! 16 | 17 | # LVGL Test App in C 18 | 19 | Here's our barebones LVGL App in C (pic above): [lvgltest.c](https://github.com/lupyuen/lvgltest-nuttx/blob/main/lvgltest.c#L107-L148) 20 | 21 | ```c 22 | static void create_widgets(void) { 23 | // Get the Active Screen 24 | lv_obj_t *screen = lv_scr_act(); 25 | 26 | // Create a Label Widget 27 | lv_obj_t *label = lv_label_create(screen, NULL); 28 | 29 | // Wrap long lines in the label text 30 | lv_label_set_long_mode(label, LV_LABEL_LONG_BREAK); 31 | 32 | // Interpret color codes in the label text 33 | lv_label_set_recolor(label, true); 34 | 35 | // Center align the label text 36 | lv_label_set_align(label, LV_LABEL_ALIGN_CENTER); 37 | 38 | // Set the label text and colors 39 | lv_label_set_text( 40 | label, 41 | "#ff0000 HELLO# " // Red Text 42 | "#00aa00 PINEDIO# " // Green Text 43 | "#0000ff STACK!# " // Blue Text 44 | ); 45 | 46 | // Set the label width 47 | lv_obj_set_width(label, 200); 48 | 49 | // Align the label to the center of the screen, shift 30 pixels up 50 | lv_obj_align(label, NULL, LV_ALIGN_CENTER, 0, -30); 51 | 52 | // Omitted: LVGL Canvas 53 | } 54 | ``` 55 | 56 | NuttX compiles the LVGL Test App with this GCC command... 57 | 58 | ```bash 59 | ## App Source Directory 60 | cd $HOME/nuttx/apps/examples/lvgltest 61 | 62 | ## Compile lvgltest.c with GCC 63 | riscv64-unknown-elf-gcc \ 64 | -c \ 65 | -fno-common \ 66 | -Wall \ 67 | -Wstrict-prototypes \ 68 | -Wshadow \ 69 | -Wundef \ 70 | -Os \ 71 | -fno-strict-aliasing \ 72 | -fomit-frame-pointer \ 73 | -fstack-protector-all \ 74 | -ffunction-sections \ 75 | -fdata-sections \ 76 | -g \ 77 | -march=rv32imafc \ 78 | -mabi=ilp32f \ 79 | -mno-relax \ 80 | -isystem "$HOME/nuttx/nuttx/include" \ 81 | -D__NuttX__ \ 82 | -DNDEBUG \ 83 | -DARCH_RISCV \ 84 | -pipe \ 85 | -I "$HOME/nuttx/apps/graphics/lvgl" \ 86 | -I "$HOME/nuttx/apps/graphics/lvgl/lvgl" \ 87 | -I "$HOME/nuttx/apps/include" \ 88 | -DLV_LVGL_H_INCLUDE_SIMPLE \ 89 | -Wno-format \ 90 | -Dmain=lvgltest_main \ 91 | -lvgltest.c \ 92 | -o lvgltest.c.home.user.nuttx.apps.examples.lvgltest.o 93 | ``` 94 | 95 | (Observed from `make --trace`) 96 | 97 | Let's convert the LVGL Test App from C to Zig... 98 | 99 | # Auto-Translate LVGL App to Zig 100 | 101 | The Zig Compiler can auto-translate C code to Zig. [(See this)](https://ziglang.org/documentation/master/#C-Translation-CLI) 102 | 103 | Here's how we auto-translate our LVGL App [lvgltest.c](https://github.com/lupyuen/lvgltest-nuttx/blob/main/lvgltest.c) from C to Zig... 104 | 105 | - Take the GCC command from above 106 | 107 | - Change `riscv64-unknown-elf-gcc` to `zig translate-c` 108 | 109 | - Add the target `-target riscv32-freestanding-none -mcpu=baseline_rv32-d` 110 | 111 | - Remove `-march=rv32imafc` 112 | 113 | - Surround the C Flags by `-cflags` ... `--` 114 | 115 | Like this... 116 | 117 | ```bash 118 | ## App Source Directory 119 | cd $HOME/nuttx/apps/examples/lvgltest 120 | 121 | ## Auto-translate lvgltest.c from C to Zig 122 | zig translate-c \ 123 | -target riscv32-freestanding-none \ 124 | -mcpu=baseline_rv32-d \ 125 | -cflags \ 126 | -fno-common \ 127 | -Wall \ 128 | -Wstrict-prototypes \ 129 | -Wshadow \ 130 | -Wundef \ 131 | -Os \ 132 | -fno-strict-aliasing \ 133 | -fomit-frame-pointer \ 134 | -fstack-protector-all \ 135 | -ffunction-sections \ 136 | -fdata-sections \ 137 | -g \ 138 | -mabi=ilp32f \ 139 | -mno-relax \ 140 | -Wno-format \ 141 | -- \ 142 | -isystem "$HOME/nuttx/nuttx/include" \ 143 | -D__NuttX__ \ 144 | -DNDEBUG \ 145 | -DARCH_RISCV \ 146 | -I "$HOME/nuttx/apps/graphics/lvgl" \ 147 | -I "$HOME/nuttx/apps/graphics/lvgl/lvgl" \ 148 | -I "$HOME/nuttx/apps/include" \ 149 | -DLV_LVGL_H_INCLUDE_SIMPLE \ 150 | -Dmain=lvgltest_main \ 151 | lvgltest.c \ 152 | >lvgltest.zig 153 | ``` 154 | 155 | To fix the translation we need to insert this... 156 | 157 | ```c 158 | #if defined(__NuttX__) && defined(__clang__) // Workaround for NuttX with zig cc 159 | #include 160 | #include "../../nuttx/include/limits.h" 161 | #define FAR 162 | #endif // defined(__NuttX__) && defined(__clang__) 163 | ``` 164 | 165 | [(Source)](https://github.com/lupyuen/lvgltest-nuttx/blob/main/lvgltest.c#L25-L29) 166 | 167 | And change this... 168 | 169 | ```c 170 | static void monitor_cb(lv_disp_drv_t * disp_drv, uint32_t time, uint32_t px) 171 | { 172 | #ifndef __clang__ // Doesn't compile with zig cc 173 | ginfo("%" PRIu32 " px refreshed in %" PRIu32 " ms\n", px, time); 174 | #endif // __clang__ 175 | } 176 | ``` 177 | 178 | [(Source)](https://github.com/lupyuen/lvgltest-nuttx/blob/main/lvgltest.c#L95-L100) 179 | 180 | [(See the changes)](https://github.com/lupyuen/lvgltest-nuttx/commit/1e8b0501c800209f0fa3f35f54b3742498d0e302) 181 | 182 | Here's the original C code: [lvgltest.c](https://github.com/lupyuen/lvgltest-nuttx/blob/main/lvgltest.c) 183 | 184 | And the auto-translation from C to Zig: [translated/lvgltest.zig](translated/lvgltest.zig) 185 | 186 | # Zig Auto-Translation is Incomplete 187 | 188 | The Auto-Translation from C to Zig is missing 2 key functions: `lvgltest_main` and `create_widgets`... 189 | 190 | ```zig 191 | // lvgltest.c:129:13: warning: unable to translate function, demoted to extern 192 | pub extern fn create_widgets() callconv(.C) void; 193 | // lvgltest.c:227:17: warning: local variable has opaque type 194 | 195 | // (no file):353:14: warning: unable to translate function, demoted to extern 196 | pub extern fn lvgltest_main(arg_argc: c_int, arg_argv: [*c][*c]u8) c_int; 197 | ``` 198 | 199 | [(Source)](https://github.com/lupyuen/zig-lvgl-nuttx/blob/9e95d800f3a429c5f35970ca35cd43bd8fbd9529/translated/lvgltest.zig#L5901-L5904) 200 | 201 | When we look up `lvgltest.c` line 227... 202 | 203 | ```c 204 | int lvgltest_main(int argc, FAR char *argv[]) 205 | { 206 | // lvgltest.c:227:17: warning: local variable has opaque type 207 | lv_disp_drv_t disp_drv; 208 | lv_disp_buf_t disp_buf; 209 | ... 210 | ``` 211 | 212 | [(Source)](https://github.com/lupyuen/lvgltest-nuttx/blob/1e8b0501c800209f0fa3f35f54b3742498d0e302/lvgltest.c#L225-L228) 213 | 214 | We see that Zig couldn't translate the type `lv_disp_drv_t` because it's opaque. 215 | 216 | Let's find out why. 217 | 218 | # Opaque Types 219 | 220 | To find out why the type is opaque, we search for `lv_disp_drv_t` in the Zig Translation... 221 | 222 | ```zig 223 | // nuttx/apps/graphics/lvgl/lvgl/src/lv_hal/lv_hal_disp.h:154:9: 224 | // warning: struct demoted to opaque type - has bitfield 225 | pub const lv_disp_drv_t = struct__disp_drv_t; 226 | pub const struct__disp_drv_t = opaque {}; 227 | 228 | // nuttx/apps/graphics/lvgl/lvgl/src/lv_hal/lv_hal_disp.h:59:23: 229 | // warning: struct demoted to opaque type - has bitfield 230 | pub const lv_disp_t = struct__disp_t; 231 | pub const struct__disp_t = opaque {}; 232 | 233 | pub const lv_disp_buf_t = opaque {}; 234 | ``` 235 | 236 | [(Source)](https://github.com/lupyuen/zig-lvgl-nuttx/blob/9e95d800f3a429c5f35970ca35cd43bd8fbd9529/translated/lvgltest.zig#L700-L704) 237 | 238 | Below are the C definitions of `lv_disp_drv_t`, `lv_disp_t` and `lv_disp_buf_t`. 239 | 240 | The structs couldn't be translated to Zig because they contain Bit Fields... 241 | 242 | ```c 243 | typedef struct _disp_drv_t { 244 | uint32_t rotated : 1; 245 | uint32_t dpi : 10; 246 | ... 247 | } lv_disp_drv_t; 248 | 249 | typedef struct _disp_t { 250 | uint8_t del_prev : 1; 251 | uint32_t inv_p : 10; 252 | ... 253 | } lv_disp_t; 254 | 255 | typedef struct { 256 | volatile uint32_t last_area : 1; 257 | volatile uint32_t last_part : 1; 258 | ... 259 | } lv_disp_buf_t; 260 | ``` 261 | 262 | Let's fix the Opaque Types. 263 | 264 | # Fix Opaque Types 265 | 266 | Earlier we saw that Zig couldn't translate and import these structs because they contain Bit Fields... 267 | 268 | - `lv_disp_drv_t` (Display Driver) 269 | 270 | - `lv_disp_buf_t` (Display Buffer) 271 | 272 | Instead of creating instances of these structs in Zig, we do it in C instead... 273 | 274 | ```c 275 | /**************************************************************************** 276 | * Name: get_disp_drv 277 | * 278 | * Description: 279 | * Return the static instance of Display Driver, because Zig can't 280 | * allocate structs wth bitfields inside. 281 | * 282 | ****************************************************************************/ 283 | 284 | lv_disp_drv_t *get_disp_drv(void) 285 | { 286 | static lv_disp_drv_t disp_drv; 287 | return &disp_drv; 288 | } 289 | 290 | /**************************************************************************** 291 | * Name: get_disp_buf 292 | * 293 | * Description: 294 | * Return the static instance of Display Buffer, because Zig can't 295 | * allocate structs wth bitfields inside. 296 | * 297 | ****************************************************************************/ 298 | 299 | lv_disp_buf_t *get_disp_buf(void) 300 | { 301 | static lv_disp_buf_t disp_buf; 302 | return &disp_buf; 303 | } 304 | 305 | /**************************************************************************** 306 | * Name: init_disp_drv 307 | * 308 | * Description: 309 | * Initialise the Display Driver, because Zig can't access its fields. 310 | * 311 | ****************************************************************************/ 312 | 313 | void init_disp_drv(lv_disp_drv_t *disp_drv, 314 | lv_disp_buf_t *disp_buf, 315 | void (*monitor_cb)(struct _disp_drv_t *, uint32_t, uint32_t)) 316 | { 317 | assert(disp_drv != NULL); 318 | assert(disp_buf != NULL); 319 | assert(monitor_cb != NULL); 320 | 321 | lv_disp_drv_init(disp_drv); 322 | disp_drv->buffer = disp_buf; 323 | disp_drv->monitor_cb = monitor_cb; 324 | } 325 | 326 | /**************************************************************************** 327 | * Name: init_disp_buf 328 | * 329 | * Description: 330 | * Initialise the Display Buffer, because Zig can't access the fields. 331 | * 332 | ****************************************************************************/ 333 | 334 | void init_disp_buf(lv_disp_buf_t *disp_buf) 335 | { 336 | assert(disp_buf != NULL); 337 | lv_disp_buf_init(disp_buf, buffer1, buffer2, DISPLAY_BUFFER_SIZE); 338 | } 339 | ``` 340 | 341 | [(Source)](https://github.com/lupyuen/lvgltest-nuttx/blob/main/lcddev.c#L335-L398) 342 | 343 | Then we fetch the pointers to these structs in our Main Function and initialise the structs... 344 | 345 | ```c 346 | int lvgltest_main(int argc, FAR char *argv[]) 347 | { 348 | lv_disp_drv_t *disp_drv = get_disp_drv(); 349 | lv_disp_buf_t *disp_buf = get_disp_buf(); 350 | ... 351 | /* Basic LVGL display driver initialization */ 352 | init_disp_buf(disp_buf); 353 | init_disp_drv(disp_drv, disp_buf, monitor_cb); 354 | ... 355 | /* Touchpad Initialization */ 356 | lv_indev_drv_t *indev_drv = get_indev_drv(); 357 | init_indev_drv(indev_drv, tp_read); 358 | ``` 359 | 360 | [(Source)](https://github.com/lupyuen/lvgltest-nuttx/blob/main/lvgltest.c#L214-L293) 361 | 362 | (`get_indev_drv` and `init_indev_drv` are explained in the next section) 363 | 364 | After this modification, our Auto-Translation from C to Zig now contains the 2 missing functions... 365 | 366 | - [`lvgltest_main`](https://github.com/lupyuen/zig-lvgl-nuttx/blob/main/translated/lvgltest.zig#L5913-L5944) 367 | 368 | - [`create_widgets`](https://github.com/lupyuen/zig-lvgl-nuttx/blob/main/translated/lvgltest.zig#L5903-L5912) 369 | 370 | # Input Driver 371 | 372 | Our Input Driver `lv_indev_drv_t` is also an Opaque Type because it contains Bit Fields. 373 | 374 | We fix `lv_indev_drv_t` the same way as other Opaque Types: We allocate and initialise the structs in C (instead of Zig)... 375 | 376 | ```c 377 | /**************************************************************************** 378 | * Name: get_indev_drv 379 | * 380 | * Description: 381 | * Return the static instance of Input Driver, because Zig can't 382 | * allocate structs wth bitfields inside. 383 | * 384 | ****************************************************************************/ 385 | 386 | lv_indev_drv_t *get_indev_drv(void) 387 | { 388 | static lv_indev_drv_t indev_drv; 389 | return &indev_drv; 390 | } 391 | 392 | /**************************************************************************** 393 | * Name: init_indev_drv 394 | * 395 | * Description: 396 | * Initialise the Input Driver, because Zig can't access its fields. 397 | * 398 | ****************************************************************************/ 399 | 400 | void init_indev_drv(lv_indev_drv_t *indev_drv, 401 | bool (*read_cb)(struct _lv_indev_drv_t *, lv_indev_data_t *)) 402 | { 403 | assert(indev_drv != NULL); 404 | assert(read_cb != NULL); 405 | 406 | lv_indev_drv_init(indev_drv); 407 | indev_drv->type = LV_INDEV_TYPE_POINTER; 408 | 409 | /* This function will be called periodically (by the library) to get the 410 | * mouse position and state. 411 | */ 412 | 413 | indev_drv->read_cb = read_cb; 414 | lv_indev_drv_register(indev_drv); 415 | } 416 | ``` 417 | 418 | [(Source)](https://github.com/lupyuen/lvgltest-nuttx/blob/main/tp.c#L282-L320) 419 | 420 | # Color Type 421 | 422 | We also commented out all references to `lv_color_t`... 423 | 424 | ```c 425 | // LVGL Canvas Demo doesn't work with zig cc because of `lv_color_t` 426 | #if defined(CONFIG_USE_LV_CANVAS) && !defined(__clang__) 427 | 428 | // Set the Canvas Buffer (Warning: Might take a lot of RAM!) 429 | static lv_color_t cbuf[LV_CANVAS_BUF_SIZE_TRUE_COLOR(CANVAS_WIDTH, CANVAS_HEIGHT)]; 430 | ... 431 | ``` 432 | 433 | [(Source)](https://github.com/lupyuen/lvgltest-nuttx/blob/main/lvgltest.c#L160-L165) 434 | 435 | That's because `lv_color_t` is also an Opaque Type... 436 | 437 | ```zig 438 | pub const lv_color_t = lv_color16_t; 439 | 440 | pub const lv_color16_t = extern union { 441 | ch: struct_unnamed_7, 442 | full: u16, 443 | }; 444 | 445 | // nuttx/apps/graphics/lvgl/lvgl/src/lv_core/../lv_draw/../lv_misc/lv_color.h:240:18: 446 | // warning: struct demoted to opaque type - has bitfield 447 | const struct_unnamed_7 = opaque {}; 448 | ``` 449 | 450 | [(Source)](https://github.com/lupyuen/zig-lvgl-nuttx/blob/main/translated/lvgltest.zig#L520-L537) 451 | 452 | That contains Bit Fields... 453 | 454 | ```c 455 | typedef union { 456 | struct { 457 | // Bit fields for lv_color16_t (aliased to lv_color_t) 458 | uint16_t blue : 5; 459 | uint16_t green : 6; 460 | uint16_t red : 5; 461 | } ch; 462 | uint16_t full; 463 | } lv_color16_t; 464 | ``` 465 | 466 | # LVGL App in Zig 467 | 468 | We copy these functions from the Auto-Translated Zig code... 469 | 470 | - [`lvgltest_main`](https://github.com/lupyuen/zig-lvgl-nuttx/blob/main/translated/lvgltest.zig#L5913-L5944) 471 | 472 | - [`create_widgets`](https://github.com/lupyuen/zig-lvgl-nuttx/blob/main/translated/lvgltest.zig#L5903-L5912) 473 | 474 | And paste them into our Zig LVGL App... 475 | 476 | https://github.com/lupyuen/zig-lvgl-nuttx/blob/ec4d58e84140cbf2b8fd6a80b65c06f6da97edfc/lvgltest.zig#L1-L164 477 | 478 | We compile our Zig LVGL App... 479 | 480 | ```bash 481 | ## Download our Zig LVGL App for NuttX 482 | git clone --recursive https://github.com/lupyuen/zig-lvgl-nuttx 483 | cd zig-lvgl-nuttx 484 | 485 | ## Compile the Zig App for BL602 (RV32IMACF with Hardware Floating-Point) 486 | zig build-obj \ 487 | --verbose-cimport \ 488 | -target riscv32-freestanding-none \ 489 | -mcpu=baseline_rv32-d \ 490 | -isystem "$HOME/nuttx/nuttx/include" \ 491 | -I "$HOME/nuttx/apps/graphics/lvgl" \ 492 | -I "$HOME/nuttx/apps/graphics/lvgl/lvgl" \ 493 | -I "$HOME/nuttx/apps/include" \ 494 | -I "$HOME/nuttx/apps/examples/lvgltest" \ 495 | lvgltest.zig 496 | 497 | ## Patch the ELF Header of `lvgltest.o` from Soft-Float ABI to Hard-Float ABI 498 | xxd -c 1 lvgltest.o \ 499 | | sed 's/00000024: 01/00000024: 03/' \ 500 | | xxd -r -c 1 - lvgltest2.o 501 | cp lvgltest2.o lvgltest.o 502 | 503 | ## Copy the compiled app to NuttX and overwrite `lvgltest.o` 504 | ## TODO: Change "$HOME/nuttx" to your NuttX Project Directory 505 | cp lvgltest.o $HOME/nuttx/apps/examples/lvgltest/lvgltest*.o 506 | 507 | ## Build NuttX to link the Zig Object from `lvgltest.o` 508 | ## TODO: Change "$HOME/nuttx" to your NuttX Project Directory 509 | cd $HOME/nuttx/nuttx 510 | make 511 | ``` 512 | 513 | When tested on PineDio Stack BL604, our Zig LVGL App correctly renders the screen and correctly handles touch input (pic below). Yay! 514 | 515 | ```text 516 | NuttShell (NSH) NuttX-10.3.0 517 | nsh> lvgltest 518 | Zig LVGL Test 519 | tp_init: Opening /dev/input0 520 | cst816s_open: 521 | cst816s_poll_notify: 522 | cst816s_get_touch_data: 523 | cst816s_i2c_read: 524 | cst816s_get_touch_data: DOWN: id=0, touch=0, x=176, y=23 525 | cst816s_get_touch_data: id: 0 526 | cst816s_get_touch_data: flags: 19 527 | cst816s_get_touch_data: x: 176 528 | cst816s_get_touch_data: y: 23 529 | ... 530 | tp_cal result 531 | offset x:23, y:14 532 | range x:189, y:162 533 | invert x/y:1, x:0, y:1 534 | ``` 535 | 536 | [(Source)](https://gist.github.com/lupyuen/795d7660679c3e0288e8fe5bec190890) 537 | 538 | ![LVGL Test App in C](https://lupyuen.github.io/images/lvgl-title.jpg) 539 | 540 | # Clean Up 541 | 542 | After cleaning up our Zig LVGL App, here's our Main Function `lvgltest_main`... 543 | 544 | ```zig 545 | /// Main Function that will be called by NuttX. We render an LVGL Screen and 546 | /// handle Touch Input. 547 | pub export fn lvgltest_main( 548 | _argc: c_int, 549 | _argv: [*]const [*]const u8 550 | ) c_int { 551 | debug("Zig LVGL Test", .{}); 552 | // Command-line args are not used 553 | _ = _argc; 554 | _ = _argv; 555 | 556 | // Init LVGL Library 557 | c.lv_init(); 558 | 559 | // Init Display Buffer 560 | const disp_buf = c.get_disp_buf().?; 561 | c.init_disp_buf(disp_buf); 562 | 563 | // Init Display Driver 564 | const disp_drv = c.get_disp_drv().?; 565 | c.init_disp_drv(disp_drv, disp_buf, monitorCallback); 566 | 567 | // Init LCD Driver 568 | if (c.lcddev_init(disp_drv) != c.EXIT_SUCCESS) { 569 | // If failed, try Framebuffer Driver 570 | if (c.fbdev_init(disp_drv) != c.EXIT_SUCCESS) { 571 | // No possible drivers left, fail 572 | return c.EXIT_FAILURE; 573 | } 574 | } 575 | 576 | // Register Display Driver 577 | _ = c.lv_disp_drv_register(disp_drv); 578 | 579 | // Init Touch Panel 580 | _ = c.tp_init(); 581 | 582 | // Init Input Device. tp_read will be called periodically 583 | // to get the touched position and state 584 | const indev_drv = c.get_indev_drv().?; 585 | c.init_indev_drv(indev_drv, c.tp_read); 586 | 587 | // Create the widgets for display 588 | createWidgetsUnwrapped() 589 | catch |e| { 590 | // In case of error, quit 591 | std.log.err("createWidgets failed: {}", .{e}); 592 | return c.EXIT_FAILURE; 593 | }; 594 | 595 | // To call the LVGL API that's wrapped in Zig, change 596 | // `createWidgetsUnwrapped` above to `createWidgetsWrapped` 597 | 598 | // Start Touch Panel calibration 599 | c.tp_cal_create(); 600 | 601 | // Loop forever handing LVGL tasks 602 | while (true) { 603 | // Handle LVGL tasks 604 | _ = c.lv_task_handler(); 605 | 606 | // Sleep a while 607 | _ = c.usleep(10000); 608 | } 609 | return 0; 610 | } 611 | ``` 612 | 613 | [(Source)](https://github.com/lupyuen/zig-lvgl-nuttx/blob/main/lvgltest.zig#L44-L109) 614 | 615 | And here's our `createWidgetsUnwrapped` function that creates widgets... 616 | 617 | ```zig 618 | /// Create the LVGL Widgets that will be rendered on the display. Calls the 619 | /// LVGL API directly, without wrapping in Zig. Based on 620 | /// https://docs.lvgl.io/7.11/widgets/label.html#label-recoloring-and-scrolling 621 | fn createWidgetsUnwrapped() !void { 622 | 623 | // Get the Active Screen 624 | const screen = c.lv_scr_act().?; 625 | 626 | // Create a Label Widget 627 | const label = c.lv_label_create(screen, null).?; 628 | 629 | // Wrap long lines in the label text 630 | c.lv_label_set_long_mode(label, c.LV_LABEL_LONG_BREAK); 631 | 632 | // Interpret color codes in the label text 633 | c.lv_label_set_recolor(label, true); 634 | 635 | // Center align the label text 636 | c.lv_label_set_align(label, c.LV_LABEL_ALIGN_CENTER); 637 | 638 | // Set the label text and colors 639 | c.lv_label_set_text( 640 | label, 641 | "#ff0000 HELLO# " ++ // Red Text 642 | "#00aa00 PINEDIO# " ++ // Green Text 643 | "#0000ff STACK!# " // Blue Text 644 | ); 645 | 646 | // Set the label width 647 | c.lv_obj_set_width(label, 200); 648 | 649 | // Align the label to the center of the screen, shift 30 pixels up 650 | c.lv_obj_align(label, null, c.LV_ALIGN_CENTER, 0, -30); 651 | } 652 | ``` 653 | 654 | [(Source)](https://github.com/lupyuen/zig-lvgl-nuttx/blob/main/lvgltest.zig#L114-L147) 655 | 656 | The Zig Functions look very similar to C: [lvgltest.c](https://github.com/lupyuen/lvgltest-nuttx/blob/main/lvgltest.c#L107-L318) 657 | 658 | Note that we used `.?` to check for Null Pointers returned by C Functions. Let's find out why... 659 | 660 | # Zig Checks Null Pointers 661 | 662 | _What happens if a C Function returns a Null Pointer..._ 663 | 664 | ```c 665 | lv_disp_drv_t *get_disp_drv(void) { 666 | // Return a Null Pointer 667 | return NULL; 668 | } 669 | ``` 670 | 671 | _And we call it from Zig?_ 672 | 673 | ```zig 674 | const disp_drv = c.get_disp_drv().?; 675 | ``` 676 | 677 | Note that we used `.?` to check for Null Pointers returned by C Functions. 678 | 679 | When we run this code, we'll see a Zig Panic... 680 | 681 | ```text 682 | nsh> lvgltest 683 | Zig LVGL Test 684 | 685 | !ZIG PANIC! 686 | attempt to use null value 687 | Stack Trace: 688 | 0x23023606 689 | ``` 690 | 691 | The Stack Trace Address `23023606` points to the line of code that encountered the Null Pointer... 692 | 693 | ```text 694 | zig-lvgl-nuttx/lvgltest.zig:50 695 | const disp_drv = c.get_disp_drv().?; 696 | 230235f4: 23089537 lui a0,0x23089 697 | 230235f8: 5ac50513 addi a0,a0,1452 # 230895ac <__unnamed_10> 698 | 230235fc: 4581 li a1,0 699 | 230235fe: 00000097 auipc ra,0x0 700 | 23023602: c92080e7 jalr -878(ra) # 23023290 701 | 23023606: ff042503 lw a0,-16(s0) 702 | 2302360a: fea42623 sw a0,-20(s0) 703 | ``` 704 | 705 | So Zig really helps us to write safer programs. 706 | 707 | _What if we omit `.?` and do this?_ 708 | 709 | ```zig 710 | const disp_drv = c.get_disp_drv(); 711 | ``` 712 | 713 | This crashes with a RISC-V Exception when the code tries to dereference the Null Pointer later. Which is not as helpful as a Zig Panic. 714 | 715 | Thus we always use `.?` to check for Null Pointers returned by C Functions! 716 | 717 | (Hopefully someday we'll have a Zig Lint Tool that will warn us if we forget to use `.?`) 718 | 719 | # Simplify LVGL API 720 | 721 | _Can we simplify the LVGL API in Zig? Such that this code..._ 722 | 723 | ```zig 724 | // Get the Active Screen 725 | const screen = c.lv_scr_act().?; 726 | 727 | // Create a Label Widget 728 | const label = c.lv_label_create(screen, null).?; 729 | 730 | // Wrap long lines in the label text 731 | c.lv_label_set_long_mode(label, c.LV_LABEL_LONG_BREAK); 732 | 733 | // Interpret color codes in the label text 734 | c.lv_label_set_recolor(label, true); 735 | ``` 736 | 737 | [(Source)](https://github.com/lupyuen/zig-lvgl-nuttx/blob/main/lvgltest.zig#L114-L148) 738 | 739 | _Becomes this?_ 740 | 741 | ```zig 742 | // Get the Active Screen 743 | var screen = try lvgl.getActiveScreen(); 744 | 745 | // Create a Label Widget 746 | var label = try screen.createLabel(); 747 | 748 | // Wrap long lines in the label text 749 | label.setLongMode(c.LV_LABEL_LONG_BREAK); 750 | 751 | // Interpret color codes in the label text 752 | label.setRecolor(true); 753 | ``` 754 | 755 | [(Source)](https://github.com/lupyuen/zig-lvgl-nuttx/blob/main/lvgltest.zig#L150-L183) 756 | 757 | Yes we can! By wrapping the LVGL API in Zig, which we'll do in the next section. 758 | 759 | Note that we now use `try` instead of `.?`. 760 | 761 | _What happens if we forget to use `try`?_ 762 | 763 | If we don't `try`, like this... 764 | 765 | ```zig 766 | // Get the Active Screen without `try` 767 | var screen = lvgl.getActiveScreen(); 768 | 769 | // Attempt to use the Active Screen 770 | _ = screen; 771 | ``` 772 | 773 | Zig Compiler stops us with an error... 774 | 775 | ```text 776 | ./lvgltest.zig:109:9: 777 | error: error is discarded. 778 | consider using `try`, `catch`, or `if` 779 | _ = screen; 780 | ^ 781 | ``` 782 | 783 | Thus `try` is actually safer than `.?`, Zig Compiler mandates that we check for errors. 784 | 785 | _What if the LVGL API returns a Null Pointer to our Zig App?_ 786 | 787 | Our app will fail with this message... 788 | 789 | ```text 790 | lv_scr_act failed 791 | createWidgets failed: error.UnknownError 792 | ``` 793 | 794 | # Wrap LVGL API 795 | 796 | Let's wrap the LVGL API in Zig. 797 | 798 | Here's the implementation of `getActiveScreen`, which returns the LVGL Active Screen... 799 | 800 | ```zig 801 | /// Return the Active Screen 802 | pub fn getActiveScreen() !Object { 803 | 804 | // Get the Active Screen 805 | const screen = c.lv_scr_act(); 806 | 807 | // If successfully fetched... 808 | if (screen) |s| { 809 | // Wrap Active Screen as Object and return it 810 | return Object.init(s); 811 | } else { 812 | // Unable to get Active Screen 813 | std.log.err("lv_scr_act failed", .{}); 814 | return LvglError.UnknownError; 815 | } 816 | } 817 | ``` 818 | 819 | [(Source)](https://github.com/lupyuen/zig-lvgl-nuttx/blob/main/lvgl.zig#L26-L34) 820 | 821 | `Object` is a Zig Struct that wraps around an LVGL Object... 822 | 823 | ```zig 824 | /// LVGL Object 825 | pub const Object = struct { 826 | 827 | /// Pointer to LVGL Object 828 | obj: *c.lv_obj_t, 829 | 830 | /// Init the Object 831 | pub fn init(obj: *c.lv_obj_t) Object { 832 | return .{ .obj = obj }; 833 | } 834 | 835 | /// Create a Label as a child of the Object 836 | pub fn createLabel(self: *Object) !Label { 837 | 838 | // Assume we won't copy from another Object 839 | const copy: ?*const c.lv_obj_t = null; 840 | 841 | // Create the Label 842 | const label = c.lv_label_create(self.obj, copy); 843 | 844 | // If successfully created... 845 | if (label) |l| { 846 | // Wrap as Label and return it 847 | return Label.init(l); 848 | } else { 849 | // Unable to create Label 850 | std.log.err("lv_label_create failed", .{}); 851 | return LvglError.UnknownError; 852 | } 853 | } 854 | }; 855 | ``` 856 | 857 | [(Source)](https://github.com/lupyuen/zig-lvgl-nuttx/blob/main/lvgl.zig#L36-L58) 858 | 859 | `Label` is a Zig Struct that wraps around an LVGL Label... 860 | 861 | ```zig 862 | /// LVGL Label 863 | pub const Label = struct { 864 | 865 | /// Pointer to LVGL Label 866 | obj: *c.lv_obj_t, 867 | 868 | /// Init the Label 869 | pub fn init(obj: *c.lv_obj_t) Label { 870 | return .{ .obj = obj }; 871 | } 872 | 873 | /// Set the wrapping of long lines in the label text 874 | pub fn setLongMode(self: *Label, long_mode: c.lv_label_long_mode_t) void { 875 | c.lv_label_set_long_mode(self.obj, long_mode); 876 | } 877 | 878 | /// Set the label text alignment 879 | pub fn setAlign(self: *Label, alignment: c.lv_label_align_t) void { 880 | c.lv_label_set_align(self.obj, alignment); 881 | } 882 | 883 | /// Enable or disable color codes in the label text 884 | pub fn setRecolor(self: *Label, en: bool) void { 885 | c.lv_label_set_recolor(self.obj, en); 886 | } 887 | 888 | /// Set the label text and colors 889 | pub fn setText(self: *Label, text: [*c]const u8) void { 890 | c.lv_label_set_text(self.obj, text); 891 | } 892 | 893 | /// Set the object width 894 | pub fn setWidth(self: *Label, w: c.lv_coord_t) void { 895 | c.lv_obj_set_width(self.obj, w); 896 | } 897 | 898 | /// Set the object alignment 899 | pub fn alignObject(self: *Label, alignment: c.lv_align_t, x_ofs: c.lv_coord_t, y_ofs: c.lv_coord_t) void { 900 | const base: ?*const c.lv_obj_t = null; 901 | c.lv_obj_align(self.obj, base, alignment, x_ofs, y_ofs); 902 | } 903 | }; 904 | ``` 905 | 906 | [(Source)](https://github.com/lupyuen/zig-lvgl-nuttx/blob/main/lvgl.zig#L60-L101) 907 | 908 | Let's call the wrapped LVGL API... 909 | 910 | # After Wrapping LVGL API 911 | 912 | With the wrapped LVGL API, our Zig App becomes simpler and safer... 913 | 914 | ```zig 915 | /// Create the LVGL Widgets that will be rendered on the display. Calls the 916 | /// LVGL API that has been wrapped in Zig. Based on 917 | /// https://docs.lvgl.io/7.11/widgets/label.html#label-recoloring-and-scrolling 918 | fn createWidgetsWrapped() !void { 919 | 920 | // Get the Active Screen 921 | var screen = try lvgl.getActiveScreen(); 922 | 923 | // Create a Label Widget 924 | var label = try screen.createLabel(); 925 | 926 | // Wrap long lines in the label text 927 | label.setLongMode(c.LV_LABEL_LONG_BREAK); 928 | 929 | // Interpret color codes in the label text 930 | label.setRecolor(true); 931 | 932 | // Center align the label text 933 | label.setAlign(c.LV_LABEL_ALIGN_CENTER); 934 | 935 | // Set the label text and colors 936 | label.setText( 937 | "#ff0000 HELLO# " ++ // Red Text 938 | "#00aa00 PINEDIO# " ++ // Green Text 939 | "#0000ff STACK!# " // Blue Text 940 | ); 941 | 942 | // Set the label width 943 | label.setWidth(200); 944 | 945 | // Align the label to the center of the screen, shift 30 pixels up 946 | label.alignObject(c.LV_ALIGN_CENTER, 0, -30); 947 | } 948 | ``` 949 | 950 | [(Source)](https://github.com/lupyuen/zig-lvgl-nuttx/blob/main/lvgltest.zig#L149-L181) 951 | 952 | (TODO: Convert `LV_LABEL_LONG_BREAK`, `LV_LABEL_ALIGN_CENTER` and other constants to Enums) 953 | 954 | Let's talk about creating the Zig Wrapper... 955 | 956 | # Auto-Generate Zig Wrapper 957 | 958 | _Can we auto-generate the Wrapper Code?_ 959 | 960 | We might use Zig Type Reflection... 961 | 962 | - ["Zig Type Reflection"](https://github.com/lupyuen/zig-bl602-nuttx/blob/main/README.md#zig-type-reflection) 963 | 964 | Or we can parse the Type Info JSON generated by Zig Compiler... 965 | 966 | ```bash 967 | ## Emit IR, BC and Type Info 968 | zig build-obj \ 969 | -femit-llvm-ir \ 970 | -femit-llvm-bc \ 971 | -femit-analysis \ 972 | --verbose-cimport \ 973 | -target riscv32-freestanding-none \ 974 | -mcpu=baseline_rv32-d \ 975 | -isystem "$HOME/nuttx/nuttx/include" \ 976 | -I "$HOME/nuttx/apps/graphics/lvgl" \ 977 | -I "$HOME/nuttx/apps/graphics/lvgl/lvgl" \ 978 | -I "$HOME/nuttx/apps/include" \ 979 | -I "$HOME/nuttx/apps/examples/lvgltest" \ 980 | lvgltest.zig 981 | ``` 982 | 983 | This produces the IR, BC and Type Info JSON files: 984 | 985 | ```text 986 | lvgltest.ll 987 | lvgltest.bc 988 | lvgltest-analysis.json 989 | ``` 990 | 991 | Let's look up the Type Info for the LVGL Function `lv_obj_align`. 992 | 993 | We search for `lv_obj_align` in `lvgltest-analysis.json`... 994 | 995 | ```json 996 | "decls": 997 | ... 998 | { 999 | "import": 99, 1000 | "src": 1962, 1001 | "name": "lv_obj_align", 1002 | "kind": "const", 1003 | "type": 148, 1004 | "value": 60 1005 | }, 1006 | ``` 1007 | 1008 | Then we look up type 148 in `lvgltest-analysis.json`... 1009 | 1010 | ```bash 1011 | $ jq '.types[148]' lvgltest-analysis.json 1012 | { 1013 | "kind": 18, 1014 | "name": "fn(?*.cimport:10:11.struct__lv_obj_t, ?*const .cimport:10:11.struct__lv_obj_t, u8, i16, i16) callconv(.C) void", 1015 | "generic": false, 1016 | "ret": 70, 1017 | "args": [ 1018 | 79, 1019 | 194, 1020 | 95, 1021 | 134, 1022 | 134 1023 | ] 1024 | } 1025 | ``` 1026 | 1027 | The First Parameter has type 79, so we look up `lvgltest-analysis.json` and follow the trail... 1028 | 1029 | ```bash 1030 | $ jq '.types[79]' lvgltest-analysis.json 1031 | { 1032 | "kind": 13, 1033 | "child": 120 1034 | } 1035 | ## Kind 13 is `?` (Optional) 1036 | 1037 | $ jq '.types[120]' lvgltest-analysis.json 1038 | { 1039 | "kind": 6, 1040 | "elem": 137 1041 | } 1042 | ## Kind 6 is `*` (Pointer) 1043 | 1044 | $ jq '.types[137]' lvgltest-analysis.json 1045 | { 1046 | "kind": 20, 1047 | "name": ".cimport:10:11.struct__lv_obj_t" 1048 | } 1049 | ## Kind 20 is `struct`??? 1050 | ``` 1051 | 1052 | Which gives us the complete type of the First Parameter... 1053 | 1054 | ```zig 1055 | ?*.cimport:10:11.struct__lv_obj_t 1056 | ``` 1057 | 1058 | We don't have the Parameter Names though, we might need to parse the `.cimport` file. 1059 | 1060 | [(More about jq)](https://stedolan.github.io/jq/manual/) 1061 | 1062 | # Object-Oriented Wrapper for LVGL 1063 | 1064 | _Is LVGL really Object-Oriented?_ 1065 | 1066 | Yep the LVGL API is actually Object-Oriented since it uses Inheritance. 1067 | 1068 | All LVGL Widgets (Labels, Buttons, etc) have the same Base Type: `lv_obj_t`. But some LVGL Functions will work only for specific Widgets, whereas some LVGL Functions will work on any Widget... 1069 | 1070 | - `lv_label_set_text` works only for Labels 1071 | 1072 | - `lv_obj_set_width` works for any Widget 1073 | 1074 | The LVGL Docs also say that LVGL is Object-Oriented... 1075 | 1076 | - ["Base object (lv_obj)"](https://docs.lvgl.io/latest/en/html/widgets/obj.html) 1077 | 1078 | Creating an Object-Oriented Zig Wrapper for LVGL might be challenging: Our Zig Wrapper needs to support `setWidth` for all LVGL Widgets. 1079 | 1080 | To do this we might use Zig Interfaces and `@fieldParentPtr`... 1081 | 1082 | - ["Interfaces in Zig"](https://zig.news/david_vanderson/interfaces-in-zig-o1c) 1083 | 1084 | - ["Zig Interfaces for the Uninitiated"](https://www.nmichaels.org/zig/interfaces.html) 1085 | 1086 | Which look somewhat similar to VTables in C++... 1087 | 1088 | - ["Allocgate is coming in Zig 0.9"](https://pithlessly.github.io/allocgate.html) 1089 | 1090 | _Are there any Object-Oriented Bindings for LVGL?_ 1091 | 1092 | The official Python Bindings for LVGL appear to be Object-Oriented. This could inspire our Object-Oriented Wrapper in Zig... 1093 | 1094 | - [Python Bindings for LVGL](https://github.com/lvgl/lv_binding_micropython) 1095 | 1096 | However the Python Bindings are Dynamically Typed, might be tricky implementing them as Static Types in Zig. 1097 | 1098 | The LVGL Wrapper in this article was inspired by the [zgt GUI Library](https://github.com/zenith391/zgt), which works with GTK, Win32 and WebAssembly... 1099 | 1100 | - ["Build a PinePhone App with Zig and zgt"](https://lupyuen.github.io/articles/pinephone) 1101 | --------------------------------------------------------------------------------