mirror of
https://github.com/LegitCamper/picocalc-os-rs.git
synced 2026-10-23 06:57:36 -06:00
WIP
This commit is contained in:
1 parent
2d31e62d82
commit
beae5b2fd9
8 files changed
+526
-60
No files matched your search
+1
-20
@@ -21,8 +21,6 @@ use embedded_hal_bus::spi::ExclusiveDevice;
|
||||
use portable_atomic::AtomicBool;
|
||||
use st7365p_lcd::{FrameBuffer, ST7365P};
|
||||
|
||||
use crate::LAST_TEXT_RECT;
|
||||
|
||||
const SCREEN_WIDTH: usize = 320;
|
||||
const SCREEN_HEIGHT: usize = 320;
|
||||
|
||||
@@ -58,24 +56,7 @@ pub async fn display_handler(
|
||||
loop {
|
||||
DISPLAY_SIGNAL.wait().await;
|
||||
|
||||
let text_string = crate::STRING.lock().await.clone();
|
||||
|
||||
let text = Text::with_alignment(
|
||||
&text_string,
|
||||
Point::new(160, 160),
|
||||
MonoTextStyle::new(&FONT_10X20, Rgb565::RED),
|
||||
Alignment::Center,
|
||||
);
|
||||
|
||||
{
|
||||
let rect = LAST_TEXT_RECT.lock().await;
|
||||
if let Some(rect) = *rect.borrow() {
|
||||
framebuffer.fill_solid(&rect, Rgb565::BLACK).unwrap();
|
||||
}
|
||||
*rect.borrow_mut() = Some(text.bounding_box());
|
||||
}
|
||||
|
||||
text.draw(&mut framebuffer).unwrap();
|
||||
// text.draw(&mut framebuffer).unwrap();
|
||||
|
||||
let start = Instant::now();
|
||||
framebuffer
|
||||
|
||||
+42
-36
@@ -6,6 +6,8 @@
|
||||
use crate::{
|
||||
display::DISPLAY_SIGNAL,
|
||||
peripherals::keyboard::{KeyCode, KeyState, read_keyboard_fifo},
|
||||
storage::SdCard,
|
||||
usb::usb_handler,
|
||||
};
|
||||
|
||||
use {defmt_rtt as _, panic_probe as _};
|
||||
@@ -13,70 +15,74 @@ use {defmt_rtt as _, panic_probe as _};
|
||||
use core::cell::RefCell;
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_futures::join::join;
|
||||
use embassy_rp::peripherals::I2C1;
|
||||
use embassy_rp::{
|
||||
gpio::{Input, Level, Output, Pull},
|
||||
peripherals::{I2C1, USB},
|
||||
spi::Spi,
|
||||
usb as embassy_rp_usb,
|
||||
};
|
||||
use embassy_rp::{i2c, i2c::I2c, spi};
|
||||
use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex;
|
||||
use embassy_sync::mutex::Mutex;
|
||||
use embassy_time::Timer;
|
||||
use embassy_time::{Delay, Timer};
|
||||
use embedded_graphics::primitives::Rectangle;
|
||||
use embedded_hal_bus::spi::ExclusiveDevice;
|
||||
use embedded_sdmmc::asynchronous::SdCard as SdmmcSdCard;
|
||||
use heapless::String;
|
||||
|
||||
mod peripherals;
|
||||
use peripherals::conf_peripherals;
|
||||
mod display;
|
||||
use display::display_handler;
|
||||
mod scsi;
|
||||
mod storage;
|
||||
mod usb;
|
||||
|
||||
embassy_rp::bind_interrupts!(struct Irqs {
|
||||
I2C1_IRQ => i2c::InterruptHandler<I2C1>;
|
||||
USBCTRL_IRQ => embassy_rp_usb::InterruptHandler<USB>;
|
||||
});
|
||||
|
||||
static STRING: Mutex<ThreadModeRawMutex, String<25>> = Mutex::new(String::new());
|
||||
static LAST_TEXT_RECT: Mutex<ThreadModeRawMutex, RefCell<Option<Rectangle>>> =
|
||||
Mutex::new(RefCell::new(None));
|
||||
|
||||
#[embassy_executor::main]
|
||||
async fn main(_spawner: Spawner) {
|
||||
let p = embassy_rp::init(Default::default());
|
||||
|
||||
STRING.lock().await.push_str("Press Del").unwrap();
|
||||
|
||||
// configure keyboard event handler
|
||||
// MCU i2c bus for peripherals
|
||||
let mut config = i2c::Config::default();
|
||||
config.frequency = 400_000;
|
||||
let i2c1 = I2c::new_async(p.I2C1, p.PIN_7, p.PIN_6, Irqs, config);
|
||||
conf_peripherals(i2c1).await;
|
||||
|
||||
// SPI1 bus display
|
||||
let mut config = spi::Config::default();
|
||||
config.frequency = 16_000_000;
|
||||
let spi1 = spi::Spi::new(
|
||||
p.SPI1, p.PIN_10, p.PIN_11, p.PIN_12, p.DMA_CH0, p.DMA_CH1, config,
|
||||
);
|
||||
|
||||
join(
|
||||
async {
|
||||
loop {
|
||||
Timer::after_millis(20).await;
|
||||
if let Some(key) = read_keyboard_fifo().await
|
||||
&& key.state == KeyState::Pressed
|
||||
{
|
||||
let mut string = STRING.lock().await;
|
||||
match key.key {
|
||||
KeyCode::Backspace => {
|
||||
string.pop().unwrap();
|
||||
}
|
||||
KeyCode::Del => {
|
||||
string.clear();
|
||||
}
|
||||
KeyCode::Char(c) => {
|
||||
string.push(c).unwrap();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
DISPLAY_SIGNAL.signal(());
|
||||
}
|
||||
}
|
||||
},
|
||||
display_handler(spi1, p.PIN_13, p.PIN_14, p.PIN_15),
|
||||
)
|
||||
.await;
|
||||
let usb = embassy_rp_usb::Driver::new(p.USB, Irqs);
|
||||
|
||||
let sdcard = {
|
||||
let mut config = spi::Config::default();
|
||||
config.frequency = 400_000;
|
||||
let spi = Spi::new(
|
||||
p.SPI0,
|
||||
p.PIN_18,
|
||||
p.PIN_19,
|
||||
p.PIN_16,
|
||||
p.DMA_CH2,
|
||||
p.DMA_CH3,
|
||||
config.clone(),
|
||||
);
|
||||
let cs = Output::new(p.PIN_5, Level::High);
|
||||
|
||||
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::new(sdcard, Input::new(p.PIN_22, Pull::None))
|
||||
};
|
||||
|
||||
usb_handler(usb, sdcard).await;
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
use embassy_usb::driver::{Driver, EndpointIn, EndpointOut};
|
||||
use embassy_usb::types::StringIndex;
|
||||
use embassy_usb::{Builder, Config};
|
||||
|
||||
mod scsi_types;
|
||||
use scsi_types::*;
|
||||
|
||||
use crate::storage::SdCard;
|
||||
|
||||
pub struct MassStorageClass<'d, 'c, D: Driver<'d>> {
|
||||
sdcard: SdCard<'c>,
|
||||
bulk_out: D::EndpointOut,
|
||||
bulk_in: D::EndpointIn,
|
||||
}
|
||||
|
||||
impl<'d, 'c, D: Driver<'d>> MassStorageClass<'d, 'c, D> {
|
||||
pub fn new(builder: &mut Builder<'d, D>, sdcard: SdCard<'c>) -> Self {
|
||||
let mut function = builder.function(0x08, 0x06, 0x50); // Mass Storage class
|
||||
let mut interface = function.interface();
|
||||
let mut alt = interface.alt_setting(0x08, 0x06, 0x50, None);
|
||||
|
||||
let bulk_out = alt.endpoint_bulk_out(64);
|
||||
let bulk_in = alt.endpoint_bulk_in(64);
|
||||
|
||||
Self {
|
||||
bulk_out,
|
||||
bulk_in,
|
||||
sdcard,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn poll(&mut self) {
|
||||
let mut cbw_buf = [0u8; 31];
|
||||
if let Ok(n) = self.bulk_out.read(&mut cbw_buf).await {
|
||||
if let Some(cbw) = CommandBlockWrapper::parse(&cbw_buf[..n]) {
|
||||
self.handle_command(&cbw.CBWCB).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_command(&self, cbw: &[u8]) {
|
||||
match parse_cb(cbw) {
|
||||
ScsiCommand::Unknown => {
|
||||
#[cfg(feature = "defmt")]
|
||||
defmt::info!("Got unexpected scsi command: {}", cbw);
|
||||
}
|
||||
ScsiCommand::Inquiry {
|
||||
evpd,
|
||||
page_code,
|
||||
alloc_len,
|
||||
} => todo!(),
|
||||
ScsiCommand::TestUnitReady => todo!(),
|
||||
ScsiCommand::RequestSense { desc, alloc_len } => todo!(),
|
||||
ScsiCommand::ModeSense6 {
|
||||
dbd,
|
||||
page_control,
|
||||
page_code,
|
||||
subpage_code,
|
||||
alloc_len,
|
||||
} => todo!(),
|
||||
ScsiCommand::ModeSense10 {
|
||||
dbd,
|
||||
page_control,
|
||||
page_code,
|
||||
subpage_code,
|
||||
alloc_len,
|
||||
} => todo!(),
|
||||
ScsiCommand::ReadCapacity10 => todo!(),
|
||||
ScsiCommand::ReadCapacity16 { alloc_len } => todo!(),
|
||||
ScsiCommand::Read { lba, len } => todo!(),
|
||||
ScsiCommand::Write { lba, len } => todo!(),
|
||||
ScsiCommand::ReadFormatCapacities { alloc_len } => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
struct CommandBlockWrapper {
|
||||
dCBWSignature: u32,
|
||||
dCBWTag: u32,
|
||||
dCBWDataTransferLength: u32,
|
||||
bmCBWFlags: u8,
|
||||
bCBWLUN: u8,
|
||||
bCBWCBLength: u8,
|
||||
CBWCB: [u8; 16],
|
||||
}
|
||||
|
||||
impl CommandBlockWrapper {
|
||||
fn parse(buf: &[u8]) -> Option<Self> {
|
||||
if buf.len() < 31 {
|
||||
return None;
|
||||
}
|
||||
let dCBWSignature = u32::from_le_bytes(buf[0..4].try_into().unwrap());
|
||||
if dCBWSignature != 0x43425355 {
|
||||
return None; // invalid signature
|
||||
}
|
||||
Some(Self {
|
||||
dCBWSignature,
|
||||
dCBWTag: u32::from_le_bytes(buf[4..8].try_into().unwrap()),
|
||||
dCBWDataTransferLength: u32::from_le_bytes(buf[8..12].try_into().unwrap()),
|
||||
bmCBWFlags: buf[12],
|
||||
bCBWLUN: buf[13],
|
||||
bCBWCBLength: buf[14],
|
||||
CBWCB: buf[15..31].try_into().unwrap(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
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
|
||||
|
||||
/* SCSI codes */
|
||||
|
||||
/* SPC */
|
||||
const TEST_UNIT_READY: u8 = 0x00;
|
||||
const REQUEST_SENSE: u8 = 0x03;
|
||||
const INQUIRY: u8 = 0x12;
|
||||
const MODE_SENSE_6: u8 = 0x1A;
|
||||
const MODE_SENSE_10: u8 = 0x5A;
|
||||
|
||||
/* SBC */
|
||||
const READ_10: u8 = 0x28;
|
||||
const READ_16: u8 = 0x88;
|
||||
const READ_CAPACITY_10: u8 = 0x25;
|
||||
const READ_CAPACITY_16: u8 = 0x9E;
|
||||
const WRITE_10: u8 = 0x2A;
|
||||
|
||||
/* MMC */
|
||||
const READ_FORMAT_CAPACITIES: u8 = 0x23;
|
||||
|
||||
/// SCSI command
|
||||
///
|
||||
/// Refer to specifications (SPC,SAM,SBC,MMC,etc.)
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[non_exhaustive]
|
||||
pub enum ScsiCommand {
|
||||
Unknown,
|
||||
|
||||
/* SPC */
|
||||
Inquiry {
|
||||
evpd: bool,
|
||||
page_code: u8,
|
||||
alloc_len: u16,
|
||||
},
|
||||
TestUnitReady,
|
||||
RequestSense {
|
||||
desc: bool,
|
||||
alloc_len: u8,
|
||||
},
|
||||
ModeSense6 {
|
||||
dbd: bool,
|
||||
page_control: PageControl,
|
||||
page_code: u8,
|
||||
subpage_code: u8,
|
||||
alloc_len: u8,
|
||||
},
|
||||
ModeSense10 {
|
||||
dbd: bool,
|
||||
page_control: PageControl,
|
||||
page_code: u8,
|
||||
subpage_code: u8,
|
||||
alloc_len: u16,
|
||||
},
|
||||
|
||||
/* SBC */
|
||||
ReadCapacity10,
|
||||
ReadCapacity16 {
|
||||
alloc_len: u32,
|
||||
},
|
||||
Read {
|
||||
lba: u64,
|
||||
len: u64,
|
||||
},
|
||||
Write {
|
||||
lba: u64,
|
||||
len: u64,
|
||||
},
|
||||
|
||||
/* MMC */
|
||||
ReadFormatCapacities {
|
||||
alloc_len: u16,
|
||||
},
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Copy, Clone, Debug, TryFromPrimitive)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum PageControl {
|
||||
CurrentValues = 0b00,
|
||||
ChangeableValues = 0b01,
|
||||
DefaultValues = 0b10,
|
||||
SavedValues = 0b11,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn parse_cb(cb: &[u8]) -> ScsiCommand {
|
||||
match cb[0] {
|
||||
TEST_UNIT_READY => ScsiCommand::TestUnitReady,
|
||||
INQUIRY => ScsiCommand::Inquiry {
|
||||
evpd: (cb[1] & 0b00000001) != 0,
|
||||
page_code: cb[2],
|
||||
alloc_len: u16::from_be_bytes([cb[3], cb[4]]),
|
||||
},
|
||||
REQUEST_SENSE => ScsiCommand::RequestSense {
|
||||
desc: (cb[1] & 0b00000001) != 0,
|
||||
alloc_len: cb[4],
|
||||
},
|
||||
READ_CAPACITY_10 => ScsiCommand::ReadCapacity10,
|
||||
READ_CAPACITY_16 => ScsiCommand::ReadCapacity16 {
|
||||
alloc_len: u32::from_be_bytes([cb[10], cb[11], cb[12], cb[13]]),
|
||||
},
|
||||
READ_10 => ScsiCommand::Read {
|
||||
lba: u32::from_be_bytes([cb[2], cb[3], cb[4], cb[5]]) as u64,
|
||||
len: u16::from_be_bytes([cb[7], cb[8]]) as u64,
|
||||
},
|
||||
READ_16 => ScsiCommand::Read {
|
||||
lba: u64::from_be_bytes((&cb[2..10]).try_into().unwrap()),
|
||||
len: u32::from_be_bytes((&cb[10..14]).try_into().unwrap()) as u64,
|
||||
},
|
||||
WRITE_10 => ScsiCommand::Write {
|
||||
lba: u32::from_be_bytes([cb[2], cb[3], cb[4], cb[5]]) as u64,
|
||||
len: u16::from_be_bytes([cb[7], cb[8]]) as u64,
|
||||
},
|
||||
MODE_SENSE_6 => ScsiCommand::ModeSense6 {
|
||||
dbd: (cb[1] & 0b00001000) != 0,
|
||||
page_control: PageControl::try_from_primitive(cb[2] >> 6).unwrap(),
|
||||
page_code: cb[2] & 0b00111111,
|
||||
subpage_code: cb[3],
|
||||
alloc_len: cb[4],
|
||||
},
|
||||
MODE_SENSE_10 => ScsiCommand::ModeSense10 {
|
||||
dbd: (cb[1] & 0b00001000) != 0,
|
||||
page_control: PageControl::try_from_primitive(cb[2] >> 6).unwrap(),
|
||||
page_code: cb[2] & 0b00111111,
|
||||
subpage_code: cb[3],
|
||||
alloc_len: u16::from_be_bytes([cb[7], cb[8]]),
|
||||
},
|
||||
READ_FORMAT_CAPACITIES => ScsiCommand::ReadFormatCapacities {
|
||||
alloc_len: u16::from_be_bytes([cb[7], cb[8]]),
|
||||
},
|
||||
_ => ScsiCommand::Unknown,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
use embassy_rp::gpio::{Input, Output};
|
||||
use embassy_rp::peripherals::SPI0;
|
||||
use embassy_rp::spi::{Async, Spi};
|
||||
use embedded_hal_bus::spi::ExclusiveDevice;
|
||||
use embedded_sdmmc;
|
||||
use embedded_sdmmc::asynchronous::{
|
||||
Directory, SdCard as SdmmcSdCard, Volume, VolumeIdx, VolumeManager,
|
||||
};
|
||||
use embedded_sdmmc::blocking::{TimeSource, Timestamp};
|
||||
|
||||
pub const MAX_DIRS: usize = 4;
|
||||
pub const MAX_FILES: usize = 5;
|
||||
pub const MAX_VOLUMES: usize = 1;
|
||||
|
||||
type Device = ExclusiveDevice<Spi<'static, SPI0, Async>, Output<'static>, embassy_time::Delay>;
|
||||
type SD = SdmmcSdCard<Device, embassy_time::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>;
|
||||
type Dir<'a> = Directory<'a, SD, DummyTimeSource, MAX_DIRS, MAX_FILES, MAX_VOLUMES>;
|
||||
|
||||
pub struct DummyTimeSource {}
|
||||
impl TimeSource for DummyTimeSource {
|
||||
fn get_timestamp(&self) -> Timestamp {
|
||||
Timestamp::from_calendar(2022, 1, 1, 0, 0, 0).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SdCard<'a> {
|
||||
det: Input<'static>,
|
||||
volume_mgr: VolMgr,
|
||||
volume: Option<Vol<'a>>,
|
||||
root: Option<Dir<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> SdCard<'a> {
|
||||
pub fn new(sdcard: SD, det: Input<'static>) -> Self {
|
||||
let volume_mgr = VolumeManager::<_, _, MAX_DIRS, MAX_FILES, MAX_VOLUMES>::new_with_limits(
|
||||
sdcard,
|
||||
DummyTimeSource {},
|
||||
5000,
|
||||
);
|
||||
Self {
|
||||
det: det,
|
||||
volume_mgr,
|
||||
volume: None,
|
||||
root: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if an SD card is inserted.
|
||||
/// The DET pin is active-low via mechanical switch in the socket.
|
||||
fn attached(&self) -> bool {
|
||||
self.det.is_low()
|
||||
}
|
||||
|
||||
async fn get_root(&'a mut self) {
|
||||
let vol = self.volume.as_mut().unwrap();
|
||||
let root = vol.open_root_dir().unwrap();
|
||||
self.root = Some(root);
|
||||
}
|
||||
|
||||
async fn get_volume(&'a mut self) {
|
||||
let vol = self.volume_mgr.open_volume(VolumeIdx(0)).await.unwrap();
|
||||
self.volume = Some(vol);
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
use crate::{scsi::MassStorageClass, storage::SdCard};
|
||||
use embassy_futures::join::{self, join};
|
||||
use embassy_rp::{
|
||||
gpio::Output,
|
||||
peripherals::{SPI0, USB},
|
||||
spi::{Async, Spi},
|
||||
usb::Driver,
|
||||
};
|
||||
use embassy_time::Delay;
|
||||
use embassy_usb::{Builder, Config};
|
||||
|
||||
pub async fn usb_handler(driver: Driver<'static, USB>, mut sdcard: SdCard<'_>) {
|
||||
let mut config = Config::new(0xc0de, 0xcafe);
|
||||
config.manufacturer = Some("LegitCamper");
|
||||
config.product = Some("PicoCalc");
|
||||
config.serial_number = Some("01001100");
|
||||
config.max_power = 100;
|
||||
config.max_packet_size_0 = 64;
|
||||
|
||||
let mut config_descriptor = [0; 256];
|
||||
let mut bos_descriptor = [0; 64];
|
||||
let mut control_buf = [0; 64];
|
||||
|
||||
let mut builder = Builder::new(
|
||||
driver,
|
||||
config,
|
||||
&mut config_descriptor,
|
||||
&mut bos_descriptor,
|
||||
&mut [],
|
||||
&mut control_buf,
|
||||
);
|
||||
|
||||
let mut scsi = MassStorageClass::new(&mut builder, sdcard);
|
||||
let mut usb = builder.build();
|
||||
|
||||
join(usb.run(), scsi.poll()).await;
|
||||
}
|
||||
Reference in new issue
Block a user