Add LVGL Rust Global Allocator feature #48
16 changed files with 149 additions and 83 deletions
|
@ -1,7 +1,6 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"lvgl",
|
||||
"lvgl-sys",
|
||||
"lvgl-codegen",
|
||||
"examples"
|
||||
"lvgl-sys",
|
||||
]
|
||||
|
|
24
README.md
24
README.md
|
@ -1,18 +1,19 @@
|
|||
<h1 align="center"> LittlevGL - Open-source Embedded GUI Library in Rust</h1>
|
||||
<h1 align="center"> LVGL - Open-source Embedded GUI Library in Rust</h1>
|
||||
|
||||
![Original LittlevGL demo image](lv_demo.png)
|
||||
![Original LVGL demo image](lv_demo.png)
|
||||
|
||||
<p align="center">
|
||||
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.
|
||||
</p>
|
||||
<p align="center">
|
||||
LittlevGL is compatible with <samp>#![no_std]</samp> environments by default.
|
||||
LVGL is compatible with <samp>#![no_std]</samp> environments by default.
|
||||
</p>
|
||||
|
||||
<h4 align="center">
|
||||
<a href="https://lvgl.io/">Official LittlevGL Website </a> ·
|
||||
<a href="https://github.com/rafaelcaricio/lvgl-rs-wasm">Rust to WASM demo</a> ·
|
||||
<a href="https://lvgl.io/">Official LVGL Website </a> ·
|
||||
<a href="https://github.com/littlevgl/lvgl">C library repository</a> ·
|
||||
<a href="https://lvgl.io/demos">Live demo</a>
|
||||
<a href="https://lvgl.io/demos">Official live demos</a>
|
||||
</h4>
|
||||
|
||||
---
|
||||
|
@ -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
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
[package]
|
||||
name = "demo"
|
||||
version = "0.1.0"
|
||||
authors = ["Rafael Caricio <crates@caric.io>"]
|
||||
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"
|
|
@ -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<Rgb565> = 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)?;
|
||||
|
|
|
@ -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:
|
||||
//
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 <crates.lvgl-sys@caric.io>"]
|
||||
edition = "2018"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 <crates.lvgl@caric.io>"]
|
||||
edition = "2018"
|
||||
|
@ -17,6 +17,10 @@ cty = "0.2.1"
|
|||
embedded-graphics = "0.6.2"
|
||||
cstr_core = "0.2.3"
|
||||
bitflags = "1.2.1"
|
||||
lazy_static = { version = "1.4.0", features = ["spin_no_std"] }
|
||||
|
||||
[features]
|
||||
lvgl_alloc = []
|
||||
|
||||
[build-dependencies]
|
||||
quote = "1.0.9"
|
||||
|
@ -24,3 +28,28 @@ 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"
|
||||
cstr_core = { version = "0.2.0", features = ["alloc"] }
|
||||
|
||||
[[example]]
|
||||
name = "demo"
|
||||
path = "../examples/demo.rs"
|
||||
|
||||
[[example]]
|
||||
name = "bar"
|
||||
path = "../examples/bar.rs"
|
||||
|
||||
[[example]]
|
||||
name = "button_click"
|
||||
path = "../examples/button_click.rs"
|
||||
|
||||
[[example]]
|
||||
name = "gauge"
|
||||
path = "../examples/gauge.rs"
|
||||
|
||||
[[example]]
|
||||
name = "arc"
|
||||
path = "../examples/arc.rs"
|
||||
|
|
20
lvgl/src/allocator.rs
Normal file
20
lvgl/src/allocator.rs
Normal file
|
@ -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!
|
||||
let _ = *crate::LVGL_INITIALIZED;
|
||||
lvgl_sys::lv_mem_alloc(layout.size() as lvgl_sys::size_t) as *mut u8
|
||||
}
|
||||
|
||||
unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
|
||||
let _ = *crate::LVGL_INITIALIZED;
|
||||
lvgl_sys::lv_mem_free(ptr as *const cty::c_void)
|
||||
}
|
||||
}
|
|
@ -17,13 +17,37 @@
|
|||
#[macro_use]
|
||||
extern crate bitflags;
|
||||
|
||||
pub(crate) mod mem;
|
||||
#[cfg(feature = "lvgl_alloc")]
|
||||
extern crate alloc;
|
||||
|
||||
mod support;
|
||||
mod ui;
|
||||
#[macro_use]
|
||||
mod lv_core;
|
||||
pub mod widgets;
|
||||
|
||||
#[cfg(feature = "lvgl_alloc")]
|
||||
mod allocator;
|
||||
|
||||
#[cfg(feature = "lvgl_alloc")]
|
||||
use ::alloc::boxed::Box;
|
||||
|
||||
#[cfg(not(feature = "lvgl_alloc"))]
|
||||
pub(crate) mod mem;
|
||||
|
||||
#[cfg(not(feature = "lvgl_alloc"))]
|
||||
use crate::mem::Box;
|
||||
|
||||
pub use lv_core::*;
|
||||
pub use support::*;
|
||||
pub use ui::*;
|
||||
|
||||
// Initialize LVGL only once.
|
||||
lazy_static::lazy_static! {
|
||||
static ref LVGL_INITIALIZED: bool = {
|
||||
unsafe {
|
||||
lvgl_sys::lv_init();
|
||||
|
||||
}
|
||||
true
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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<ptr::NonNull<lvgl_sys::lv_obj_t>>;
|
||||
|
@ -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<u8>;
|
||||
|
@ -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::<Self, F>),
|
||||
|
|
|
@ -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::<lvgl_sys::lv_style_t>::uninit();
|
||||
lvgl_sys::lv_style_init(style.as_mut_ptr());
|
||||
Box::new(style.assume_init()).unwrap()
|
||||
Box::new(style.assume_init())
|
||||
};
|
||||
Self { raw }
|
||||
}
|
||||
|
|
|
@ -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<T>(NonNull<T>);
|
|||
|
||||
impl<T> Box<T> {
|
||||
/// Allocate memory using LVGL memory API and place `T` in the LVGL tracked memory.
|
||||
pub fn new(value: T) -> LvResult<Box<T>> {
|
||||
pub fn new(value: T) -> Box<T> {
|
||||
let size = mem::size_of::<T>();
|
||||
let inner = unsafe {
|
||||
let ptr = lvgl_sys::lv_mem_alloc(size as lvgl_sys::size_t) as *mut T;
|
||||
|
@ -29,9 +28,11 @@ impl<T> Box<T> {
|
|||
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 {
|
||||
|
@ -88,9 +89,9 @@ mod test {
|
|||
fn place_value_in_lv_mem() {
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -119,11 +120,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 +156,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 _);
|
||||
|
|
|
@ -65,7 +65,7 @@ impl From<Color> 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
|
||||
|
|
|
@ -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>,
|
||||
C: PixelColor + From<Color>,
|
||||
{
|
||||
// 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<DisplayUserData<T, C>>,
|
||||
}
|
||||
|
||||
// LittlevGL does not use thread locals.
|
||||
// LVGL does not use thread locals.
|
||||
unsafe impl<T, C> Send for UI<T, C>
|
||||
where
|
||||
T: DrawTarget<C>,
|
||||
|
@ -47,9 +47,7 @@ where
|
|||
.compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
|
||||
.is_ok()
|
||||
{
|
||||
unsafe {
|
||||
lvgl_sys::lv_init();
|
||||
}
|
||||
let _ = *crate::LVGL_INITIALIZED;
|
||||
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::<T, C>);
|
||||
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<T, C>(
|
|||
// 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<T, C>(
|
||||
display: &mut T,
|
||||
|
|
Loading…
Reference in a new issue
We could enable the LVGL side to use Rust memory allocator, if it's available and the feature "alloc" enabled in lvgl-rs. That way we wouldn't need to implement this custom
Box
implementation for that case. We would still need this implementation for cases where all features in lvgl-rs are disabled and we cannot assume that LVGL is using the same memory space.The problem is that LVGL C always support "dynamically" allocated memory backed by a static array-based backend, just like wee_alloc. LVGL-rs is a library and the choice of allocator must be made by the firmware/application developer. Besides that, the developer might decide that they don't want to have an allocator at all on their Rust firmware. LVGL C will still use their own memory management implementation.
One option is to always require users of LVGL-rs to define a global allocator in their project. This would be a limitation for some use cases?! In that scenario, we wouldn't need the code in
mem
module at all, since the memory space would be shared by LVGL C and Rust.