This commit is contained in:
2025-09-15 19:54:40 -06:00
parent 903a4ab631
commit 4c6e16270e
12 changed files with 222 additions and 262 deletions

View File

@@ -72,10 +72,11 @@ 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 = "1d15123929fa7ef73d5d6aead7faf1bba50ce915" } # async branch
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"
static_cell = "2.1.1"
bitflags = "2.9.1"

View File

@@ -50,12 +50,12 @@ pub async fn init_display(
}
pub fn clear_fb() {
let bounds = unsafe { FRAMEBUFFER.bounding_box() };
unsafe {
let _ = FRAMEBUFFER.fill_solid(&bounds, Rgb565::BLACK);
FRAMEBUFFER.clear(Rgb565::WHITE).unwrap();
}
}
#[embassy_executor::task]
pub async fn display_handler(mut display: DISPLAY) {
loop {
unsafe {

View File

@@ -26,7 +26,7 @@ const SIZE: usize = SCREEN_HEIGHT * SCREEN_WIDTH;
static mut BUFFER: [u16; SIZE] = [0; SIZE];
static mut DIRTY_TILES: LazyLock<Vec<AtomicBool, TILE_COUNT>> = LazyLock::new(|| {
static mut DIRTY_TILES: LazyLock<heapless::Vec<AtomicBool, TILE_COUNT>> = LazyLock::new(|| {
let mut tiles = Vec::new();
for _ in 0..TILE_COUNT {
tiles.push(AtomicBool::new(true)).unwrap();
@@ -71,10 +71,10 @@ impl AtomicFrameBuffer {
ey: u16,
colors: P,
) -> Result<(), ()> {
if sx >= self.size().width as u16 - 1
|| ex >= self.size().width as u16 - 1
|| sy >= self.size().height as u16 - 1
|| ey >= self.size().height as u16 - 1
if sx >= self.size().width as u16
|| ex >= self.size().width as u16
|| sy >= self.size().height as u16
|| ey >= self.size().height as u16
{
return Err(()); // Bounds check
}
@@ -101,7 +101,7 @@ impl AtomicFrameBuffer {
// walk the dirty tiles and mark groups of tiles(meta-tiles) for batched updates
fn find_meta_tiles(&mut self, tiles_x: usize, tiles_y: usize) -> MetaTileVec {
let mut meta_tiles: MetaTileVec = Vec::new();
let mut meta_tiles: MetaTileVec = heapless::Vec::new();
for ty in 0..tiles_y {
let mut tx = 0;
@@ -415,10 +415,8 @@ impl DrawTarget for AtomicFrameBuffer {
.take((self.size().width * self.size().height) as usize),
)?;
unsafe {
for tile in DIRTY_TILES.get_mut().iter() {
tile.store(true, Ordering::Relaxed);
}
for tile in unsafe { DIRTY_TILES.get_mut() }.iter() {
tile.store(true, Ordering::Release);
}
Ok(())

View File

@@ -19,12 +19,12 @@ mod usb;
mod utils;
use crate::{
display::{FRAMEBUFFER, clear_fb, display_handler, init_display},
elf::load_binary,
display::{clear_fb, display_handler, init_display},
peripherals::{
conf_peripherals,
keyboard::{KeyCode, KeyState, read_keyboard_fifo},
keyboard::{KeyState, read_keyboard_fifo},
},
scsi::MSC_SHUTDOWN,
storage::{SDCARD, SdCard},
ui::{SELECTIONS, ui_handler},
usb::usb_handler,
@@ -33,10 +33,12 @@ use abi_sys::EntryFn;
use {defmt_rtt as _, panic_probe as _};
use assign_resources::assign_resources;
use defmt::unwrap;
use embassy_executor::{Executor, Spawner};
use embassy_futures::join::{join, join3, join4, join5};
use embassy_futures::{
join::{join, join3, join5},
yield_now,
};
use embassy_rp::{
Peri,
gpio::{Input, Level, Output, Pull},
@@ -86,7 +88,7 @@ enum TaskState {
}
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
async fn main(spawner: Spawner) {
let p = embassy_rp::init(Default::default());
spawn_core1(
@@ -123,7 +125,7 @@ async fn main(_spawner: Spawner) {
data: p.PIN_6,
};
let executor0 = EXECUTOR0.init(Executor::new());
executor0.run(|spawner| unwrap!(spawner.spawn(kernel_task(display, sd, mcu, p.USB))));
executor0.run(|spawner| unwrap!(spawner.spawn(kernel_task(spawner, display, sd, mcu, p.USB))));
}
// One-slot channel to pass EntryFn from core1
@@ -141,10 +143,11 @@ async fn userland_task() {
{
let mut state = TASK_STATE.lock().await;
*state = TaskState::Kernel;
TASK_STATE_CHANGED.signal(());
// clear_fb();
MSC_SHUTDOWN.signal(());
}
// clear_fb();
defmt::info!("Executing Binary");
entry().await;
@@ -152,6 +155,8 @@ async fn userland_task() {
{
let mut state = TASK_STATE.lock().await;
*state = TaskState::Ui;
TASK_STATE_CHANGED.signal(());
// clear_fb();
}
}
}
@@ -181,16 +186,15 @@ struct Mcu {
data: Peri<'static, PIN_6>,
}
#[embassy_executor::task]
async fn kernel_task(display: Display, sd: Sd, mcu: Mcu, usb: Peri<'static, USB>) {
// MCU i2c bus for peripherals
async fn setup_mcu(mcu: Mcu) {
// MCU i2c bus for peripherals( keyboard)
let mut config = i2c::Config::default();
config.frequency = 400_000;
let i2c1 = I2c::new_async(mcu.i2c, mcu.clk, mcu.data, Irqs, config);
conf_peripherals(i2c1).await;
}
Timer::after_millis(250).await;
async fn setup_display(display: Display, spawner: Spawner) {
let mut config = spi::Config::default();
config.frequency = 16_000_000;
let spi = Spi::new(
@@ -203,66 +207,85 @@ async fn kernel_task(display: Display, sd: Sd, mcu: Mcu, usb: Peri<'static, USB>
config,
);
let display = init_display(spi, display.cs, display.data, display.reset).await;
spawner.spawn(display_handler(display)).unwrap();
}
let display_fut = display_handler(display);
async fn setup_sd(sd: Sd) {
let mut config = spi::Config::default();
config.frequency = 400_000;
let spi = Spi::new_blocking(sd.spi, sd.clk, sd.mosi, sd.miso, config.clone());
let cs = Output::new(sd.cs, Level::High);
let det = Input::new(sd.det, Pull::None);
let ui_fut = ui_handler();
let device = ExclusiveDevice::new(spi, cs, Delay).unwrap();
let sdcard = SdmmcSdCard::new(device, Delay);
let binary_search_fut = async {
loop {
{
let mut guard = SDCARD.get().lock().await;
config.frequency = 32_000_000;
sdcard.spi(|dev| dev.bus_mut().set_config(&config));
SDCARD.get().lock().await.replace(SdCard::new(sdcard, det));
}
if let Some(sd) = guard.as_mut() {
let files = sd.list_files_by_extension(".bin").unwrap();
let mut select = SELECTIONS.lock().await;
if select.selections != files {
select.selections = files;
select.reset();
}
}
}
Timer::after_secs(5).await;
}
};
{
let mut config = spi::Config::default();
config.frequency = 400_000;
let spi = Spi::new_blocking(sd.spi, sd.clk, sd.mosi, sd.miso, config.clone());
let cs = Output::new(sd.cs, Level::High);
let det = Input::new(sd.det, Pull::None);
let device = ExclusiveDevice::new(spi, cs, Delay).unwrap();
let sdcard = SdmmcSdCard::new(device, Delay);
config.frequency = 32_000_000;
sdcard.spi(|dev| dev.bus_mut().set_config(&config));
SDCARD.get().lock().await.replace(SdCard::new(sdcard, det));
};
#[embassy_executor::task]
async fn kernel_task(
spawner: Spawner,
display: Display,
sd: Sd,
mcu: Mcu,
usb: Peri<'static, USB>,
) {
setup_mcu(mcu).await;
Timer::after_millis(250).await;
setup_display(display, spawner).await;
setup_sd(sd).await;
let usb = embassy_rp_usb::Driver::new(usb, Irqs);
let usb_fut = usb_handler(usb);
spawner.spawn(usb_handler(usb)).unwrap();
let key_abi_fut = async {
loop {
Timer::after_millis(100).await;
get_keys().await
spawner.spawn(key_handler()).unwrap();
loop {
if let TaskState::Ui = *TASK_STATE.lock().await {
let ui_fut = ui_handler();
let binary_search_fut = prog_search_handler();
join(ui_fut, binary_search_fut).await;
}
};
join5(display_fut, ui_fut, usb_fut, binary_search_fut, key_abi_fut).await;
yield_now().await;
}
}
async fn prog_search_handler() {
loop {
{
let mut guard = SDCARD.get().lock().await;
if let Some(sd) = guard.as_mut() {
let files = sd.list_files_by_extension(".bin").unwrap();
let mut select = SELECTIONS.lock().await;
if select.selections != files {
select.selections = files;
select.reset();
}
}
}
Timer::after_secs(5).await;
}
}
static mut KEY_CACHE: Queue<KeyEvent, 32> = Queue::new();
async fn get_keys() {
if let Some(event) = read_keyboard_fifo().await {
if let KeyState::Pressed = event.state {
unsafe {
let _ = KEY_CACHE.enqueue(event);
#[embassy_executor::task]
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,7 +1,7 @@
use crate::format;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::signal::Signal;
use embassy_usb::Builder;
use embassy_usb::driver::{Driver, EndpointIn, EndpointOut};
use embassy_usb::types::StringIndex;
use embassy_usb::{Builder, Config};
use embedded_sdmmc::{Block, BlockIdx};
use heapless::Vec;
@@ -12,6 +12,8 @@ use crate::storage::{SDCARD, SdCard};
const BULK_ENDPOINT_PACKET_SIZE: usize = 64;
pub static MSC_SHUTDOWN: Signal<CriticalSectionRawMutex, ()> = Signal::new();
pub struct MassStorageClass<'d, D: Driver<'d>> {
bulk_out: D::EndpointOut,
bulk_in: D::EndpointIn,
@@ -31,16 +33,24 @@ impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, D> {
pub async fn poll(&mut self) {
loop {
let mut cbw_buf = [0u8; 31];
if let Ok(n) = self.bulk_out.read(&mut cbw_buf).await {
if n == 31 {
if let Some(cbw) = CommandBlockWrapper::parse(&cbw_buf[..n]) {
// TODO: validate cbw
if self.handle_command(&cbw.CBWCB).await.is_ok() {
self.send_csw_success(cbw.dCBWTag).await
} else {
self.send_csw_fail(cbw.dCBWTag).await
}
embassy_futures::select::select(self.handle_cbw(), MSC_SHUTDOWN.wait()).await;
if MSC_SHUTDOWN.signaled() {
defmt::info!("MSC shutting down");
return; // or break
}
}
}
async fn handle_cbw(&mut self) {
let mut cbw_buf = [0u8; 31];
if let Ok(n) = self.bulk_out.read(&mut cbw_buf).await {
if n == 31 {
if let Some(cbw) = CommandBlockWrapper::parse(&cbw_buf[..n]) {
if self.handle_command(&cbw.CBWCB).await.is_ok() {
self.send_csw_success(cbw.dCBWTag).await
} else {
self.send_csw_fail(cbw.dCBWTag).await
}
}
}

View File

@@ -14,7 +14,10 @@ use embassy_sync::{
};
use embedded_graphics::{
Drawable,
mono_font::{MonoTextStyle, ascii::FONT_9X15},
mono_font::{
MonoTextStyle,
ascii::{self, FONT_9X15},
},
pixelcolor::Rgb565,
prelude::{Dimensions, Point, RgbColor, Size},
primitives::Rectangle,
@@ -27,6 +30,7 @@ use embedded_layout::{
prelude::*,
};
use embedded_text::TextBox;
use kolibri_embedded_gui::{label::Label, style::medsize_rgb565_style, ui::Ui};
use shared::keyboard::{KeyCode, KeyState};
pub static SELECTIONS: Mutex<CriticalSectionRawMutex, SelectionList> =
@@ -34,89 +38,50 @@ pub static SELECTIONS: Mutex<CriticalSectionRawMutex, SelectionList> =
pub async fn ui_handler() {
loop {
if let TaskState::Ui = *TASK_STATE.lock().await {
if let Some(event) = keyboard::read_keyboard_fifo().await {
if let KeyState::Pressed = event.state {
match event.key {
KeyCode::JoyUp => {
let mut selections = SELECTIONS.lock().await;
selections.up();
}
KeyCode::JoyDown => {
let mut selections = SELECTIONS.lock().await;
selections.down();
}
KeyCode::Enter | KeyCode::JoyRight => {
let selections = SELECTIONS.lock().await;
let selection = selections.selections
[selections.current_selection as usize - 1]
.clone();
let entry =
unsafe { load_binary(&selection.short_name).await.unwrap() };
BINARY_CH.send(entry).await;
}
_ => (),
if let Some(event) = keyboard::read_keyboard_fifo().await {
if let KeyState::Pressed = event.state {
match event.key {
KeyCode::JoyUp => {
let mut selections = SELECTIONS.lock().await;
selections.up();
}
KeyCode::JoyDown => {
let mut selections = SELECTIONS.lock().await;
selections.down();
}
KeyCode::Enter | KeyCode::JoyRight => {
let selections = SELECTIONS.lock().await;
let selection = selections.selections
[selections.current_selection as usize - 1]
.clone();
let entry = unsafe { load_binary(&selection.short_name).await.unwrap() };
BINARY_CH.send(entry).await;
}
_ => (),
}
}
draw_selection().await;
} else {
embassy_time::Timer::after_millis(50).await;
}
draw_selection().await;
}
}
async fn draw_selection() {
const NO_BINS: &str = "No Programs found on SD Card. Ensure programs end with '.bin', and are located in the root directory";
let file_names: Vec<FileName> = {
let guard = SELECTIONS.lock().await;
guard.selections.clone()
};
let text_style = MonoTextStyle::new(&FONT_9X15, Rgb565::WHITE);
let display_area = unsafe { FRAMEBUFFER.bounding_box() };
const NO_BINS: &str = "No Programs found on SD Card. Ensure programs end with '.bin', and are located in the root directory";
let no_bins = String::from_str(NO_BINS).unwrap();
let mut ui = Ui::new_fullscreen(unsafe { &mut FRAMEBUFFER }, medsize_rgb565_style());
if file_names.is_empty() {
TextBox::new(
&no_bins,
Rectangle::new(
Point::new(25, 25),
Size::new(display_area.size.width - 50, display_area.size.width - 50),
),
text_style,
)
.draw(unsafe { &mut FRAMEBUFFER })
.unwrap();
ui.add(Label::new(NO_BINS).with_font(ascii::FONT_10X20));
} else {
let mut file_names = file_names.iter();
let Some(first) = file_names.next() else {
Text::new(NO_BINS, Point::zero(), text_style)
.draw(unsafe { &mut FRAMEBUFFER })
.unwrap();
return;
};
let chain = Chain::new(Text::new(&first.long_name, Point::zero(), text_style));
// for _ in 0..file_names.len() {
// let chain = chain.append(Text::new(
// file_names.next().unwrap(),
// Point::zero(),
// text_style,
// ));
// }
LinearLayout::vertical(chain)
.with_alignment(horizontal::Center)
.arrange()
.align_to(&display_area, horizontal::Center, vertical::Center)
.draw(unsafe { &mut FRAMEBUFFER })
.unwrap();
for file in file_names {
ui.add(Label::new(&file.long_name).with_font(ascii::FONT_10X20));
}
}
}

View File

@@ -1,11 +1,9 @@
use crate::{TASK_STATE, TASK_STATE_CHANGED, TaskState, scsi::MassStorageClass};
use embassy_futures::{
join::join,
select::{select, select3},
};
use crate::scsi::MassStorageClass;
use embassy_futures::join::join;
use embassy_rp::{peripherals::USB, usb::Driver};
use embassy_usb::{Builder, Config};
#[embassy_executor::task]
pub async fn usb_handler(driver: Driver<'static, USB>) {
let mut config = Config::new(0xc0de, 0xbabe);
config.manufacturer = Some("LegitCamper");
@@ -30,14 +28,5 @@ pub async fn usb_handler(driver: Driver<'static, USB>) {
let mut scsi = MassStorageClass::new(&mut builder);
let mut usb = builder.build();
loop {
defmt::info!("in: {}", *TASK_STATE.lock().await as u32);
if *TASK_STATE.lock().await == TaskState::Ui {
defmt::info!("running scsi and usb");
select(join(usb.run(), scsi.poll()), TASK_STATE_CHANGED.wait()).await;
} else {
defmt::info!("not in ui state");
TASK_STATE_CHANGED.wait().await;
}
}
join(usb.run(), scsi.poll()).await;
}