setup basic file structure

This commit is contained in:
2025-07-19 23:15:27 -06:00
parent 2d31e62d82
commit 0f4b4f8ffc
15 changed files with 362 additions and 275 deletions

83
kernel/Cargo.toml Normal file
View File

@@ -0,0 +1,83 @@
[package]
name = "picocalc-os-rs"
version = "0.1.0"
edition = "2024"
[[bin]]
name = "picocalc-os-rs"
path = "src/main.rs"
test = false
doctest = false
bench = false
[features]
default = ["rp235x", "defmt"]
rp2040 = ["embassy-rp/rp2040"]
rp235x = ["embassy-rp/rp235xb"]
trouble = ["dep:bt-hci", "dep:cyw43", "dep:cyw43-pio", "dep:trouble-host"]
defmt = [
"dep:defmt",
"panic-probe/print-defmt",
"embassy-executor/defmt",
"embassy-time/defmt",
"embassy-time/defmt-timestamp-uptime",
"embassy-rp/defmt",
"embassy-sync/defmt",
"embedded-graphics/defmt",
"embedded-sdmmc/defmt-log",
# "bt-hci/defmt",
# "cyw43/defmt",
# "cyw43-pio/defmt",
]
[dependencies]
embassy-executor = { version = "0.7", features = [
"arch-cortex-m",
"executor-interrupt",
"executor-thread",
"nightly",
] }
embassy-rp = { version = "0.4.0", features = [
"critical-section-impl",
"unstable-pac",
"time-driver",
"binary-info",
] }
embassy-futures = "0.1.1"
embassy-time = "0.4.0"
embassy-embedded-hal = "0.3.0"
embassy-sync = { version = "0.7" }
trouble-host = { version = "0.1", features = [
"derive",
"scan",
], optional = true }
bt-hci = { version = "0.2", default-features = false, optional = true }
cyw43 = { version = "0.3.0", features = [
"firmware-logs",
"bluetooth",
], optional = true }
cyw43-pio = { version = "0.3.0", optional = true }
embedded-hal-bus = { version = "0.3.0", features = ["async"] }
embedded-hal = "0.2.7"
embedded-hal-async = "1.0.0"
cortex-m = { version = "0.7.7" }
cortex-m-rt = "0.7.5"
panic-probe = "0.3"
portable-atomic = { version = "1.11", features = ["critical-section"] }
defmt = { version = "0.3", optional = true }
defmt-rtt = "0.4.2"
embedded-graphics = { version = "0.8.1" }
embedded-sdmmc = { git = "https://github.com/Be-ing/embedded-sdmmc-rs", branch = "bisync", default-features = false }
st7365p-lcd = { git = "https://github.com/legitcamper/st7365p-lcd-rs", branch = "async" }
shared = { path = "../shared" }
abi = { path = "../abi" }
static_cell = "2.1.1"
bitflags = "2.9.1"
talc = "4.4.3"
spin = "0.10.0"
heapless = "0.8.0"

35
kernel/build.rs Normal file
View File

@@ -0,0 +1,35 @@
//! 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());
// By default, Cargo will re-run a build script whenever
// any file in the project changes. By specifying `memory.x`
// here, we ensure the build script is only re-run when
// `memory.x` is changed.
println!("cargo:rerun-if-changed=memory.x");
println!("cargo:rustc-link-arg-bins=--nmagic");
println!("cargo:rustc-link-arg-bins=-Tlink.x");
println!("cargo:rustc-link-arg-bins=-Tdefmt.x");
}

87
kernel/src/display.rs Normal file
View File

@@ -0,0 +1,87 @@
use core::sync::atomic::Ordering;
use defmt::info;
use embassy_rp::{
gpio::{Level, Output},
peripherals::{PIN_13, PIN_14, PIN_15, SPI1},
spi::{Async, Spi},
};
use embassy_sync::{blocking_mutex::raw::ThreadModeRawMutex, signal::Signal};
use embassy_time::{Delay, Instant, Timer};
use embedded_graphics::{
Drawable,
draw_target::DrawTarget,
mono_font::{MonoTextStyle, ascii::FONT_10X20},
pixelcolor::Rgb565,
prelude::{Dimensions, Point, RgbColor, Size},
primitives::Rectangle,
text::{Alignment, Text},
};
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;
pub static DISPLAY_SIGNAL: Signal<ThreadModeRawMutex, ()> = Signal::new();
pub async fn display_handler(
spi: Spi<'static, SPI1, Async>,
cs: PIN_13,
data: PIN_14,
reset: PIN_15,
) {
let spi_device = ExclusiveDevice::new(spi, Output::new(cs, Level::Low), Delay).unwrap();
let mut display = ST7365P::new(
spi_device,
Output::new(data, Level::Low),
Some(Output::new(reset, Level::High)),
false,
true,
Delay,
);
let mut framebuffer: FrameBuffer<
SCREEN_WIDTH,
SCREEN_HEIGHT,
{ SCREEN_WIDTH * SCREEN_HEIGHT },
> = FrameBuffer::new();
display.init().await.unwrap();
display.set_custom_orientation(0x40).await.unwrap();
framebuffer.draw(&mut display).await.unwrap();
display.set_on().await.unwrap();
DISPLAY_SIGNAL.signal(());
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();
let start = Instant::now();
framebuffer
.partial_draw_batched(&mut display)
.await
.unwrap();
info!("Elapsed {}ms", start.elapsed().as_millis());
}
}

95
kernel/src/main.rs Normal file
View File

@@ -0,0 +1,95 @@
#![feature(impl_trait_in_assoc_type)]
#![feature(ascii_char)]
#![cfg_attr(not(test), no_std)]
#![cfg_attr(not(test), no_main)]
use crate::{
display::DISPLAY_SIGNAL,
peripherals::keyboard::{KeyCode, KeyState, read_keyboard_fifo},
};
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::{i2c, i2c::I2c, spi};
use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex;
use embassy_sync::mutex::Mutex;
use embassy_time::Timer;
use embedded_graphics::primitives::Rectangle;
use heapless::String;
mod peripherals;
use peripherals::conf_peripherals;
mod display;
use display::display_handler;
embassy_rp::bind_interrupts!(struct Irqs {
I2C1_IRQ => i2c::InterruptHandler<I2C1>;
});
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
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;
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;
}
use abi::Syscall;
#[no_mangle]
pub extern "C" fn syscall_dispatch(call: *const Syscall) -> usize {
let call = unsafe { &*call };
match call {
Syscall::DrawPixel { x, y, color } => {
draw_pixel(*x, *y, *color);
0
}
}
}

View File

@@ -0,0 +1,59 @@
use crate::peripherals::PERIPHERAL_BUS;
pub use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers};
const REG_ID_KEY: u8 = 0x04;
const REG_ID_FIF: u8 = 0x09;
const KEY_CAPSLOCK: u8 = 1 << 5;
const KEY_NUMLOCK: u8 = 1 << 6;
const KEY_COUNT_MASK: u8 = 0x1F; // 0x1F == 31
pub async fn read_keyboard_fifo() -> Option<KeyEvent> {
let mut i2c = PERIPHERAL_BUS.get().lock().await;
let i2c = i2c.as_mut().unwrap();
let mut key_status = [0_u8; 1];
if i2c
.write_read_async(super::MCU_ADDR, [REG_ID_KEY], &mut key_status)
.await
.is_ok()
{
let _caps = key_status[0] & KEY_CAPSLOCK == KEY_CAPSLOCK;
let _num = key_status[0] & KEY_NUMLOCK == KEY_NUMLOCK;
let fifo_count = key_status[0] & KEY_COUNT_MASK;
if fifo_count >= 1 {
let mut event = [0_u8; 2];
if i2c
.write_read_async(super::MCU_ADDR, [REG_ID_FIF], &mut event)
.await
.is_ok()
{
return Some(KeyEvent {
state: KeyState::from(event[0]),
key: KeyCode::from(event[1]),
mods: Modifiers::NONE,
});
}
}
}
None
}
const REG_ID_DEB: u8 = 0x06;
const REG_ID_FRQ: u8 = 0x07;
pub async fn configure_keyboard(debounce: u8, poll_freq: u8) {
let mut i2c = PERIPHERAL_BUS.get().lock().await;
let i2c = i2c.as_mut().unwrap();
let _ = i2c
.write_read_async(super::MCU_ADDR, [REG_ID_DEB], &mut [debounce])
.await;
let _ = i2c
.write_read_async(super::MCU_ADDR, [REG_ID_FRQ], &mut [poll_freq])
.await;
}

View File

@@ -0,0 +1,98 @@
//! handles all the peripherals exposed by mcu through i2c (keyboard & battery registers)
//!
use embassy_rp::{
i2c::{Async, I2c},
peripherals::I2C1,
};
use embassy_sync::{blocking_mutex::raw::NoopRawMutex, lazy_lock::LazyLock, mutex::Mutex};
use embassy_time::Timer;
pub mod keyboard;
use crate::peripherals::keyboard::{configure_keyboard, read_keyboard_fifo};
const MCU_ADDR: u8 = 0x1F;
type I2CBUS = I2c<'static, I2C1, Async>;
pub static PERIPHERAL_BUS: LazyLock<Mutex<NoopRawMutex, Option<I2CBUS>>> =
LazyLock::new(|| Mutex::new(None));
const REG_ID_VER: u8 = 0x01;
const REG_ID_RST: u8 = 0x08;
const REG_ID_INT: u8 = 0x03;
pub async fn conf_peripherals(i2c: I2CBUS) {
Timer::after(embassy_time::Duration::from_millis(100)).await;
PERIPHERAL_BUS.get().lock().await.replace(i2c);
configure_keyboard(200, 100).await;
// empty keys
while read_keyboard_fifo().await.is_some() {}
// set_lcd_backlight(255).await;
set_key_backlight(0).await;
}
/// return major & minor mcu version
async fn get_version() -> (u8, u8) {
let mut i2c = PERIPHERAL_BUS.get().lock().await;
let i2c = i2c.as_mut().unwrap();
let mut ver = [0_u8; 1];
let _ = i2c.write_read_async(MCU_ADDR, [REG_ID_VER], &mut ver).await;
(ver[0] >> 4, ver[0] & 0x0F)
}
const REG_ID_BKL: u8 = 0x05;
pub async fn set_lcd_backlight(brightness: u8) {
let mut i2c = PERIPHERAL_BUS.get().lock().await;
let i2c = i2c.as_mut().unwrap();
let _ = i2c
.write_read_async(MCU_ADDR, [REG_ID_BKL], &mut [brightness])
.await;
}
pub async fn get_lcd_backlight() -> u8 {
let mut i2c = PERIPHERAL_BUS.get().lock().await;
let i2c = i2c.as_mut().unwrap();
let mut buf = [0_u8; 2];
let _ = i2c.write_read_async(MCU_ADDR, [REG_ID_BKL], &mut buf).await;
buf[1]
}
const REG_ID_BK2: u8 = 0x0A;
pub async fn set_key_backlight(brightness: u8) {
let mut i2c = PERIPHERAL_BUS.get().lock().await;
let i2c = i2c.as_mut().unwrap();
let _ = i2c
.write_read_async(MCU_ADDR, [REG_ID_BK2], &mut [brightness])
.await;
}
pub async fn get_key_backlight() -> u8 {
let mut i2c = PERIPHERAL_BUS.get().lock().await;
let i2c = i2c.as_mut().unwrap();
let mut buf = [0_u8; 2];
let _ = i2c.write_read_async(MCU_ADDR, [REG_ID_BK2], &mut buf).await;
buf[1]
}
const REG_ID_BAT: u8 = 0x0b;
pub async fn get_battery() -> u8 {
let mut i2c = PERIPHERAL_BUS.get().lock().await;
let i2c = i2c.as_mut().unwrap();
let mut buf = [0_u8; 2];
let _ = i2c.write_read_async(MCU_ADDR, [REG_ID_BAT], &mut buf).await;
buf[1]
}