Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ chip. It currently contains three primary crates:
abstraction, and routes events from ISRs to the app.
- `ui-tauri`: Code to instantiate the app in a Tauri webview

## `cortex-m-stack`

The `cortex-m-stack` crate contains some basic tools for measuring stack usage
on STM32, leveraging the stack painting feature in the `cortex-m-rt` crate.

## CMOX

The `cmox` directory contains crates that enable the use of the STM
Expand Down
1 change: 1 addition & 0 deletions ui-app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ defmt = { version = "1.0.1", default-features = false }
embedded-graphics = "0.8.1"
embedded-graphics-core = { version = "0.4.0", default-features = false }
heapless = "0.9.1"
hex = { version = "0.4.3", default-features = false }
21 changes: 21 additions & 0 deletions ui-app/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use embedded_graphics::{
text::Text,
};
use heapless::String;
use hex::ToHex;

#[derive(Copy, Clone, Debug, PartialEq, Format)]
pub enum Key {
Expand Down Expand Up @@ -190,10 +191,16 @@ pub trait NetTx {
fn write(&mut self, to_net: &ToNet);
}

pub trait Eeprom {
fn read(&mut self, data: &mut [u8; 256]);
fn write(&mut self, data: &[u8; 256]);
}

pub trait Outputs {
fn status_led(&mut self) -> &mut impl Led;
fn screen(&mut self) -> &mut impl DrawTarget<Color = Rgb565>;
fn net_tx(&mut self) -> &mut impl NetTx;
fn eeprom(&mut self) -> impl Eeprom;
fn log(&mut self, message: &str);
}

Expand Down Expand Up @@ -221,6 +228,20 @@ impl App {
// Extinguish the status LED
out.status_led().set_color(Color::Black);

// Read the EEPROM, XOR with 0xFF, write it, then read it back
let mut eeprom_data = [0u8; 256];
out.eeprom().read(&mut eeprom_data);
let hex: heapless::String<1024> = eeprom_data.encode_hex();
defmt::info!("eeprom before {}", hex);

eeprom_data.iter_mut().for_each(|x| *x ^= 0xff);
out.eeprom().write(&eeprom_data);

eeprom_data.fill(0);
out.eeprom().read(&mut eeprom_data);
let hex: heapless::String<1024> = eeprom_data.encode_hex();
defmt::info!("eeprom after {}", hex);

// Draw a test pattern to the screen
let rect = out.screen().bounding_box();

Expand Down
25 changes: 25 additions & 0 deletions ui-app/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,32 @@ impl NetTx for MockNetTx {
}
}

struct MockEeprom {
data: [u8; 256],
}

impl Default for MockEeprom {
fn default() -> Self {
Self { data: [0; 256] }
}
}

impl Eeprom for &mut MockEeprom {
fn read(&mut self, data: &mut [u8; 256]) {
data.copy_from_slice(&self.data);
}

fn write(&mut self, data: &[u8; 256]) {
self.data.copy_from_slice(data);
}
}

#[derive(Default)]
struct MockOutputs {
status_led: MockLed,
screen: MockScreen,
net_tx: MockNetTx,
eeprom: MockEeprom,
last_message: String,
}

Expand All @@ -70,6 +91,10 @@ impl Outputs for MockOutputs {
&mut self.net_tx
}

fn eeprom(&mut self) -> impl Eeprom {
&mut self.eeprom
}

fn log(&mut self, message: &str) {
self.last_message = message.into();
}
Expand Down
3 changes: 2 additions & 1 deletion ui-stm32/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ defmt-rtt = "1.0.0"
embassy-executor = { version = "0.8.0", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] }
embassy-futures = { version = "0.1.1", features = ["defmt"] }
embassy-stm32 = { version = "0.3.0", features = ["defmt", "stm32f405rg", "memory-x", "time-driver-tim1", "exti", "chrono"] }
embassy-time = { version = "0.4.0", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
embassy-time = { version = "0.5.0", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
panic-probe = { version = "1.0.0", features = ["print-defmt"] }
embassy-sync = { version = "0.7.1", features = ["defmt"] }
num_enum = { version = "0.7.4", default-features = false }
Expand All @@ -34,6 +34,7 @@ display-interface = "0.5.0"
embedded-graphics-core = { version = "0.4.0", default-features = false }
bitmap-font = "0.3.0"
embedded-graphics = "0.8.1"
embedded-hal = { version = "1.0.0", default-features = false }

[profile.release]
debug = 2
Expand Down
17 changes: 14 additions & 3 deletions ui-stm32/src/board/ev12.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use super::{Button, Keyboard, NetTx, StatusLed};
use super::{Button, Eeprom, Keyboard, NetTx, StatusLed};
use display_interface::{DataFormat, DisplayError, WriteOnlyDataCommand};
use embassy_stm32::{
bind_interrupts,
exti::ExtiInput,
gpio::{Input, Level, Output, Pull, Speed},
i2c::{mode::Master, I2c},
mode::{Async, Blocking},
peripherals,
spi::{Spi, Word},
Expand Down Expand Up @@ -65,15 +66,16 @@ impl DisplayData {
&mut self,
iter: &mut dyn Iterator<Item = W>,
) -> Result<(), DisplayError> {
const CHUNK_SIZE: usize = 128;
// 1kb of render buffer
const CHUNK_SIZE: usize = 512;

// XXX(RLB) Very C-style iteration, could probably write this in a way that would optimize
// better.
let mut data = [W::default(); CHUNK_SIZE];
let mut n = 0;
for (i, x) in iter.enumerate() {
data[i % CHUNK_SIZE] = x;
n = i + 1;
n += 1;

if n > 0 && n % CHUNK_SIZE == 0 {
self.spi.blocking_write(&data).unwrap();
Expand Down Expand Up @@ -102,6 +104,7 @@ pub struct Board {
status_led: StatusLed,
screen: Ili9341<DisplayData, Output<'static>>,
net_tx: NetTx<UartTx<'static, Async>>,
i2c: I2c<'static, Blocking, Master>,
pub button_a: Option<Button>,
pub button_b: Option<Button>,
pub keyboard: Option<Keyboard>,
Expand Down Expand Up @@ -215,10 +218,14 @@ impl Board {
let (net_tx, net_rx) = net_uart.split();
let net_tx = NetTx::new(net_tx);

// I2C interface for EEPROM and audio chip control
let i2c = I2c::new_blocking(p.I2C1, p.PB6, p.PB7, Default::default());

Self {
status_led,
screen,
net_tx,
i2c,
button_a: Some(button_a),
button_b: Some(button_b),
keyboard: Some(keyboard),
Expand All @@ -240,6 +247,10 @@ impl Outputs for Board {
&mut self.net_tx
}

fn eeprom(&mut self) -> impl ui_app::Eeprom {
Eeprom { i2c: &mut self.i2c }
}

fn log(&mut self, message: &str) {
defmt::info!("{}", message);
}
Expand Down
12 changes: 11 additions & 1 deletion ui-stm32/src/board/ev13.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use super::{Button, Keyboard, NetTx, StatusLed};
use super::{Button, Eeprom, Keyboard, NetTx, StatusLed};
use display_interface::{DataFormat, DisplayError, WriteOnlyDataCommand};
use embassy_stm32::{
bind_interrupts,
exti::ExtiInput,
gpio::{Input, Level, Output, Pull, Speed},
i2c::{mode::Master, I2c},
mode::{Async, Blocking},
peripherals,
spi::{Spi, Word},
Expand Down Expand Up @@ -103,6 +104,7 @@ pub struct Board {
status_led: StatusLed,
screen: Ili9341<DisplayData, Output<'static>>,
net_tx: NetTx<UartTx<'static, Async>>,
i2c: I2c<'static, Blocking, Master>,
pub button_a: Option<Button>,
pub button_b: Option<Button>,
pub keyboard: Option<Keyboard>,
Expand Down Expand Up @@ -216,10 +218,14 @@ impl Board {
let (net_tx, net_rx) = net_uart.split();
let net_tx = NetTx::new(net_tx);

// I2C interface for EEPROM and audio chip control
let i2c = I2c::new_blocking(p.I2C1, p.PB6, p.PB7, Default::default());

Self {
status_led,
screen,
net_tx,
i2c,
button_a: Some(button_a),
button_b: Some(button_b),
keyboard: Some(keyboard),
Expand All @@ -241,6 +247,10 @@ impl Outputs for Board {
&mut self.net_tx
}

fn eeprom(&mut self) -> impl ui_app::Eeprom {
Eeprom { i2c: &mut self.i2c }
}

fn log(&mut self, message: &str) {
defmt::info!("{}", message);
}
Expand Down
47 changes: 46 additions & 1 deletion ui-stm32/src/board/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,14 @@ pub use ev12::*;
pub use ev13::*;

// Provide some common functionality
use embassy_stm32::{exti::ExtiInput, gpio::Output};
use embassy_stm32::{
exti::ExtiInput,
gpio::Output,
i2c::{mode::Master, I2c},
mode::Blocking,
};
use embassy_time::Delay;
use embedded_hal::delay::DelayNs;
use ui_app::Led;

mod keyboard;
Expand All @@ -45,3 +52,41 @@ impl Led for StatusLed {
}

pub type Button = ExtiInput<'static>;

struct Eeprom<'a> {
i2c: &'a mut I2c<'static, Blocking, Master>,
}

impl Eeprom<'_> {
const I2C_ADDR: u8 = 0x50;
}

impl<'a> ui_app::Eeprom for Eeprom<'a> {
fn read(&mut self, data: &mut [u8; 256]) {
const START_ADDR: u8 = 0;
self.i2c
.blocking_write_read(Self::I2C_ADDR, &[START_ADDR], data)
.unwrap();
}

fn write(&mut self, data: &[u8; 256]) {
// EEPROM allows us to write 16 bytes at a time. The first byte in the write is the start
// address.
const CHUNK_SIZE: usize = 16;
let mut write_data = [0; CHUNK_SIZE + 1];
for start in (0_usize..0xff).step_by(16) {
write_data[0] = start as u8;
write_data[1..].copy_from_slice(&data[start..(start + CHUNK_SIZE)]);

self.i2c
.blocking_write(Self::I2C_ADDR, &write_data)
.unwrap();

// XXX(RLB) The chip takes some time to write. The datasheet says there's a way to
// poll on ACK so that you don't have to wait, but it's not clear that this can be
// implemented through the abstractions we have. These operations are not
// time-sensitive, so a delay should be fine.
Delay.delay_ns(10_000_000);
}
}
}
2 changes: 2 additions & 0 deletions ui-stm32/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,11 @@ async fn main(spawner: Spawner) {
debug!("app start");
app.start(&mut board);

/*
// Main event loop
loop {
let event = EVENT_QUEUE.receive().await;
app.handle(event, &mut board);
}
*/
}
Loading