Merge branch 'main' into audio-driver

This commit is contained in:
2025-11-17 10:19:14 -07:00
40 changed files with 3048 additions and 906 deletions

View File

@@ -2,7 +2,12 @@
#![no_main]
extern crate alloc;
use abi::{KeyCode, KeyState, display::Display, get_key, lock_display, print};
use abi::{
display::Display,
get_key,
keyboard::{KeyCode, KeyState},
println,
};
use alloc::{format, string::String, vec, vec::Vec};
use core::panic::PanicInfo;
use embedded_graphics::{
@@ -23,11 +28,7 @@ use embedded_layout::{
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
print(&format!(
"user panic: {} @ {:?}",
info.message(),
info.location(),
));
println!("user panic: {} @ {:?}", info.message(), info.location(),);
loop {}
}
@@ -37,8 +38,8 @@ pub extern "Rust" fn _start() {
}
pub fn main() {
print("Starting Calculator app");
let mut display = Display;
println!("Starting Calculator app");
let mut display = Display::take().unwrap();
let mut input = vec!['e', 'x', 'p', 'r', ':', ' '];
let input_min = input.len();
@@ -57,8 +58,6 @@ pub fn main() {
loop {
if dirty {
lock_display(true);
let style = PrimitiveStyle::with_fill(Rgb565::BLACK);
if let Some(area) = last_area {
Rectangle::new(area.0.top_left, area.0.size)
@@ -102,11 +101,10 @@ pub fn main() {
eq_layout.draw(&mut display).unwrap();
dirty = false;
lock_display(false);
}
let event = get_key();
if event.state != KeyState::Idle {
if event.state == KeyState::Released {
match event.key {
KeyCode::Char(ch) => {
input.push(ch);

View File

@@ -4,11 +4,13 @@
extern crate alloc;
use abi::{
KeyCode, KeyState,
display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH},
get_key, list_dir, lock_display, print, read_file,
fs::{Entries, list_dir, read_file},
get_key,
keyboard::{KeyCode, KeyState},
println,
};
use alloc::{format, string::ToString};
use alloc::{format, vec};
use core::panic::PanicInfo;
use embedded_graphics::{
Drawable, image::Image, mono_font::MonoTextStyle, mono_font::ascii::FONT_6X10,
@@ -18,11 +20,7 @@ use tinybmp::Bmp;
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
print(&format!(
"user panic: {} @ {:?}",
info.message(),
info.location(),
));
println!("user panic: {} @ {:?}", info.message(), info.location());
loop {}
}
@@ -32,11 +30,10 @@ pub extern "Rust" fn _start() {
}
pub fn main() {
print("Starting Gallery app");
static mut BMP_BUF: [u8; 100_000] = [0_u8; 100_000];
let mut display = Display;
println!("Starting Gallery app");
let mut bmp_buf = vec![0_u8; 100_000];
let mut display = Display::take().unwrap();
// Grid parameters
let grid_cols = 3;
let grid_rows = 3;
let cell_width = SCREEN_WIDTH as i32 / grid_cols;
@@ -44,53 +41,44 @@ pub fn main() {
let mut images_drawn = 0;
let mut files = [const { None }; 18];
let files_num = list_dir("/images", &mut files);
let mut entries = Entries::new();
let files_num = list_dir("/images", &mut entries);
for file in &files[2..files_num] {
for file in &entries.entries()[2..files_num] {
if images_drawn >= grid_cols * grid_rows {
break; // only draw 3x3
}
if let Some(f) = file {
print(&format!("file: {}", f.name));
if f.name.extension() == b"bmp" || f.name.extension() == b"BMP" {
let file = format!("/images/{}", f.name);
println!("file: {}", file);
if file.extension().unwrap_or("") == "bmp" || file.extension().unwrap_or("") == "BMP" {
let file_path = format!("/images/{}", file);
let read = read_file(&file, 0, &mut unsafe { &mut BMP_BUF[..] });
if read > 0 {
let bmp = Bmp::from_slice(unsafe { &BMP_BUF }).expect("failed to parse bmp");
let read = read_file(&file_path, 0, &mut &mut bmp_buf[..]);
if read > 0 {
let bmp = Bmp::from_slice(&bmp_buf).expect("failed to parse bmp");
let row = images_drawn / grid_cols;
let col = images_drawn % grid_cols;
let cell_x = col * cell_width;
let cell_y = row * cell_height;
let row = images_drawn / grid_cols;
let col = images_drawn % grid_cols;
let cell_x = col * cell_width;
let cell_y = row * cell_height;
// Center image inside cell
let bmp_w = bmp.size().width as i32;
let bmp_h = bmp.size().height as i32;
let x = cell_x + (cell_width - bmp_w) / 2;
let y = cell_y + 5; // 5px top margin
// Center image inside cell
let bmp_w = bmp.size().width as i32;
let bmp_h = bmp.size().height as i32;
let x = cell_x + (cell_width - bmp_w) / 2;
let y = cell_y + 5; // 5px top margin
lock_display(true);
Image::new(&bmp, Point::new(x, y))
.draw(&mut display)
.unwrap();
let text_style = MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE);
let text_y = y + bmp_h + 2; // 2px gap under image
Text::new(
f.name.to_string().as_str(),
Point::new(cell_x + 2, text_y),
text_style,
)
Image::new(&bmp, Point::new(x, y))
.draw(&mut display)
.unwrap();
lock_display(false);
let text_style = MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE);
let text_y = y + bmp_h + 2; // 2px gap under image
Text::new(&file.base(), Point::new(cell_x + 2, text_y), text_style)
.draw(&mut display)
.unwrap();
images_drawn += 1;
}
images_drawn += 1;
}
}
}

10
user-apps/gif/Cargo.toml Normal file
View File

@@ -0,0 +1,10 @@
[package]
name = "gif"
version = "0.1.0"
edition = "2024"
[dependencies]
abi = { path = "../../abi" }
selection_ui = { path = "../../selection_ui" }
embedded-graphics = "0.8.1"
tinygif = { git = "https://github.com/LegitCamper/tinygif" }

28
user-apps/gif/build.rs Normal file
View File

@@ -0,0 +1,28 @@
//! This build script copies the `memory.x` file from the crate root into
//! a directory where the linker can always find it at build time.
//! For many projects this is optional, as the linker always searches the
//! project root directory -- wherever `Cargo.toml` is. However, if you
//! are using a workspace or have a more complicated build setup, this
//! build script becomes required. Additionally, by requesting that
//! Cargo re-run the build script whenever `memory.x` is changed,
//! updating `memory.x` ensures a rebuild of the application with the
//! new memory settings.
use std::env;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
fn main() {
// Put `memory.x` in our output directory and ensure it's
// on the linker search path.
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
File::create(out.join("memory.x"))
.unwrap()
.write_all(include_bytes!("../memory.x"))
.unwrap();
println!("cargo:rustc-link-search={}", out.display());
println!("cargo:rerun-if-changed=memory.x");
println!("cargo:rustc-link-arg-bins=-Tmemory.x");
}

103
user-apps/gif/src/main.rs Normal file
View File

@@ -0,0 +1,103 @@
#![no_std]
#![no_main]
extern crate alloc;
use abi::{
display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH},
fs::{Entries, file_len, list_dir, read_file},
get_key, get_ms,
keyboard::{KeyCode, KeyState},
println, sleep,
};
use alloc::{format, vec, vec::Vec};
use core::panic::PanicInfo;
use embedded_graphics::{
image::ImageDrawable,
mono_font::{MonoTextStyle, ascii::FONT_6X10},
pixelcolor::Rgb565,
prelude::{Point, RgbColor},
transform::Transform,
};
use selection_ui::{SelectionUi, SelectionUiError, draw_text_center};
use tinygif::Gif;
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
println!("user panic: {} @ {:?}", info.message(), info.location(),);
loop {}
}
#[unsafe(no_mangle)]
pub extern "Rust" fn _start() {
main()
}
pub fn main() {
println!("Starting Gif app");
let mut display = Display::take().unwrap();
let mut entries = Entries::new();
list_dir("/gifs", &mut entries);
let mut files = entries.entries();
files.retain(|e| e.extension().unwrap_or("") == "gif");
let mut gifs = files.iter().map(|e| e.full_name()).collect::<Vec<&str>>();
gifs.sort();
let mut selection_ui = SelectionUi::new(&mut gifs);
let selection = match selection_ui.run_selection_ui(&mut display) {
Ok(maybe_sel) => maybe_sel,
Err(e) => match e {
SelectionUiError::SelectionListEmpty => {
draw_text_center(
&mut display,
"No Gifs were found in /gifs",
MonoTextStyle::new(&FONT_6X10, Rgb565::RED),
)
.expect("Display Error");
None
}
SelectionUiError::DisplayError(_) => panic!("Display Error"),
},
};
assert!(selection.is_some());
let file_name = format!("/gifs/{}", gifs[selection.unwrap()]);
let size = file_len(&file_name);
let mut buf = vec![0_u8; size];
let read = read_file(&file_name, 0, &mut buf);
println!("read: {}, file size: {}", read, size);
assert!(read == size);
let gif = Gif::<Rgb565>::from_slice(&buf).expect("Failed to parse gif");
let translation = Point::new(
(SCREEN_WIDTH as i32 - gif.width() as i32) / 2,
(SCREEN_HEIGHT as i32 - gif.height() as i32) / 2,
);
let mut frame_num = 0;
loop {
for mut frame in gif.frames() {
let start = get_ms();
frame.translate_mut(translation).draw(&mut display).unwrap();
frame_num += 1;
if frame_num % 5 == 0 {
let event = get_key();
if event.state != KeyState::Idle {
match event.key {
KeyCode::Esc => {
drop(buf);
return;
}
_ => (),
};
};
}
sleep(((frame.delay_centis as u64) * 10).saturating_sub(start));
}
}
}

View File

@@ -1,6 +1,6 @@
MEMORY
{
RAM : ORIGIN = 0x0, LENGTH = 150K
RAM : ORIGIN = 0x0, LENGTH = 250K
}
SECTIONS

View File

@@ -6,5 +6,5 @@ edition = "2024"
[dependencies]
abi = { path = "../../abi" }
embedded-graphics = "0.8.1"
embedded-snake = { path = "../../../embedded-snake-rs" }
embedded-snake = { git = "https://github.com/LegitCamper/embedded-snake-rs" }
rand = { version = "0.9.0", default-features = false }

View File

@@ -3,22 +3,19 @@
extern crate alloc;
use abi::{
KeyCode, KeyState, Rng,
Rng,
display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH},
get_key, lock_display, print, sleep,
get_key,
keyboard::{KeyCode, KeyState},
println, sleep,
};
use alloc::format;
use core::panic::PanicInfo;
use embedded_graphics::{pixelcolor::Rgb565, prelude::RgbColor};
use embedded_snake::{Direction, SnakeGame};
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
print(&format!(
"user panic: {} @ {:?}",
info.message(),
info.location(),
));
println!("user panic: {} @ {:?}", info.message(), info.location(),);
loop {}
}
@@ -30,8 +27,8 @@ pub extern "Rust" fn _start() {
const CELL_SIZE: usize = 8;
pub fn main() {
print("Starting Snake app");
let mut display = Display;
println!("Starting Snake app");
let mut display = Display::take().unwrap();
let mut game = SnakeGame::<100, Rgb565, Rng>::new(
SCREEN_WIDTH as u16,
@@ -60,10 +57,8 @@ pub fn main() {
};
// ensure all draws show up at once
lock_display(true);
game.pre_draw(&mut display);
game.draw(&mut display);
lock_display(false);
sleep(15);
}

View File

@@ -3,22 +3,20 @@
extern crate alloc;
use abi::{
AUDIO_BUFFER_LEN, KeyCode, KeyState, Rng, audio_buffer_ready,
display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH},
file_len, get_key, lock_display, print, read_file, send_audio_buffer, sleep,
audio::{AUDIO_BUFFER_LEN, audio_buffer_ready, send_audio_buffer},
display::Display,
fs::{file_len, read_file},
get_key,
keyboard::{KeyCode, KeyState},
println,
};
use alloc::{format, string::String};
use alloc::string::String;
use core::panic::PanicInfo;
use embedded_audio::{AudioFile, PlatformFile, PlatformFileError, wav::Wav};
use embedded_graphics::{pixelcolor::Rgb565, prelude::RgbColor};
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
print(&format!(
"user panic: {} @ {:?}",
info.message(),
info.location(),
));
println!("user panic: {} @ {:?}", info.message(), info.location(),);
loop {}
}
@@ -28,8 +26,8 @@ pub extern "Rust" fn _start() {
}
pub fn main() {
print("Starting Wav player app");
let mut display = Display;
println!("Starting Wav player app");
let mut _display = Display::take();
let mut buf = [0_u8; AUDIO_BUFFER_LEN];
@@ -41,7 +39,7 @@ pub fn main() {
wav.restart().unwrap()
}
let read = wav.read(&mut buf).unwrap();
let _read = wav.read(&mut buf).unwrap();
send_audio_buffer(&buf);
}