firmware: add firmware
This commit is contained in:
Vendored
+4
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"rust-analyzer.check.allTargets": false,
|
||||||
|
"rust-analyzer.check.targets": "thumbv7em-none-eabihf"
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
[target.thumbv7em-none-eabihf]
|
||||||
|
runner = 'probe-rs run --chip STM32F411CEUx'
|
||||||
|
|
||||||
|
[build]
|
||||||
|
target = "thumbv7em-none-eabihf"
|
||||||
|
|
||||||
|
[env]
|
||||||
|
DEFMT_LOG = "debug"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/target
|
||||||
Generated
+1057
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,48 @@
|
|||||||
|
[package]
|
||||||
|
name = "inkclip-firmware"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
embassy-stm32 = { version = "0.2", features = [
|
||||||
|
"defmt",
|
||||||
|
"stm32f411ce",
|
||||||
|
"memory-x",
|
||||||
|
"time-driver-any",
|
||||||
|
"exti",
|
||||||
|
] }
|
||||||
|
embassy-sync = { version = "0.6", features = ["defmt"] }
|
||||||
|
embassy-executor = { version = "0.7", features = [
|
||||||
|
"nightly",
|
||||||
|
"arch-cortex-m",
|
||||||
|
"executor-thread",
|
||||||
|
"executor-interrupt",
|
||||||
|
"defmt",
|
||||||
|
] }
|
||||||
|
embassy-time = { version = "0.4", features = [
|
||||||
|
"defmt",
|
||||||
|
"defmt-timestamp-uptime",
|
||||||
|
"tick-hz-32_768",
|
||||||
|
] }
|
||||||
|
embassy-usb = { version = "0.4", features = ["defmt"] }
|
||||||
|
embassy-futures = { version = "0.1", features = ["defmt"] }
|
||||||
|
|
||||||
|
defmt = "1.0"
|
||||||
|
defmt-rtt = "1.0"
|
||||||
|
|
||||||
|
cortex-m = { version = "0.7", features = [
|
||||||
|
"inline-asm",
|
||||||
|
"critical-section-single-core",
|
||||||
|
] }
|
||||||
|
cortex-m-rt = "0.7"
|
||||||
|
embedded-hal-bus = { version = "0.3", features = ["async"] }
|
||||||
|
panic-probe = { version = "1.0", features = ["print-defmt"] }
|
||||||
|
critical-section = "1.1"
|
||||||
|
|
||||||
|
epd-waveshare = "0.6"
|
||||||
|
embedded-graphics = "0.8"
|
||||||
|
usbd-hid = "0.8"
|
||||||
|
base64 = { version = "0.22", default-features = false }
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
opt-level = "z"
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
fn main() {
|
||||||
|
println!("cargo:rustc-link-arg-bins=--nmagic");
|
||||||
|
println!("cargo:rustc-link-arg-bins=-Tlink.x");
|
||||||
|
println!("cargo:rustc-link-arg-bins=-Tdefmt.x");
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
MEMORY
|
||||||
|
{
|
||||||
|
/* NOTE K = KiBi = 1024 bytes */
|
||||||
|
FLASH : ORIGIN = 0x08000000, LENGTH = 512K
|
||||||
|
RAM : ORIGIN = 0x20000000, LENGTH = 128K
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This is where the call stack will be allocated. */
|
||||||
|
/* The stack is of the full descending type. */
|
||||||
|
/* NOTE Do NOT modify `_stack_start` unless you know what you are doing */
|
||||||
|
_stack_start = ORIGIN(RAM) + LENGTH(RAM);
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
use usbd_hid::descriptor::generator_prelude::*;
|
||||||
|
|
||||||
|
// Watch out the terminology!
|
||||||
|
// - An "output" report is a report from the host to the device (as the device is the one doing the output);
|
||||||
|
// - An "input" report is a report from the device to the host (as it is presumably triggered by user input).
|
||||||
|
#[gen_hid_descriptor(
|
||||||
|
(usage_page = VENDOR_DEFINED_END, usage = 0x01, collection = APPLICATION) = {
|
||||||
|
(report_id = 0x01, usage = 0x02) = {
|
||||||
|
write_pattern = output;
|
||||||
|
};
|
||||||
|
(report_id = 0x02, usage = 0x03) = {
|
||||||
|
serial_number_request = output;
|
||||||
|
};
|
||||||
|
(report_id = 0x02, usage = 0x03) = {
|
||||||
|
serial_number_response = input;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)]
|
||||||
|
pub struct HIDReports {
|
||||||
|
write_pattern: [u8; 5000],
|
||||||
|
serial_number_request: u8,
|
||||||
|
serial_number_response: [u8; 16],
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
use defmt::*;
|
||||||
|
|
||||||
|
use embedded_graphics::prelude::*;
|
||||||
|
use epd_waveshare::epd1in54_v2::Display1in54;
|
||||||
|
use epd_waveshare::prelude::*;
|
||||||
|
|
||||||
|
use crate::device::epd::Epd;
|
||||||
|
use crate::device::usb_hid::Hid;
|
||||||
|
use crate::util::{uid_base64, uid_base64_bytes};
|
||||||
|
|
||||||
|
pub mod descriptor;
|
||||||
|
|
||||||
|
pub const USB_READ_BUFFER_SIZE: usize = 8192;
|
||||||
|
pub const USB_WRITE_BUFFER_SIZE: usize = 32;
|
||||||
|
|
||||||
|
const WRITE_PATTERN_REPORT_ID: u8 = 0x01;
|
||||||
|
const SERIAL_NUMBER_REPORT_ID: u8 = 0x02;
|
||||||
|
|
||||||
|
fn color_at(byte: u8, ix: i32) -> Color {
|
||||||
|
defmt::debug_assert!(0 <= ix && ix < 8);
|
||||||
|
|
||||||
|
if byte & (1u8 << ix) == 0 {
|
||||||
|
Color::White
|
||||||
|
} else {
|
||||||
|
Color::Black
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MainLoop<'a> {
|
||||||
|
pub hid: Hid<'a, USB_READ_BUFFER_SIZE, USB_WRITE_BUFFER_SIZE>,
|
||||||
|
pub epd: Epd,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> MainLoop<'a> {
|
||||||
|
fn draw_pattern(&mut self, buf: &[u8]) {
|
||||||
|
debug!("Updating display");
|
||||||
|
|
||||||
|
let mut buf_size = buf.len();
|
||||||
|
|
||||||
|
if buf_size < 5000 {
|
||||||
|
warn!("WRITE_PATTERN buffer too short (buf_size = {})", buf_size);
|
||||||
|
} else if buf_size > 5000 {
|
||||||
|
warn!(
|
||||||
|
"WRITE_PATTERN buffer too long; truncating (buf_size = {})",
|
||||||
|
buf_size
|
||||||
|
);
|
||||||
|
buf_size = 5000;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut display = Display1in54::default();
|
||||||
|
display.set_rotation(DisplayRotation::Rotate180);
|
||||||
|
let width = display.size().width as i32;
|
||||||
|
|
||||||
|
for (stride, &byte) in buf[..buf_size].iter().enumerate() {
|
||||||
|
for stroll in 0..8 {
|
||||||
|
let ix = stride as i32 * 8 + stroll;
|
||||||
|
let x = ix % width;
|
||||||
|
let y = ix / width;
|
||||||
|
display.set_pixel(Pixel(Point { x, y }, color_at(byte, stroll)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.epd.update_display(&display);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_serial_number(&mut self) {
|
||||||
|
debug!("Sending serial number {}", uid_base64());
|
||||||
|
|
||||||
|
self.hid
|
||||||
|
.write(SERIAL_NUMBER_REPORT_ID, uid_base64_bytes())
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run(&mut self) -> ! {
|
||||||
|
let mut read_buf = [0u8; USB_READ_BUFFER_SIZE];
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let (id, buf) = self.hid.read(&mut read_buf).await;
|
||||||
|
match id {
|
||||||
|
WRITE_PATTERN_REPORT_ID => self.draw_pattern(buf),
|
||||||
|
SERIAL_NUMBER_REPORT_ID => self.send_serial_number().await,
|
||||||
|
_ => warn!("Unknown report ID: {}", buf[0]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
use embassy_stm32::gpio::{self, Level, Pull, Speed};
|
||||||
|
use embassy_stm32::spi::{self, MosiPin, SckPin, Spi};
|
||||||
|
use embassy_stm32::time::Hertz;
|
||||||
|
use embassy_time::Delay;
|
||||||
|
use embedded_hal_bus::spi::ExclusiveDevice;
|
||||||
|
|
||||||
|
use epd_waveshare::epd1in54::Display1in54;
|
||||||
|
use epd_waveshare::epd1in54_v2::Epd1in54;
|
||||||
|
use epd_waveshare::prelude::*;
|
||||||
|
|
||||||
|
type EpdSpiDevice =
|
||||||
|
ExclusiveDevice<Spi<'static, embassy_stm32::mode::Blocking>, gpio::Output<'static>, Delay>;
|
||||||
|
|
||||||
|
pub struct Epd {
|
||||||
|
spi_device: EpdSpiDevice,
|
||||||
|
epd: Epd1in54<
|
||||||
|
EpdSpiDevice,
|
||||||
|
gpio::Input<'static>,
|
||||||
|
gpio::Output<'static>,
|
||||||
|
gpio::Output<'static>,
|
||||||
|
Delay,
|
||||||
|
>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Epd {
|
||||||
|
pub fn new<SPI: spi::Instance>(
|
||||||
|
spi: SPI,
|
||||||
|
cs: impl gpio::Pin,
|
||||||
|
sck: impl SckPin<SPI>,
|
||||||
|
mosi: impl MosiPin<SPI>,
|
||||||
|
busy: impl gpio::Pin,
|
||||||
|
dc: impl gpio::Pin,
|
||||||
|
rst: impl gpio::Pin,
|
||||||
|
) -> Epd {
|
||||||
|
let cs_pin = gpio::Output::new(cs, Level::High, Speed::VeryHigh);
|
||||||
|
let busy_pin = gpio::Input::new(busy, Pull::Down);
|
||||||
|
let dc_pin = gpio::Output::new(dc, Level::High, Speed::VeryHigh);
|
||||||
|
let rst_pin = gpio::Output::new(rst, Level::High, Speed::VeryHigh);
|
||||||
|
|
||||||
|
let mut spi_config = spi::Config::default();
|
||||||
|
spi_config.frequency = Hertz(20_000_000);
|
||||||
|
let spi_bus = Spi::new_blocking_txonly(spi, sck, mosi, spi_config);
|
||||||
|
let mut spi_device = ExclusiveDevice::new(spi_bus, cs_pin, Delay {}).unwrap();
|
||||||
|
|
||||||
|
let epd = Epd1in54::new(
|
||||||
|
&mut spi_device,
|
||||||
|
busy_pin,
|
||||||
|
dc_pin,
|
||||||
|
rst_pin,
|
||||||
|
&mut Delay {},
|
||||||
|
Some(0),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
return Epd { spi_device, epd };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_display(&mut self, display: &Display1in54) {
|
||||||
|
self.epd
|
||||||
|
.update_and_display_frame(&mut self.spi_device, display.buffer(), &mut Delay {})
|
||||||
|
.unwrap_or(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
pub mod epd;
|
||||||
|
pub mod usb_hid;
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
use defmt::*;
|
||||||
|
|
||||||
|
use embassy_stm32::peripherals::USB_OTG_FS;
|
||||||
|
use embassy_stm32::usb::{self, DmPin, DpPin};
|
||||||
|
use embassy_usb::class::hid::{self, HidReader, HidReaderWriter, HidWriter};
|
||||||
|
use embassy_usb::driver::EndpointError;
|
||||||
|
|
||||||
|
use crate::init::Irqs;
|
||||||
|
|
||||||
|
pub type UsbBuilder<'d> = embassy_usb::Builder<'d, usb::Driver<'d, USB_OTG_FS>>;
|
||||||
|
|
||||||
|
pub fn get_usb_builder<'d>(
|
||||||
|
usb_peri: USB_OTG_FS,
|
||||||
|
dp: impl DpPin<USB_OTG_FS>,
|
||||||
|
dm: impl DmPin<USB_OTG_FS>,
|
||||||
|
ep_out_buffer: &'d mut [u8],
|
||||||
|
driver_config: usb::Config,
|
||||||
|
builder_config: embassy_usb::Config<'d>,
|
||||||
|
config_descriptor: &'d mut [u8],
|
||||||
|
bos_descriptor: &'d mut [u8],
|
||||||
|
msos_descriptor: &'d mut [u8],
|
||||||
|
control_buf: &'d mut [u8],
|
||||||
|
) -> UsbBuilder<'d> {
|
||||||
|
let driver = usb::Driver::new_fs(usb_peri, Irqs, dp, dm, ep_out_buffer, driver_config);
|
||||||
|
|
||||||
|
return embassy_usb::Builder::new(
|
||||||
|
driver,
|
||||||
|
builder_config,
|
||||||
|
config_descriptor,
|
||||||
|
bos_descriptor,
|
||||||
|
msos_descriptor,
|
||||||
|
control_buf,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Hid<'d, const READ_N: usize, const WRITE_N: usize> {
|
||||||
|
reader: HidReader<'d, usb::Driver<'d, USB_OTG_FS>, READ_N>,
|
||||||
|
writer: HidWriter<'d, usb::Driver<'d, USB_OTG_FS>, WRITE_N>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'d, const READ_N: usize, const WRITE_N: usize> Hid<'d, READ_N, WRITE_N> {
|
||||||
|
pub fn new(
|
||||||
|
usb_builder: &mut UsbBuilder<'d>,
|
||||||
|
report_descriptor: &'d [u8],
|
||||||
|
state: &'d mut hid::State<'d>,
|
||||||
|
) -> Hid<'d, READ_N, WRITE_N> {
|
||||||
|
let hid_config = hid::Config {
|
||||||
|
report_descriptor,
|
||||||
|
request_handler: None,
|
||||||
|
poll_ms: 10,
|
||||||
|
max_packet_size: 64,
|
||||||
|
};
|
||||||
|
|
||||||
|
let (reader, writer) = HidReaderWriter::new(usb_builder, state, hid_config).split();
|
||||||
|
return Hid { reader, writer };
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn internal_read<'a, const USE_ID: bool>(
|
||||||
|
&mut self,
|
||||||
|
buf: &'a mut [u8; READ_N],
|
||||||
|
) -> (u8, &'a [u8]) {
|
||||||
|
loop {
|
||||||
|
match self.reader.read(buf).await {
|
||||||
|
Ok(len) => {
|
||||||
|
if USE_ID {
|
||||||
|
if len < 1 {
|
||||||
|
warn!("Received no report ID")
|
||||||
|
} else {
|
||||||
|
return (buf[0], &buf[1..len]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return (0, &buf[..len]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(hid::ReadError::BufferOverflow) => {
|
||||||
|
warn!("Buffer overflow when reading from host")
|
||||||
|
}
|
||||||
|
Err(hid::ReadError::Disabled) => {
|
||||||
|
info!("Endpoint disabled");
|
||||||
|
self.reader.ready().await
|
||||||
|
}
|
||||||
|
Err(hid::ReadError::Sync(_)) => defmt::unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn read<'a>(&mut self, buf: &'a mut [u8; READ_N]) -> (u8, &'a [u8]) {
|
||||||
|
self.internal_read::<true>(buf).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub async fn read_no_id<'a>(&mut self, buf: &'a mut [u8; READ_N]) -> &'a [u8] {
|
||||||
|
self.internal_read::<false>(buf).await.1
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn write(&mut self, id: u8, report: &[u8]) {
|
||||||
|
let payload_size = report.len() + 1;
|
||||||
|
|
||||||
|
if payload_size > WRITE_N {
|
||||||
|
error!(
|
||||||
|
"Buffer overflow when writing to host (report_size = {})",
|
||||||
|
payload_size
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut payload = [id; WRITE_N];
|
||||||
|
payload[1..payload_size].copy_from_slice(report);
|
||||||
|
|
||||||
|
self.writer
|
||||||
|
.write(&payload[..payload_size])
|
||||||
|
.await
|
||||||
|
.unwrap_or_else(|err| match err {
|
||||||
|
EndpointError::BufferOverflow => error!("Buffer overflow when writing to host"),
|
||||||
|
EndpointError::Disabled => warn!("Endpoint disabled"),
|
||||||
|
});
|
||||||
|
|
||||||
|
info!("Written report #{}: {}", id, report);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
use embassy_stm32::bind_interrupts;
|
||||||
|
use embassy_stm32::peripherals::USB_OTG_FS;
|
||||||
|
use embassy_stm32::time::Hertz;
|
||||||
|
use embassy_stm32::usb;
|
||||||
|
use embassy_stm32::{Config, Peripherals};
|
||||||
|
|
||||||
|
bind_interrupts!(pub struct Irqs {
|
||||||
|
OTG_FS => usb::InterruptHandler<USB_OTG_FS>;
|
||||||
|
});
|
||||||
|
|
||||||
|
pub fn init_peripherals() -> Peripherals {
|
||||||
|
let mut config = Config::default();
|
||||||
|
{
|
||||||
|
use embassy_stm32::rcc::*;
|
||||||
|
config.rcc.hse = Some(Hse {
|
||||||
|
freq: Hertz(12_000_000),
|
||||||
|
mode: HseMode::Oscillator,
|
||||||
|
});
|
||||||
|
config.rcc.pll_src = PllSource::HSE;
|
||||||
|
config.rcc.pll = Some(Pll {
|
||||||
|
prediv: PllPreDiv::DIV12, // 12 / 12 = 1 MHz
|
||||||
|
mul: PllMul::MUL192, // 1 * 192 = 192 MHz
|
||||||
|
divp: Some(PllPDiv::DIV2), // 192 / 2 = 96 MHz <= 100 MHz max freq
|
||||||
|
divq: Some(PllQDiv::DIV4), // 192 / 4 = 48 MHz necessary for USB
|
||||||
|
divr: None,
|
||||||
|
});
|
||||||
|
config.rcc.ahb_pre = AHBPrescaler::DIV1;
|
||||||
|
config.rcc.apb1_pre = APBPrescaler::DIV2;
|
||||||
|
config.rcc.apb2_pre = APBPrescaler::DIV4;
|
||||||
|
config.rcc.sys = Sysclk::PLL1_P;
|
||||||
|
config.rcc.mux.clk48sel = mux::Clk48sel::PLL1_Q;
|
||||||
|
}
|
||||||
|
|
||||||
|
return embassy_stm32::init(config);
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
#![no_main]
|
||||||
|
#![no_std]
|
||||||
|
#![feature(impl_trait_in_assoc_type)]
|
||||||
|
|
||||||
|
use defmt::*;
|
||||||
|
use embassy_executor::Spawner;
|
||||||
|
use embassy_futures::join::join;
|
||||||
|
|
||||||
|
use device::epd::Epd;
|
||||||
|
use device::usb_hid::Hid;
|
||||||
|
use device::usb_hid::get_usb_builder;
|
||||||
|
use embassy_stm32::usb;
|
||||||
|
use embassy_usb::UsbVersion;
|
||||||
|
use embassy_usb::class::hid;
|
||||||
|
use usbd_hid::descriptor::generator_prelude::*;
|
||||||
|
|
||||||
|
use init::init_peripherals;
|
||||||
|
use util::uid_base64;
|
||||||
|
|
||||||
|
use app::MainLoop;
|
||||||
|
use app::descriptor::HIDReports;
|
||||||
|
|
||||||
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
|
|
||||||
|
mod app;
|
||||||
|
mod device;
|
||||||
|
mod init;
|
||||||
|
mod util;
|
||||||
|
|
||||||
|
#[embassy_executor::main]
|
||||||
|
async fn main(_spawner: Spawner) {
|
||||||
|
let peri = init_peripherals();
|
||||||
|
|
||||||
|
info!("Initialized peripherals");
|
||||||
|
|
||||||
|
let epd = Epd::new(
|
||||||
|
peri.SPI1, peri.PA4, peri.PA5, peri.PA7, peri.PB10, peri.PB0, peri.PB1,
|
||||||
|
);
|
||||||
|
|
||||||
|
info!("Established SPI link to e-paper display");
|
||||||
|
|
||||||
|
let mut ep_out_buffer = [0u8; 256];
|
||||||
|
let mut config_descriptor = [0; 256];
|
||||||
|
let mut control_buf = [0; 256];
|
||||||
|
let mut state = hid::State::new();
|
||||||
|
|
||||||
|
let mut builder_config = embassy_usb::Config::new(0x1209, 0xc9c9);
|
||||||
|
builder_config.manufacturer = Some("daylily");
|
||||||
|
builder_config.product = Some("“Inkclip” 1.54″ ePaper Accessory");
|
||||||
|
builder_config.serial_number = Some(uid_base64());
|
||||||
|
|
||||||
|
// Caution: past USB 2.0 (including USB 2.1 aka 2.0 LPM), BOS descriptors become necessary
|
||||||
|
builder_config.bcd_usb = UsbVersion::Two;
|
||||||
|
|
||||||
|
let mut builder = get_usb_builder(
|
||||||
|
peri.USB_OTG_FS,
|
||||||
|
peri.PA12,
|
||||||
|
peri.PA11,
|
||||||
|
&mut ep_out_buffer,
|
||||||
|
usb::Config::default(),
|
||||||
|
builder_config,
|
||||||
|
&mut config_descriptor,
|
||||||
|
&mut [], // USB 2.0 does not require BOS descriptors
|
||||||
|
&mut [], // Non vendor-specific devices/interfaces do not require MS OS descriptors
|
||||||
|
&mut control_buf,
|
||||||
|
);
|
||||||
|
|
||||||
|
let usb_hid = Hid::new(&mut builder, HIDReports::desc(), &mut state);
|
||||||
|
|
||||||
|
let mut main_loop = MainLoop { epd, hid: usb_hid };
|
||||||
|
let main_fut = main_loop.run();
|
||||||
|
|
||||||
|
let mut usb_device = builder.build();
|
||||||
|
let usb_fut = usb_device.run();
|
||||||
|
|
||||||
|
info!("Initialized USB HID device");
|
||||||
|
|
||||||
|
join(main_fut, usb_fut).await;
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
use base64::prelude::*;
|
||||||
|
use embassy_stm32::uid::uid;
|
||||||
|
|
||||||
|
pub fn uid_base64() -> &'static str {
|
||||||
|
unsafe { core::str::from_utf8_unchecked(uid_base64_bytes()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(static_mut_refs)]
|
||||||
|
pub fn uid_base64_bytes() -> &'static [u8] {
|
||||||
|
static mut UID_BASE64: [u8; 16] = [0; 16];
|
||||||
|
static mut LOADED: bool = false;
|
||||||
|
critical_section::with(|_| unsafe {
|
||||||
|
if !LOADED {
|
||||||
|
let uid = uid();
|
||||||
|
BASE64_STANDARD.encode_slice(uid, &mut UID_BASE64).unwrap();
|
||||||
|
LOADED = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
unsafe { &*core::ptr::addr_of!(UID_BASE64) }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user