mirror of
https://github.com/LegitCamper/picocalc-os-rs.git
synced 2025-12-27 15:55:25 +00:00
WIP
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -1291,6 +1291,7 @@ dependencies = [
|
|||||||
"bindgen",
|
"bindgen",
|
||||||
"cc",
|
"cc",
|
||||||
"embedded-graphics",
|
"embedded-graphics",
|
||||||
|
"selection_ui",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
|
|
||||||
extern crate alloc;
|
extern crate alloc;
|
||||||
|
|
||||||
pub use abi_sys::{self, keyboard};
|
|
||||||
use abi_sys::{RngRequest, alloc, dealloc, keyboard::KeyEvent};
|
use abi_sys::{RngRequest, alloc, dealloc, keyboard::KeyEvent};
|
||||||
|
pub use abi_sys::{keyboard, print};
|
||||||
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;
|
||||||
@@ -28,7 +28,7 @@ unsafe impl GlobalAlloc for Alloc {
|
|||||||
macro_rules! print {
|
macro_rules! print {
|
||||||
($($arg:tt)*) => {{
|
($($arg:tt)*) => {{
|
||||||
let s = $crate::format!($($arg)*);
|
let s = $crate::format!($($arg)*);
|
||||||
$crate::abi_sys::print(s.as_ptr(), s.len());
|
$crate::print(s.as_ptr(), s.len());
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,10 +61,8 @@ pub mod display {
|
|||||||
|
|
||||||
pub type Pixel565 = Pixel<Rgb565>;
|
pub type Pixel565 = Pixel<Rgb565>;
|
||||||
|
|
||||||
const BUF_SIZE: usize = 15 * 1024; // tune this for performance
|
const BUF_SIZE: usize = 1024;
|
||||||
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);
|
static DISPLAY_TAKEN: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
@@ -160,20 +158,30 @@ impl RngCore for Rng {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub mod fs {
|
pub mod fs {
|
||||||
|
use alloc::vec::Vec;
|
||||||
use core::fmt::Display;
|
use core::fmt::Display;
|
||||||
|
|
||||||
use alloc::{format, vec::Vec};
|
pub fn read_file(file: &str, start_from: usize, buf: &mut [u8]) -> usize {
|
||||||
|
|
||||||
pub fn read_file(file: &str, read_from: usize, buf: &mut [u8]) -> usize {
|
|
||||||
abi_sys::read_file(
|
abi_sys::read_file(
|
||||||
file.as_ptr(),
|
file.as_ptr(),
|
||||||
file.len(),
|
file.len(),
|
||||||
read_from,
|
start_from,
|
||||||
buf.as_mut_ptr(),
|
buf.as_mut_ptr(),
|
||||||
buf.len(),
|
buf.len(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn write_file(file: &str, start_from: usize, buf: &[u8]) {
|
||||||
|
abi_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,
|
||||||
@@ -217,7 +225,7 @@ 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)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct Entries([[u8; MAX_ENTRY_NAME_LEN]; MAX_ENTRIES]);
|
pub struct Entries([[u8; MAX_ENTRY_NAME_LEN]; MAX_ENTRIES]);
|
||||||
|
|
||||||
impl Entries {
|
impl Entries {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use strum::{EnumCount, EnumIter};
|
|||||||
|
|
||||||
pub type EntryFn = fn();
|
pub type EntryFn = fn();
|
||||||
|
|
||||||
pub const ABI_CALL_TABLE_COUNT: usize = 11;
|
pub const ABI_CALL_TABLE_COUNT: usize = 12;
|
||||||
const _: () = assert!(ABI_CALL_TABLE_COUNT == CallTable::COUNT);
|
const _: () = assert!(ABI_CALL_TABLE_COUNT == CallTable::COUNT);
|
||||||
|
|
||||||
#[derive(Clone, Copy, EnumIter, EnumCount)]
|
#[derive(Clone, Copy, EnumIter, EnumCount)]
|
||||||
@@ -28,7 +28,8 @@ pub enum CallTable {
|
|||||||
GenRand = 7,
|
GenRand = 7,
|
||||||
ListDir = 8,
|
ListDir = 8,
|
||||||
ReadFile = 9,
|
ReadFile = 9,
|
||||||
FileLen = 10,
|
WriteFile = 10,
|
||||||
|
FileLen = 11,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
@@ -438,6 +439,24 @@ pub extern "C" fn read_file(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 = CALL_ABI_TABLE[CallTable::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)]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use abi_sys::{
|
use abi_sys::{
|
||||||
AllocAbi, CLayout, CPixel, DeallocAbi, DrawIterAbi, FileLen, GenRand, GetMsAbi, ListDir,
|
AllocAbi, CLayout, CPixel, DeallocAbi, DrawIterAbi, FileLen, GenRand, GetMsAbi, ListDir,
|
||||||
PrintAbi, ReadFile, RngRequest, SleepMsAbi, keyboard::*,
|
PrintAbi, ReadFile, RngRequest, SleepMsAbi, WriteFile, keyboard::*,
|
||||||
};
|
};
|
||||||
use alloc::{string::ToString, vec::Vec};
|
use alloc::{string::ToString, vec::Vec};
|
||||||
use core::{ffi::c_char, ptr, sync::atomic::Ordering};
|
use core::{ffi::c_char, ptr, sync::atomic::Ordering};
|
||||||
@@ -207,6 +207,7 @@ fn recurse_file<T>(
|
|||||||
dirs: &[&str],
|
dirs: &[&str],
|
||||||
mut access: impl FnMut(&mut File) -> T,
|
mut access: impl FnMut(&mut File) -> T,
|
||||||
) -> Result<T, ()> {
|
) -> Result<T, ()> {
|
||||||
|
defmt::info!("dir: {}, dirs: {}", dir, dirs);
|
||||||
if dirs.len() == 1 {
|
if dirs.len() == 1 {
|
||||||
let mut b = [0_u8; 50];
|
let mut b = [0_u8; 50];
|
||||||
let mut buf = LfnBuffer::new(&mut b);
|
let mut buf = LfnBuffer::new(&mut b);
|
||||||
@@ -218,7 +219,8 @@ fn recurse_file<T>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.unwrap();
|
.expect("Failed to iterate dir");
|
||||||
|
|
||||||
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)
|
||||||
@@ -242,7 +244,17 @@ 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 mut buf = unsafe { core::slice::from_raw_parts_mut(buf, buf_len) };
|
let mut buf = unsafe { core::slice::from_raw_parts_mut(buf, buf_len) };
|
||||||
|
|
||||||
@@ -252,8 +264,8 @@ 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, &file[1..], |file| {
|
if let Ok(result) = recurse_file(&root, &components[1..count], |file| {
|
||||||
file.seek_from_start(start_from as u32).unwrap();
|
file.seek_from_start(start_from as u32).unwrap_or(());
|
||||||
file.read(&mut buf).unwrap()
|
file.read(&mut buf).unwrap()
|
||||||
}) {
|
}) {
|
||||||
read = result
|
read = result
|
||||||
@@ -263,9 +275,46 @@ 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 `ptr` is valid for `len` bytes
|
// SAFETY: caller guarantees str 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();
|
||||||
|
|
||||||
|
|||||||
@@ -87,8 +87,14 @@ 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 {
|
||||||
// renders fps text to canvas
|
let start = Instant::now();
|
||||||
|
|
||||||
#[cfg(feature = "fps")]
|
#[cfg(feature = "fps")]
|
||||||
unsafe {
|
unsafe {
|
||||||
if FPS_COUNTER.should_draw() {
|
if FPS_COUNTER.should_draw() {
|
||||||
@@ -103,11 +109,15 @@ pub async fn display_handler(mut display: DISPLAY) {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.partial_draw(&mut display)
|
.partial_draw(&mut display)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap();
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// small yield to allow other tasks to run
|
let elapsed = start.elapsed().as_millis() as u64;
|
||||||
Timer::after_millis(10).await;
|
if elapsed < FRAME_TIME_MS {
|
||||||
|
Timer::after_millis(FRAME_TIME_MS - elapsed).await;
|
||||||
|
} else {
|
||||||
|
Timer::after_millis(1).await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -206,6 +206,7 @@ fn patch_abi(
|
|||||||
CallTable::GenRand => abi::gen_rand as usize,
|
CallTable::GenRand => abi::gen_rand as usize,
|
||||||
CallTable::ListDir => abi::list_dir as usize,
|
CallTable::ListDir => abi::list_dir as usize,
|
||||||
CallTable::ReadFile => abi::read_file as usize,
|
CallTable::ReadFile => abi::read_file as usize,
|
||||||
|
CallTable::WriteFile => abi::write_file as usize,
|
||||||
CallTable::FileLen => abi::file_len as usize,
|
CallTable::FileLen => abi::file_len as usize,
|
||||||
};
|
};
|
||||||
unsafe {
|
unsafe {
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ static UI_CHANGE: Signal<CriticalSectionRawMutex, ()> = Signal::new();
|
|||||||
#[embassy_executor::main]
|
#[embassy_executor::main]
|
||||||
async fn main(_spawner: Spawner) {
|
async fn main(_spawner: Spawner) {
|
||||||
let p = if cfg!(feature = "overclock") {
|
let p = if cfg!(feature = "overclock") {
|
||||||
let clocks = ClockConfig::system_freq(192_000_000).unwrap();
|
let clocks = ClockConfig::system_freq(300_000_000).unwrap();
|
||||||
let config = Config::new(clocks);
|
let config = Config::new(clocks);
|
||||||
embassy_rp::init(config)
|
embassy_rp::init(config)
|
||||||
} else {
|
} else {
|
||||||
@@ -339,6 +339,9 @@ async fn kernel_task(
|
|||||||
.spawn(watchdog_task(Watchdog::new(watchdog)))
|
.spawn(watchdog_task(Watchdog::new(watchdog)))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
#[cfg(feature = "debug")]
|
||||||
|
defmt::info!("Clock: {}", embassy_rp::clocks::clk_sys_freq());
|
||||||
|
|
||||||
setup_mcu(mcu).await;
|
setup_mcu(mcu).await;
|
||||||
|
|
||||||
#[cfg(feature = "defmt")]
|
#[cfg(feature = "defmt")]
|
||||||
@@ -372,7 +375,8 @@ 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 files = sd.list_files_by_extension(".bin").unwrap();
|
let mut 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 {
|
||||||
@@ -387,12 +391,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 {
|
||||||
if let KeyState::Pressed = event.state {
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let _ = KEY_CACHE.enqueue(event);
|
let _ = KEY_CACHE.enqueue(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Timer::after_millis(50).await;
|
Timer::after_millis(50).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,12 +35,24 @@ impl TimeSource for DummyTimeSource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq, Eq)]
|
||||||
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.long_name.cmp(&other.long_name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,6 +55,9 @@ impl<'a> SelectionUi<'a> {
|
|||||||
if key.state == KeyState::Pressed {
|
if key.state == KeyState::Pressed {
|
||||||
if let Some(s) = self.update(display, key.key)? {
|
if let Some(s) = self.update(display, key.key)? {
|
||||||
selection = Some(s);
|
selection = Some(s);
|
||||||
|
display
|
||||||
|
.clear(Rgb565::BLACK)
|
||||||
|
.map_err(|e| SelectionUiError::DisplayError(e))?;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -69,7 +72,6 @@ impl<'a> SelectionUi<'a> {
|
|||||||
display: &mut Display,
|
display: &mut Display,
|
||||||
key: KeyCode,
|
key: KeyCode,
|
||||||
) -> Result<Option<usize>, SelectionUiError<<Display as DrawTarget>::Error>> {
|
) -> Result<Option<usize>, SelectionUiError<<Display as DrawTarget>::Error>> {
|
||||||
print!("Got Key: {:?}", key);
|
|
||||||
match key {
|
match key {
|
||||||
KeyCode::Down => {
|
KeyCode::Down => {
|
||||||
self.selection = (self.selection + 1).min(self.items.len() - 1);
|
self.selection = (self.selection + 1).min(self.items.len() - 1);
|
||||||
@@ -80,7 +82,6 @@ impl<'a> SelectionUi<'a> {
|
|||||||
KeyCode::Enter | KeyCode::Right => return Ok(Some(self.selection)),
|
KeyCode::Enter | KeyCode::Right => return Ok(Some(self.selection)),
|
||||||
_ => return Ok(None),
|
_ => return Ok(None),
|
||||||
};
|
};
|
||||||
print!("new selection: {:?}", self.selection);
|
|
||||||
self.draw(display)?;
|
self.draw(display)?;
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ pub fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let event = get_key();
|
let event = get_key();
|
||||||
if event.state != KeyState::Idle {
|
if event.state == KeyState::Released {
|
||||||
match event.key {
|
match event.key {
|
||||||
KeyCode::Char(ch) => {
|
KeyCode::Char(ch) => {
|
||||||
input.push(ch);
|
input.push(ch);
|
||||||
|
|||||||
@@ -9,4 +9,5 @@ cc = "1.2.44"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
abi = { path = "../../abi" }
|
abi = { path = "../../abi" }
|
||||||
|
selection_ui = { path = "../../selection_ui" }
|
||||||
embedded-graphics = "0.8.1"
|
embedded-graphics = "0.8.1"
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ fn bindgen() {
|
|||||||
.expect("Couldn't write bindings!");
|
.expect("Couldn't write bindings!");
|
||||||
|
|
||||||
cc::Build::new()
|
cc::Build::new()
|
||||||
|
.define("PEANUT_GB_IS_LITTLE_ENDIAN", None)
|
||||||
.file("peanut_gb_stub.c")
|
.file("peanut_gb_stub.c")
|
||||||
.include("Peanut-GB")
|
.include("Peanut-GB")
|
||||||
// optimization flags
|
// optimization flags
|
||||||
|
|||||||
@@ -5,24 +5,34 @@
|
|||||||
extern crate alloc;
|
extern crate alloc;
|
||||||
use abi::{
|
use abi::{
|
||||||
display::Display,
|
display::Display,
|
||||||
fs::{file_len, read_file},
|
format,
|
||||||
|
fs::{Entries, file_len, list_dir, read_file, write_file},
|
||||||
get_key,
|
get_key,
|
||||||
keyboard::{KeyCode, KeyState},
|
keyboard::{KeyCode, KeyState},
|
||||||
print,
|
print,
|
||||||
};
|
};
|
||||||
use alloc::{vec, vec::Vec};
|
use alloc::{string::String, vec, vec::Vec};
|
||||||
use core::{ffi::c_void, mem::MaybeUninit, panic::PanicInfo};
|
use core::{cell::LazyCell, ffi::c_void, mem::MaybeUninit, panic::PanicInfo};
|
||||||
|
use embedded_graphics::{
|
||||||
|
mono_font::{MonoTextStyle, ascii::FONT_6X10},
|
||||||
|
pixelcolor::Rgb565,
|
||||||
|
prelude::RgbColor,
|
||||||
|
};
|
||||||
|
use selection_ui::{SelectionUi, SelectionUiError, draw_text_center};
|
||||||
|
|
||||||
mod peanut;
|
mod peanut;
|
||||||
use peanut::gb_run_frame;
|
use peanut::gb_run_frame;
|
||||||
|
|
||||||
use crate::peanut::{
|
use crate::peanut::{
|
||||||
JOYPAD_A, JOYPAD_B, JOYPAD_DOWN, JOYPAD_LEFT, JOYPAD_RIGHT, JOYPAD_SELECT, JOYPAD_START,
|
JOYPAD_A, JOYPAD_B, JOYPAD_DOWN, JOYPAD_LEFT, JOYPAD_RIGHT, JOYPAD_SELECT, JOYPAD_START,
|
||||||
JOYPAD_UP, gb_cart_ram_read, gb_cart_ram_write, gb_error, gb_init, gb_init_lcd, gb_rom_read,
|
JOYPAD_UP, gb_cart_ram_read, gb_cart_ram_write, gb_error, gb_get_rom_name, gb_get_save_size,
|
||||||
gb_s, lcd_draw_line,
|
gb_init, gb_init_lcd, gb_rom_read, gb_s, lcd_draw_line,
|
||||||
};
|
};
|
||||||
|
|
||||||
static mut DISPLAY: Display = Display;
|
static mut DISPLAY: LazyCell<Display> = LazyCell::new(|| Display::take().unwrap());
|
||||||
|
|
||||||
|
const RAM_SIZE: usize = 32 * 1024; // largest ram size is 32k
|
||||||
|
static mut RAM: [u8; RAM_SIZE] = [0; RAM_SIZE];
|
||||||
|
|
||||||
#[panic_handler]
|
#[panic_handler]
|
||||||
fn panic(info: &PanicInfo) -> ! {
|
fn panic(info: &PanicInfo) -> ! {
|
||||||
@@ -35,7 +45,7 @@ pub extern "Rust" fn _start() {
|
|||||||
main()
|
main()
|
||||||
}
|
}
|
||||||
|
|
||||||
const GAME: &'static str = "/games/gameboy/zelda.gb";
|
const GAME_PATH: &'static str = "/games/gameboy";
|
||||||
|
|
||||||
static mut GAME_ROM: Option<Vec<u8>> = None;
|
static mut GAME_ROM: Option<Vec<u8>> = None;
|
||||||
|
|
||||||
@@ -45,9 +55,43 @@ struct Priv {}
|
|||||||
pub fn main() {
|
pub fn main() {
|
||||||
print!("Starting Gameboy app");
|
print!("Starting Gameboy app");
|
||||||
|
|
||||||
let size = file_len(GAME);
|
let mut entries = Entries::new();
|
||||||
|
list_dir(GAME_PATH, &mut entries);
|
||||||
|
|
||||||
|
let mut files = entries.entries();
|
||||||
|
files.retain(|e| {
|
||||||
|
let ext = e.extension().unwrap_or("");
|
||||||
|
ext == "gb" || ext == "GB"
|
||||||
|
});
|
||||||
|
let mut roms = files.iter().map(|e| e.full_name()).collect::<Vec<&str>>();
|
||||||
|
roms.sort();
|
||||||
|
|
||||||
|
let selection = {
|
||||||
|
let display = unsafe { &mut *DISPLAY };
|
||||||
|
let mut selection_ui = SelectionUi::new(&roms);
|
||||||
|
match selection_ui.run_selection_ui(display) {
|
||||||
|
Ok(maybe_sel) => maybe_sel,
|
||||||
|
Err(e) => match e {
|
||||||
|
SelectionUiError::SelectionListEmpty => {
|
||||||
|
draw_text_center(
|
||||||
|
display,
|
||||||
|
&format!("No Roms were found in {}", GAME_PATH),
|
||||||
|
MonoTextStyle::new(&FONT_6X10, Rgb565::RED),
|
||||||
|
)
|
||||||
|
.expect("Display Error");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
SelectionUiError::DisplayError(_) => panic!("Display Error"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(selection.is_some());
|
||||||
|
|
||||||
|
let file_name = format!("{}/{}", GAME_PATH, roms[selection.unwrap()]);
|
||||||
|
let size = file_len(&file_name);
|
||||||
unsafe { GAME_ROM = Some(vec![0_u8; size]) };
|
unsafe { GAME_ROM = Some(vec![0_u8; size]) };
|
||||||
let read = read_file(GAME, 0, unsafe { GAME_ROM.as_mut().unwrap() });
|
let read = read_file(&file_name, 0, unsafe { GAME_ROM.as_mut().unwrap() });
|
||||||
assert!(size == read);
|
assert!(size == read);
|
||||||
print!("Rom size: {}", read);
|
print!("Rom size: {}", read);
|
||||||
|
|
||||||
@@ -66,40 +110,92 @@ pub fn main() {
|
|||||||
};
|
};
|
||||||
print!("gb init status: {}", init_status);
|
print!("gb init status: {}", init_status);
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
load_save(&mut gb.assume_init());
|
||||||
|
}
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
gb_init_lcd(gb.as_mut_ptr(), Some(lcd_draw_line));
|
gb_init_lcd(gb.as_mut_ptr(), Some(lcd_draw_line));
|
||||||
|
|
||||||
// enable frame skip
|
// enable frame skip
|
||||||
gb.assume_init().direct.set_frame_skip(true);
|
gb.assume_init().direct.set_frame_skip(!true); // active low
|
||||||
};
|
};
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let event = get_key();
|
let event = get_key();
|
||||||
let keycode = match event.key {
|
let button = match event.key {
|
||||||
KeyCode::Esc => break,
|
KeyCode::Esc => {
|
||||||
KeyCode::Tab => Some(JOYPAD_START),
|
unsafe { write_save(&mut gb.assume_init()) };
|
||||||
KeyCode::Del => Some(JOYPAD_SELECT),
|
break;
|
||||||
KeyCode::Enter => Some(JOYPAD_A),
|
}
|
||||||
KeyCode::Backspace => Some(JOYPAD_B),
|
KeyCode::Tab => JOYPAD_START as u8,
|
||||||
KeyCode::JoyUp => Some(JOYPAD_UP),
|
KeyCode::Del => JOYPAD_SELECT as u8,
|
||||||
KeyCode::JoyDown => Some(JOYPAD_DOWN),
|
KeyCode::Enter => JOYPAD_A as u8,
|
||||||
KeyCode::JoyLeft => Some(JOYPAD_LEFT),
|
KeyCode::Backspace => JOYPAD_B as u8,
|
||||||
KeyCode::JoyRight => Some(JOYPAD_RIGHT),
|
KeyCode::Up => JOYPAD_UP as u8,
|
||||||
_ => None,
|
KeyCode::Down => JOYPAD_DOWN as u8,
|
||||||
|
KeyCode::Left => JOYPAD_LEFT as u8,
|
||||||
|
KeyCode::Right => JOYPAD_RIGHT as u8,
|
||||||
|
_ => 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(keycode) = keycode {
|
if button != 0 {
|
||||||
|
let mut joypad = unsafe { (*gb.as_mut_ptr()).direct.__bindgen_anon_1.joypad };
|
||||||
match event.state {
|
match event.state {
|
||||||
KeyState::Pressed => unsafe {
|
KeyState::Pressed => joypad &= !button,
|
||||||
(*gb.as_mut_ptr()).direct.__bindgen_anon_1.joypad &= !keycode as u8
|
KeyState::Released => joypad |= button,
|
||||||
},
|
_ => {}
|
||||||
KeyState::Released => unsafe {
|
}
|
||||||
(*gb.as_mut_ptr()).direct.__bindgen_anon_1.joypad |= keycode as u8
|
|
||||||
},
|
print!("joypad now: {:#010b}\n", joypad);
|
||||||
_ => (),
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
gb_run_frame(gb.as_mut_ptr());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe { gb_run_frame(gb.as_mut_ptr()) };
|
unsafe fn load_save(gb: &mut gb_s) {
|
||||||
|
let mut buf = [0; 16];
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
gb_get_rom_name(gb, buf.as_mut_ptr());
|
||||||
|
|
||||||
|
let save_size = gb_get_save_size(gb);
|
||||||
|
|
||||||
|
if save_size > 0 {
|
||||||
|
read_file(
|
||||||
|
&format!(
|
||||||
|
"{}/saves/{}.sav",
|
||||||
|
GAME_PATH,
|
||||||
|
str::from_utf8(&buf).expect("bad rom name")
|
||||||
|
),
|
||||||
|
0,
|
||||||
|
&mut RAM,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn write_save(gb: &mut gb_s) {
|
||||||
|
let mut buf = [0; 16];
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
gb_get_rom_name(gb, buf.as_mut_ptr());
|
||||||
|
|
||||||
|
let save_size = gb_get_save_size(gb);
|
||||||
|
|
||||||
|
if save_size > 0 {
|
||||||
|
write_file(
|
||||||
|
&format!(
|
||||||
|
"{}/saves/{}.sav",
|
||||||
|
GAME_PATH,
|
||||||
|
str::from_utf8(&buf).expect("bad rom name")
|
||||||
|
),
|
||||||
|
0,
|
||||||
|
&mut RAM,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,27 +2,42 @@
|
|||||||
#![allow(non_camel_case_types)]
|
#![allow(non_camel_case_types)]
|
||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
use crate::{DISPLAY, GAME_ROM};
|
use crate::{DISPLAY, GAME_ROM, RAM};
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
|
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
|
||||||
|
|
||||||
use abi::{display::Pixel565, fs::read_file};
|
use abi::{display::Pixel565, print};
|
||||||
use embedded_graphics::{Drawable, pixelcolor::Rgb565, prelude::Point};
|
use embedded_graphics::{Drawable, pixelcolor::Rgb565, prelude::Point};
|
||||||
|
|
||||||
pub const GBOY_WIDTH: usize = 160;
|
pub const GBOY_WIDTH: usize = 160;
|
||||||
pub const GBOY_HEIGHT: usize = 144;
|
pub const GBOY_HEIGHT: usize = 144;
|
||||||
|
|
||||||
pub unsafe extern "C" fn gb_rom_read(gb: *mut gb_s, addr: u32) -> u8 {
|
pub unsafe extern "C" fn gb_rom_read(_gb: *mut gb_s, addr: u32) -> u8 {
|
||||||
unsafe { GAME_ROM.as_ref().unwrap()[addr as usize] }
|
unsafe { GAME_ROM.as_ref().unwrap()[addr as usize] }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub unsafe extern "C" fn gb_cart_ram_read(gb: *mut gb_s, addr: u32) -> u8 {
|
pub unsafe extern "C" fn gb_cart_ram_read(_gb: *mut gb_s, addr: u32) -> u8 {
|
||||||
0
|
unsafe { RAM[addr as usize] }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub unsafe extern "C" fn gb_cart_ram_write(gb: *mut gb_s, addr: u32, val: u8) {}
|
pub unsafe extern "C" fn gb_cart_ram_write(_gb: *mut gb_s, addr: u32, val: u8) {
|
||||||
|
unsafe { RAM[addr as usize] = val }
|
||||||
|
}
|
||||||
|
|
||||||
pub unsafe extern "C" fn gb_error(gb: *mut gb_s, err: gb_error_e, addr: u16) {}
|
pub unsafe extern "C" fn gb_error(_gb: *mut gb_s, err: gb_error_e, addr: u16) {
|
||||||
|
let e = match err {
|
||||||
|
0 => "UNKNOWN ERROR",
|
||||||
|
1 => "INVALID OPCODE",
|
||||||
|
2 => "INVALID READ",
|
||||||
|
3 => "INVALID WRITE",
|
||||||
|
4 => "HALT FOREVER",
|
||||||
|
5 => "INVALID MAX",
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
print!("PeanutGB error: {}, addr: {}", e, addr);
|
||||||
|
}
|
||||||
|
|
||||||
const NUM_PALETTES: usize = 3;
|
const NUM_PALETTES: usize = 3;
|
||||||
const SHADES_PER_PALETTE: usize = 4;
|
const SHADES_PER_PALETTE: usize = 4;
|
||||||
@@ -82,6 +97,6 @@ fn draw_color(color: Rgb565, x: u16, y: u16) {
|
|||||||
pixel.1 = color;
|
pixel.1 = color;
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
pixel.draw(&mut DISPLAY).unwrap();
|
pixel.draw(&mut *DISPLAY).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,6 @@ edition = "2024"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
abi = { path = "../../abi" }
|
abi = { path = "../../abi" }
|
||||||
embedded-graphics = "0.8.1"
|
|
||||||
selection_ui = { path = "../../selection_ui" }
|
selection_ui = { path = "../../selection_ui" }
|
||||||
|
embedded-graphics = "0.8.1"
|
||||||
tinygif = { git = "https://github.com/LegitCamper/tinygif" }
|
tinygif = { git = "https://github.com/LegitCamper/tinygif" }
|
||||||
|
|||||||
@@ -41,9 +41,10 @@ pub fn main() {
|
|||||||
|
|
||||||
let mut files = entries.entries();
|
let mut files = entries.entries();
|
||||||
files.retain(|e| e.extension().unwrap_or("") == "gif");
|
files.retain(|e| e.extension().unwrap_or("") == "gif");
|
||||||
let gifs = &files.iter().map(|e| e.full_name()).collect::<Vec<&str>>();
|
let mut gifs = files.iter().map(|e| e.full_name()).collect::<Vec<&str>>();
|
||||||
|
gifs.sort();
|
||||||
|
|
||||||
let mut selection_ui = SelectionUi::new(&gifs);
|
let mut selection_ui = SelectionUi::new(&mut gifs);
|
||||||
let selection = match selection_ui.run_selection_ui(&mut display) {
|
let selection = match selection_ui.run_selection_ui(&mut display) {
|
||||||
Ok(maybe_sel) => maybe_sel,
|
Ok(maybe_sel) => maybe_sel,
|
||||||
Err(e) => match e {
|
Err(e) => match e {
|
||||||
|
|||||||
Reference in New Issue
Block a user