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
*.uf2
userlib_sys.h
abi_sys.h
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"
members = [
"kernel",
"userlib_sys",
"userlib",
"abi_sys",
"abi",
"selection_ui",
"user_apps/calculator",
"user_apps/snake",
"user_apps/gallery",
"user_apps/gif",
"user_apps/wav_player",
"user-apps/calculator",
"user-apps/snake",
"user-apps/gallery",
"user-apps/gif",
]
[profile.release]
@@ -24,7 +23,7 @@ codegen-units = 1
lto = true
debug = false
opt-level = "z"
panic = "unwind"
panic = "abort"
[profile.dev]
lto = true

View File

@@ -1,27 +1,26 @@
# PicoCalc OS (Rust)
A simple kernel and applications 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.
A simple operating system for the **Clockwork PicoCalc**, written in Rust.
This project provides a minimal kernel, ABI, and user-space applications to experiment with OS development on constrained hardware.
## Status
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
- **`kernel/`** The core OS kernel
- **`userlib_sys/`** C FFI bindings for kernel syscall
- **`userlib/`** Rust wrapper on top of `userlib_sys`
- **`picolib/`** Built with ```just newlib```, and provides libc symbols when linking with C libraries
- **`user-apps/`** Collection of userspace programs (gif player, wav player, calculator, snake, etc.)
- **`kernel/`** The core OS kernel (task scheduling, drivers, memory, etc.)
- **`abi_sys/`** Shared application binary interface definitions for kernel ↔ userspace (Repr "C")
- **`abi/`** Rust focused ABI helpers and abstractions for easier development
- **`user-apps/`** Collection of userspace programs (calculator, snake, etc.)
## Features
- 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
- Hardware drivers tailored for the PicoCalc( Audio, Display, Keyboard, ans Storage )
- Hardware drivers tailored for the PicoCalc
## Getting Started

View File

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

View File

@@ -3,11 +3,11 @@
extern crate alloc;
pub use abi_sys::{self, keyboard};
use abi_sys::{RngRequest, alloc, dealloc, keyboard::KeyEvent};
pub use alloc::format;
use core::alloc::{GlobalAlloc, Layout};
use rand_core::RngCore;
use userlib_sys::{RngRequest, keyboard::KeyEvent};
pub use userlib_sys::{keyboard, print};
#[global_allocator]
static ALLOC: Alloc = Alloc;
@@ -16,37 +16,36 @@ struct Alloc;
unsafe impl GlobalAlloc for Alloc {
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) {
userlib_sys::dealloc(ptr, layout.into());
dealloc(ptr, layout.into());
}
}
#[macro_export]
macro_rules! println {
macro_rules! print {
($($arg:tt)*) => {{
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) {
userlib_sys::sleep(ms);
abi_sys::sleep(ms);
}
pub fn get_ms() -> u64 {
userlib_sys::get_ms()
abi_sys::get_ms()
}
pub fn get_key() -> KeyEvent {
userlib_sys::keyboard::get_key().into()
abi_sys::keyboard::get_key().into()
}
pub mod display {
use core::sync::atomic::{AtomicBool, Ordering};
use abi_sys::CPixel;
use embedded_graphics::{
Pixel,
geometry::{Dimensions, Point},
@@ -54,35 +53,18 @@ pub mod display {
prelude::{DrawTarget, Size},
primitives::Rectangle,
};
use userlib_sys::CPixel;
pub const SCREEN_WIDTH: usize = 320;
pub const SCREEN_HEIGHT: usize = 320;
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];
// 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 {
_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
}
}
}
pub struct Display;
impl Dimensions for Display {
fn bounding_box(&self) -> Rectangle {
@@ -110,13 +92,13 @@ pub mod display {
count += 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;
}
}
if count > 0 {
userlib_sys::draw_iter(unsafe { BUF.as_ptr() }, count);
abi_sys::draw_iter(unsafe { BUF.as_ptr() }, count);
}
Ok(())
@@ -125,7 +107,7 @@ pub mod display {
}
fn gen_rand(req: &mut RngRequest) {
userlib_sys::gen_rand(req);
abi_sys::gen_rand(req);
}
pub struct Rng;
@@ -158,30 +140,20 @@ impl RngCore for Rng {
}
pub mod fs {
use alloc::vec::Vec;
use core::fmt::Display;
pub fn read_file(file: &str, start_from: usize, buf: &mut [u8]) -> usize {
userlib_sys::read_file(
use alloc::{format, vec::Vec};
pub fn read_file(file: &str, read_from: usize, buf: &mut [u8]) -> usize {
abi_sys::read_file(
file.as_ptr(),
file.len(),
start_from,
read_from,
buf.as_mut_ptr(),
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> {
full: &'a str,
base: &'a str,
@@ -225,15 +197,9 @@ pub mod fs {
const MAX_ENTRY_NAME_LEN: usize = 25;
const MAX_ENTRIES: usize = 25;
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy)]
pub struct Entries([[u8; MAX_ENTRY_NAME_LEN]; MAX_ENTRIES]);
impl Default for Entries {
fn default() -> Self {
Self::new()
}
}
impl Entries {
pub fn new() -> Self {
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 {
userlib_sys::list_dir(
abi_sys::list_dir(
path.as_ptr(),
path.len(),
entries.as_ptrs().as_mut_ptr(),
@@ -270,14 +236,6 @@ pub mod fs {
}
pub fn file_len(str: &str) -> usize {
userlib_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())
abi_sys::file_len(str.as_ptr(), str.len())
}
}

View File

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

View File

@@ -12,12 +12,12 @@ use strum::{EnumCount, EnumIter};
pub type EntryFn = fn();
pub const SYS_CALL_TABLE_COUNT: usize = 14;
const _: () = assert!(SYS_CALL_TABLE_COUNT == SyscallTable::COUNT);
pub const ABI_CALL_TABLE_COUNT: usize = 11;
const _: () = assert!(ABI_CALL_TABLE_COUNT == CallTable::COUNT);
#[derive(Clone, Copy, EnumIter, EnumCount)]
#[repr(u8)]
pub enum SyscallTable {
pub enum CallTable {
Alloc = 0,
Dealloc = 1,
PrintString = 2,
@@ -28,15 +28,12 @@ pub enum SyscallTable {
GenRand = 7,
ListDir = 8,
ReadFile = 9,
WriteFile = 10,
FileLen = 11,
AudioBufferReady = 12,
SendAudioBuffer = 13,
FileLen = 10,
}
#[unsafe(no_mangle)]
#[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")]
#[repr(C)]
@@ -46,9 +43,9 @@ pub struct CLayout {
}
#[cfg(feature = "alloc")]
impl From<CLayout> for Layout {
fn from(val: CLayout) -> Self {
unsafe { Layout::from_size_align_unchecked(val.size, val.alignment) }
impl Into<Layout> for CLayout {
fn into(self) -> Layout {
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)]
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)
}
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)]
pub extern "C" fn dealloc(ptr: *mut u8, layout: CLayout) {
let f: Dealloc =
unsafe { core::mem::transmute(SYS_CALL_TABLE[SyscallTable::Dealloc as usize]) };
let f: DeallocAbi =
unsafe { core::mem::transmute(CALL_ABI_TABLE[CallTable::Dealloc as usize]) };
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)]
pub extern "C" fn print(ptr: *const u8, len: usize) {
let f: Print =
unsafe { core::mem::transmute(SYS_CALL_TABLE[SyscallTable::PrintString as usize]) };
let f: PrintAbi =
unsafe { core::mem::transmute(CALL_ABI_TABLE[CallTable::PrintString as usize]) };
f(ptr, len);
}
pub type SleepMs = extern "C" fn(ms: u64);
pub type SleepMsAbi = extern "C" fn(ms: u64);
#[unsafe(no_mangle)]
pub extern "C" fn sleep(ms: u64) {
let f: SleepMs =
unsafe { core::mem::transmute(SYS_CALL_TABLE[SyscallTable::SleepMs as usize]) };
let f: SleepMsAbi =
unsafe { core::mem::transmute(CALL_ABI_TABLE[CallTable::SleepMs as usize]) };
f(ms);
}
pub type GetMs = extern "C" fn() -> u64;
pub type GetMsAbi = extern "C" fn() -> u64;
#[unsafe(no_mangle)]
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()
}
#[repr(C)]
#[derive(Default, Copy, Clone)]
#[derive(Copy, Clone)]
pub struct CPixel {
pub x: i32,
pub y: i32,
@@ -123,36 +120,33 @@ impl CPixel {
}
}
impl From<CPixel> for Pixel<Rgb565> {
fn from(value: CPixel) -> Self {
Pixel(
Point::new(value.x, value.y),
RawU16::new(value.color).into(),
)
}
}
impl From<Pixel<Rgb565>> for CPixel {
fn from(value: Pixel<Rgb565>) -> Self {
impl Into<CPixel> for Pixel<Rgb565> {
fn into(self) -> CPixel {
CPixel {
x: value.0.x,
y: value.0.y,
color: value.1.into_storage(),
x: self.0.x,
y: self.0.y,
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)]
pub extern "C" fn draw_iter(ptr: *const CPixel, len: usize) {
let f: DrawIter =
unsafe { core::mem::transmute(SYS_CALL_TABLE[SyscallTable::DrawIter as usize]) };
let f: DrawIterAbi =
unsafe { core::mem::transmute(CALL_ABI_TABLE[CallTable::DrawIter as usize]) };
f(ptr, len);
}
pub mod keyboard {
use crate::{SYS_CALL_TABLE, SyscallTable};
use crate::{CALL_ABI_TABLE, CallTable};
bitflags::bitflags! {
#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)]
@@ -174,12 +168,12 @@ pub mod keyboard {
pub mods: Modifiers,
}
impl From<KeyEventC> for KeyEvent {
fn from(val: KeyEventC) -> Self {
impl Into<KeyEvent> for KeyEventC {
fn into(self) -> KeyEvent {
KeyEvent {
key: val.key.into(),
state: val.state,
mods: val.mods,
key: self.key.into(),
state: self.state,
mods: self.mods,
}
}
}
@@ -191,12 +185,12 @@ pub mod keyboard {
pub mods: Modifiers,
}
impl From<KeyEvent> for KeyEventC {
fn from(val: KeyEvent) -> Self {
impl Into<KeyEventC> for KeyEvent {
fn into(self) -> KeyEventC {
KeyEventC {
key: val.key.into(),
state: val.state,
mods: val.mods,
key: self.key.into(),
state: self.state,
mods: self.mods,
}
}
}
@@ -270,9 +264,9 @@ pub mod keyboard {
Unknown(u8),
}
impl From<KeyCode> for u8 {
fn from(val: KeyCode) -> Self {
match val {
impl Into<u8> for KeyCode {
fn into(self) -> u8 {
match self {
KeyCode::JoyUp => 0x01,
KeyCode::JoyDown => 0x02,
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)]
pub extern "C" fn get_key() -> KeyEventC {
let f: GetKey =
unsafe { core::mem::transmute(SYS_CALL_TABLE[SyscallTable::GetKey as usize]) };
let f: GetKeyAbi =
unsafe { core::mem::transmute(CALL_ABI_TABLE[CallTable::GetKey as usize]) };
f()
}
}
@@ -392,7 +386,7 @@ pub type GenRand = extern "C" fn(req: &mut RngRequest);
#[unsafe(no_mangle)]
pub extern "C" fn gen_rand(req: &mut RngRequest) {
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);
f(req)
}
@@ -415,7 +409,7 @@ pub extern "C" fn list_dir(
max_entry_str_len: usize,
) -> usize {
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);
f(str, len, entries, entry_count, max_entry_str_len)
}
@@ -438,62 +432,19 @@ pub extern "C" fn read_file(
buf_len: usize,
) -> usize {
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);
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;
#[unsafe(no_mangle)]
pub extern "C" fn file_len(str: *const u8, len: usize) -> usize {
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);
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:
cargo run --bin kernel --features {{board}} --features fps --features defmt
kernel-release-probe board:
cargo run --bin kernel --profile release --features {{board}} --features fps
cargo run --bin kernel --features {{board}}
kernel-release 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\""
cbindgen:
cbindgen userlib_sys --output userlib_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
cbindgen abi_sys --output abi_sys.h -q
userapp app:
{{binary-args}} cargo build --bin {{app}} --profile release-binary
@@ -41,20 +17,3 @@ userapps: cbindgen
just userapp snake
just userapp gallery
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
[features]
default = ["rp235x"]
pimoroni2w = ["rp235x", "psram"]
# rp2040 = ["embassy-rp/rp2040"] # unsupported, ram too small for fb
default = ["rp235x", "defmt"]
pimoroni2w = ["rp235x"]
rp2040 = ["embassy-rp/rp2040"]
rp235x = ["embassy-rp/rp235xb"]
trouble = ["dep:bt-hci", "dep:cyw43", "dep:cyw43-pio", "dep:trouble-host"]
psram = ["dep:embedded-alloc"]
overclock = []
fps = []
defmt = [
"dep:defmt",
"panic-probe/print-defmt",
@@ -73,17 +70,16 @@ panic-probe = "0.3"
portable-atomic = { version = "1.11", features = ["critical-section"] }
assign-resources = "0.5.0"
defmt = { version = "1.0.1", optional = true }
defmt-rtt = "1.1.0"
defmt = { version = "0.3", optional = true }
defmt-rtt = "0.4.2"
embedded-sdmmc = { version = "0.9", default-features = false }
st7365p-lcd = { git = "https://github.com/legitcamper/st7365p-lcd-rs", rev = "a784b9e6df0769371dfc522528e770cf8fc6403a" } # async branch
embedded-graphics = { version = "0.8.1" }
embedded-text = "0.7.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 }
rand = { version = "0.9.0", 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 }
goblin = { version = "0.10.1", default-features = false, features = ["elf32"] }
talc = "4.4.3"
embedded-alloc = { version = "0.6.0", features = [
"allocator_api",
], optional = true }
embedded-alloc = { version = "0.6.0", features = ["allocator_api"] }
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;
#[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")]
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 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_time::Instant;
use embedded_graphics::draw_target::DrawTarget;
use embedded_sdmmc::LfnBuffer;
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::{
audio::{AUDIO_BUFFER, AUDIO_BUFFER_READY, AUDIO_BUFFER_WRITTEN},
display::FRAMEBUFFER,
framebuffer::FB_PAUSED,
storage::{Dir, File, SDCARD},
};
const _: Alloc = alloc;
const _: AllocAbi = alloc;
pub extern "C" fn alloc(layout: CLayout) -> *mut u8 {
// SAFETY: caller guarantees layout is valid
unsafe {
#[cfg(feature = "psram")]
{
return HEAP.alloc(layout.into());
}
#[cfg(not(feature = "psram"))]
{
if cfg!(feature = "pimoroni2w") {
crate::heap::HEAP.alloc(layout.into())
} else {
alloc::alloc::alloc(layout.into())
}
}
}
const _: Dealloc = dealloc;
const _: DeallocAbi = dealloc;
pub extern "C" fn dealloc(ptr: *mut u8, layout: CLayout) {
// SAFETY: caller guarantees ptr and layout are valid
#[cfg(feature = "psram")]
{
unsafe { HEAP.dealloc(ptr, layout.into()) }
unsafe {
if cfg!(feature = "pimoroni2w") {
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) {
// SAFETY: caller guarantees `ptr` is valid for `len` bytes
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) {
let cycles_per_ms = clk_sys_freq() / 1000;
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;
const _: GetMs = get_ms;
const _: GetMsAbi = get_ms;
pub extern "C" fn get_ms() -> u64 {
Instant::now()
.duration_since(unsafe { MS_SINCE_LAUNCH.unwrap() })
.as_millis()
}
const _: DrawIter = draw_iter;
const _: DrawIterAbi = draw_iter;
pub extern "C" fn draw_iter(cpixels: *const CPixel, len: usize) {
// SAFETY: caller guarantees `ptr` is valid for `len` bytes
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();
const _: GetKey = get_key;
const _: GetKeyAbi = get_key;
pub extern "C" fn get_key() -> KeyEventC {
if let Some(event) = unsafe { KEY_CACHE.dequeue() } {
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() {
let len = name.len().min(max_str_len - 1);
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
}
}
@@ -191,7 +177,7 @@ pub extern "C" fn list_dir(
let mut wrote = 0;
sd.access_root_dir(|root| {
if dirs[0].is_empty() && dirs.len() >= 2 {
if dirs[0] == "" && dirs.len() >= 2 {
unsafe {
if dir == "/" {
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 short_name = None;
dir.iterate_dir_lfn(&mut buf, |entry, name| {
if let Some(name) = name
&& (name == dirs[0] || entry.name.to_string().as_str() == dirs[0])
{
if let Some(name) = name {
if name == dirs[0] || entry.name.to_string().as_str() == dirs[0] {
short_name = Some(entry.name.clone());
}
}
})
.expect("Failed to iterate dir");
.unwrap();
if let Some(name) = short_name {
let mut file = dir
.open_file_in_dir(name, embedded_sdmmc::Mode::ReadWriteAppend)
@@ -245,19 +230,9 @@ pub extern "C" fn read_file(
) -> usize {
// SAFETY: caller guarantees `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;
}
let file: Vec<&str> = file.split('/').collect();
// 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;
@@ -265,9 +240,9 @@ pub extern "C" fn read_file(
let sd = guard.as_mut().unwrap();
if !file.is_empty() {
sd.access_root_dir(|root| {
if let Ok(result) = recurse_file(&root, &components[1..count], |file| {
file.seek_from_start(start_from as u32).unwrap_or(());
file.read(buf).unwrap()
if let Ok(result) = recurse_file(&root, &file[1..], |file| {
file.seek_from_start(start_from as u32).unwrap();
file.read(&mut buf).unwrap()
}) {
read = result
};
@@ -276,46 +251,9 @@ pub extern "C" fn read_file(
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;
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: Vec<&str> = file.split('/').collect();
@@ -332,31 +270,3 @@ pub extern "C" fn file_len(str: *const u8, len: usize) -> 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 core::alloc::{GlobalAlloc, Layout};
use core::sync::atomic::Ordering;
use embassy_futures::yield_now;
use embassy_rp::{
Peri,
gpio::{Level, Output},
peripherals::{PIN_13, PIN_14, PIN_15, SPI1},
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 st7365p_lcd::ST7365P;
#[cfg(feature = "psram")]
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<
type DISPLAY = ST7365P<
ExclusiveDevice<Spi<'static, SPI1, Async>, Output<'static>, Delay>,
Output<'static>,
Output<'static>,
@@ -35,21 +29,18 @@ pub static mut FRAMEBUFFER: Option<AtomicFrameBuffer> = None;
fn init_fb() {
unsafe {
#[cfg(feature = "psram")]
{
let slab = HEAP.alloc(Layout::array::<u16>(framebuffer::SIZE).unwrap()) as *mut u16;
FRAMEBUFFER = Some(if cfg!(not(feature = "pimoroni2w")) {
static mut BUF: [u16; framebuffer::SIZE] = [0; framebuffer::SIZE];
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 mut fb = AtomicFrameBuffer::new(buf);
fb.clear(Rgb565::BLACK).unwrap();
FRAMEBUFFER = Some(fb);
}
#[cfg(not(feature = "psram"))]
{
static mut BUF: [u16; framebuffer::SIZE] = [0; framebuffer::SIZE];
FRAMEBUFFER = Some(AtomicFrameBuffer::new(&mut BUF));
}
fb
});
}
}
@@ -58,7 +49,7 @@ pub async fn init_display(
cs: Peri<'static, PIN_13>,
data: Peri<'static, PIN_14>,
reset: Peri<'static, PIN_15>,
) -> Display {
) -> DISPLAY {
init_fb();
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]
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;
pub async fn display_handler(mut display: DISPLAY) {
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) {
unsafe {
FRAMEBUFFER
@@ -109,15 +86,11 @@ pub async fn display_handler(mut display: Display) {
.unwrap()
.partial_draw(&mut display)
.await
.unwrap();
}
.unwrap()
};
}
let elapsed = start.elapsed().as_millis();
if elapsed < FRAME_TIME_MS {
Timer::after_millis(FRAME_TIME_MS - elapsed).await;
} else {
yield_now().await;
}
// small yield to allow other tasks to run
Timer::after_nanos(500).await;
}
}

View File

@@ -1,7 +1,9 @@
use crate::{
abi,
storage::{File, SDCARD},
syscalls,
};
use abi_sys::CallTable;
use abi_sys::EntryFn;
use alloc::{vec, vec::Vec};
use bumpalo::Bump;
use core::ptr;
@@ -16,7 +18,6 @@ use goblin::{
elf32::{header, reloc::Rel, section_header::SectionHeader, sym::Sym},
};
use strum::IntoEnumIterator;
use userlib_sys::{EntryFn, SyscallTable};
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 (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 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 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 {
load_segment(&mut file, &ph, segment).unwrap();
load_segment(&mut file, &ph, &mut segment).unwrap();
}
}
for i in 0..elf_header.e_shnum {
let sh = read_section(&mut file, elf_header, i.into());
if sh.sh_type == SHT_REL {
match sh.sh_type {
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)
let entry_ptr: EntryFn = unsafe {
@@ -147,14 +151,14 @@ fn apply_relocations(
Ok(())
}
fn patch_syscalls(
fn patch_abi(
elf_header: &Header,
base: *mut u8,
min_vaddr: u32,
file: &mut File,
) -> Result<(), ()> {
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
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];
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();
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();
if symbol_name == stringify!(SYS_CALL_TABLE) {
if symbol_name == "CALL_ABI_TABLE" {
let table_base =
unsafe { base.add((sym.st_value as usize) - min_vaddr as usize) }
as *mut usize;
for (idx, call) in SyscallTable::iter().enumerate() {
for (idx, call) in CallTable::iter().enumerate() {
let ptr = match call {
SyscallTable::Alloc => syscalls::alloc as usize,
SyscallTable::Dealloc => syscalls::dealloc as usize,
SyscallTable::PrintString => syscalls::print as usize,
SyscallTable::SleepMs => syscalls::sleep as usize,
SyscallTable::GetMs => syscalls::get_ms as usize,
SyscallTable::DrawIter => syscalls::draw_iter as usize,
SyscallTable::GetKey => syscalls::get_key as usize,
SyscallTable::GenRand => syscalls::gen_rand as usize,
SyscallTable::ListDir => syscalls::list_dir as usize,
SyscallTable::ReadFile => syscalls::read_file as usize,
SyscallTable::WriteFile => syscalls::write_file 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,
CallTable::Alloc => abi::alloc as usize,
CallTable::Dealloc => abi::dealloc as usize,
CallTable::PrintString => abi::print as usize,
CallTable::SleepMs => abi::sleep as usize,
CallTable::GetMs => abi::get_ms as usize,
CallTable::DrawIter => abi::draw_iter as usize,
CallTable::GetKey => abi::get_key as usize,
CallTable::GenRand => abi::gen_rand as usize,
CallTable::ListDir => abi::list_dir as usize,
CallTable::ReadFile => abi::read_file as usize,
CallTable::FileLen => abi::file_len as usize,
};
unsafe {
table_base.add(idx).write(ptr);
table_base.add(idx as usize).write(ptr);
}
}
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)
.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_vaddr < min_vaddr {

View File

@@ -13,9 +13,6 @@ use embedded_hal_2::digital::OutputPin;
use embedded_hal_async::{delay::DelayNs, spi::SpiDevice};
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_COUNT: usize = (SCREEN_WIDTH / TILE_SIZE) * (SCREEN_HEIGHT / TILE_SIZE); // 400 tiles
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) {
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 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;
@@ -133,34 +130,9 @@ impl<'a> AtomicFrameBuffer<'a> {
tile.store(false, Ordering::Release);
}
#[cfg(feature = "fps")]
unsafe {
crate::display::FPS_COUNTER.measure()
}
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
fn append_tiles_to_batch(
&mut self,
@@ -180,45 +152,6 @@ impl<'a> AtomicFrameBuffer<'a> {
&self.fb[fb_row_offset..fb_row_offset + (total_tiles as usize * TILE_SIZE)];
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,
{
if self.should_full_draw() {
#[cfg(feature = "fps")]
self.draw_fps_into_fb();
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 {
let row_start_idx = tile_row * NUM_TILE_COLS;
let mut col = 0;
@@ -261,15 +178,14 @@ impl<'a> AtomicFrameBuffer<'a> {
// Check for dirty tile
if self.dirty_tiles[row_start_idx + col].swap(false, Ordering::Acquire) {
let run_start = col;
let mut scan_col = col;
let mut run_len = 1;
// 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)
&& run_len < MAX_BATCH_TILES
{
scan_col += 1;
col += 1;
run_len += 1;
}
@@ -294,19 +210,12 @@ impl<'a> AtomicFrameBuffer<'a> {
&self.batch_tile_buf[..run_len * TILE_SIZE * TILE_SIZE],
)
.await?;
col = scan_col;
}
col += 1;
}
}
#[cfg(feature = "fps")]
unsafe {
crate::display::FPS_COUNTER.measure()
}
Ok(())
}
}
@@ -349,9 +258,11 @@ impl<'a> DrawTarget for AtomicFrameBuffer<'a> {
}
}
if changed && let Some(rect) = dirty_rect {
if changed {
if let Some(rect) = dirty_rect {
self.mark_tiles_dirty(rect);
}
}
Ok(())
}
@@ -403,7 +314,7 @@ impl<'a> DrawTarget for AtomicFrameBuffer<'a> {
fn fill_solid(&mut self, area: &Rectangle, color: Self::Color) -> Result<(), Self::Error> {
self.fill_contiguous(
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,
self.size().width as u16 - 1,
self.size().height as u16 - 1,
core::iter::repeat_n(
RawU16::from(color).into_inner(),
(self.size().width * self.size().height) as usize,
),
core::iter::repeat(RawU16::from(color).into_inner())
.take((self.size().width * self.size().height) as usize),
)?;
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)
}
}
#[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 {
let start = self.start.load(Ordering::Relaxed);
let end = start + self.size.load(Ordering::Relaxed);
(start..end).contains(&address)
let end = self.start.load(Ordering::Relaxed);
(start..start + end).contains(&address)
}
fn new(start: usize, size: usize) -> Self {

View File

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

View File

@@ -1,5 +1,5 @@
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_FIF: u8 = 0x09;

View File

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

View File

@@ -30,7 +30,7 @@ pub struct MassStorageClass<'d, D: Driver<'d>> {
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 {
let mut function = builder.function(0x08, SUBCLASS_SCSI, 0x50); // Mass Storage class
let mut interface = function.interface();
@@ -72,10 +72,9 @@ impl<'d, D: Driver<'d>> MassStorageClass<'d, D> {
async fn handle_cbw(&mut self) {
let mut cbw_buf = [0u8; 31];
if let Ok(n) = self.bulk_out.read(&mut cbw_buf).await
&& n == 31
&& let Some(cbw) = CommandBlockWrapper::parse(&cbw_buf[..n])
{
if let Ok(n) = self.bulk_out.read(&mut cbw_buf).await {
if n == 31 {
if let Some(cbw) = CommandBlockWrapper::parse(&cbw_buf[..n]) {
// Take sdcard to increase speed
if self.temp_sd.is_none() {
let mut guard = SDCARD.get().lock().await;
@@ -95,13 +94,15 @@ impl<'d, D: Driver<'d>> MassStorageClass<'d, D> {
self.send_csw_fail(cbw.dCBWTag).await
}
if self.pending_eject
&& let ScsiCommand::Write { lba: _, len: _ } = command
{
if self.pending_eject {
if let ScsiCommand::Write { lba: _, len: _ } = command {
MSC_SHUTDOWN.signal(());
}
}
}
}
}
}
async fn handle_command(&mut self, command: ScsiCommand) -> Result<(), ()> {
let mut response: Vec<u8, BULK_ENDPOINT_PACKET_SIZE> = Vec::new();
@@ -237,7 +238,7 @@ impl<'d, D: Driver<'d>> MassStorageClass<'d, D> {
let block_size = SdCard::BLOCK_SIZE as u64;
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(&(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 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(&(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))?;
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(|_| ())?;
}
}
@@ -280,7 +281,7 @@ impl<'d, D: Driver<'d>> MassStorageClass<'d, D> {
.read_blocks(&mut block_buf[..blocks as usize], BlockIdx(idx as u32))?;
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(|_| ())?;
}
}
@@ -300,7 +301,8 @@ impl<'d, D: Driver<'d>> MassStorageClass<'d, D> {
while blocks > 0 {
if blocks >= block_buf.len() as u64 {
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(|_| ())?;
}
}
@@ -311,7 +313,8 @@ impl<'d, D: Driver<'d>> MassStorageClass<'d, D> {
idx += block_buf.len() as u64;
} else {
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(|_| ())?;
}
}

View File

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

View File

@@ -9,8 +9,8 @@ use embassy_sync::mutex::Mutex;
use embassy_time::Delay;
use embedded_hal_bus::spi::ExclusiveDevice;
use embedded_sdmmc::{
Block, BlockDevice, BlockIdx, Directory, SdCard as SdmmcSdCard, TimeSource, Timestamp,
VolumeIdx, VolumeManager, sdcard::Error,
Block, BlockCount, BlockDevice, BlockIdx, Directory, SdCard as SdmmcSdCard, TimeSource,
Timestamp, Volume, VolumeIdx, VolumeManager, sdcard::Error,
};
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 SD = SdmmcSdCard<Device, Delay>;
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 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 long_name: String,
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 {
det: Input<'static>,
volume_mgr: VolMgr,
@@ -66,7 +55,10 @@ impl SdCard {
DummyTimeSource {},
5000,
);
Self { det, volume_mgr }
Self {
det: det,
volume_mgr,
}
}
/// Returns true if an SD card is inserted.
@@ -86,6 +78,17 @@ impl SdCard {
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<(), ()> {
let mut res: Result<(), Error> = Ok(());
self.volume_mgr.device(|sd| {

View File

@@ -2,6 +2,7 @@ use crate::{
BINARY_CH, display::FRAMEBUFFER, elf::load_binary, framebuffer::FB_PAUSED,
peripherals::keyboard, storage::FileName,
};
use abi_sys::keyboard::{KeyCode, KeyState};
use alloc::{str::FromStr, string::String, vec::Vec};
use core::sync::atomic::Ordering;
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex};
@@ -19,16 +20,14 @@ use embedded_layout::{
prelude::*,
};
use embedded_text::TextBox;
use userlib_sys::keyboard::{KeyCode, KeyState};
pub static SELECTIONS: Mutex<CriticalSectionRawMutex, SelectionList> =
Mutex::new(SelectionList::new());
pub async fn ui_handler() {
loop {
if let Some(event) = keyboard::read_keyboard_fifo().await
&& let KeyState::Pressed = event.state
{
if let Some(event) = keyboard::read_keyboard_fifo().await {
if let KeyState::Pressed = event.state {
match event.key {
KeyCode::Up => {
let mut selections = SELECTIONS.lock().await;
@@ -53,6 +52,7 @@ pub async fn ui_handler() {
_ => (),
}
}
}
let changed = SELECTIONS.lock().await.changed;
if changed {
@@ -75,7 +75,7 @@ pub async fn clear_selection() {
async fn draw_selection() {
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 display_area = unsafe { FRAMEBUFFER.as_mut().unwrap().bounding_box() };
@@ -99,7 +99,7 @@ async fn draw_selection() {
} else {
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));
}

Submodule picolibc deleted from d664643068

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,6 +3,13 @@
#![allow(static_mut_refs)]
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 core::panic::PanicInfo;
use embedded_graphics::{
@@ -10,17 +17,10 @@ use embedded_graphics::{
pixelcolor::Rgb565, prelude::*, text::Text,
};
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]
fn panic(info: &PanicInfo) -> ! {
println!("user panic: {} @ {:?}", info.message(), info.location());
print!("user panic: {} @ {:?}", info.message(), info.location());
loop {}
}
@@ -30,9 +30,9 @@ pub extern "Rust" fn _start() {
}
pub fn main() {
println!("Starting Gallery app");
print!("Starting Gallery app");
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_rows = 3;
@@ -49,11 +49,11 @@ pub fn main() {
break; // only draw 3x3
}
println!("file: {}", file);
print!("file: {}", file);
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 {
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_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)
.unwrap();
@@ -85,8 +85,11 @@ pub fn main() {
loop {
let event = get_key();
if event.state != KeyState::Idle && event.key == KeyCode::Esc {
return;
if event.state != KeyState::Idle {
match event.key {
KeyCode::Esc => return,
_ => (),
}
};
}
}

View File

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

View File

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