2 Commits

Author SHA1 Message Date
513042b561 works 2025-11-04 16:50:57 -07:00
375b5d1d47 init 2025-11-04 13:43:48 -07:00
45 changed files with 814 additions and 1650 deletions

4
.gitignore vendored
View File

@@ -1,6 +1,4 @@
/target /target
*.uf2 *.uf2
userlib_sys.h abi_sys.h
assets/gif/bad_apple.gif assets/gif/bad_apple.gif
picolibc
user-apps/gboy/Peanut-GB

6
.gitmodules vendored
View File

@@ -1,6 +0,0 @@
[submodule "picolibc"]
path = picolibc
url = https://github.com/picolibc/picolibc
[submodule "user-apps/gboy/Peanut-GB"]
path = user-apps/gboy/Peanut-GB
url = https://github.com/deltabeard/Peanut-GB

492
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,14 +2,13 @@
resolver = "3" resolver = "3"
members = [ members = [
"kernel", "kernel",
"userlib_sys", "abi_sys",
"userlib", "abi",
"selection_ui", "selection_ui",
"user_apps/calculator", "user-apps/calculator",
"user_apps/snake", "user-apps/snake",
"user_apps/gallery", "user-apps/gallery",
"user_apps/gif", "user-apps/gif",
"user_apps/wav_player",
] ]
[profile.release] [profile.release]
@@ -24,7 +23,7 @@ codegen-units = 1
lto = true lto = true
debug = false debug = false
opt-level = "z" opt-level = "z"
panic = "unwind" panic = "abort"
[profile.dev] [profile.dev]
lto = true lto = true

View File

@@ -1,27 +1,26 @@
# PicoCalc OS (Rust) # PicoCalc OS (Rust)
A simple kernel and applications for the **Clockwork PicoCalc**, written in Rust. A simple operating system for the **Clockwork PicoCalc**, written in Rust.
This project provides a minimal kernel, syscall table, and user-space applications to experiment with kernel development on constrained hardware. This project provides a minimal kernel, ABI, and user-space applications to experiment with OS development on constrained hardware.
## Status ## Status
Basic synchronous applications are working great. Basic synchronous applications are working great.
Current focus is on exanding applications and porting software, finding bugs in ffi, and making sure the kernel is as stable as possible. Current focus is on **expanding the ABI syscalls** and **fixing the MSC/USB-SCSI driver** to make application development easier and smoother.
## Project Structure ## Project Structure
- **`kernel/`** The core OS kernel - **`kernel/`** The core OS kernel (task scheduling, drivers, memory, etc.)
- **`userlib_sys/`** C FFI bindings for kernel syscall - **`abi_sys/`** Shared application binary interface definitions for kernel ↔ userspace (Repr "C")
- **`userlib/`** Rust wrapper on top of `userlib_sys` - **`abi/`** Rust focused ABI helpers and abstractions for easier development
- **`picolib/`** Built with ```just newlib```, and provides libc symbols when linking with C libraries - **`user-apps/`** Collection of userspace programs (calculator, snake, etc.)
- **`user-apps/`** Collection of userspace programs (gif player, wav player, calculator, snake, etc.)
## Features ## Features
- Minimal Rust-based kernel targeting the PicoCalc - Minimal Rust-based kernel targeting the PicoCalc
- Custom ABI for *Mostly* safe communication between kernel and applications - Custom ABI for safe communication between kernel and applications
- Support for multiple user-space applications - Support for multiple user-space applications
- Hardware drivers tailored for the PicoCalc( Audio, Display, Keyboard, ans Storage ) - Hardware drivers tailored for the PicoCalc
## Getting Started ## Getting Started

View File

@@ -1,10 +1,10 @@
[package] [package]
name = "userlib" name = "abi"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
userlib_sys = { path = "../userlib_sys" } abi_sys = { path = "../abi_sys" }
embedded-graphics = "0.8.1" embedded-graphics = "0.8.1"
once_cell = { version = "1", default-features = false } once_cell = { version = "1", default-features = false }
rand_core = "0.9.3" rand_core = "0.9.3"

View File

@@ -3,11 +3,11 @@
extern crate alloc; extern crate alloc;
pub use abi_sys::{self, keyboard};
use abi_sys::{RngRequest, alloc, dealloc, keyboard::KeyEvent};
pub use alloc::format; pub use alloc::format;
use core::alloc::{GlobalAlloc, Layout}; use core::alloc::{GlobalAlloc, Layout};
use rand_core::RngCore; use rand_core::RngCore;
use userlib_sys::{RngRequest, keyboard::KeyEvent};
pub use userlib_sys::{keyboard, print};
#[global_allocator] #[global_allocator]
static ALLOC: Alloc = Alloc; static ALLOC: Alloc = Alloc;
@@ -16,37 +16,36 @@ struct Alloc;
unsafe impl GlobalAlloc for Alloc { unsafe impl GlobalAlloc for Alloc {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 { unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
userlib_sys::alloc(layout.into()) alloc(layout.into())
} }
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
userlib_sys::dealloc(ptr, layout.into()); dealloc(ptr, layout.into());
} }
} }
#[macro_export] #[macro_export]
macro_rules! println { macro_rules! print {
($($arg:tt)*) => {{ ($($arg:tt)*) => {{
let s = $crate::format!($($arg)*); let s = $crate::format!($($arg)*);
$crate::print(s.as_ptr(), s.len()); $crate::abi_sys::print(s.as_ptr(), s.len());
}}; }};
} }
pub fn sleep(ms: u64) { pub fn sleep(ms: u64) {
userlib_sys::sleep(ms); abi_sys::sleep(ms);
} }
pub fn get_ms() -> u64 { pub fn get_ms() -> u64 {
userlib_sys::get_ms() abi_sys::get_ms()
} }
pub fn get_key() -> KeyEvent { pub fn get_key() -> KeyEvent {
userlib_sys::keyboard::get_key().into() abi_sys::keyboard::get_key().into()
} }
pub mod display { pub mod display {
use core::sync::atomic::{AtomicBool, Ordering}; use abi_sys::CPixel;
use embedded_graphics::{ use embedded_graphics::{
Pixel, Pixel,
geometry::{Dimensions, Point}, geometry::{Dimensions, Point},
@@ -54,35 +53,18 @@ pub mod display {
prelude::{DrawTarget, Size}, prelude::{DrawTarget, Size},
primitives::Rectangle, primitives::Rectangle,
}; };
use userlib_sys::CPixel;
pub const SCREEN_WIDTH: usize = 320; pub const SCREEN_WIDTH: usize = 320;
pub const SCREEN_HEIGHT: usize = 320; pub const SCREEN_HEIGHT: usize = 320;
pub type Pixel565 = Pixel<Rgb565>; pub type Pixel565 = Pixel<Rgb565>;
const BUF_SIZE: usize = 1024; const BUF_SIZE: usize = 15 * 1024; // tune this for performance
static mut BUF: [CPixel; BUF_SIZE] = [CPixel::new(); BUF_SIZE]; static mut BUF: [CPixel; BUF_SIZE] = [CPixel::new(); BUF_SIZE];
// const BUF_SIZE: usize = 250 * 1024; // tune this for performance
// static mut BUF: Lazy<Vec<CPixel>> = Lazy::new(|| vec![const { CPixel::new() }; BUF_SIZE]);
static DISPLAY_TAKEN: AtomicBool = AtomicBool::new(false); pub struct Display;
pub struct Display {
_private: (),
}
impl Display {
/// Only one instance of Display can be taken
pub fn take() -> Option<Display> {
if DISPLAY_TAKEN
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_ok()
{
Some(Self { _private: () })
} else {
None
}
}
}
impl Dimensions for Display { impl Dimensions for Display {
fn bounding_box(&self) -> Rectangle { fn bounding_box(&self) -> Rectangle {
@@ -110,13 +92,13 @@ pub mod display {
count += 1; count += 1;
if count == BUF_SIZE - 1 { if count == BUF_SIZE - 1 {
userlib_sys::draw_iter(unsafe { BUF.as_ptr() }, count); abi_sys::draw_iter(unsafe { BUF.as_ptr() }, count);
count = 0; count = 0;
} }
} }
if count > 0 { if count > 0 {
userlib_sys::draw_iter(unsafe { BUF.as_ptr() }, count); abi_sys::draw_iter(unsafe { BUF.as_ptr() }, count);
} }
Ok(()) Ok(())
@@ -125,7 +107,7 @@ pub mod display {
} }
fn gen_rand(req: &mut RngRequest) { fn gen_rand(req: &mut RngRequest) {
userlib_sys::gen_rand(req); abi_sys::gen_rand(req);
} }
pub struct Rng; pub struct Rng;
@@ -158,30 +140,20 @@ impl RngCore for Rng {
} }
pub mod fs { pub mod fs {
use alloc::vec::Vec;
use core::fmt::Display; use core::fmt::Display;
pub fn read_file(file: &str, start_from: usize, buf: &mut [u8]) -> usize { use alloc::{format, vec::Vec};
userlib_sys::read_file(
pub fn read_file(file: &str, read_from: usize, buf: &mut [u8]) -> usize {
abi_sys::read_file(
file.as_ptr(), file.as_ptr(),
file.len(), file.len(),
start_from, read_from,
buf.as_mut_ptr(), buf.as_mut_ptr(),
buf.len(), buf.len(),
) )
} }
pub fn write_file(file: &str, start_from: usize, buf: &[u8]) {
userlib_sys::write_file(
file.as_ptr(),
file.len(),
start_from,
buf.as_ptr(),
buf.len(),
)
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct FileName<'a> { pub struct FileName<'a> {
full: &'a str, full: &'a str,
base: &'a str, base: &'a str,
@@ -225,15 +197,9 @@ pub mod fs {
const MAX_ENTRY_NAME_LEN: usize = 25; const MAX_ENTRY_NAME_LEN: usize = 25;
const MAX_ENTRIES: usize = 25; const MAX_ENTRIES: usize = 25;
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy)]
pub struct Entries([[u8; MAX_ENTRY_NAME_LEN]; MAX_ENTRIES]); pub struct Entries([[u8; MAX_ENTRY_NAME_LEN]; MAX_ENTRIES]);
impl Default for Entries {
fn default() -> Self {
Self::new()
}
}
impl Entries { impl Entries {
pub fn new() -> Self { pub fn new() -> Self {
Self([[0; MAX_ENTRY_NAME_LEN]; MAX_ENTRIES]) Self([[0; MAX_ENTRY_NAME_LEN]; MAX_ENTRIES])
@@ -260,7 +226,7 @@ pub mod fs {
} }
pub fn list_dir(path: &str, entries: &mut Entries) -> usize { pub fn list_dir(path: &str, entries: &mut Entries) -> usize {
userlib_sys::list_dir( abi_sys::list_dir(
path.as_ptr(), path.as_ptr(),
path.len(), path.len(),
entries.as_ptrs().as_mut_ptr(), entries.as_ptrs().as_mut_ptr(),
@@ -270,14 +236,6 @@ pub mod fs {
} }
pub fn file_len(str: &str) -> usize { pub fn file_len(str: &str) -> usize {
userlib_sys::file_len(str.as_ptr(), str.len()) abi_sys::file_len(str.as_ptr(), str.len())
}
}
pub mod audio {
pub use userlib_sys::{AUDIO_BUFFER_LEN, AUDIO_BUFFER_SAMPLES, audio_buffer_ready};
pub fn send_audio_buffer(buf: &[u8]) {
userlib_sys::send_audio_buffer(buf.as_ptr(), buf.len())
} }
} }

View File

@@ -1,5 +1,5 @@
[package] [package]
name = "userlib_sys" name = "abi_sys"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"

View File

@@ -12,12 +12,12 @@ use strum::{EnumCount, EnumIter};
pub type EntryFn = fn(); pub type EntryFn = fn();
pub const SYS_CALL_TABLE_COUNT: usize = 14; pub const ABI_CALL_TABLE_COUNT: usize = 11;
const _: () = assert!(SYS_CALL_TABLE_COUNT == SyscallTable::COUNT); const _: () = assert!(ABI_CALL_TABLE_COUNT == CallTable::COUNT);
#[derive(Clone, Copy, EnumIter, EnumCount)] #[derive(Clone, Copy, EnumIter, EnumCount)]
#[repr(u8)] #[repr(u8)]
pub enum SyscallTable { pub enum CallTable {
Alloc = 0, Alloc = 0,
Dealloc = 1, Dealloc = 1,
PrintString = 2, PrintString = 2,
@@ -28,15 +28,12 @@ pub enum SyscallTable {
GenRand = 7, GenRand = 7,
ListDir = 8, ListDir = 8,
ReadFile = 9, ReadFile = 9,
WriteFile = 10, FileLen = 10,
FileLen = 11,
AudioBufferReady = 12,
SendAudioBuffer = 13,
} }
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
#[unsafe(link_section = ".syscall_table")] #[unsafe(link_section = ".syscall_table")]
pub static mut SYS_CALL_TABLE: [usize; SYS_CALL_TABLE_COUNT] = [0; SYS_CALL_TABLE_COUNT]; pub static mut CALL_ABI_TABLE: [usize; ABI_CALL_TABLE_COUNT] = [0; ABI_CALL_TABLE_COUNT];
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
#[repr(C)] #[repr(C)]
@@ -46,9 +43,9 @@ pub struct CLayout {
} }
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
impl From<CLayout> for Layout { impl Into<Layout> for CLayout {
fn from(val: CLayout) -> Self { fn into(self) -> Layout {
unsafe { Layout::from_size_align_unchecked(val.size, val.alignment) } unsafe { Layout::from_size_align_unchecked(self.size, self.alignment) }
} }
} }
@@ -62,51 +59,51 @@ impl From<Layout> for CLayout {
} }
} }
pub type Alloc = extern "C" fn(layout: CLayout) -> *mut u8; pub type AllocAbi = extern "C" fn(layout: CLayout) -> *mut u8;
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub extern "C" fn alloc(layout: CLayout) -> *mut u8 { pub extern "C" fn alloc(layout: CLayout) -> *mut u8 {
let f: Alloc = unsafe { core::mem::transmute(SYS_CALL_TABLE[SyscallTable::Alloc as usize]) }; let f: AllocAbi = unsafe { core::mem::transmute(CALL_ABI_TABLE[CallTable::Alloc as usize]) };
f(layout) f(layout)
} }
pub type Dealloc = extern "C" fn(ptr: *mut u8, layout: CLayout); pub type DeallocAbi = extern "C" fn(ptr: *mut u8, layout: CLayout);
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub extern "C" fn dealloc(ptr: *mut u8, layout: CLayout) { pub extern "C" fn dealloc(ptr: *mut u8, layout: CLayout) {
let f: Dealloc = let f: DeallocAbi =
unsafe { core::mem::transmute(SYS_CALL_TABLE[SyscallTable::Dealloc as usize]) }; unsafe { core::mem::transmute(CALL_ABI_TABLE[CallTable::Dealloc as usize]) };
f(ptr, layout) f(ptr, layout)
} }
pub type Print = extern "C" fn(ptr: *const u8, len: usize); pub type PrintAbi = extern "C" fn(ptr: *const u8, len: usize);
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub extern "C" fn print(ptr: *const u8, len: usize) { pub extern "C" fn print(ptr: *const u8, len: usize) {
let f: Print = let f: PrintAbi =
unsafe { core::mem::transmute(SYS_CALL_TABLE[SyscallTable::PrintString as usize]) }; unsafe { core::mem::transmute(CALL_ABI_TABLE[CallTable::PrintString as usize]) };
f(ptr, len); f(ptr, len);
} }
pub type SleepMs = extern "C" fn(ms: u64); pub type SleepMsAbi = extern "C" fn(ms: u64);
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub extern "C" fn sleep(ms: u64) { pub extern "C" fn sleep(ms: u64) {
let f: SleepMs = let f: SleepMsAbi =
unsafe { core::mem::transmute(SYS_CALL_TABLE[SyscallTable::SleepMs as usize]) }; unsafe { core::mem::transmute(CALL_ABI_TABLE[CallTable::SleepMs as usize]) };
f(ms); f(ms);
} }
pub type GetMs = extern "C" fn() -> u64; pub type GetMsAbi = extern "C" fn() -> u64;
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub extern "C" fn get_ms() -> u64 { pub extern "C" fn get_ms() -> u64 {
let f: GetMs = unsafe { core::mem::transmute(SYS_CALL_TABLE[SyscallTable::GetMs as usize]) }; let f: GetMsAbi = unsafe { core::mem::transmute(CALL_ABI_TABLE[CallTable::GetMs as usize]) };
f() f()
} }
#[repr(C)] #[repr(C)]
#[derive(Default, Copy, Clone)] #[derive(Copy, Clone)]
pub struct CPixel { pub struct CPixel {
pub x: i32, pub x: i32,
pub y: i32, pub y: i32,
@@ -123,36 +120,33 @@ impl CPixel {
} }
} }
impl From<CPixel> for Pixel<Rgb565> { impl Into<CPixel> for Pixel<Rgb565> {
fn from(value: CPixel) -> Self { fn into(self) -> CPixel {
Pixel(
Point::new(value.x, value.y),
RawU16::new(value.color).into(),
)
}
}
impl From<Pixel<Rgb565>> for CPixel {
fn from(value: Pixel<Rgb565>) -> Self {
CPixel { CPixel {
x: value.0.x, x: self.0.x,
y: value.0.y, y: self.0.y,
color: value.1.into_storage(), color: self.1.into_storage(),
} }
} }
} }
pub type DrawIter = extern "C" fn(ptr: *const CPixel, len: usize); impl Into<Pixel<Rgb565>> for CPixel {
fn into(self) -> Pixel<Rgb565> {
Pixel(Point::new(self.x, self.y), RawU16::new(self.color).into())
}
}
pub type DrawIterAbi = extern "C" fn(ptr: *const CPixel, len: usize);
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub extern "C" fn draw_iter(ptr: *const CPixel, len: usize) { pub extern "C" fn draw_iter(ptr: *const CPixel, len: usize) {
let f: DrawIter = let f: DrawIterAbi =
unsafe { core::mem::transmute(SYS_CALL_TABLE[SyscallTable::DrawIter as usize]) }; unsafe { core::mem::transmute(CALL_ABI_TABLE[CallTable::DrawIter as usize]) };
f(ptr, len); f(ptr, len);
} }
pub mod keyboard { pub mod keyboard {
use crate::{SYS_CALL_TABLE, SyscallTable}; use crate::{CALL_ABI_TABLE, CallTable};
bitflags::bitflags! { bitflags::bitflags! {
#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)] #[derive(Default, Debug, PartialEq, Eq, Clone, Copy)]
@@ -174,12 +168,12 @@ pub mod keyboard {
pub mods: Modifiers, pub mods: Modifiers,
} }
impl From<KeyEventC> for KeyEvent { impl Into<KeyEvent> for KeyEventC {
fn from(val: KeyEventC) -> Self { fn into(self) -> KeyEvent {
KeyEvent { KeyEvent {
key: val.key.into(), key: self.key.into(),
state: val.state, state: self.state,
mods: val.mods, mods: self.mods,
} }
} }
} }
@@ -191,12 +185,12 @@ pub mod keyboard {
pub mods: Modifiers, pub mods: Modifiers,
} }
impl From<KeyEvent> for KeyEventC { impl Into<KeyEventC> for KeyEvent {
fn from(val: KeyEvent) -> Self { fn into(self) -> KeyEventC {
KeyEventC { KeyEventC {
key: val.key.into(), key: self.key.into(),
state: val.state, state: self.state,
mods: val.mods, mods: self.mods,
} }
} }
} }
@@ -270,9 +264,9 @@ pub mod keyboard {
Unknown(u8), Unknown(u8),
} }
impl From<KeyCode> for u8 { impl Into<u8> for KeyCode {
fn from(val: KeyCode) -> Self { fn into(self) -> u8 {
match val { match self {
KeyCode::JoyUp => 0x01, KeyCode::JoyUp => 0x01,
KeyCode::JoyDown => 0x02, KeyCode::JoyDown => 0x02,
KeyCode::JoyLeft => 0x03, KeyCode::JoyLeft => 0x03,
@@ -370,12 +364,12 @@ pub mod keyboard {
} }
} }
pub type GetKey = extern "C" fn() -> KeyEventC; pub type GetKeyAbi = extern "C" fn() -> KeyEventC;
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub extern "C" fn get_key() -> KeyEventC { pub extern "C" fn get_key() -> KeyEventC {
let f: GetKey = let f: GetKeyAbi =
unsafe { core::mem::transmute(SYS_CALL_TABLE[SyscallTable::GetKey as usize]) }; unsafe { core::mem::transmute(CALL_ABI_TABLE[CallTable::GetKey as usize]) };
f() f()
} }
} }
@@ -392,7 +386,7 @@ pub type GenRand = extern "C" fn(req: &mut RngRequest);
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub extern "C" fn gen_rand(req: &mut RngRequest) { pub extern "C" fn gen_rand(req: &mut RngRequest) {
unsafe { unsafe {
let ptr = SYS_CALL_TABLE[SyscallTable::GenRand as usize]; let ptr = CALL_ABI_TABLE[CallTable::GenRand as usize];
let f: GenRand = core::mem::transmute(ptr); let f: GenRand = core::mem::transmute(ptr);
f(req) f(req)
} }
@@ -415,7 +409,7 @@ pub extern "C" fn list_dir(
max_entry_str_len: usize, max_entry_str_len: usize,
) -> usize { ) -> usize {
unsafe { unsafe {
let ptr = SYS_CALL_TABLE[SyscallTable::ListDir as usize]; let ptr = CALL_ABI_TABLE[CallTable::ListDir as usize];
let f: ListDir = core::mem::transmute(ptr); let f: ListDir = core::mem::transmute(ptr);
f(str, len, entries, entry_count, max_entry_str_len) f(str, len, entries, entry_count, max_entry_str_len)
} }
@@ -438,62 +432,19 @@ pub extern "C" fn read_file(
buf_len: usize, buf_len: usize,
) -> usize { ) -> usize {
unsafe { unsafe {
let ptr = SYS_CALL_TABLE[SyscallTable::ReadFile as usize]; let ptr = CALL_ABI_TABLE[CallTable::ReadFile as usize];
let f: ReadFile = core::mem::transmute(ptr); let f: ReadFile = core::mem::transmute(ptr);
f(str, len, read_from, buf, buf_len) f(str, len, read_from, buf, buf_len)
} }
} }
pub type WriteFile =
extern "C" fn(str: *const u8, len: usize, write_from: usize, buf: *const u8, buf_len: usize);
#[unsafe(no_mangle)]
pub extern "C" fn write_file(
str: *const u8,
len: usize,
write_from: usize,
buf: *const u8,
buf_len: usize,
) {
unsafe {
let ptr = SYS_CALL_TABLE[SyscallTable::WriteFile as usize];
let f: WriteFile = core::mem::transmute(ptr);
f(str, len, write_from, buf, buf_len)
}
}
pub type FileLen = extern "C" fn(str: *const u8, len: usize) -> usize; pub type FileLen = extern "C" fn(str: *const u8, len: usize) -> usize;
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
pub extern "C" fn file_len(str: *const u8, len: usize) -> usize { pub extern "C" fn file_len(str: *const u8, len: usize) -> usize {
unsafe { unsafe {
let ptr = SYS_CALL_TABLE[SyscallTable::FileLen as usize]; let ptr = CALL_ABI_TABLE[CallTable::FileLen as usize];
let f: FileLen = core::mem::transmute(ptr); let f: FileLen = core::mem::transmute(ptr);
f(str, len) f(str, len)
} }
} }
pub type AudioBufferReady = extern "C" fn() -> bool;
#[allow(unused)]
pub fn audio_buffer_ready() -> bool {
unsafe {
let ptr = SYS_CALL_TABLE[SyscallTable::AudioBufferReady as usize];
let f: AudioBufferReady = core::mem::transmute(ptr);
f()
}
}
pub const AUDIO_BUFFER_SAMPLES: usize = 1024;
pub const AUDIO_BUFFER_LEN: usize = AUDIO_BUFFER_SAMPLES * 2;
pub type SendAudioBuffer = extern "C" fn(ptr: *const u8, len: usize);
#[allow(unused)]
pub fn send_audio_buffer(buf: *const u8, len: usize) {
unsafe {
let ptr = SYS_CALL_TABLE[SyscallTable::SendAudioBuffer as usize];
let f: SendAudioBuffer = core::mem::transmute(ptr);
f(buf, len)
}
}

View File

@@ -1,37 +1,13 @@
target := "thumbv8m.main-none-eabihf"
kernel-dev board: kernel-dev board:
cargo run --bin kernel --features {{board}} --features fps --features defmt cargo run --bin kernel --features {{board}}
kernel-release-probe board:
cargo run --bin kernel --profile release --features {{board}} --features fps
kernel-release board: kernel-release board:
cargo build --bin kernel --release --no-default-features --features {{board}} cargo build --bin kernel --release --no-default-features --features {{board}}
elf2uf2-rs -d target/{{target}}/release/kernel elf2uf2-rs -d target/thumbv8m.main-none-eabihf/release/kernel
binary-args := "RUSTFLAGS=\"-C link-arg=-pie -C relocation-model=pic\"" binary-args := "RUSTFLAGS=\"-C link-arg=-pie -C relocation-model=pic\""
cbindgen: cbindgen:
cbindgen userlib_sys --output userlib_sys.h -q cbindgen abi_sys --output abi_sys.h -q
newlib:
#!/bin/bash
cd picolibc
mkdir build
cd build
CONFIG_PICOLIBC=true ../scripts/do-configure thumbv8m_main_fp-none-eabi \
--buildtype=minsize \
-Dtests=true \
-Dtinystdio=false \
-Dsingle-thread=true \
-Db_pie=true \
-Ddefault_library=static \
-Dtinystdio=false \
-Dnewlib-nano-malloc=true \
-Dmultilib=false \
-Dpicolib=true \
"$@"
DESTDIR=./install meson install
ninja
userapp app: userapp app:
{{binary-args}} cargo build --bin {{app}} --profile release-binary {{binary-args}} cargo build --bin {{app}} --profile release-binary
@@ -41,20 +17,3 @@ userapps: cbindgen
just userapp snake just userapp snake
just userapp gallery just userapp gallery
just userapp gif just userapp gif
just userapp wav_player
copy-userapp app:
cp ./target/{{target}}/release-binary/{{app}} /run/media/$(whoami)/PICOCALC/{{app}}.bin
copy-userapps:
#!/bin/bash
just userapps
just copy-userapp calculator
just copy-userapp snake
just copy-userapp gallery
just copy-userapp gif
just copy-userapp wav_player
DEV=$(lsblk -o LABEL,NAME -nr | awk -v L="PICOCALC" '$1==L {print "/dev/" $2}')
udisksctl unmount -b "$DEV"
udisksctl power-off -b "$DEV"

View File

@@ -11,14 +11,11 @@ doctest = false
bench = false bench = false
[features] [features]
default = ["rp235x"] default = ["rp235x", "defmt"]
pimoroni2w = ["rp235x", "psram"] pimoroni2w = ["rp235x"]
# rp2040 = ["embassy-rp/rp2040"] # unsupported, ram too small for fb rp2040 = ["embassy-rp/rp2040"]
rp235x = ["embassy-rp/rp235xb"] rp235x = ["embassy-rp/rp235xb"]
trouble = ["dep:bt-hci", "dep:cyw43", "dep:cyw43-pio", "dep:trouble-host"] trouble = ["dep:bt-hci", "dep:cyw43", "dep:cyw43-pio", "dep:trouble-host"]
psram = ["dep:embedded-alloc"]
overclock = []
fps = []
defmt = [ defmt = [
"dep:defmt", "dep:defmt",
"panic-probe/print-defmt", "panic-probe/print-defmt",
@@ -73,17 +70,16 @@ panic-probe = "0.3"
portable-atomic = { version = "1.11", features = ["critical-section"] } portable-atomic = { version = "1.11", features = ["critical-section"] }
assign-resources = "0.5.0" assign-resources = "0.5.0"
defmt = { version = "1.0.1", optional = true } defmt = { version = "0.3", optional = true }
defmt-rtt = "1.1.0" defmt-rtt = "0.4.2"
embedded-sdmmc = { version = "0.9", default-features = false } embedded-sdmmc = { version = "0.9", default-features = false }
st7365p-lcd = { git = "https://github.com/legitcamper/st7365p-lcd-rs", rev = "a784b9e6df0769371dfc522528e770cf8fc6403a" } # async branch st7365p-lcd = { git = "https://github.com/legitcamper/st7365p-lcd-rs", rev = "a784b9e6df0769371dfc522528e770cf8fc6403a" } # async branch
embedded-graphics = { version = "0.8.1" } embedded-graphics = { version = "0.8.1" }
embedded-text = "0.7.2" embedded-text = "0.7.2"
embedded-layout = "0.4.2" embedded-layout = "0.4.2"
kolibri-embedded-gui = "0.1.0"
micromath = "2.1.0"
fixed = "1.29.0"
strum = { version = "0.27.2", default-features = false } strum = { version = "0.27.2", default-features = false }
rand = { version = "0.9.0", default-features = false } rand = { version = "0.9.0", default-features = false }
once_cell = { version = "1.21.3", default-features = false } once_cell = { version = "1.21.3", default-features = false }
@@ -94,9 +90,7 @@ spin = "0.10.0"
num_enum = { version = "0.7.4", default-features = false } num_enum = { version = "0.7.4", default-features = false }
goblin = { version = "0.10.1", default-features = false, features = ["elf32"] } goblin = { version = "0.10.1", default-features = false, features = ["elf32"] }
talc = "4.4.3" talc = "4.4.3"
embedded-alloc = { version = "0.6.0", features = [ embedded-alloc = { version = "0.6.0", features = ["allocator_api"] }
"allocator_api",
], optional = true }
bumpalo = "3.19.0" bumpalo = "3.19.0"
userlib_sys = { path = "../userlib_sys" } abi_sys = { path = "../abi_sys" }

View File

@@ -14,7 +14,7 @@ use std::io::Write;
use std::path::PathBuf; use std::path::PathBuf;
#[cfg(all(feature = "rp235x", not(feature = "pimoroni2w")))] #[cfg(all(feature = "rp235x", not(feature = "pimoroni2w")))]
const MEMORY: &[u8] = include_bytes!("rp2350.x"); const MEMORY: &'static [u8] = include_bytes!("rp2350.x");
#[cfg(feature = "pimoroni2w")] #[cfg(feature = "pimoroni2w")]
const MEMORY: &'static [u8] = include_bytes!("rp2350.x"); const MEMORY: &'static [u8] = include_bytes!("rp2350.x");

View File

@@ -1,60 +1,46 @@
use abi_sys::{
AllocAbi, CLayout, CPixel, DeallocAbi, DrawIterAbi, FileLen, GenRand, GetMsAbi, ListDir,
PrintAbi, ReadFile, RngRequest, SleepMsAbi, keyboard::*,
};
use alloc::{string::ToString, vec::Vec}; use alloc::{string::ToString, vec::Vec};
use core::{ffi::c_char, ptr, sync::atomic::Ordering}; use core::{alloc::GlobalAlloc, ffi::c_char, ptr, sync::atomic::Ordering};
use embassy_rp::clocks::{RoscRng, clk_sys_freq}; use embassy_rp::clocks::{RoscRng, clk_sys_freq};
use embassy_time::Instant; use embassy_time::Instant;
use embedded_graphics::draw_target::DrawTarget; use embedded_graphics::draw_target::DrawTarget;
use embedded_sdmmc::LfnBuffer; use embedded_sdmmc::LfnBuffer;
use heapless::spsc::Queue; use heapless::spsc::Queue;
use userlib_sys::{
AUDIO_BUFFER_SAMPLES, Alloc, AudioBufferReady, CLayout, CPixel, Dealloc, DrawIter, FileLen,
GenRand, GetMs, ListDir, Print, ReadFile, RngRequest, SendAudioBuffer, SleepMs, WriteFile,
keyboard::*,
};
#[cfg(feature = "psram")]
use crate::heap::HEAP;
#[cfg(feature = "psram")]
use core::alloc::GlobalAlloc;
use crate::{ use crate::{
audio::{AUDIO_BUFFER, AUDIO_BUFFER_READY, AUDIO_BUFFER_WRITTEN},
display::FRAMEBUFFER, display::FRAMEBUFFER,
framebuffer::FB_PAUSED, framebuffer::FB_PAUSED,
storage::{Dir, File, SDCARD}, storage::{Dir, File, SDCARD},
}; };
const _: Alloc = alloc; const _: AllocAbi = alloc;
pub extern "C" fn alloc(layout: CLayout) -> *mut u8 { pub extern "C" fn alloc(layout: CLayout) -> *mut u8 {
// SAFETY: caller guarantees layout is valid // SAFETY: caller guarantees layout is valid
unsafe { unsafe {
#[cfg(feature = "psram")] if cfg!(feature = "pimoroni2w") {
{ crate::heap::HEAP.alloc(layout.into())
return HEAP.alloc(layout.into()); } else {
}
#[cfg(not(feature = "psram"))]
{
alloc::alloc::alloc(layout.into()) alloc::alloc::alloc(layout.into())
} }
} }
} }
const _: Dealloc = dealloc; const _: DeallocAbi = dealloc;
pub extern "C" fn dealloc(ptr: *mut u8, layout: CLayout) { pub extern "C" fn dealloc(ptr: *mut u8, layout: CLayout) {
// SAFETY: caller guarantees ptr and layout are valid // SAFETY: caller guarantees ptr and layout are valid
#[cfg(feature = "psram")] unsafe {
{ if cfg!(feature = "pimoroni2w") {
unsafe { HEAP.dealloc(ptr, layout.into()) } crate::heap::HEAP.dealloc(ptr, layout.into())
} } else {
alloc::alloc::dealloc(ptr, layout.into())
#[cfg(not(feature = "psram"))] }
{
unsafe { alloc::alloc::dealloc(ptr, layout.into()) }
} }
} }
const _: Print = print; const _: PrintAbi = print;
pub extern "C" fn print(ptr: *const u8, len: usize) { pub extern "C" fn print(ptr: *const u8, len: usize) {
// SAFETY: caller guarantees `ptr` is valid for `len` bytes // SAFETY: caller guarantees `ptr` is valid for `len` bytes
let slice = unsafe { core::slice::from_raw_parts(ptr, len) }; let slice = unsafe { core::slice::from_raw_parts(ptr, len) };
@@ -68,7 +54,7 @@ pub extern "C" fn print(ptr: *const u8, len: usize) {
} }
} }
const _: SleepMs = sleep; const _: SleepMsAbi = sleep;
pub extern "C" fn sleep(ms: u64) { pub extern "C" fn sleep(ms: u64) {
let cycles_per_ms = clk_sys_freq() / 1000; let cycles_per_ms = clk_sys_freq() / 1000;
let total_cycles = ms * cycles_per_ms as u64; let total_cycles = ms * cycles_per_ms as u64;
@@ -80,14 +66,14 @@ pub extern "C" fn sleep(ms: u64) {
pub static mut MS_SINCE_LAUNCH: Option<Instant> = None; pub static mut MS_SINCE_LAUNCH: Option<Instant> = None;
const _: GetMs = get_ms; const _: GetMsAbi = get_ms;
pub extern "C" fn get_ms() -> u64 { pub extern "C" fn get_ms() -> u64 {
Instant::now() Instant::now()
.duration_since(unsafe { MS_SINCE_LAUNCH.unwrap() }) .duration_since(unsafe { MS_SINCE_LAUNCH.unwrap() })
.as_millis() .as_millis()
} }
const _: DrawIter = draw_iter; const _: DrawIterAbi = draw_iter;
pub extern "C" fn draw_iter(cpixels: *const CPixel, len: usize) { pub extern "C" fn draw_iter(cpixels: *const CPixel, len: usize) {
// SAFETY: caller guarantees `ptr` is valid for `len` bytes // SAFETY: caller guarantees `ptr` is valid for `len` bytes
let cpixels = unsafe { core::slice::from_raw_parts(cpixels, len) }; let cpixels = unsafe { core::slice::from_raw_parts(cpixels, len) };
@@ -101,7 +87,7 @@ pub extern "C" fn draw_iter(cpixels: *const CPixel, len: usize) {
pub static mut KEY_CACHE: Queue<KeyEvent, 32> = Queue::new(); pub static mut KEY_CACHE: Queue<KeyEvent, 32> = Queue::new();
const _: GetKey = get_key; const _: GetKeyAbi = get_key;
pub extern "C" fn get_key() -> KeyEventC { pub extern "C" fn get_key() -> KeyEventC {
if let Some(event) = unsafe { KEY_CACHE.dequeue() } { if let Some(event) = unsafe { KEY_CACHE.dequeue() } {
event.into() event.into()
@@ -134,7 +120,7 @@ unsafe fn copy_entry_to_user_buf(name: &[u8], dest: *mut c_char, max_str_len: us
if !dest.is_null() { if !dest.is_null() {
let len = name.len().min(max_str_len - 1); let len = name.len().min(max_str_len - 1);
unsafe { unsafe {
ptr::copy_nonoverlapping(name.as_ptr(), dest, len); ptr::copy_nonoverlapping(name.as_ptr(), dest as *mut u8, len);
*dest.add(len) = 0; // nul terminator *dest.add(len) = 0; // nul terminator
} }
} }
@@ -191,7 +177,7 @@ pub extern "C" fn list_dir(
let mut wrote = 0; let mut wrote = 0;
sd.access_root_dir(|root| { sd.access_root_dir(|root| {
if dirs[0].is_empty() && dirs.len() >= 2 { if dirs[0] == "" && dirs.len() >= 2 {
unsafe { unsafe {
if dir == "/" { if dir == "/" {
wrote = get_dir_entries(&root, files, max_entry_str_len); wrote = get_dir_entries(&root, files, max_entry_str_len);
@@ -214,14 +200,13 @@ fn recurse_file<T>(
let mut buf = LfnBuffer::new(&mut b); let mut buf = LfnBuffer::new(&mut b);
let mut short_name = None; let mut short_name = None;
dir.iterate_dir_lfn(&mut buf, |entry, name| { dir.iterate_dir_lfn(&mut buf, |entry, name| {
if let Some(name) = name if let Some(name) = name {
&& (name == dirs[0] || entry.name.to_string().as_str() == dirs[0]) if name == dirs[0] || entry.name.to_string().as_str() == dirs[0] {
{ short_name = Some(entry.name.clone());
short_name = Some(entry.name.clone()); }
} }
}) })
.expect("Failed to iterate dir"); .unwrap();
if let Some(name) = short_name { if let Some(name) = short_name {
let mut file = dir let mut file = dir
.open_file_in_dir(name, embedded_sdmmc::Mode::ReadWriteAppend) .open_file_in_dir(name, embedded_sdmmc::Mode::ReadWriteAppend)
@@ -245,19 +230,9 @@ pub extern "C" fn read_file(
) -> usize { ) -> usize {
// SAFETY: caller guarantees `ptr` is valid for `len` bytes // SAFETY: caller guarantees `ptr` is valid for `len` bytes
let file = unsafe { core::str::from_raw_parts(str, len) }; let file = unsafe { core::str::from_raw_parts(str, len) };
let file: Vec<&str> = file.split('/').collect();
let mut components: [&str; 8] = [""; 8];
let mut count = 0;
for part in file.split('/') {
if count >= components.len() {
break;
}
components[count] = part;
count += 1;
}
// SAFETY: caller guarantees `ptr` is valid for `len` bytes // SAFETY: caller guarantees `ptr` is valid for `len` bytes
let buf = unsafe { core::slice::from_raw_parts_mut(buf, buf_len) }; let mut buf = unsafe { core::slice::from_raw_parts_mut(buf, buf_len) };
let mut read = 0; let mut read = 0;
@@ -265,9 +240,9 @@ pub extern "C" fn read_file(
let sd = guard.as_mut().unwrap(); let sd = guard.as_mut().unwrap();
if !file.is_empty() { if !file.is_empty() {
sd.access_root_dir(|root| { sd.access_root_dir(|root| {
if let Ok(result) = recurse_file(&root, &components[1..count], |file| { if let Ok(result) = recurse_file(&root, &file[1..], |file| {
file.seek_from_start(start_from as u32).unwrap_or(()); file.seek_from_start(start_from as u32).unwrap();
file.read(buf).unwrap() file.read(&mut buf).unwrap()
}) { }) {
read = result read = result
}; };
@@ -276,46 +251,9 @@ pub extern "C" fn read_file(
read read
} }
const _: WriteFile = write_file;
pub extern "C" fn write_file(
str: *const u8,
len: usize,
start_from: usize,
buf: *const u8,
buf_len: usize,
) {
// SAFETY: caller guarantees str ptr is valid for `len` bytes
let file = unsafe { core::str::from_raw_parts(str, len) };
let mut components: [&str; 8] = [""; 8];
let mut count = 0;
for part in file.split('/') {
if count >= components.len() {
break;
}
components[count] = part;
count += 1;
}
// SAFETY: caller guarantees buf ptr is valid for `buf_len` bytes
let buf = unsafe { core::slice::from_raw_parts(buf, buf_len) };
let mut guard = SDCARD.get().try_lock().expect("Failed to get sdcard");
let sd = guard.as_mut().unwrap();
if !file.is_empty() {
sd.access_root_dir(|root| {
recurse_file(&root, &components[1..count], |file| {
file.seek_from_start(start_from as u32).unwrap();
file.write(buf).unwrap()
})
.unwrap_or(())
});
};
}
const _: FileLen = file_len; const _: FileLen = file_len;
pub extern "C" fn file_len(str: *const u8, len: usize) -> usize { pub extern "C" fn file_len(str: *const u8, len: usize) -> usize {
// SAFETY: caller guarantees str ptr is valid for `len` bytes // SAFETY: caller guarantees `ptr` is valid for `len` bytes
let file = unsafe { core::str::from_raw_parts(str, len) }; let file = unsafe { core::str::from_raw_parts(str, len) };
let file: Vec<&str> = file.split('/').collect(); let file: Vec<&str> = file.split('/').collect();
@@ -332,31 +270,3 @@ pub extern "C" fn file_len(str: *const u8, len: usize) -> usize {
} }
len as usize len as usize
} }
const _: AudioBufferReady = audio_buffer_ready;
pub extern "C" fn audio_buffer_ready() -> bool {
AUDIO_BUFFER_READY.load(Ordering::Acquire)
}
const _: SendAudioBuffer = send_audio_buffer;
pub extern "C" fn send_audio_buffer(ptr: *const u8, len: usize) {
// SAFETY: caller guarantees `ptr` is valid for `len` bytes
let buf = unsafe { core::slice::from_raw_parts(ptr, len) };
while !AUDIO_BUFFER_READY.load(Ordering::Acquire) {
core::hint::spin_loop();
}
if buf.len() == AUDIO_BUFFER_SAMPLES * 2 {
AUDIO_BUFFER_READY.store(false, Ordering::Release);
unsafe { AUDIO_BUFFER.copy_from_slice(buf) };
AUDIO_BUFFER_WRITTEN.store(true, Ordering::Release);
} else {
#[cfg(feature = "defmt")]
defmt::warn!(
"user audio stream was wrong size: {} should be {}",
buf.len(),
AUDIO_BUFFER_SAMPLES * 2
)
}
}

View File

@@ -1,174 +0,0 @@
use crate::Audio;
use core::sync::atomic::{AtomicBool, Ordering};
use embassy_futures::{join::join, yield_now};
use embassy_rp::{
Peri,
clocks::clk_sys_freq,
dma::{AnyChannel, Channel},
gpio::Level,
pio::{
Common, Config, Direction, FifoJoin, Instance, LoadedProgram, PioPin, ShiftConfig,
StateMachine, program::pio_asm,
},
};
use fixed::traits::ToFixed;
pub const SAMPLE_RATE_HZ: u32 = 22_050;
const AUDIO_BUFFER_SAMPLES: usize = 1024;
const _: () = assert!(AUDIO_BUFFER_SAMPLES == userlib_sys::AUDIO_BUFFER_SAMPLES);
const SILENCE: u8 = u8::MAX / 2;
// 8bit stereo interleaved PCM audio buffers
pub static mut AUDIO_BUFFER: [u8; AUDIO_BUFFER_SAMPLES * 2] = [SILENCE; AUDIO_BUFFER_SAMPLES * 2];
static mut AUDIO_BUFFER_1: [u8; AUDIO_BUFFER_SAMPLES * 2] = [SILENCE; AUDIO_BUFFER_SAMPLES * 2];
pub static AUDIO_BUFFER_READY: AtomicBool = AtomicBool::new(true);
pub static AUDIO_BUFFER_WRITTEN: AtomicBool = AtomicBool::new(false);
pub fn clear_audio_buffers() {
unsafe {
AUDIO_BUFFER.fill(SILENCE);
AUDIO_BUFFER_1.fill(SILENCE);
}
}
#[embassy_executor::task]
pub async fn audio_handler(mut audio: Audio) {
let prg = PioPwmAudioProgram8Bit::new(&mut audio.pio);
let mut pwm_pio_left =
PioPwmAudio::new(audio.dma0, &mut audio.pio, audio.sm0, audio.left, &prg);
let mut pwm_pio_right =
PioPwmAudio::new(audio.dma1, &mut audio.pio, audio.sm1, audio.right, &prg);
loop {
unsafe {
if AUDIO_BUFFER_WRITTEN.load(Ordering::Acquire) {
write_samples(&mut pwm_pio_left, &mut pwm_pio_right, &AUDIO_BUFFER_1).await;
AUDIO_BUFFER_1.fill(SILENCE);
core::mem::swap(&mut AUDIO_BUFFER, &mut AUDIO_BUFFER_1);
AUDIO_BUFFER_READY.store(true, Ordering::Release)
} else {
yield_now().await;
}
}
}
}
async fn write_samples<PIO: Instance>(
left: &mut PioPwmAudio<'static, PIO, 0>,
right: &mut PioPwmAudio<'static, PIO, 1>,
buf: &[u8],
) {
// pack two samples per word
static mut PACKED_BUF_L: [u32; AUDIO_BUFFER_SAMPLES / 2] = [0; AUDIO_BUFFER_SAMPLES / 2];
static mut PACKED_BUF_R: [u32; AUDIO_BUFFER_SAMPLES / 2] = [0; AUDIO_BUFFER_SAMPLES / 2];
unsafe {
for ((pl, pr), sample) in PACKED_BUF_L
.iter_mut()
.zip(PACKED_BUF_R.iter_mut())
.zip(buf.chunks(4))
{
*pl = pack_u8_samples(sample[0], sample[2]);
*pr = pack_u8_samples(sample[1], sample[3]);
}
let left_fut = left
.sm
.tx()
.dma_push(left.dma.reborrow(), &PACKED_BUF_L, false);
let right_fut = right
.sm
.tx()
.dma_push(right.dma.reborrow(), &PACKED_BUF_R, false);
join(left_fut, right_fut).await;
}
}
struct PioPwmAudioProgram8Bit<'d, PIO: Instance>(LoadedProgram<'d, PIO>);
/// Writes one sample to pwm as high and low time
impl<'d, PIO: Instance> PioPwmAudioProgram8Bit<'d, PIO> {
fn new(common: &mut Common<'d, PIO>) -> Self {
// only uses 16 bits top for pwm high, bottom for pwm low
// allows storing two samples per word
let prg = pio_asm!(
".side_set 1",
"set x, 0 side 1",
"set y, 0 side 0",
".wrap_target",
"check:",
"pull ifempty noblock side 1", // gets new osr or loads 0 into x, gets second sample if osr not empty
"out x, 8 side 0", // pwm high time
"out y, 8 side 1", // pwm low time
"jmp x!=y play_sample side 0", // x & y are never equal unless osr was empty
"set x, 1 side 1",
"set y, 1 side 0",
"play_sample:"
"loop_high:",
"jmp x-- loop_high side 1", // keep pwm high, decrement X until 0
"loop_low:",
"jmp y-- loop_low side 0", // keep pwm low, decrement Y until 0
".wrap"
);
let prg = common.load_program(&prg.program);
Self(prg)
}
}
struct PioPwmAudio<'d, PIO: Instance, const SM: usize> {
dma: Peri<'d, AnyChannel>,
sm: StateMachine<'d, PIO, SM>,
}
impl<'d, PIO: Instance, const SM: usize> PioPwmAudio<'d, PIO, SM> {
fn new(
dma: Peri<'d, impl Channel>,
pio: &mut Common<'d, PIO>,
mut sm: StateMachine<'d, PIO, SM>,
pin: Peri<'d, impl PioPin>,
prg: &PioPwmAudioProgram8Bit<'d, PIO>,
) -> Self {
let pin = pio.make_pio_pin(pin);
sm.set_pins(Level::High, &[&pin]);
sm.set_pin_dirs(Direction::Out, &[&pin]);
let mut cfg = Config::default();
cfg.fifo_join = FifoJoin::TxOnly;
let shift_cfg = ShiftConfig {
auto_fill: false,
..Default::default()
};
cfg.shift_out = shift_cfg;
cfg.use_program(&prg.0, &[&pin]);
sm.set_config(&cfg);
let target_clock = (u8::MAX as u32 + 1) * SAMPLE_RATE_HZ;
let divider = (clk_sys_freq() / target_clock).to_fixed();
sm.set_clock_divider(divider);
sm.set_enable(true);
Self {
dma: dma.into(),
sm,
}
}
}
/// packs two u8 samples into 32bit word
fn pack_u8_samples(sample1: u8, sample2: u8) -> u32 {
(u8_pcm_to_pwm(sample1) as u32) << 16 | u8_pcm_to_pwm(sample2) as u32
}
fn u8_pcm_to_pwm(sample: u8) -> u16 {
((sample as u16) << 8) | ((u8::MAX - sample) as u16)
}

View File

@@ -1,27 +1,21 @@
use crate::framebuffer::{self, AtomicFrameBuffer, FB_PAUSED}; use crate::framebuffer::{self, AtomicFrameBuffer, FB_PAUSED};
use core::alloc::{GlobalAlloc, Layout};
use core::sync::atomic::Ordering; use core::sync::atomic::Ordering;
use embassy_futures::yield_now;
use embassy_rp::{ use embassy_rp::{
Peri, Peri,
gpio::{Level, Output}, gpio::{Level, Output},
peripherals::{PIN_13, PIN_14, PIN_15, SPI1}, peripherals::{PIN_13, PIN_14, PIN_15, SPI1},
spi::{Async, Spi}, spi::{Async, Spi},
}; };
use embassy_time::Delay; use embassy_time::{Delay, Timer};
use embedded_graphics::{
pixelcolor::Rgb565,
prelude::{DrawTarget, RgbColor},
};
use embedded_hal_bus::spi::ExclusiveDevice; use embedded_hal_bus::spi::ExclusiveDevice;
use st7365p_lcd::ST7365P; use st7365p_lcd::ST7365P;
#[cfg(feature = "psram")] type DISPLAY = ST7365P<
use crate::heap::HEAP;
#[cfg(feature = "psram")]
use core::alloc::{GlobalAlloc, Layout};
#[cfg(feature = "psram")]
use embedded_graphics::{draw_target::DrawTarget, pixelcolor::Rgb565, prelude::RgbColor};
#[cfg(feature = "fps")]
pub use framebuffer::fps::{FPS_CANVAS, FPS_COUNTER};
type Display = ST7365P<
ExclusiveDevice<Spi<'static, SPI1, Async>, Output<'static>, Delay>, ExclusiveDevice<Spi<'static, SPI1, Async>, Output<'static>, Delay>,
Output<'static>, Output<'static>,
Output<'static>, Output<'static>,
@@ -35,21 +29,18 @@ pub static mut FRAMEBUFFER: Option<AtomicFrameBuffer> = None;
fn init_fb() { fn init_fb() {
unsafe { unsafe {
#[cfg(feature = "psram")] FRAMEBUFFER = Some(if cfg!(not(feature = "pimoroni2w")) {
{ static mut BUF: [u16; framebuffer::SIZE] = [0; framebuffer::SIZE];
let slab = HEAP.alloc(Layout::array::<u16>(framebuffer::SIZE).unwrap()) as *mut u16; AtomicFrameBuffer::new(&mut BUF)
} else {
let slab = crate::heap::HEAP.alloc(Layout::array::<u16>(framebuffer::SIZE).unwrap())
as *mut u16;
let buf = core::slice::from_raw_parts_mut(slab, framebuffer::SIZE); let buf = core::slice::from_raw_parts_mut(slab, framebuffer::SIZE);
let mut fb = AtomicFrameBuffer::new(buf); let mut fb = AtomicFrameBuffer::new(buf);
fb.clear(Rgb565::BLACK).unwrap(); fb.clear(Rgb565::BLACK).unwrap();
FRAMEBUFFER = Some(fb); fb
} });
#[cfg(not(feature = "psram"))]
{
static mut BUF: [u16; framebuffer::SIZE] = [0; framebuffer::SIZE];
FRAMEBUFFER = Some(AtomicFrameBuffer::new(&mut BUF));
}
} }
} }
@@ -58,7 +49,7 @@ pub async fn init_display(
cs: Peri<'static, PIN_13>, cs: Peri<'static, PIN_13>,
data: Peri<'static, PIN_14>, data: Peri<'static, PIN_14>,
reset: Peri<'static, PIN_15>, reset: Peri<'static, PIN_15>,
) -> Display { ) -> DISPLAY {
init_fb(); init_fb();
let spi_device = ExclusiveDevice::new(spi, Output::new(cs, Level::Low), Delay).unwrap(); let spi_device = ExclusiveDevice::new(spi, Output::new(cs, Level::Low), Delay).unwrap();
@@ -86,22 +77,8 @@ pub async fn init_display(
} }
#[embassy_executor::task] #[embassy_executor::task]
pub async fn display_handler(mut display: Display) { pub async fn display_handler(mut display: DISPLAY) {
use embassy_time::{Instant, Timer};
// Target ~60 Hz refresh (≈16.67 ms per frame)
const FRAME_TIME_MS: u64 = 1000 / 60;
loop { loop {
let start = Instant::now();
#[cfg(feature = "fps")]
unsafe {
if FPS_COUNTER.should_draw() {
FPS_CANVAS.draw_fps().await;
}
}
if !FB_PAUSED.load(Ordering::Acquire) { if !FB_PAUSED.load(Ordering::Acquire) {
unsafe { unsafe {
FRAMEBUFFER FRAMEBUFFER
@@ -109,15 +86,11 @@ pub async fn display_handler(mut display: Display) {
.unwrap() .unwrap()
.partial_draw(&mut display) .partial_draw(&mut display)
.await .await
.unwrap(); .unwrap()
} };
} }
let elapsed = start.elapsed().as_millis(); // small yield to allow other tasks to run
if elapsed < FRAME_TIME_MS { Timer::after_nanos(500).await;
Timer::after_millis(FRAME_TIME_MS - elapsed).await;
} else {
yield_now().await;
}
} }
} }

View File

@@ -1,7 +1,9 @@
use crate::{ use crate::{
abi,
storage::{File, SDCARD}, storage::{File, SDCARD},
syscalls,
}; };
use abi_sys::CallTable;
use abi_sys::EntryFn;
use alloc::{vec, vec::Vec}; use alloc::{vec, vec::Vec};
use bumpalo::Bump; use bumpalo::Bump;
use core::ptr; use core::ptr;
@@ -16,7 +18,6 @@ use goblin::{
elf32::{header, reloc::Rel, section_header::SectionHeader, sym::Sym}, elf32::{header, reloc::Rel, section_header::SectionHeader, sym::Sym},
}; };
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use userlib_sys::{EntryFn, SyscallTable};
const ELF32_HDR_SIZE: usize = 52; const ELF32_HDR_SIZE: usize = 52;
@@ -39,7 +40,7 @@ pub async unsafe fn load_binary(name: &ShortFileName) -> Option<(EntryFn, Bump)>
let mut ph_buf = vec![0_u8; elf_header.e_phentsize as usize]; let mut ph_buf = vec![0_u8; elf_header.e_phentsize as usize];
let (total_size, min_vaddr, _max_vaddr) = let (total_size, min_vaddr, _max_vaddr) =
total_loadable_size(&mut file, elf_header, &mut ph_buf); total_loadable_size(&mut file, &elf_header, &mut ph_buf);
let bump = Bump::with_capacity(total_size); let bump = Bump::with_capacity(total_size);
let base = bump.alloc_slice_fill_default::<u8>(total_size); let base = bump.alloc_slice_fill_default::<u8>(total_size);
@@ -52,22 +53,25 @@ pub async unsafe fn load_binary(name: &ShortFileName) -> Option<(EntryFn, Bump)>
let ph = cast_phdr(&ph_buf); let ph = cast_phdr(&ph_buf);
let seg_offset = (ph.p_vaddr - min_vaddr) as usize; let seg_offset = (ph.p_vaddr - min_vaddr) as usize;
let segment = &mut base[seg_offset..seg_offset + ph.p_memsz as usize]; let mut segment = &mut base[seg_offset..seg_offset + ph.p_memsz as usize];
if ph.p_type == PT_LOAD { if ph.p_type == PT_LOAD {
load_segment(&mut file, &ph, segment).unwrap(); load_segment(&mut file, &ph, &mut segment).unwrap();
} }
} }
for i in 0..elf_header.e_shnum { for i in 0..elf_header.e_shnum {
let sh = read_section(&mut file, elf_header, i.into()); let sh = read_section(&mut file, elf_header, i.into());
if sh.sh_type == SHT_REL { match sh.sh_type {
apply_relocations(&sh, min_vaddr, base.as_mut_ptr(), &mut file).unwrap(); SHT_REL => {
apply_relocations(&sh, min_vaddr, base.as_mut_ptr(), &mut file).unwrap();
}
_ => {}
} }
} }
patch_syscalls(elf_header, base.as_mut_ptr(), min_vaddr, &mut file).unwrap(); patch_abi(&elf_header, base.as_mut_ptr(), min_vaddr, &mut file).unwrap();
// entry pointer is base_ptr + (entry - min_vaddr) // entry pointer is base_ptr + (entry - min_vaddr)
let entry_ptr: EntryFn = unsafe { let entry_ptr: EntryFn = unsafe {
@@ -147,14 +151,14 @@ fn apply_relocations(
Ok(()) Ok(())
} }
fn patch_syscalls( fn patch_abi(
elf_header: &Header, elf_header: &Header,
base: *mut u8, base: *mut u8,
min_vaddr: u32, min_vaddr: u32,
file: &mut File, file: &mut File,
) -> Result<(), ()> { ) -> Result<(), ()> {
for i in 1..=elf_header.e_shnum { for i in 1..=elf_header.e_shnum {
let sh = read_section(file, elf_header, i.into()); let sh = read_section(file, &elf_header, i.into());
// find the symbol table // find the symbol table
if sh.sh_type == SHT_SYMTAB { if sh.sh_type == SHT_SYMTAB {
@@ -169,7 +173,7 @@ fn patch_syscalls(
&symtab_buf[i * sh.sh_entsize as usize..(i + 1) * sh.sh_entsize as usize]; &symtab_buf[i * sh.sh_entsize as usize..(i + 1) * sh.sh_entsize as usize];
let sym = cast_sym(sym_bytes); let sym = cast_sym(sym_bytes);
let str_sh = read_section(file, elf_header, sh.sh_link); let str_sh = read_section(file, &elf_header, sh.sh_link);
let mut name = Vec::new(); let mut name = Vec::new();
file.seek_from_start(str_sh.sh_offset + sym.st_name) file.seek_from_start(str_sh.sh_offset + sym.st_name)
@@ -185,30 +189,27 @@ fn patch_syscalls(
} }
let symbol_name = core::str::from_utf8(&name).unwrap(); let symbol_name = core::str::from_utf8(&name).unwrap();
if symbol_name == stringify!(SYS_CALL_TABLE) { if symbol_name == "CALL_ABI_TABLE" {
let table_base = let table_base =
unsafe { base.add((sym.st_value as usize) - min_vaddr as usize) } unsafe { base.add((sym.st_value as usize) - min_vaddr as usize) }
as *mut usize; as *mut usize;
for (idx, call) in SyscallTable::iter().enumerate() { for (idx, call) in CallTable::iter().enumerate() {
let ptr = match call { let ptr = match call {
SyscallTable::Alloc => syscalls::alloc as usize, CallTable::Alloc => abi::alloc as usize,
SyscallTable::Dealloc => syscalls::dealloc as usize, CallTable::Dealloc => abi::dealloc as usize,
SyscallTable::PrintString => syscalls::print as usize, CallTable::PrintString => abi::print as usize,
SyscallTable::SleepMs => syscalls::sleep as usize, CallTable::SleepMs => abi::sleep as usize,
SyscallTable::GetMs => syscalls::get_ms as usize, CallTable::GetMs => abi::get_ms as usize,
SyscallTable::DrawIter => syscalls::draw_iter as usize, CallTable::DrawIter => abi::draw_iter as usize,
SyscallTable::GetKey => syscalls::get_key as usize, CallTable::GetKey => abi::get_key as usize,
SyscallTable::GenRand => syscalls::gen_rand as usize, CallTable::GenRand => abi::gen_rand as usize,
SyscallTable::ListDir => syscalls::list_dir as usize, CallTable::ListDir => abi::list_dir as usize,
SyscallTable::ReadFile => syscalls::read_file as usize, CallTable::ReadFile => abi::read_file as usize,
SyscallTable::WriteFile => syscalls::write_file as usize, CallTable::FileLen => abi::file_len as usize,
SyscallTable::FileLen => syscalls::file_len as usize,
SyscallTable::AudioBufferReady => syscalls::audio_buffer_ready as usize,
SyscallTable::SendAudioBuffer => syscalls::send_audio_buffer as usize,
}; };
unsafe { unsafe {
table_base.add(idx).write(ptr); table_base.add(idx as usize).write(ptr);
} }
} }
return Ok(()); return Ok(());
@@ -230,7 +231,7 @@ fn total_loadable_size(
file.seek_from_start(elf_header.e_phoff + (elf_header.e_phentsize * i) as u32) file.seek_from_start(elf_header.e_phoff + (elf_header.e_phentsize * i) as u32)
.unwrap(); .unwrap();
file.read(ph_buf).unwrap(); file.read(ph_buf).unwrap();
let ph = cast_phdr(ph_buf); let ph = cast_phdr(&ph_buf);
if ph.p_type == PT_LOAD { if ph.p_type == PT_LOAD {
if ph.p_vaddr < min_vaddr { if ph.p_vaddr < min_vaddr {

View File

@@ -13,9 +13,6 @@ use embedded_hal_2::digital::OutputPin;
use embedded_hal_async::{delay::DelayNs, spi::SpiDevice}; use embedded_hal_async::{delay::DelayNs, spi::SpiDevice};
use st7365p_lcd::ST7365P; use st7365p_lcd::ST7365P;
#[cfg(feature = "fps")]
use fps::{FPS_CANVAS, FPS_CANVAS_HEIGHT, FPS_CANVAS_WIDTH, FPS_CANVAS_X, FPS_CANVAS_Y};
const TILE_SIZE: usize = 16; // 16x16 tile const TILE_SIZE: usize = 16; // 16x16 tile
const TILE_COUNT: usize = (SCREEN_WIDTH / TILE_SIZE) * (SCREEN_HEIGHT / TILE_SIZE); // 400 tiles const TILE_COUNT: usize = (SCREEN_WIDTH / TILE_SIZE) * (SCREEN_HEIGHT / TILE_SIZE); // 400 tiles
const NUM_TILE_ROWS: usize = SCREEN_WIDTH / TILE_SIZE; const NUM_TILE_ROWS: usize = SCREEN_WIDTH / TILE_SIZE;
@@ -46,7 +43,7 @@ impl<'a> AtomicFrameBuffer<'a> {
} }
fn mark_tiles_dirty(&mut self, rect: Rectangle) { fn mark_tiles_dirty(&mut self, rect: Rectangle) {
let tiles_x = SCREEN_WIDTH.div_ceil(TILE_SIZE); let tiles_x = (SCREEN_WIDTH + TILE_SIZE - 1) / TILE_SIZE;
let start_tx = (rect.top_left.x as usize) / TILE_SIZE; let start_tx = (rect.top_left.x as usize) / TILE_SIZE;
let end_tx = ((rect.top_left.x + rect.size.width as i32 - 1) as usize) / TILE_SIZE; let end_tx = ((rect.top_left.x + rect.size.width as i32 - 1) as usize) / TILE_SIZE;
let start_ty = (rect.top_left.y as usize) / TILE_SIZE; let start_ty = (rect.top_left.y as usize) / TILE_SIZE;
@@ -133,34 +130,9 @@ impl<'a> AtomicFrameBuffer<'a> {
tile.store(false, Ordering::Release); tile.store(false, Ordering::Release);
} }
#[cfg(feature = "fps")]
unsafe {
crate::display::FPS_COUNTER.measure()
}
Ok(()) Ok(())
} }
// used when doing a full screen refresh fps must be drawn into fb
// unfortunately it is not garenteed to not be drawn over before
// being pushed to the display
#[cfg(feature = "fps")]
pub fn draw_fps_into_fb(&mut self) {
unsafe {
let canvas = &FPS_CANVAS.canvas;
for y in 0..FPS_CANVAS_HEIGHT {
let fb_y = FPS_CANVAS_Y + y;
let fb_row_start = fb_y * SCREEN_WIDTH + FPS_CANVAS_X;
let canvas_row_start = y * FPS_CANVAS_WIDTH;
self.fb[fb_row_start..fb_row_start + FPS_CANVAS_WIDTH].copy_from_slice(
&canvas[canvas_row_start..canvas_row_start + FPS_CANVAS_WIDTH],
);
}
}
}
// copy N tiles horizontally to the right into batch tile buf // copy N tiles horizontally to the right into batch tile buf
fn append_tiles_to_batch( fn append_tiles_to_batch(
&mut self, &mut self,
@@ -180,45 +152,6 @@ impl<'a> AtomicFrameBuffer<'a> {
&self.fb[fb_row_offset..fb_row_offset + (total_tiles as usize * TILE_SIZE)]; &self.fb[fb_row_offset..fb_row_offset + (total_tiles as usize * TILE_SIZE)];
batch_row.copy_from_slice(fb_row); batch_row.copy_from_slice(fb_row);
// override fps pixel region with fps
// avoids writing to fps, and having it overridden before draw
#[cfg(feature = "fps")]
{
let global_y = tile_y as usize * TILE_SIZE + batch_row_num;
if global_y >= FPS_CANVAS_Y && global_y < FPS_CANVAS_Y + FPS_CANVAS_HEIGHT {
let start_x = tile_x as usize * TILE_SIZE;
let end_x = start_x + (total_tiles as usize * TILE_SIZE);
// horizontal overlap check
let fps_x0 = FPS_CANVAS_X;
let fps_x1 = FPS_CANVAS_X + FPS_CANVAS_WIDTH;
let x0 = start_x.max(fps_x0);
let x1 = end_x.min(fps_x1);
if x1 > x0 {
let row_in_fps = global_y - FPS_CANVAS_Y;
let fps_off = row_in_fps
.checked_mul(FPS_CANVAS_WIDTH)
.and_then(|v| v.checked_add(x0 - fps_x0));
let batch_off = x0 - start_x;
let len = x1 - x0;
if let Some(fps_off) = fps_off {
let fps_len_ok = fps_off + len <= unsafe { FPS_CANVAS.canvas.len() };
let batch_len_ok = batch_off + len <= batch_row.len();
if fps_len_ok && batch_len_ok {
batch_row[batch_off..batch_off + len].copy_from_slice(unsafe {
&FPS_CANVAS.canvas[fps_off..fps_off + len]
});
}
}
}
}
}
} }
} }
@@ -234,25 +167,9 @@ impl<'a> AtomicFrameBuffer<'a> {
DELAY: DelayNs, DELAY: DelayNs,
{ {
if self.should_full_draw() { if self.should_full_draw() {
#[cfg(feature = "fps")]
self.draw_fps_into_fb();
return self.draw(display).await; return self.draw(display).await;
} }
#[cfg(feature = "fps")]
{
let fps_tile_x = FPS_CANVAS_X / TILE_SIZE;
let fps_tile_y = FPS_CANVAS_Y / TILE_SIZE;
let fps_tile_w = (FPS_CANVAS_WIDTH + TILE_SIZE - 1) / TILE_SIZE;
let fps_tile_h = (FPS_CANVAS_HEIGHT + TILE_SIZE - 1) / TILE_SIZE;
for ty in fps_tile_y..fps_tile_y + fps_tile_h {
for tx in fps_tile_x..fps_tile_x + fps_tile_w {
self.dirty_tiles[ty * NUM_TILE_COLS + tx].store(true, Ordering::Release);
}
}
}
for tile_row in 0..NUM_TILE_ROWS { for tile_row in 0..NUM_TILE_ROWS {
let row_start_idx = tile_row * NUM_TILE_COLS; let row_start_idx = tile_row * NUM_TILE_COLS;
let mut col = 0; let mut col = 0;
@@ -261,15 +178,14 @@ impl<'a> AtomicFrameBuffer<'a> {
// Check for dirty tile // Check for dirty tile
if self.dirty_tiles[row_start_idx + col].swap(false, Ordering::Acquire) { if self.dirty_tiles[row_start_idx + col].swap(false, Ordering::Acquire) {
let run_start = col; let run_start = col;
let mut scan_col = col;
let mut run_len = 1; let mut run_len = 1;
// Extend run while contiguous dirty tiles and within MAX_BATCH_TILES // Extend run while contiguous dirty tiles and within MAX_BATCH_TILES
while scan_col + 1 < NUM_TILE_COLS while col + 1 < NUM_TILE_COLS
&& self.dirty_tiles[row_start_idx + col + 1].load(Ordering::Acquire) && self.dirty_tiles[row_start_idx + col + 1].load(Ordering::Acquire)
&& run_len < MAX_BATCH_TILES && run_len < MAX_BATCH_TILES
{ {
scan_col += 1; col += 1;
run_len += 1; run_len += 1;
} }
@@ -294,19 +210,12 @@ impl<'a> AtomicFrameBuffer<'a> {
&self.batch_tile_buf[..run_len * TILE_SIZE * TILE_SIZE], &self.batch_tile_buf[..run_len * TILE_SIZE * TILE_SIZE],
) )
.await?; .await?;
col = scan_col;
} }
col += 1; col += 1;
} }
} }
#[cfg(feature = "fps")]
unsafe {
crate::display::FPS_COUNTER.measure()
}
Ok(()) Ok(())
} }
} }
@@ -349,8 +258,10 @@ impl<'a> DrawTarget for AtomicFrameBuffer<'a> {
} }
} }
if changed && let Some(rect) = dirty_rect { if changed {
self.mark_tiles_dirty(rect); if let Some(rect) = dirty_rect {
self.mark_tiles_dirty(rect);
}
} }
Ok(()) Ok(())
@@ -403,7 +314,7 @@ impl<'a> DrawTarget for AtomicFrameBuffer<'a> {
fn fill_solid(&mut self, area: &Rectangle, color: Self::Color) -> Result<(), Self::Error> { fn fill_solid(&mut self, area: &Rectangle, color: Self::Color) -> Result<(), Self::Error> {
self.fill_contiguous( self.fill_contiguous(
area, area,
core::iter::repeat_n(color, (self.size().width * self.size().height) as usize), core::iter::repeat(color).take((self.size().width * self.size().height) as usize),
) )
} }
@@ -413,10 +324,8 @@ impl<'a> DrawTarget for AtomicFrameBuffer<'a> {
0, 0,
self.size().width as u16 - 1, self.size().width as u16 - 1,
self.size().height as u16 - 1, self.size().height as u16 - 1,
core::iter::repeat_n( core::iter::repeat(RawU16::from(color).into_inner())
RawU16::from(color).into_inner(), .take((self.size().width * self.size().height) as usize),
(self.size().width * self.size().height) as usize,
),
)?; )?;
for tile in self.dirty_tiles.iter() { for tile in self.dirty_tiles.iter() {
@@ -432,146 +341,3 @@ impl<'a> OriginDimensions for AtomicFrameBuffer<'a> {
Size::new(SCREEN_WIDTH as u32, SCREEN_HEIGHT as u32) Size::new(SCREEN_WIDTH as u32, SCREEN_HEIGHT as u32)
} }
} }
#[cfg(feature = "fps")]
pub mod fps {
use crate::display::SCREEN_WIDTH;
use core::fmt::Write;
use embassy_time::{Duration, Instant};
use embedded_graphics::{
Drawable, Pixel,
draw_target::DrawTarget,
geometry::Point,
mono_font::{MonoTextStyle, ascii::FONT_8X13},
pixelcolor::Rgb565,
prelude::{IntoStorage, OriginDimensions, RgbColor, Size},
text::{Alignment, Text},
};
pub static mut FPS_COUNTER: FpsCounter = FpsCounter::new();
pub static mut FPS_CANVAS: FpsCanvas = FpsCanvas::new();
// "FPS: 120" = 8 len
const FPS_LEN: usize = 8;
pub const FPS_CANVAS_WIDTH: usize = (FONT_8X13.character_size.width + 4) as usize * FPS_LEN;
pub const FPS_CANVAS_HEIGHT: usize = FONT_8X13.character_size.height as usize;
// puts canvas in the top right of the display
// top left point of canvas
pub const FPS_CANVAS_X: usize = SCREEN_WIDTH - FPS_CANVAS_WIDTH;
pub const FPS_CANVAS_Y: usize = 0;
pub struct FpsCanvas {
pub canvas: [u16; FPS_CANVAS_HEIGHT * FPS_CANVAS_WIDTH],
}
impl FpsCanvas {
const fn new() -> Self {
Self {
canvas: [0; FPS_CANVAS_HEIGHT * FPS_CANVAS_WIDTH],
}
}
fn clear(&mut self) {
for p in &mut self.canvas {
*p = 0;
}
}
pub async fn draw_fps(&mut self) {
let mut buf: heapless::String<FPS_LEN> = heapless::String::new();
let fps = unsafe { FPS_COUNTER.smoothed };
let _ = write!(buf, "FPS: {}", fps as u8);
self.clear();
let text_style = MonoTextStyle::new(&FONT_8X13, Rgb565::WHITE);
Text::with_alignment(
buf.as_str(),
Point::new(
FPS_CANVAS_WIDTH as i32 / 2,
(FPS_CANVAS_HEIGHT as i32 + 8) / 2,
),
text_style,
Alignment::Center,
)
.draw(self)
.unwrap();
}
}
impl DrawTarget for FpsCanvas {
type Error = ();
type Color = Rgb565;
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
for Pixel(point, color) in pixels {
if point.x < 0
|| point.x >= FPS_CANVAS_WIDTH as i32
|| point.y < 0
|| point.y >= FPS_CANVAS_HEIGHT as i32
{
continue;
}
let index = (point.y as usize) * FPS_CANVAS_WIDTH + point.x as usize;
self.canvas[index] = color.into_storage();
}
Ok(())
}
}
impl OriginDimensions for FpsCanvas {
fn size(&self) -> Size {
Size::new(FPS_CANVAS_WIDTH as u32, FPS_CANVAS_HEIGHT as u32)
}
}
pub struct FpsCounter {
last_frame: Option<Instant>,
smoothed: f32,
last_draw: Option<Instant>,
}
impl FpsCounter {
pub const fn new() -> Self {
Self {
last_frame: None,
smoothed: 0.0,
last_draw: None,
}
}
// Is called once per frame or partial frame to update FPS
pub fn measure(&mut self) {
let now = Instant::now();
if let Some(last) = self.last_frame {
let dt_us = (now - last).as_micros() as f32;
if dt_us > 0.0 {
let current = 1_000_000.0 / dt_us;
self.smoothed = if self.smoothed == 0.0 {
current
} else {
0.9 * self.smoothed + 0.1 * current
};
}
}
self.last_frame = Some(now);
}
pub fn should_draw(&mut self) -> bool {
let now = Instant::now();
match self.last_draw {
Some(last) if now - last < Duration::from_millis(200) => false,
_ => {
self.last_draw = Some(now);
true
}
}
}
}
}

View File

@@ -22,8 +22,8 @@ impl Region {
fn contains(&self, address: usize) -> bool { fn contains(&self, address: usize) -> bool {
let start = self.start.load(Ordering::Relaxed); let start = self.start.load(Ordering::Relaxed);
let end = start + self.size.load(Ordering::Relaxed); let end = self.start.load(Ordering::Relaxed);
(start..end).contains(&address) (start..start + end).contains(&address)
} }
fn new(start: usize, size: usize) -> Self { fn new(start: usize, size: usize) -> Self {

View File

@@ -8,58 +8,53 @@
extern crate alloc; extern crate alloc;
mod audio; mod abi;
mod display; mod display;
mod elf; mod elf;
mod framebuffer; mod framebuffer;
// TODO: NEED TO UPDATE MCU TO TEST BATTERY READS
#[allow(unused)] #[allow(unused)]
mod heap;
mod peripherals; mod peripherals;
#[allow(unused)] #[allow(unused)]
mod psram;
mod scsi; mod scsi;
mod storage; mod storage;
mod syscalls;
mod ui; mod ui;
#[allow(unused)]
mod usb; mod usb;
mod utils; mod utils;
#[cfg(feature = "psram")] #[cfg(feature = "pimoroni2w")]
#[allow(unused)]
mod heap;
#[cfg(feature = "psram")]
#[allow(unused)]
mod psram;
#[cfg(feature = "psram")]
use crate::{heap::init_qmi_psram_heap, psram::init_psram_qmi}; use crate::{heap::init_qmi_psram_heap, psram::init_psram_qmi};
use crate::{ use crate::{
audio::{AUDIO_BUFFER_WRITTEN, audio_handler, clear_audio_buffers}, abi::{KEY_CACHE, MS_SINCE_LAUNCH},
display::{FRAMEBUFFER, display_handler, init_display}, display::{FRAMEBUFFER, display_handler, init_display},
peripherals::{conf_peripherals, keyboard::read_keyboard_fifo}, heap::HEAP,
peripherals::{
conf_peripherals,
keyboard::{KeyState, read_keyboard_fifo},
},
psram::init_psram,
scsi::MSC_SHUTDOWN, scsi::MSC_SHUTDOWN,
storage::{SDCARD, SdCard}, storage::{SDCARD, SdCard},
syscalls::{KEY_CACHE, MS_SINCE_LAUNCH},
ui::{SELECTIONS, clear_selection, ui_handler}, ui::{SELECTIONS, clear_selection, ui_handler},
}; };
use abi_sys::EntryFn;
use bumpalo::Bump; use bumpalo::Bump;
use core::sync::atomic::{AtomicBool, Ordering}; use core::sync::atomic::{AtomicBool, Ordering};
use embassy_executor::{Executor, Spawner}; use embassy_executor::{Executor, Spawner};
use embassy_futures::{join::join, select::select}; use embassy_futures::{join::join, select::select};
use embassy_rp::{ use embassy_rp::{
Peri, Peri,
clocks::ClockConfig,
config::Config,
gpio::{Input, Level, Output, Pull}, gpio::{Input, Level, Output, Pull},
i2c::{self, I2c}, i2c::{self, I2c},
multicore::{Stack, spawn_core1}, multicore::{Stack, spawn_core1},
peripherals::{ peripherals::{
DMA_CH0, DMA_CH1, DMA_CH3, DMA_CH4, I2C1, PIN_2, PIN_3, PIN_6, PIN_7, PIN_10, PIN_11, DMA_CH0, DMA_CH1, DMA_CH3, DMA_CH4, I2C1, PIN_2, PIN_3, PIN_6, PIN_7, PIN_10, PIN_11,
PIN_12, PIN_13, PIN_14, PIN_15, PIN_16, PIN_17, PIN_18, PIN_19, PIN_20, PIN_21, PIN_22, PIN_12, PIN_13, PIN_14, PIN_15, PIN_16, PIN_17, PIN_18, PIN_19, PIN_20, PIN_21, PIN_22,
PIN_26, PIN_27, PIO0, SPI0, SPI1, USB, WATCHDOG, PIO0, SPI0, SPI1, USB, WATCHDOG,
}, },
pio::{self, Common, Pio, StateMachine}, pio,
spi::{self, Spi}, spi::{self, Spi},
usb as embassy_rp_usb, usb as embassy_rp_usb,
watchdog::{ResetReason, Watchdog}, watchdog::{ResetReason, Watchdog},
@@ -76,7 +71,6 @@ use embedded_hal_bus::spi::ExclusiveDevice;
use embedded_sdmmc::SdCard as SdmmcSdCard; use embedded_sdmmc::SdCard as SdmmcSdCard;
use static_cell::StaticCell; use static_cell::StaticCell;
use talc::*; use talc::*;
use userlib_sys::EntryFn;
use {defmt_rtt as _, panic_probe as _}; use {defmt_rtt as _, panic_probe as _};
embassy_rp::bind_interrupts!(struct Irqs { embassy_rp::bind_interrupts!(struct Irqs {
@@ -106,7 +100,7 @@ async fn watchdog_task(mut watchdog: Watchdog) {
ResetReason::Forced => "forced", ResetReason::Forced => "forced",
ResetReason::TimedOut => "timed out", ResetReason::TimedOut => "timed out",
}; };
#[cfg(feature = "defmt")] #[cfg(feature = "debug")]
defmt::error!("Watchdog reset reason: {}", _reason); defmt::error!("Watchdog reset reason: {}", _reason);
} }
@@ -122,17 +116,9 @@ async fn watchdog_task(mut watchdog: Watchdog) {
static ENABLE_UI: AtomicBool = AtomicBool::new(true); static ENABLE_UI: AtomicBool = AtomicBool::new(true);
static UI_CHANGE: Signal<CriticalSectionRawMutex, ()> = Signal::new(); static UI_CHANGE: Signal<CriticalSectionRawMutex, ()> = Signal::new();
const OVERCLOCK: u32 = 300_000_000;
#[embassy_executor::main] #[embassy_executor::main]
async fn main(_spawner: Spawner) { async fn main(_spawner: Spawner) {
let p = if cfg!(feature = "overclock") { let p = embassy_rp::init(Default::default());
let clocks = ClockConfig::system_freq(OVERCLOCK).unwrap();
let config = Config::new(clocks);
embassy_rp::init(config)
} else {
embassy_rp::init(Default::default())
};
spawn_core1( spawn_core1(
p.CORE1, p.CORE1,
@@ -154,19 +140,6 @@ async fn main(_spawner: Spawner) {
data: p.PIN_14, data: p.PIN_14,
reset: p.PIN_15, reset: p.PIN_15,
}; };
let Pio {
common, sm0, sm1, ..
} = Pio::new(p.PIO0, Irqs);
let audio = Audio {
pio: common,
sm0,
dma0: p.DMA_CH3,
left: p.PIN_26,
sm1,
dma1: p.DMA_CH4,
right: p.PIN_27,
};
let sd = Sd { let sd = Sd {
spi: p.SPI0, spi: p.SPI0,
clk: p.PIN_18, clk: p.PIN_18,
@@ -175,15 +148,15 @@ async fn main(_spawner: Spawner) {
cs: p.PIN_17, cs: p.PIN_17,
det: p.PIN_22, det: p.PIN_22,
}; };
// let psram = Psram { let psram = Psram {
// pio: p.PIO0, pio: p.PIO0,
// sclk: p.PIN_21, sclk: p.PIN_21,
// mosi: p.PIN_2, mosi: p.PIN_2,
// miso: p.PIN_3, miso: p.PIN_3,
// cs: p.PIN_20, cs: p.PIN_20,
// dma1: p.DMA_CH3, dma1: p.DMA_CH3,
// dma2: p.DMA_CH4, dma2: p.DMA_CH4,
// }; };
let mcu = Mcu { let mcu = Mcu {
i2c: p.I2C1, i2c: p.I2C1,
clk: p.PIN_7, clk: p.PIN_7,
@@ -193,7 +166,7 @@ async fn main(_spawner: Spawner) {
executor0.run(|spawner| { executor0.run(|spawner| {
spawner spawner
.spawn(kernel_task( .spawn(kernel_task(
spawner, p.WATCHDOG, display, audio, sd, mcu, p.USB, spawner, p.WATCHDOG, display, sd, psram, mcu, p.USB,
)) ))
.unwrap() .unwrap()
}); });
@@ -226,9 +199,6 @@ async fn userland_task() {
// enable kernel ui // enable kernel ui
{ {
AUDIO_BUFFER_WRITTEN.store(false, Ordering::Release);
clear_audio_buffers();
ENABLE_UI.store(true, Ordering::Release); ENABLE_UI.store(true, Ordering::Release);
UI_CHANGE.signal(()); UI_CHANGE.signal(());
unsafe { FRAMEBUFFER.as_mut().unwrap().clear(Rgb565::BLACK).unwrap() }; unsafe { FRAMEBUFFER.as_mut().unwrap().clear(Rgb565::BLACK).unwrap() };
@@ -250,15 +220,6 @@ struct Display {
data: Peri<'static, PIN_14>, data: Peri<'static, PIN_14>,
reset: Peri<'static, PIN_15>, reset: Peri<'static, PIN_15>,
} }
struct Audio {
pio: Common<'static, PIO0>,
dma0: Peri<'static, DMA_CH3>,
sm0: StateMachine<'static, PIO0, 0>,
left: Peri<'static, PIN_26>,
dma1: Peri<'static, DMA_CH4>,
sm1: StateMachine<'static, PIO0, 1>,
right: Peri<'static, PIN_27>,
}
struct Sd { struct Sd {
spi: Peri<'static, SPI0>, spi: Peri<'static, SPI0>,
clk: Peri<'static, PIN_18>, clk: Peri<'static, PIN_18>,
@@ -293,7 +254,7 @@ async fn setup_mcu(mcu: Mcu) {
async fn setup_display(display: Display, spawner: Spawner) { async fn setup_display(display: Display, spawner: Spawner) {
let mut config = spi::Config::default(); let mut config = spi::Config::default();
config.frequency = 192_000_000; config.frequency = 64_000_000;
let spi = Spi::new( let spi = Spi::new(
display.spi, display.spi,
display.clk, display.clk,
@@ -309,26 +270,26 @@ async fn setup_display(display: Display, spawner: Spawner) {
// psram is kind of useless on the pico calc // psram is kind of useless on the pico calc
// ive opted to use the pimoroni with on onboard xip psram instead // ive opted to use the pimoroni with on onboard xip psram instead
// async fn setup_psram(psram: Psram) { async fn setup_psram(psram: Psram) {
// let psram = init_psram( let psram = init_psram(
// psram.pio, psram.sclk, psram.mosi, psram.miso, psram.cs, psram.dma1, psram.dma2, psram.pio, psram.sclk, psram.mosi, psram.miso, psram.cs, psram.dma1, psram.dma2,
// ) )
// .await; .await;
// #[cfg(feature = "defmt")] #[cfg(feature = "defmt")]
// defmt::info!("psram size: {}", psram.size); defmt::info!("psram size: {}", psram.size);
// if psram.size == 0 { if psram.size == 0 {
// #[cfg(feature = "defmt")] #[cfg(feature = "defmt")]
// defmt::info!("\u{1b}[1mExternal PSRAM was NOT found!\u{1b}[0m"); defmt::info!("\u{1b}[1mExternal PSRAM was NOT found!\u{1b}[0m");
// } }
// } }
#[cfg(feature = "psram")] #[cfg(feature = "pimoroni2w")]
async fn setup_qmi_psram() { async fn setup_qmi_psram() {
Timer::after_millis(250).await; Timer::after_millis(250).await;
let psram_qmi_size = init_psram_qmi(&embassy_rp::pac::QMI, &embassy_rp::pac::XIP_CTRL); let psram_qmi_size = init_psram_qmi(&embassy_rp::pac::QMI, &embassy_rp::pac::XIP_CTRL);
#[cfg(feature = "defmt")] #[cfg(feature = "debug")]
defmt::info!("size: {}", psram_qmi_size); defmt::info!("size: {}", psram_qmi_size);
Timer::after_millis(100).await; Timer::after_millis(100).await;
@@ -360,9 +321,8 @@ async fn kernel_task(
spawner: Spawner, spawner: Spawner,
watchdog: Peri<'static, WATCHDOG>, watchdog: Peri<'static, WATCHDOG>,
display: Display, display: Display,
audio: Audio,
sd: Sd, sd: Sd,
// _psram: Psram, psram: Psram,
mcu: Mcu, mcu: Mcu,
usb: Peri<'static, USB>, usb: Peri<'static, USB>,
) { ) {
@@ -370,25 +330,19 @@ async fn kernel_task(
.spawn(watchdog_task(Watchdog::new(watchdog))) .spawn(watchdog_task(Watchdog::new(watchdog)))
.unwrap(); .unwrap();
#[cfg(feature = "defmt")]
defmt::info!("Clock: {}", embassy_rp::clocks::clk_sys_freq());
setup_mcu(mcu).await; setup_mcu(mcu).await;
#[cfg(feature = "defmt")]
defmt::info!("setting up psram"); defmt::info!("setting up psram");
Timer::after_millis(100).await; Timer::after_millis(100).await;
// setup_psram(psram).await; // setup_psram(psram).await;
#[cfg(feature = "psram")] #[cfg(feature = "pimoroni2w")]
setup_qmi_psram().await; setup_qmi_psram().await;
Timer::after_millis(100).await; Timer::after_millis(100).await;
setup_display(display, spawner).await; setup_display(display, spawner).await;
setup_sd(sd).await; setup_sd(sd).await;
spawner.spawn(audio_handler(audio)).unwrap();
let _usb = embassy_rp_usb::Driver::new(usb, Irqs); let _usb = embassy_rp_usb::Driver::new(usb, Irqs);
// spawner.spawn(usb_handler(usb)).unwrap(); // spawner.spawn(usb_handler(usb)).unwrap();
@@ -408,8 +362,7 @@ async fn prog_search_handler() {
let mut guard = SDCARD.get().lock().await; let mut guard = SDCARD.get().lock().await;
let sd = guard.as_mut().unwrap(); let sd = guard.as_mut().unwrap();
let mut files = sd.list_files_by_extension(".bin").unwrap(); let files = sd.list_files_by_extension(".bin").unwrap();
files.sort();
let mut select = SELECTIONS.lock().await; let mut select = SELECTIONS.lock().await;
if *select.selections() != files { if *select.selections() != files {
@@ -424,8 +377,10 @@ async fn prog_search_handler() {
async fn key_handler() { async fn key_handler() {
loop { loop {
if let Some(event) = read_keyboard_fifo().await { if let Some(event) = read_keyboard_fifo().await {
unsafe { if let KeyState::Pressed = event.state {
let _ = KEY_CACHE.enqueue(event); unsafe {
let _ = KEY_CACHE.enqueue(event);
}
} }
} }
Timer::after_millis(50).await; Timer::after_millis(50).await;

View File

@@ -1,5 +1,5 @@
use crate::peripherals::PERIPHERAL_BUS; use crate::peripherals::PERIPHERAL_BUS;
pub use userlib_sys::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; pub use abi_sys::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers};
const REG_ID_KEY: u8 = 0x04; const REG_ID_KEY: u8 = 0x04;
const REG_ID_FIF: u8 = 0x09; const REG_ID_FIF: u8 = 0x09;

View File

@@ -526,7 +526,7 @@ pub fn init_psram_qmi(
let psram_size = detect_psram_qmi(qmi); let psram_size = detect_psram_qmi(qmi);
if psram_size == 0 { if psram_size == 0 {
#[cfg(feature = "defmt")] #[cfg(feature = "debug")]
defmt::error!("qmi psram size 0"); defmt::error!("qmi psram size 0");
return 0; return 0;
} }

View File

@@ -30,7 +30,7 @@ pub struct MassStorageClass<'d, D: Driver<'d>> {
bulk_in: D::EndpointIn, bulk_in: D::EndpointIn,
} }
impl<'d, D: Driver<'d>> MassStorageClass<'d, D> { impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, D> {
pub fn new(builder: &mut Builder<'d, D>, temp_sd: Option<SdCard>) -> Self { pub fn new(builder: &mut Builder<'d, D>, temp_sd: Option<SdCard>) -> Self {
let mut function = builder.function(0x08, SUBCLASS_SCSI, 0x50); // Mass Storage class let mut function = builder.function(0x08, SUBCLASS_SCSI, 0x50); // Mass Storage class
let mut interface = function.interface(); let mut interface = function.interface();
@@ -72,34 +72,35 @@ impl<'d, D: Driver<'d>> MassStorageClass<'d, D> {
async fn handle_cbw(&mut self) { async fn handle_cbw(&mut self) {
let mut cbw_buf = [0u8; 31]; let mut cbw_buf = [0u8; 31];
if let Ok(n) = self.bulk_out.read(&mut cbw_buf).await if let Ok(n) = self.bulk_out.read(&mut cbw_buf).await {
&& n == 31 if n == 31 {
&& let Some(cbw) = CommandBlockWrapper::parse(&cbw_buf[..n]) if let Some(cbw) = CommandBlockWrapper::parse(&cbw_buf[..n]) {
{ // Take sdcard to increase speed
// Take sdcard to increase speed if self.temp_sd.is_none() {
if self.temp_sd.is_none() { let mut guard = SDCARD.get().lock().await;
let mut guard = SDCARD.get().lock().await; if let Some(sd) = guard.take() {
if let Some(sd) = guard.take() { self.temp_sd = Some(sd);
self.temp_sd = Some(sd); } else {
} else { #[cfg(feature = "defmt")]
#[cfg(feature = "defmt")] defmt::warn!("Tried to take SDCARD but it was already taken");
defmt::warn!("Tried to take SDCARD but it was already taken"); return;
return; }
}
let command = parse_cb(&cbw.CBWCB);
if self.handle_command(command).await.is_ok() {
self.send_csw_success(cbw.dCBWTag).await
} else {
self.send_csw_fail(cbw.dCBWTag).await
}
if self.pending_eject {
if let ScsiCommand::Write { lba: _, len: _ } = command {
MSC_SHUTDOWN.signal(());
}
}
} }
} }
let command = parse_cb(&cbw.CBWCB);
if self.handle_command(command).await.is_ok() {
self.send_csw_success(cbw.dCBWTag).await
} else {
self.send_csw_fail(cbw.dCBWTag).await
}
if self.pending_eject
&& let ScsiCommand::Write { lba: _, len: _ } = command
{
MSC_SHUTDOWN.signal(());
}
} }
} }
@@ -237,7 +238,7 @@ impl<'d, D: Driver<'d>> MassStorageClass<'d, D> {
let block_size = SdCard::BLOCK_SIZE as u64; let block_size = SdCard::BLOCK_SIZE as u64;
let total_blocks = self.temp_sd.as_ref().unwrap().size() / block_size; let total_blocks = self.temp_sd.as_ref().unwrap().size() / block_size;
let last_lba = total_blocks.saturating_sub(1); let last_lba = total_blocks.checked_sub(1).unwrap_or(0);
response.extend_from_slice(&(last_lba as u32).to_be_bytes())?; response.extend_from_slice(&(last_lba as u32).to_be_bytes())?;
response.extend_from_slice(&(block_size as u32).to_be_bytes())?; response.extend_from_slice(&(block_size as u32).to_be_bytes())?;
@@ -248,7 +249,7 @@ impl<'d, D: Driver<'d>> MassStorageClass<'d, D> {
let block_size = SdCard::BLOCK_SIZE as u64; let block_size = SdCard::BLOCK_SIZE as u64;
let total_blocks = self.temp_sd.as_ref().unwrap().size() / block_size; let total_blocks = self.temp_sd.as_ref().unwrap().size() / block_size;
let last_lba = total_blocks.saturating_sub(1); let last_lba = total_blocks.checked_sub(1).unwrap_or(0);
response.extend_from_slice(&last_lba.to_be_bytes())?; // 8 bytes last LBA response.extend_from_slice(&last_lba.to_be_bytes())?; // 8 bytes last LBA
response.extend_from_slice(&(block_size as u32).to_be_bytes())?; // 4 bytes block length response.extend_from_slice(&(block_size as u32).to_be_bytes())?; // 4 bytes block length
@@ -268,7 +269,7 @@ impl<'d, D: Driver<'d>> MassStorageClass<'d, D> {
sdcard.read_blocks(block_buf, BlockIdx(idx as u32))?; sdcard.read_blocks(block_buf, BlockIdx(idx as u32))?;
for block in &mut *block_buf { for block in &mut *block_buf {
for chunk in block.contents.chunks(BULK_ENDPOINT_PACKET_SIZE) { for chunk in block.contents.chunks(BULK_ENDPOINT_PACKET_SIZE.into()) {
self.bulk_in.write(chunk).await.map_err(|_| ())?; self.bulk_in.write(chunk).await.map_err(|_| ())?;
} }
} }
@@ -280,7 +281,7 @@ impl<'d, D: Driver<'d>> MassStorageClass<'d, D> {
.read_blocks(&mut block_buf[..blocks as usize], BlockIdx(idx as u32))?; .read_blocks(&mut block_buf[..blocks as usize], BlockIdx(idx as u32))?;
for block in &block_buf[..blocks as usize] { for block in &block_buf[..blocks as usize] {
for chunk in block.contents.chunks(BULK_ENDPOINT_PACKET_SIZE) { for chunk in block.contents.chunks(BULK_ENDPOINT_PACKET_SIZE.into()) {
self.bulk_in.write(chunk).await.map_err(|_| ())?; self.bulk_in.write(chunk).await.map_err(|_| ())?;
} }
} }
@@ -300,7 +301,8 @@ impl<'d, D: Driver<'d>> MassStorageClass<'d, D> {
while blocks > 0 { while blocks > 0 {
if blocks >= block_buf.len() as u64 { if blocks >= block_buf.len() as u64 {
for block in block_buf.as_mut() { for block in block_buf.as_mut() {
for chunk in block.contents.chunks_mut(BULK_ENDPOINT_PACKET_SIZE) { for chunk in block.contents.chunks_mut(BULK_ENDPOINT_PACKET_SIZE.into())
{
self.bulk_out.read(chunk).await.map_err(|_| ())?; self.bulk_out.read(chunk).await.map_err(|_| ())?;
} }
} }
@@ -311,7 +313,8 @@ impl<'d, D: Driver<'d>> MassStorageClass<'d, D> {
idx += block_buf.len() as u64; idx += block_buf.len() as u64;
} else { } else {
for block in block_buf[..blocks as usize].as_mut() { for block in block_buf[..blocks as usize].as_mut() {
for chunk in block.contents.chunks_mut(BULK_ENDPOINT_PACKET_SIZE) { for chunk in block.contents.chunks_mut(BULK_ENDPOINT_PACKET_SIZE.into())
{
self.bulk_out.read(chunk).await.map_err(|_| ())?; self.bulk_out.read(chunk).await.map_err(|_| ())?;
} }
} }

View File

@@ -1,7 +1,7 @@
use num_enum::TryFromPrimitive; use num_enum::TryFromPrimitive;
/// THE CODE BELOW ORIGINATES FROM: https://github.com/apohrebniak/usbd-storage/blob/master/usbd-storage/src/subclass/scsi.rs /// THE CODE BELOW ORIGINATES FROM: https://github.com/apohrebniak/usbd-storage/blob/master/usbd-storage/src/subclass/scsi.rs
///
/// SCSI device subclass code /// SCSI device subclass code
pub const SUBCLASS_SCSI: u8 = 0x06; // SCSI Transparent command set pub const SUBCLASS_SCSI: u8 = 0x06; // SCSI Transparent command set
@@ -91,7 +91,6 @@ pub enum ScsiCommand {
}, },
} }
#[allow(clippy::enum_variant_names)]
#[repr(u8)] #[repr(u8)]
#[derive(Copy, Clone, Debug, TryFromPrimitive)] #[derive(Copy, Clone, Debug, TryFromPrimitive)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]

View File

@@ -9,8 +9,8 @@ use embassy_sync::mutex::Mutex;
use embassy_time::Delay; use embassy_time::Delay;
use embedded_hal_bus::spi::ExclusiveDevice; use embedded_hal_bus::spi::ExclusiveDevice;
use embedded_sdmmc::{ use embedded_sdmmc::{
Block, BlockDevice, BlockIdx, Directory, SdCard as SdmmcSdCard, TimeSource, Timestamp, Block, BlockCount, BlockDevice, BlockIdx, Directory, SdCard as SdmmcSdCard, TimeSource,
VolumeIdx, VolumeManager, sdcard::Error, Timestamp, Volume, VolumeIdx, VolumeManager, sdcard::Error,
}; };
use embedded_sdmmc::{File as SdFile, LfnBuffer, Mode, ShortFileName}; use embedded_sdmmc::{File as SdFile, LfnBuffer, Mode, ShortFileName};
@@ -21,6 +21,7 @@ pub const MAX_VOLUMES: usize = 1;
type Device = ExclusiveDevice<Spi<'static, SPI0, Blocking>, Output<'static>, embassy_time::Delay>; type Device = ExclusiveDevice<Spi<'static, SPI0, Blocking>, Output<'static>, embassy_time::Delay>;
type SD = SdmmcSdCard<Device, Delay>; type SD = SdmmcSdCard<Device, Delay>;
type VolMgr = VolumeManager<SD, DummyTimeSource, MAX_DIRS, MAX_FILES, MAX_VOLUMES>; type VolMgr = VolumeManager<SD, DummyTimeSource, MAX_DIRS, MAX_FILES, MAX_VOLUMES>;
type Vol<'a> = Volume<'a, SD, DummyTimeSource, MAX_DIRS, MAX_FILES, MAX_VOLUMES>;
pub type Dir<'a> = Directory<'a, SD, DummyTimeSource, MAX_DIRS, MAX_FILES, MAX_VOLUMES>; pub type Dir<'a> = Directory<'a, SD, DummyTimeSource, MAX_DIRS, MAX_FILES, MAX_VOLUMES>;
pub type File<'a> = SdFile<'a, SD, DummyTimeSource, MAX_DIRS, MAX_FILES, MAX_VOLUMES>; pub type File<'a> = SdFile<'a, SD, DummyTimeSource, MAX_DIRS, MAX_FILES, MAX_VOLUMES>;
@@ -34,24 +35,12 @@ impl TimeSource for DummyTimeSource {
} }
} }
#[derive(Clone, PartialEq, Eq)] #[derive(Clone, PartialEq)]
pub struct FileName { pub struct FileName {
pub long_name: String, pub long_name: String,
pub short_name: ShortFileName, pub short_name: ShortFileName,
} }
impl PartialOrd for FileName {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for FileName {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.long_name.cmp(&other.long_name)
}
}
pub struct SdCard { pub struct SdCard {
det: Input<'static>, det: Input<'static>,
volume_mgr: VolMgr, volume_mgr: VolMgr,
@@ -66,7 +55,10 @@ impl SdCard {
DummyTimeSource {}, DummyTimeSource {},
5000, 5000,
); );
Self { det, volume_mgr } Self {
det: det,
volume_mgr,
}
} }
/// Returns true if an SD card is inserted. /// Returns true if an SD card is inserted.
@@ -86,6 +78,17 @@ impl SdCard {
result result
} }
pub fn num_blocks(&self) -> u32 {
let mut result = 0;
self.volume_mgr.device(|sd| {
result = sd.num_blocks().unwrap_or(BlockCount(0)).0;
DummyTimeSource {}
});
result
}
pub fn read_blocks(&self, blocks: &mut [Block], start_block_idx: BlockIdx) -> Result<(), ()> { pub fn read_blocks(&self, blocks: &mut [Block], start_block_idx: BlockIdx) -> Result<(), ()> {
let mut res: Result<(), Error> = Ok(()); let mut res: Result<(), Error> = Ok(());
self.volume_mgr.device(|sd| { self.volume_mgr.device(|sd| {

View File

@@ -2,6 +2,7 @@ use crate::{
BINARY_CH, display::FRAMEBUFFER, elf::load_binary, framebuffer::FB_PAUSED, BINARY_CH, display::FRAMEBUFFER, elf::load_binary, framebuffer::FB_PAUSED,
peripherals::keyboard, storage::FileName, peripherals::keyboard, storage::FileName,
}; };
use abi_sys::keyboard::{KeyCode, KeyState};
use alloc::{str::FromStr, string::String, vec::Vec}; use alloc::{str::FromStr, string::String, vec::Vec};
use core::sync::atomic::Ordering; use core::sync::atomic::Ordering;
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex}; use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex};
@@ -19,38 +20,37 @@ use embedded_layout::{
prelude::*, prelude::*,
}; };
use embedded_text::TextBox; use embedded_text::TextBox;
use userlib_sys::keyboard::{KeyCode, KeyState};
pub static SELECTIONS: Mutex<CriticalSectionRawMutex, SelectionList> = pub static SELECTIONS: Mutex<CriticalSectionRawMutex, SelectionList> =
Mutex::new(SelectionList::new()); Mutex::new(SelectionList::new());
pub async fn ui_handler() { pub async fn ui_handler() {
loop { loop {
if let Some(event) = keyboard::read_keyboard_fifo().await if let Some(event) = keyboard::read_keyboard_fifo().await {
&& let KeyState::Pressed = event.state if let KeyState::Pressed = event.state {
{ match event.key {
match event.key { KeyCode::Up => {
KeyCode::Up => { let mut selections = SELECTIONS.lock().await;
let mut selections = SELECTIONS.lock().await; selections.up();
selections.up(); }
} KeyCode::Down => {
KeyCode::Down => { let mut selections = SELECTIONS.lock().await;
let mut selections = SELECTIONS.lock().await; selections.down();
selections.down(); }
} KeyCode::Enter | KeyCode::Right => {
KeyCode::Enter | KeyCode::Right => { let selections = SELECTIONS.lock().await;
let selections = SELECTIONS.lock().await; let selection =
let selection = selections.selections[selections.current_selection as usize].clone();
selections.selections[selections.current_selection as usize].clone();
let entry = unsafe { let entry = unsafe {
load_binary(&selection.short_name) load_binary(&selection.short_name)
.await .await
.expect("unable to load binary") .expect("unable to load binary")
}; };
BINARY_CH.send(entry).await; BINARY_CH.send(entry).await;
}
_ => (),
} }
_ => (),
} }
} }
@@ -75,7 +75,7 @@ pub async fn clear_selection() {
async fn draw_selection() { async fn draw_selection() {
let mut guard = SELECTIONS.lock().await; let mut guard = SELECTIONS.lock().await;
let file_names = guard.selections.clone(); let file_names = &guard.selections.clone();
let text_style = MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE); let text_style = MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE);
let display_area = unsafe { FRAMEBUFFER.as_mut().unwrap().bounding_box() }; let display_area = unsafe { FRAMEBUFFER.as_mut().unwrap().bounding_box() };
@@ -99,7 +99,7 @@ async fn draw_selection() {
} else { } else {
let mut views: alloc::vec::Vec<Text<MonoTextStyle<Rgb565>>> = Vec::new(); let mut views: alloc::vec::Vec<Text<MonoTextStyle<Rgb565>>> = Vec::new();
for i in &file_names { for i in file_names {
views.push(Text::new(&i.long_name, Point::zero(), text_style)); views.push(Text::new(&i.long_name, Point::zero(), text_style));
} }

Submodule picolibc deleted from d664643068

View File

@@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
userlib = { path = "../userlib" } abi = { path = "../abi" }
embedded-graphics = "0.8.1" embedded-graphics = "0.8.1"
embedded-layout = "0.4.2" embedded-layout = "0.4.2"
embedded-text = "0.7.3" embedded-text = "0.7.3"

View File

@@ -2,104 +2,93 @@
extern crate alloc; extern crate alloc;
use abi::{
display::Display,
get_key,
keyboard::{KeyCode, KeyState},
print, sleep,
};
use alloc::vec::Vec; use alloc::vec::Vec;
use embedded_graphics::{ use embedded_graphics::{
mono_font::{ascii::FONT_10X20, MonoTextStyle},
pixelcolor::Rgb565,
prelude::{Dimensions, DrawTarget, Point, Primitive, RgbColor},
primitives::{PrimitiveStyle, Rectangle},
text::{renderer::TextRenderer, Alignment, Text},
Drawable, Drawable,
mono_font::{MonoTextStyle, ascii::FONT_10X20},
pixelcolor::Rgb565,
prelude::{Dimensions, Point, Primitive, RgbColor, Size},
primitives::{PrimitiveStyle, Rectangle},
text::Text,
}; };
use embedded_layout::{ use embedded_layout::{
align::{horizontal, vertical}, align::{horizontal, vertical},
layout::linear::{FixedMargin, LinearLayout}, layout::linear::{FixedMargin, LinearLayout},
prelude::*, prelude::*,
}; };
use userlib::{ use embedded_text::TextBox;
display::Display,
get_key,
keyboard::{KeyCode, KeyState},
};
#[derive(Debug)]
pub enum SelectionUiError<DisplayError> {
SelectionListEmpty,
DisplayError(DisplayError),
}
pub struct SelectionUi<'a> { pub struct SelectionUi<'a> {
selection: usize, selection: usize,
items: &'a [&'a str], items: &'a [&'a str],
error: &'a str,
last_bounds: Option<Rectangle>, last_bounds: Option<Rectangle>,
} }
impl<'a> SelectionUi<'a> { impl<'a> SelectionUi<'a> {
pub fn new(items: &'a [&'a str]) -> Self { pub fn new(items: &'a [&'a str], error: &'a str) -> Self {
Self { Self {
selection: 0, selection: 0,
items, items,
error,
last_bounds: None, last_bounds: None,
} }
} }
pub fn run_selection_ui( pub fn run_selection_ui(&mut self, display: &mut Display) -> Result<Option<usize>, ()> {
&mut self,
display: &mut Display,
) -> Result<Option<usize>, SelectionUiError<<Display as DrawTarget>::Error>> {
self.draw(display)?; self.draw(display)?;
let selection; let selection;
loop { loop {
let key = get_key(); let key = get_key();
if key.state == KeyState::Pressed if key.state == KeyState::Pressed {
&& let Some(s) = self.update(display, key.key)? { print!("Got Key press: {:?}", key.key);
if let Some(s) = self.update(display, key.key)? {
selection = Some(s); selection = Some(s);
display
.clear(Rgb565::BLACK)
.map_err(SelectionUiError::DisplayError)?;
break; break;
} }
}
} }
Ok(selection) Ok(selection)
} }
/// updates the display with a new keypress. /// updates the display with a new keypress.
/// returns selection idx if selected /// returns selection idx if selected
pub fn update( pub fn update(&mut self, display: &mut Display, key: KeyCode) -> Result<Option<usize>, ()> {
&mut self,
display: &mut Display,
key: KeyCode,
) -> Result<Option<usize>, SelectionUiError<<Display as DrawTarget>::Error>> {
match key { match key {
KeyCode::Down => { KeyCode::JoyUp => {
self.selection = (self.selection + 1).min(self.items.len() - 1);
}
KeyCode::Up => {
self.selection = self.selection.saturating_sub(1); self.selection = self.selection.saturating_sub(1);
} }
KeyCode::Enter | KeyCode::Right => return Ok(Some(self.selection)), KeyCode::JoyDown => {
_ => return Ok(None), self.selection = self.selection.saturating_add(1);
}
KeyCode::Enter | KeyCode::JoyRight => return Ok(Some(self.selection)),
_ => return Ok(Some(self.selection)),
}; };
self.draw(display)?; self.draw(display)?;
Ok(None) Ok(None)
} }
fn draw( fn draw(&mut self, display: &mut Display) -> Result<(), ()> {
&mut self,
display: &mut Display,
) -> Result<(), SelectionUiError<<Display as DrawTarget>::Error>> {
let text_style = MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE); let text_style = MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE);
let display_area = display.bounding_box(); let display_area = display.bounding_box();
if self.items.is_empty() { if self.items.is_empty() {
return Err(SelectionUiError::SelectionListEmpty); TextBox::new(
} &self.error,
Rectangle::new(
if let Some(bounds) = self.last_bounds { Point::new(25, 25),
Rectangle::new(bounds.top_left, bounds.size) Size::new(display_area.size.width - 50, display_area.size.width - 50),
.into_styled(PrimitiveStyle::with_fill(Rgb565::BLACK)) ),
.draw(display) text_style,
.map_err(SelectionUiError::DisplayError)?; )
.draw(display)
.unwrap();
} }
let mut views: Vec<Text<MonoTextStyle<Rgb565>>> = Vec::new(); let mut views: Vec<Text<MonoTextStyle<Rgb565>>> = Vec::new();
@@ -116,38 +105,15 @@ impl<'a> SelectionUi<'a> {
.arrange() .arrange()
.align_to(&display_area, horizontal::Center, vertical::Center); .align_to(&display_area, horizontal::Center, vertical::Center);
layout
.draw(display)
.map_err(SelectionUiError::DisplayError)?;
// draw selected box // draw selected box
if let Some(selected_bounds) = layout.inner().get(self.selection) { let selected_bounds = layout.inner().get(self.selection).ok_or(())?.bounding_box();
let selected_bounds = selected_bounds.bounding_box(); Rectangle::new(selected_bounds.top_left, selected_bounds.size)
Rectangle::new(selected_bounds.top_left, selected_bounds.size) .into_styled(PrimitiveStyle::with_stroke(Rgb565::WHITE, 1))
.into_styled(PrimitiveStyle::with_stroke(Rgb565::WHITE, 1)) .draw(display)?;
.draw(display)
.map_err(SelectionUiError::DisplayError)?;
self.last_bounds = Some(selected_bounds); self.last_bounds = Some(layout.bounds());
}
layout.draw(display)?;
Ok(()) Ok(())
} }
} }
pub fn draw_text_center<S>(
display: &mut Display,
text: &str,
style: S,
) -> Result<Point, <Display as DrawTarget>::Error>
where
S: TextRenderer<Color = <Display as DrawTarget>::Color>,
{
Text::with_alignment(
text,
display.bounding_box().center(),
style,
Alignment::Center,
)
.draw(display)
}

View File

@@ -4,6 +4,6 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
userlib = { path = "../../userlib" } abi = { path = "../../abi" }
embedded-graphics = "0.8.1" embedded-graphics = "0.8.1"
embedded-layout = "0.4.2" embedded-layout = "0.4.2"

View File

@@ -2,6 +2,12 @@
#![no_main] #![no_main]
extern crate alloc; extern crate alloc;
use abi::{
display::Display,
get_key,
keyboard::{KeyCode, KeyState},
print,
};
use alloc::{format, string::String, vec, vec::Vec}; use alloc::{format, string::String, vec, vec::Vec};
use core::panic::PanicInfo; use core::panic::PanicInfo;
use embedded_graphics::{ use embedded_graphics::{
@@ -19,16 +25,10 @@ use embedded_layout::{
layout::linear::LinearLayout, layout::linear::LinearLayout,
prelude::Chain, prelude::Chain,
}; };
use userlib::{
display::Display,
get_key,
keyboard::{KeyCode, KeyState},
println,
};
#[panic_handler] #[panic_handler]
fn panic(info: &PanicInfo) -> ! { fn panic(info: &PanicInfo) -> ! {
println!("user panic: {} @ {:?}", info.message(), info.location(),); print!("user panic: {} @ {:?}", info.message(), info.location(),);
loop {} loop {}
} }
@@ -38,8 +38,8 @@ pub extern "Rust" fn _start() {
} }
pub fn main() { pub fn main() {
println!("Starting Calculator app"); print!("Starting Calculator app");
let mut display = Display::take().unwrap(); let mut display = Display;
let mut input = vec!['e', 'x', 'p', 'r', ':', ' ']; let mut input = vec!['e', 'x', 'p', 'r', ':', ' '];
let input_min = input.len(); let input_min = input.len();
@@ -83,7 +83,7 @@ pub fn main() {
.align_to(&display.bounding_box(), horizontal::Left, vertical::Center); .align_to(&display.bounding_box(), horizontal::Left, vertical::Center);
let result = if let Ok(result) = evaluate(&input[input_min..]) { let result = if let Ok(result) = evaluate(&input[input_min..]) {
&format!(" = {result}") &format!(" = {}", result)
} else { } else {
" = Error" " = Error"
}; };
@@ -104,7 +104,7 @@ pub fn main() {
} }
let event = get_key(); let event = get_key();
if event.state == KeyState::Released { if event.state != KeyState::Idle {
match event.key { match event.key {
KeyCode::Char(ch) => { KeyCode::Char(ch) => {
input.push(ch); input.push(ch);

View File

@@ -4,6 +4,6 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
userlib = { path = "../../userlib" } abi = { path = "../../abi" }
embedded-graphics = "0.8.1" embedded-graphics = "0.8.1"
tinybmp = "0.6.0" tinybmp = "0.6.0"

View File

@@ -3,6 +3,13 @@
#![allow(static_mut_refs)] #![allow(static_mut_refs)]
extern crate alloc; extern crate alloc;
use abi::{
display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH},
fs::{Entries, list_dir, read_file},
get_key,
keyboard::{KeyCode, KeyState},
print,
};
use alloc::{format, vec}; use alloc::{format, vec};
use core::panic::PanicInfo; use core::panic::PanicInfo;
use embedded_graphics::{ use embedded_graphics::{
@@ -10,17 +17,10 @@ use embedded_graphics::{
pixelcolor::Rgb565, prelude::*, text::Text, pixelcolor::Rgb565, prelude::*, text::Text,
}; };
use tinybmp::Bmp; use tinybmp::Bmp;
use userlib::{
display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH},
fs::{Entries, list_dir, read_file},
get_key,
keyboard::{KeyCode, KeyState},
println,
};
#[panic_handler] #[panic_handler]
fn panic(info: &PanicInfo) -> ! { fn panic(info: &PanicInfo) -> ! {
println!("user panic: {} @ {:?}", info.message(), info.location()); print!("user panic: {} @ {:?}", info.message(), info.location());
loop {} loop {}
} }
@@ -30,9 +30,9 @@ pub extern "Rust" fn _start() {
} }
pub fn main() { pub fn main() {
println!("Starting Gallery app"); print!("Starting Gallery app");
let mut bmp_buf = vec![0_u8; 100_000]; let mut bmp_buf = vec![0_u8; 100_000];
let mut display = Display::take().unwrap(); let mut display = Display;
let grid_cols = 3; let grid_cols = 3;
let grid_rows = 3; let grid_rows = 3;
@@ -49,11 +49,11 @@ pub fn main() {
break; // only draw 3x3 break; // only draw 3x3
} }
println!("file: {}", file); print!("file: {}", file);
if file.extension().unwrap_or("") == "bmp" || file.extension().unwrap_or("") == "BMP" { if file.extension().unwrap_or("") == "bmp" || file.extension().unwrap_or("") == "BMP" {
let file_path = format!("/images/{file}"); let file_path = format!("/images/{}", file);
let read = read_file(&file_path, 0, &mut bmp_buf[..]); let read = read_file(&file_path, 0, &mut &mut bmp_buf[..]);
if read > 0 { if read > 0 {
let bmp = Bmp::from_slice(&bmp_buf).expect("failed to parse bmp"); let bmp = Bmp::from_slice(&bmp_buf).expect("failed to parse bmp");
@@ -74,7 +74,7 @@ pub fn main() {
let text_style = MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE); let text_style = MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE);
let text_y = y + bmp_h + 2; // 2px gap under image let text_y = y + bmp_h + 2; // 2px gap under image
Text::new(file.base(), Point::new(cell_x + 2, text_y), text_style) Text::new(&file.base(), Point::new(cell_x + 2, text_y), text_style)
.draw(&mut display) .draw(&mut display)
.unwrap(); .unwrap();
@@ -85,8 +85,11 @@ pub fn main() {
loop { loop {
let event = get_key(); let event = get_key();
if event.state != KeyState::Idle && event.key == KeyCode::Esc { if event.state != KeyState::Idle {
return; match event.key {
} KeyCode::Esc => return,
_ => (),
}
};
} }
} }

View File

@@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
userlib = { path = "../../userlib" } abi = { path = "../../abi" }
selection_ui = { path = "../../selection_ui" }
embedded-graphics = "0.8.1" embedded-graphics = "0.8.1"
selection_ui = { path = "../../selection_ui" }
tinygif = { git = "https://github.com/LegitCamper/tinygif" } tinygif = { git = "https://github.com/LegitCamper/tinygif" }

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

@@ -0,0 +1,84 @@
#![no_std]
#![no_main]
extern crate alloc;
use abi::{
display::Display,
fs::{Entries, file_len, list_dir, read_file},
get_key, get_ms,
keyboard::{KeyCode, KeyState},
print, sleep,
};
use alloc::{format, vec, vec::Vec};
use core::panic::PanicInfo;
use embedded_graphics::{
image::ImageDrawable, pixelcolor::Rgb565, prelude::Point, transform::Transform,
};
use selection_ui::SelectionUi;
use tinygif::Gif;
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
print!("user panic: {} @ {:?}", info.message(), info.location(),);
loop {}
}
#[unsafe(no_mangle)]
pub extern "Rust" fn _start() {
main()
}
pub fn main() {
print!("Starting Gif app");
let mut display = Display;
let mut entries = Entries::new();
list_dir("/gifs", &mut entries);
let mut files = entries.entries();
files.retain(|e| e.extension().unwrap_or("") == "gif");
let gifs = &files.iter().map(|e| e.full_name()).collect::<Vec<&str>>();
let mut selection_ui = SelectionUi::new(&gifs, "No Gif files found in /gifs");
let selection = selection_ui
.run_selection_ui(&mut display)
.expect("failed to draw")
.expect("Failed to get user selection");
let file_name = format!("/gifs/{}", gifs[selection]);
let size = file_len(&file_name);
let mut buf = vec![0_u8; size];
let read = read_file(&file_name, 0, &mut buf);
print!("read: {}, file size: {}", read, size);
assert!(read == size);
let gif = Gif::<Rgb565>::from_slice(&buf).expect("Failed to parse gif");
let height = gif.height();
let mut frame_num = 0;
loop {
for mut frame in gif.frames() {
let start = get_ms();
frame
.translate_mut(Point::new(0, (320 - height as i32) / 2))
.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

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

View File

@@ -2,20 +2,20 @@
#![no_main] #![no_main]
extern crate alloc; extern crate alloc;
use core::panic::PanicInfo; use abi::{
use embedded_graphics::{pixelcolor::Rgb565, prelude::RgbColor};
use embedded_snake::{Direction, SnakeGame};
use userlib::{
Rng, Rng,
display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH}, display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH},
get_key, get_key,
keyboard::{KeyCode, KeyState}, keyboard::{KeyCode, KeyState},
println, sleep, print, sleep,
}; };
use core::panic::PanicInfo;
use embedded_graphics::{pixelcolor::Rgb565, prelude::RgbColor};
use embedded_snake::{Direction, SnakeGame};
#[panic_handler] #[panic_handler]
fn panic(info: &PanicInfo) -> ! { fn panic(info: &PanicInfo) -> ! {
println!("user panic: {} @ {:?}", info.message(), info.location(),); print!("user panic: {} @ {:?}", info.message(), info.location(),);
loop {} loop {}
} }
@@ -27,8 +27,8 @@ pub extern "Rust" fn _start() {
const CELL_SIZE: usize = 8; const CELL_SIZE: usize = 8;
pub fn main() { pub fn main() {
println!("Starting Snake app"); print!("Starting Snake app");
let mut display = Display::take().unwrap(); let mut display = Display;
let mut game = SnakeGame::<100, Rgb565, Rng>::new( let mut game = SnakeGame::<100, Rgb565, Rng>::new(
SCREEN_WIDTH as u16, SCREEN_WIDTH as u16,

View File

@@ -1,98 +0,0 @@
#![no_std]
#![no_main]
extern crate alloc;
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;
use userlib::{
display::{Display, SCREEN_HEIGHT, SCREEN_WIDTH},
fs::{Entries, file_len, list_dir, read_file},
get_key, get_ms,
keyboard::{KeyCode, KeyState},
println, sleep,
};
#[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(&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 && event.key == KeyCode::Esc {
drop(buf);
return;
};
}
sleep(((frame.delay_centis as u64) * 10).saturating_sub(start));
}
}
}

View File

@@ -1,11 +0,0 @@
[package]
name = "wav_player"
version = "0.1.0"
edition = "2024"
[dependencies]
userlib = { path = "../../userlib" }
selection_ui = { path = "../../selection_ui" }
embedded-graphics = "0.8.1"
rand = { version = "0.9.0", default-features = false }
embedded-audio = { git = "https://github.com/LegitCamper/embedded-audio" }

View File

@@ -1,28 +0,0 @@
//! 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");
}

View File

@@ -1,143 +0,0 @@
#![no_std]
#![no_main]
extern crate alloc;
use alloc::{string::String, vec::Vec};
use core::panic::PanicInfo;
use embedded_audio::{AudioFile, PlatformFile, PlatformFileError, wav::Wav};
use embedded_graphics::{
mono_font::{MonoTextStyle, ascii::FONT_6X10},
pixelcolor::Rgb565,
prelude::RgbColor,
};
use selection_ui::{SelectionUi, SelectionUiError, draw_text_center};
use userlib::{
audio::{AUDIO_BUFFER_LEN, audio_buffer_ready, send_audio_buffer},
display::Display,
format,
fs::{Entries, file_len, list_dir, read_file},
get_key,
keyboard::{KeyCode, KeyState},
println,
};
#[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 Wav player app");
let mut display = Display::take().unwrap();
loop {
let mut entries = Entries::new();
list_dir("/music", &mut entries);
let mut files = entries.entries();
files.retain(|e| e.extension().unwrap_or("") == "wav");
let mut wavs = files.iter().map(|e| e.full_name()).collect::<Vec<&str>>();
wavs.sort();
let mut selection_ui = SelectionUi::new(&wavs);
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 Wavs were found in /music",
MonoTextStyle::new(&FONT_6X10, Rgb565::RED),
)
.expect("Display Error");
None
}
SelectionUiError::DisplayError(_) => panic!("Display Error"),
},
};
assert!(selection.is_some());
draw_text_center(
&mut display,
&format!("Now playing {}", wavs[selection.unwrap()]),
MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE),
)
.expect("Display Error");
let file_name = format!("/music/{}", wavs[selection.unwrap()]);
let file = File::new(file_name);
let mut wav = Wav::new(file).unwrap();
println!("sample rate: {}", wav.sample_rate());
println!("channels: {:?}", wav.channels() as u8);
let mut buf = [0_u8; AUDIO_BUFFER_LEN];
loop {
if audio_buffer_ready() {
if wav.is_eof() {
break;
}
let _read = wav.read(&mut buf).unwrap();
send_audio_buffer(&buf);
}
let event = get_key();
if event.state == KeyState::Released && event.key == KeyCode::Esc {
return;
}
}
}
}
struct File {
current_pos: usize,
file: String,
}
impl File {
fn new(file: String) -> Self {
Self {
current_pos: 0,
file,
}
}
}
impl PlatformFile for File {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, PlatformFileError> {
let read = read_file(&self.file, self.current_pos, buf);
self.current_pos += read;
Ok(read)
}
fn seek_from_current(&mut self, offset: i64) -> Result<(), PlatformFileError> {
if offset.is_positive() {
self.current_pos += offset as usize;
} else {
self.current_pos -= offset as usize;
}
Ok(())
}
fn seek_from_start(&mut self, offset: usize) -> Result<(), PlatformFileError> {
self.current_pos = offset;
Ok(())
}
fn seek_from_end(&mut self, offset: usize) -> Result<(), PlatformFileError> {
self.current_pos = self.length() - offset;
Ok(())
}
fn length(&mut self) -> usize {
file_len(&self.file)
}
}