diff --git a/Cargo.toml b/Cargo.toml index 4f3767e..06b993d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,6 @@ [workspace] members = [ "lvgl", - "lvgl-sys", "lvgl-codegen", - "examples" + "lvgl-sys", ] diff --git a/README.md b/README.md index 348843b..355d2a9 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,19 @@ -

LittlevGL - Open-source Embedded GUI Library in Rust

+

LVGL - Open-source Embedded GUI Library in Rust

-![Original LittlevGL demo image](lv_demo.png) +![Original LVGL demo image](lv_demo.png)

-LittlevGL provides everything you need to create a Graphical User Interface (GUI) on embedded systems with easy-to-use graphical elements, beautiful visual effects and low memory footprint. +LVGL provides everything you need to create a Graphical User Interface (GUI) on embedded systems with easy-to-use graphical elements, beautiful visual effects and low memory footprint.

-LittlevGL is compatible with #![no_std] environments by default. +LVGL is compatible with #![no_std] environments by default.

-Official LittlevGL Website · +Rust to WASM demo · +Official LVGL Website · C library repository · -Live demo +Official live demos

--- @@ -42,7 +43,7 @@ $ cargo add lvgl The build requires the environment variable bellow to be set: -- `DEP_LV_CONFIG_PATH`: Path to the directory containing the `lv_conf.h` header file used for configuration of LittlevGL library. +- `DEP_LV_CONFIG_PATH`: Path to the directory containing the `lv_conf.h` header file used for configuration of LVGL library. We recommend the `lv_conf.h` file to be in your project's root directory. If so, the command to build your project would be: ```shell script @@ -58,10 +59,17 @@ for `no_std`, so we need to use a workaround to build "lvgl-rs". The mainstrem i ```shell $ DEP_LV_CONFIG_PATH=`pwd` cargo build -Zfeatures=build_dep ``` +### LVGL Global Allocator + +A [global allocator](https://doc.rust-lang.org/std/alloc/trait.GlobalAlloc.html) for Rust leveraging the +[LVGL memory allocator](https://github.com/lvgl/lvgl/blob/master/src/misc/lv_mem.h) is provided, but not enabled +by default. Can be enabled by the feature `lvgl_alloc`. This will make all dynamic memory to be allocated by LVGL +internal memory manager. ## Running the demo -**Hint for macOS users**: Before you run the demos you need to make sure you have [libsdl](https://www.libsdl.org) installed on your machine. To install it, use HomeBrew: +**Hint for macOS users**: Before you run the demos you need to make sure you have [libsdl](https://www.libsdl.org) +installed on your machine. To install it, use HomeBrew: ```shell $ brew install sdl2 diff --git a/examples/Cargo.toml b/examples/Cargo.toml deleted file mode 100644 index 8d2efac..0000000 --- a/examples/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -[package] -name = "demo" -version = "0.1.0" -authors = ["Rafael Caricio "] -edition = "2018" -publish = false - -[dev-dependencies] -lvgl = { path = "../lvgl" } -lvgl-sys = { path = "../lvgl-sys" } -embedded-graphics = "0.6.2" -embedded-graphics-simulator = "0.2.1" -heapless = "0.5.5" -cstr_core = { version = "0.2.0", features = ["alloc"] } - -[[example]] -name = "demo" -path = "demo.rs" - -[[example]] -name = "bar" -path = "bar.rs" - -[[example]] -name = "button_click" -path = "button_click.rs" - -[[example]] -name = "gauge" -path = "gauge.rs" - -[[example]] -name = "arc" -path = "arc.rs" diff --git a/examples/arc.rs b/examples/arc.rs index 05523a4..eb2a1f5 100644 --- a/examples/arc.rs +++ b/examples/arc.rs @@ -11,7 +11,31 @@ use lvgl::{LvError, Widget}; use lvgl_sys; use std::time::Instant; +fn mem_info() -> lvgl_sys::lv_mem_monitor_t { + let mut info = lvgl_sys::lv_mem_monitor_t { + total_size: 0, + free_cnt: 0, + free_size: 0, + free_biggest_size: 0, + used_cnt: 0, + max_used: 0, + used_pct: 0, + frag_pct: 0, + }; + unsafe { + lvgl_sys::lv_mem_monitor(&mut info as *mut _); + } + info +} + fn main() -> Result<(), LvError> { + println!("meminfo init: {:?}", mem_info()); + run_arc_demo()?; + println!("meminfo end: {:?}", mem_info()); + Ok(()) +} + +fn run_arc_demo() -> Result<(), LvError> { let display: SimulatorDisplay = SimulatorDisplay::new(Size::new( lvgl_sys::LV_HOR_RES_MAX, lvgl_sys::LV_VER_RES_MAX, @@ -58,6 +82,7 @@ fn main() -> Result<(), LvError> { if i > 270 { forward = if forward { false } else { true }; i = 1; + println!("meminfo running: {:?}", mem_info()); } angle = if forward { angle + 1 } else { angle - 1 }; arc.set_end_angle(angle + 135)?; diff --git a/examples/demo.rs b/examples/demo.rs index 956e4fc..d77f732 100644 --- a/examples/demo.rs +++ b/examples/demo.rs @@ -88,7 +88,7 @@ fn main() -> Result<(), LvError> { Ok(()) } -// Reference to native font for LittlevGL, defined in the file: "fonts_noto_sans_numeric_80.c" +// Reference to native font for LVGL, defined in the file: "fonts_noto_sans_numeric_80.c" // TODO: Create a macro for defining a safe wrapper for fonts. // Maybe sometihng like: // diff --git a/examples/include/lv_conf.h b/examples/include/lv_conf.h index 6fe446c..d6be99d 100644 --- a/examples/include/lv_conf.h +++ b/examples/include/lv_conf.h @@ -80,7 +80,7 @@ typedef int16_t lv_coord_t; #define LV_MEM_CUSTOM 0 #if LV_MEM_CUSTOM == 0 /* Size of the memory used by `lv_mem_alloc` in bytes (>= 2kB)*/ -# define LV_MEM_SIZE (32U * 1024U) +# define LV_MEM_SIZE (1048576U) // 1Mb /* Complier prefix for a big array declaration */ # define LV_MEM_ATTR diff --git a/lvgl-sys/Cargo.toml b/lvgl-sys/Cargo.toml index b50b140..ba7b648 100644 --- a/lvgl-sys/Cargo.toml +++ b/lvgl-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lvgl-sys" -description = "Raw bindings to the LittlevGL C library." +description = "Raw bindings to the LVGL C library." version = "0.5.2" authors = ["Rafael Caricio "] edition = "2018" diff --git a/lvgl-sys/README.md b/lvgl-sys/README.md index 0d8a775..5c3ab3f 100644 --- a/lvgl-sys/README.md +++ b/lvgl-sys/README.md @@ -5,7 +5,7 @@ Rust raw bindings for LittlevGL library. Build requires environment variables to be set: -- `DEP_LV_CONFIG_PATH`: Path to the directory containing the `lv_conf.h` header file used for configuration of LittlevGL library. +- `DEP_LV_CONFIG_PATH`: Path to the directory containing the `lv_conf.h` header file used for configuration of LVGL library. We recommend the `lv_conf.h` file to be in your project's root directory. If so, the command to build your project would be: ```shell script diff --git a/lvgl/Cargo.toml b/lvgl/Cargo.toml index e460295..dfa8971 100644 --- a/lvgl/Cargo.toml +++ b/lvgl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lvgl" -description = "LittlevGL bindings for Rust. A powerful and easy-to-use embedded GUI with many widgets, advanced visual effects (opacity, antialiasing, animations) and low memory requirements (16K RAM, 64K Flash)." +description = "LVGL bindings for Rust. A powerful and easy-to-use embedded GUI with many widgets, advanced visual effects (opacity, antialiasing, animations) and low memory requirements (16K RAM, 64K Flash)." version = "0.5.2" authors = ["Rafael Caricio "] edition = "2018" @@ -18,9 +18,40 @@ embedded-graphics = "0.6.2" cstr_core = "0.2.3" bitflags = "1.2.1" +[features] +alloc = ["cstr_core/alloc"] +lvgl_alloc = ["alloc"] + [build-dependencies] quote = "1.0.9" proc-macro2 = "1.0.24" lvgl-codegen = { version = "0.5.2", path = "../lvgl-codegen" } lvgl-sys = { version = "0.5.2", path = "../lvgl-sys" } +[dev-dependencies] +embedded-graphics-simulator = "0.2.1" +heapless = "0.5.5" + +[[example]] +name = "demo" +path = "../examples/demo.rs" +required-features = ["alloc"] + +[[example]] +name = "bar" +path = "../examples/bar.rs" +required-features = ["alloc"] + +[[example]] +name = "button_click" +path = "../examples/button_click.rs" +required-features = ["alloc"] + +[[example]] +name = "gauge" +path = "../examples/gauge.rs" + +[[example]] +name = "arc" +path = "../examples/arc.rs" +required-features = ["lvgl_alloc"] diff --git a/lvgl/src/allocator.rs b/lvgl/src/allocator.rs new file mode 100644 index 0000000..90e60fd --- /dev/null +++ b/lvgl/src/allocator.rs @@ -0,0 +1,20 @@ +use core::alloc::{GlobalAlloc, Layout}; + +// Register the global allocator +#[global_allocator] +static ALLOCATOR: LvglAlloc = LvglAlloc; + +pub struct LvglAlloc; + +unsafe impl GlobalAlloc for LvglAlloc { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + // Make sure LVGL is initialized! + crate::lvgl_init(); + lvgl_sys::lv_mem_alloc(layout.size() as lvgl_sys::size_t) as *mut u8 + } + + unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) { + crate::lvgl_init(); + lvgl_sys::lv_mem_free(ptr as *const cty::c_void) + } +} diff --git a/lvgl/src/lib.rs b/lvgl/src/lib.rs index 8a4a17f..e46452c 100644 --- a/lvgl/src/lib.rs +++ b/lvgl/src/lib.rs @@ -17,13 +17,52 @@ #[macro_use] extern crate bitflags; -pub(crate) mod mem; +#[cfg(feature = "lvgl_alloc")] +extern crate alloc; + +// We can ONLY use `alloc::boxed::Box` if `lvgl_alloc` is enabled. +// That is because we use `Box` to send memory references to LVGL. Since the global allocator, when +// `lvgl_alloc` feature is enabled, is the LVGL memory manager then everything is in LVGL +// managed memory anyways. In that case we can use the Rust's provided Box definition. +// +#[cfg(feature = "lvgl_alloc")] +use ::alloc::boxed::Box; + +#[cfg(feature = "lvgl_alloc")] +mod allocator; + mod support; mod ui; #[macro_use] mod lv_core; pub mod widgets; +#[cfg(not(feature = "lvgl_alloc"))] +pub(crate) mod mem; + +// When LVGL allocator is not used on the Rust code, we need a way to add objects to the LVGL +// managed memory. We implement a very simple `Box` that has the minimal features to copy memory +// safely to the LVGL managed memory. +// +#[cfg(not(feature = "lvgl_alloc"))] +use crate::mem::Box; + pub use lv_core::*; pub use support::*; pub use ui::*; + +use core::sync::atomic::{AtomicBool, Ordering}; + +// Initialize LVGL only once. +static LVGL_INITIALIZED: AtomicBool = AtomicBool::new(false); + +pub(crate) fn lvgl_init() { + if LVGL_INITIALIZED + .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) + .is_ok() + { + unsafe { + lvgl_sys::lv_init(); + } + } +} diff --git a/lvgl/src/lv_core/obj.rs b/lvgl/src/lv_core/obj.rs index 1129c0a..d3ce4f6 100644 --- a/lvgl/src/lv_core/obj.rs +++ b/lvgl/src/lv_core/obj.rs @@ -1,9 +1,9 @@ use crate::lv_core::style::Style; -use crate::mem::Box; +use crate::Box; use crate::{Align, LvError, LvResult}; use core::ptr; -/// Represents a native LittlevGL object +/// Represents a native LVGL object pub trait NativeObject { /// Provide common way to access to the underlying native object pointer. fn raw(&self) -> LvResult>; @@ -28,7 +28,7 @@ impl NativeObject for Obj { } } -/// A wrapper for all LittlevGL common operations on generic objects. +/// A wrapper for all LVGL common operations on generic objects. pub trait Widget: NativeObject { type SpecialEvent; type Part: Into; @@ -146,8 +146,8 @@ macro_rules! define_object { unsafe { let mut raw = self.raw()?; let obj = raw.as_mut(); - let user_closure = $crate::mem::Box::new(f)?; - obj.user_data = $crate::mem::Box::into_raw(user_closure) as *mut cty::c_void; + let user_closure = $crate::Box::new(f); + obj.user_data = $crate::Box::into_raw(user_closure) as *mut cty::c_void; lvgl_sys::lv_obj_set_event_cb( obj, lvgl_sys::lv_event_cb_t::Some($crate::support::event_callback::), diff --git a/lvgl/src/lv_core/style.rs b/lvgl/src/lv_core/style.rs index ace0c46..46611b8 100644 --- a/lvgl/src/lv_core/style.rs +++ b/lvgl/src/lv_core/style.rs @@ -1,4 +1,4 @@ -use crate::mem::Box; +use crate::Box; use crate::{Color, LvResult, State}; use core::mem; use cstr_core::CStr; @@ -31,7 +31,7 @@ impl Default for Style { let raw = unsafe { let mut style = mem::MaybeUninit::::uninit(); lvgl_sys::lv_style_init(style.as_mut_ptr()); - Box::new(style.assume_init()).unwrap() + Box::new(style.assume_init()) }; Self { raw } } diff --git a/lvgl/src/mem.rs b/lvgl/src/mem.rs index 55ca9f6..0d193d7 100644 --- a/lvgl/src/mem.rs +++ b/lvgl/src/mem.rs @@ -1,4 +1,3 @@ -use crate::{LvError, LvResult}; use core::mem; use core::ops::{Deref, DerefMut}; use core::ptr::NonNull; @@ -12,7 +11,7 @@ pub(crate) struct Box(NonNull); impl Box { /// Allocate memory using LVGL memory API and place `T` in the LVGL tracked memory. - pub fn new(value: T) -> LvResult> { + pub fn new(value: T) -> Box { let size = mem::size_of::(); let inner = unsafe { let ptr = lvgl_sys::lv_mem_alloc(size as lvgl_sys::size_t) as *mut T; @@ -29,9 +28,11 @@ impl Box { p.as_ptr().write(value); p }) - .ok_or(LvError::LvOOMemory)? + .unwrap_or_else(|| { + panic!("Could not allocate memory {} bytes", size); + }) }; - Ok(Box(inner)) + Box(inner) } pub fn into_raw(self) -> *mut T { @@ -71,32 +72,21 @@ impl AsMut for Box { #[cfg(test)] mod test { use super::*; - use std::sync::Once; use std::vec::Vec; - static INIT_LVGL: Once = Once::new(); - - fn init() { - INIT_LVGL.call_once(|| { - unsafe { - lvgl_sys::lv_init(); - }; - }); - } - #[test] fn place_value_in_lv_mem() { - init(); + crate::lvgl_init(); - let v = Box::new(5).unwrap(); + let v = Box::new(5); drop(v); - let v = Box::new(10).unwrap(); + let v = Box::new(10); drop(v); } #[test] fn place_complex_value_in_lv_mem() { - init(); + crate::lvgl_init(); #[repr(C)] #[derive(Debug)] @@ -119,11 +109,7 @@ mod test { }; println!("{:?}", p); - let mut b = Box::new(p).unwrap_or_else(|_| { - let info = mem_info(); - println!("mem info: {:?}", &info); - panic!("OOM"); - }); + let mut b = Box::new(p); println!("memory address is {:p}", b.as_mut()); @@ -159,7 +145,7 @@ mod test { used_cnt: 0, max_used: 0, used_pct: 0, - frag_pct: 0 + frag_pct: 0, }; unsafe { lvgl_sys::lv_mem_monitor(&mut info as *mut _); diff --git a/lvgl/src/support.rs b/lvgl/src/support.rs index 1d529a7..c3861ae 100644 --- a/lvgl/src/support.rs +++ b/lvgl/src/support.rs @@ -65,7 +65,7 @@ impl From for Rgb565 { } } -/// Events are triggered in LittlevGL when something happens which might be interesting to +/// Events are triggered in LVGL when something happens which might be interesting to /// the user, e.g. if an object: /// - is clicked /// - is dragged diff --git a/lvgl/src/ui.rs b/lvgl/src/ui.rs index 4a96f90..b6190ed 100644 --- a/lvgl/src/ui.rs +++ b/lvgl/src/ui.rs @@ -1,4 +1,4 @@ -use crate::mem::Box; +use crate::Box; use crate::{Color, Event, LvError, LvResult, Obj, Widget}; use core::marker::PhantomData; use core::mem::MaybeUninit; @@ -10,7 +10,7 @@ use embedded_graphics::pixelcolor::PixelColor; use embedded_graphics::prelude::*; use embedded_graphics::{drawable, DrawTarget}; -// There can only be a single reference to LittlevGL library. +// There can only be a single reference to LVGL library. static LVGL_IN_USE: AtomicBool = AtomicBool::new(false); // TODO: Make this an external configuration @@ -23,13 +23,13 @@ where T: DrawTarget, C: PixelColor + From, { - // LittlevGL is not thread-safe by default. + // LVGL is not thread-safe by default. _not_sync: PhantomData<*mut ()>, // Later we can add possibility to have multiple displays by using `heapless::Vec` display_data: Option>, } -// LittlevGL does not use thread locals. +// LVGL does not use thread locals. unsafe impl Send for UI where T: DrawTarget, @@ -47,9 +47,7 @@ where .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) .is_ok() { - unsafe { - lvgl_sys::lv_init(); - } + crate::lvgl_init(); Ok(Self { _not_sync: PhantomData, display_data: None, @@ -75,15 +73,15 @@ where // Initialize the display buffer lvgl_sys::lv_disp_buf_init( disp_buf.as_mut_ptr(), - Box::into_raw(Box::new(refresh_buffer1)?) as *mut cty::c_void, - Box::into_raw(Box::new(refresh_buffer2)?) as *mut cty::c_void, + Box::into_raw(Box::new(refresh_buffer1)) as *mut cty::c_void, + Box::into_raw(Box::new(refresh_buffer2)) as *mut cty::c_void, lvgl_sys::LV_HOR_RES_MAX * REFRESH_BUFFER_LEN as u32, ); // Basic initialization of the display driver lvgl_sys::lv_disp_drv_init(disp_drv.as_mut_ptr()); - let mut disp_drv = Box::new(disp_drv.assume_init())?; + let mut disp_drv = Box::new(disp_drv.assume_init()); // Assign the buffer to the display - disp_drv.buffer = Box::into_raw(Box::new(disp_buf.assume_init())?); + disp_drv.buffer = Box::into_raw(Box::new(disp_buf.assume_init())); // Set your driver function disp_drv.flush_cb = Some(display_callback_wrapper::); disp_drv.user_data = &mut self.display_data as *mut _ as *mut cty::c_void; @@ -166,12 +164,12 @@ unsafe extern "C" fn display_callback_wrapper( // TODO: Can we do anything when there is a error while flushing? let _ = display_flush(&mut user_data.display, (x1, x2), (y1, y2), color_p); } - // Indicate to LittlevGL that we are ready with the flushing + // Indicate to LVGL that we are ready with the flushing lvgl_sys::lv_disp_flush_ready(disp_drv); } // We separate this display flush function to reduce the amount of unsafe code we need to write. -// This also provides a good separation of concerns, what is necessary from LittlevGL to work and +// This also provides a good separation of concerns, what is necessary from LVGL to work and // what is the lvgl-rs wrapper responsibility. fn display_flush( display: &mut T,