This commit is contained in:
2025-11-05 11:00:05 -07:00
parent 513042b561
commit 4ec5afbc65
4 changed files with 201 additions and 100 deletions

View File

@@ -24,20 +24,19 @@ mod usb;
mod utils;
#[cfg(feature = "pimoroni2w")]
use crate::{heap::init_qmi_psram_heap, psram::init_psram_qmi};
use crate::{heap::HEAP, heap::init_qmi_psram_heap, psram::init_psram_qmi};
use crate::{
abi::{KEY_CACHE, MS_SINCE_LAUNCH},
display::{FRAMEBUFFER, display_handler, init_display},
heap::HEAP,
peripherals::{
conf_peripherals,
keyboard::{KeyState, read_keyboard_fifo},
},
psram::init_psram,
scsi::MSC_SHUTDOWN,
storage::{SDCARD, SdCard},
ui::{SELECTIONS, clear_selection, ui_handler},
usb::usb_handler,
};
use abi_sys::EntryFn;
use bumpalo::Bump;
@@ -188,8 +187,6 @@ async fn userland_task() {
UI_CHANGE.signal(());
clear_selection().await;
MSC_SHUTDOWN.signal(());
}
unsafe { MS_SINCE_LAUNCH = Some(Instant::now()) };
@@ -270,6 +267,7 @@ 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
#[allow(unused)]
async fn setup_psram(psram: Psram) {
let psram = init_psram(
psram.pio, psram.sclk, psram.mosi, psram.miso, psram.cs, psram.dma1, psram.dma2,
@@ -322,7 +320,7 @@ async fn kernel_task(
watchdog: Peri<'static, WATCHDOG>,
display: Display,
sd: Sd,
psram: Psram,
_psram: Psram,
mcu: Mcu,
usb: Peri<'static, USB>,
) {
@@ -343,37 +341,19 @@ async fn kernel_task(
setup_display(display, spawner).await;
setup_sd(sd).await;
let _usb = embassy_rp_usb::Driver::new(usb, Irqs);
// spawner.spawn(usb_handler(usb)).unwrap();
let usb_driver = embassy_rp_usb::Driver::new(usb, Irqs);
spawner.spawn(usb_handler(usb_driver)).unwrap();
loop {
let ui_enabled = ENABLE_UI.load(Ordering::Relaxed);
if ui_enabled {
select(join(ui_handler(), prog_search_handler()), UI_CHANGE.wait()).await;
select(ui_handler(), UI_CHANGE.wait()).await;
} else {
select(key_handler(), UI_CHANGE.wait()).await;
}
}
}
async fn prog_search_handler() {
loop {
{
let mut guard = SDCARD.get().lock().await;
let sd = guard.as_mut().unwrap();
let files = sd.list_files_by_extension(".bin").unwrap();
let mut select = SELECTIONS.lock().await;
if *select.selections() != files {
select.update_selections(files);
select.reset();
}
}
Timer::after_secs(5).await;
}
}
async fn key_handler() {
loop {
if let Some(event) = read_keyboard_fifo().await {

View File

@@ -1,7 +1,5 @@
use embassy_futures::select::select;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use core::sync::atomic::{AtomicBool, Ordering};
use embassy_sync::lazy_lock::LazyLock;
use embassy_sync::signal::Signal;
use embassy_usb::Builder;
use embassy_usb::driver::{Driver, EndpointIn, EndpointOut};
use embedded_sdmmc::{Block, BlockIdx};
@@ -11,10 +9,13 @@ mod scsi_types;
use scsi_types::*;
use crate::storage::{SDCARD, SdCard};
use crate::usb::stop_usb;
const BULK_ENDPOINT_PACKET_SIZE: usize = 64;
pub static MSC_SHUTDOWN: Signal<CriticalSectionRawMutex, ()> = Signal::new();
// indicates that a scsi transaction is occurring and is
// NOT safe to disable usb, or acquire sdcard
static SCSI_BUSY: AtomicBool = AtomicBool::new(false);
// number of blocks to read from sd at once
// higher is better, but is larger. Size is BLOCKS * 512 bytes
@@ -24,7 +25,6 @@ static mut BLOCK_BUF: LazyLock<[Block; BLOCKS]> =
pub struct MassStorageClass<'d, D: Driver<'d>> {
temp_sd: Option<SdCard>, // temporary owns sdcard when scsi is running
ejected: bool,
pending_eject: bool,
bulk_out: D::EndpointOut,
bulk_in: D::EndpointIn,
@@ -42,7 +42,6 @@ impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, D> {
Self {
temp_sd,
pending_eject: false,
ejected: false,
bulk_out,
bulk_in,
}
@@ -50,22 +49,11 @@ impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, D> {
pub async fn poll(&mut self) {
loop {
if !self.ejected {
select(self.handle_cbw(), MSC_SHUTDOWN.wait()).await;
self.handle_cbw().await;
if MSC_SHUTDOWN.signaled() {
#[cfg(feature = "defmt")]
defmt::info!("MSC shutting down");
if self.temp_sd.is_some() {
let mut guard = SDCARD.get().lock().await;
guard.replace(self.temp_sd.take().unwrap()).unwrap();
}
self.ejected = true;
return;
}
if self.temp_sd.is_some() {
let mut guard = SDCARD.get().lock().await;
guard.replace(self.temp_sd.take().unwrap()).unwrap();
}
}
}
@@ -75,15 +63,15 @@ impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, D> {
if let Ok(n) = self.bulk_out.read(&mut cbw_buf).await {
if n == 31 {
if let Some(cbw) = CommandBlockWrapper::parse(&cbw_buf[..n]) {
SCSI_BUSY.store(true, Ordering::Release);
// Take sdcard to increase speed
if self.temp_sd.is_none() {
let mut guard = SDCARD.get().lock().await;
if let Some(sd) = guard.take() {
self.temp_sd = Some(sd);
} else {
#[cfg(feature = "defmt")]
defmt::warn!("Tried to take SDCARD but it was already taken");
return;
panic!("Tried to take SDCARD but it was already taken");
}
}
@@ -94,9 +82,11 @@ impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, D> {
self.send_csw_fail(cbw.dCBWTag).await
}
SCSI_BUSY.store(false, Ordering::Release);
if self.pending_eject {
if let ScsiCommand::Write { lba: _, len: _ } = command {
MSC_SHUTDOWN.signal(());
stop_usb();
}
}
}

View File

@@ -1,18 +1,24 @@
use crate::{
BINARY_CH, display::FRAMEBUFFER, elf::load_binary, framebuffer::FB_PAUSED,
peripherals::keyboard, storage::FileName,
BINARY_CH,
display::FRAMEBUFFER,
elf::load_binary,
framebuffer::FB_PAUSED,
peripherals::keyboard,
storage::{FileName, SDCARD},
usb::{USB_ACTIVE, start_usb, stop_usb},
};
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};
use embassy_time::Timer;
use embedded_graphics::{
Drawable,
mono_font::{MonoTextStyle, ascii::FONT_10X20},
pixelcolor::Rgb565,
prelude::{Dimensions, Point, Primitive, RgbColor, Size},
primitives::{PrimitiveStyle, Rectangle},
text::Text,
text::{Alignment, Text},
};
use embedded_layout::{
align::{horizontal, vertical},
@@ -25,55 +31,149 @@ 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 {
if let KeyState::Pressed = event.state {
match event.key {
KeyCode::Up => {
let mut selections = SELECTIONS.lock().await;
selections.up();
}
KeyCode::Down => {
let mut selections = SELECTIONS.lock().await;
selections.down();
}
KeyCode::Enter | KeyCode::Right => {
let selections = SELECTIONS.lock().await;
let selection =
selections.selections[selections.current_selection as usize].clone();
update_selections().await;
let mut scsi = Scsi { last_bounds: None };
let mut scsi_button = ScsiButton { last_bounds: None };
let entry = unsafe {
load_binary(&selection.short_name)
.await
.expect("unable to load binary")
};
BINARY_CH.send(entry).await;
}
_ => (),
}
loop {
input_handler().await;
if USB_ACTIVE.load(Ordering::Acquire) {
scsi.draw(&mut scsi_button).await;
Timer::after_millis(100).await;
} else {
let changed = SELECTIONS.lock().await.changed;
if changed {
clear_selection().await;
draw_selection(&mut scsi_button).await;
}
}
}
}
let changed = SELECTIONS.lock().await.changed;
if changed {
clear_selection().await;
draw_selection().await;
async fn input_handler() {
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;
selections.up();
}
KeyCode::Down => {
let mut selections = SELECTIONS.lock().await;
selections.down();
}
KeyCode::F1 => {
match USB_ACTIVE.load(Ordering::Acquire) {
true => stop_usb(),
false => start_usb(),
};
update_selections().await;
}
KeyCode::Enter | KeyCode::Right => {
stop_usb();
let selections = SELECTIONS.lock().await;
let selection =
selections.selections[selections.current_selection as usize].clone();
let entry = unsafe {
load_binary(&selection.short_name)
.await
.expect("unable to load binary")
};
BINARY_CH.send(entry).await;
}
_ => (),
}
}
}
}
async fn clear_rect(rect: Rectangle) {
Rectangle::new(rect.top_left, rect.size)
.into_styled(PrimitiveStyle::with_fill(Rgb565::BLACK))
.draw(unsafe { &mut *FRAMEBUFFER.as_mut().unwrap() })
.unwrap();
}
async fn update_selections() {
let mut guard = SDCARD.get().lock().await;
let sd = guard.as_mut().unwrap();
let files = sd.list_files_by_extension(".bin").unwrap();
let mut select = SELECTIONS.lock().await;
if *select.selections() != files {
select.update_selections(files);
select.reset();
}
}
struct Scsi {
last_bounds: Option<Rectangle>,
}
impl Scsi {
async fn draw(&mut self, scsi_button: &mut ScsiButton) {
if let Some(rect) = self.last_bounds {
clear_rect(rect).await
}
let text_style = MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE);
let text = Text::with_alignment(
"Usb Mass Storage active",
unsafe { FRAMEBUFFER.as_ref().unwrap() }
.bounding_box()
.center(),
text_style,
Alignment::Center,
);
self.last_bounds = Some(text.bounds());
text.draw(unsafe { &mut *FRAMEBUFFER.as_mut().unwrap() })
.unwrap();
scsi_button.draw().await;
}
}
struct ScsiButton {
last_bounds: Option<Rectangle>,
}
impl ScsiButton {
async fn draw(&mut self) {
if let Some(rect) = self.last_bounds {
clear_rect(rect).await
}
let text_style = MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE);
let text = Text::with_alignment(
"Press F1 to enable/disable mass storage",
unsafe { FRAMEBUFFER.as_ref().unwrap() }
.bounding_box()
.bottom_right()
.unwrap(),
text_style,
Alignment::Left,
);
self.last_bounds = Some(text.bounds());
text.draw(unsafe { &mut *FRAMEBUFFER.as_mut().unwrap() })
.unwrap();
}
}
pub async fn clear_selection() {
let sel = SELECTIONS.lock().await;
if let Some(area) = sel.last_bounds {
Rectangle::new(area.top_left, area.size)
.into_styled(PrimitiveStyle::with_fill(Rgb565::BLACK))
.draw(unsafe { &mut *FRAMEBUFFER.as_mut().unwrap() })
.unwrap();
if let Some(rect) = sel.last_bounds {
clear_rect(rect).await
}
}
async fn draw_selection() {
async fn draw_selection(scsi_button: &mut ScsiButton) {
let mut guard = SELECTIONS.lock().await;
let file_names = &guard.selections.clone();
@@ -130,6 +230,7 @@ async fn draw_selection() {
}
guard.changed = false;
scsi_button.draw().await;
FB_PAUSED.store(false, Ordering::Release); // ensure all elements show up at once
}

View File

@@ -1,9 +1,16 @@
use crate::{scsi::MassStorageClass, storage::SdCard};
use core::sync::atomic::{AtomicBool, Ordering};
use embassy_futures::select::select;
use crate::{scsi::MassStorageClass, storage::SdCard};
use embassy_futures::{join::join, select::select};
use embassy_rp::{peripherals::USB, usb::Driver};
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, signal::Signal};
use embassy_usb::{Builder, Config, UsbDevice};
static START_USB: Signal<CriticalSectionRawMutex, ()> = Signal::new();
static STOP_USB: Signal<CriticalSectionRawMutex, ()> = Signal::new();
// for other tasks to query the status of usb (like ui)
// this is read only for ALL other tasks
pub static USB_ACTIVE: AtomicBool = AtomicBool::new(false);
#[embassy_executor::task]
@@ -30,16 +37,39 @@ pub async fn usb_handler(driver: Driver<'static, USB>) {
let temp_sd: Option<SdCard> = None;
let mut scsi = MassStorageClass::new(&mut builder, temp_sd);
let usb = builder.build();
let mut usb = builder.build();
select(run(usb), scsi.poll()).await;
}
async fn run<'d>(mut usb: UsbDevice<'d, Driver<'d, USB>>) -> ! {
loop {
usb.wait_resume().await;
USB_ACTIVE.store(true, Ordering::Release);
usb.run_until_suspend().await;
START_USB.wait().await;
START_USB.reset();
#[cfg(feature = "defmt")]
defmt::info!("starting usb");
select(
// waits for cancellation signal, and then waits for
// transfers to stop before dropping usb future
async {
STOP_USB.wait().await;
STOP_USB.reset();
},
// runs the usb, until cancelled
join(usb.run(), scsi.poll()),
)
.await;
#[cfg(feature = "defmt")]
defmt::info!("disabling usb");
usb.disable().await;
USB_ACTIVE.store(false, Ordering::Release);
}
}
pub fn start_usb() {
STOP_USB.reset();
START_USB.signal(());
USB_ACTIVE.store(true, Ordering::Release);
}
pub fn stop_usb() {
START_USB.reset();
STOP_USB.signal(());
USB_ACTIVE.store(false, Ordering::Release);
}