Reorganize to support Pico

This commit is contained in:
Aode (Lion) 2022-02-26 16:55:26 -06:00
parent ccd5b0e741
commit f6202c3b16
22 changed files with 516 additions and 54 deletions

View file

@ -1,45 +1,7 @@
[package]
authors = ["asonix <asonix@asonix.dog>"]
edition = "2018"
readme = "README.md"
name = "trinket-streamdeck"
version = "0.1.0"
build = "src/build.rs"
[dependencies]
apa102-spi = "0.3.2"
atsamd-hal = { version = "0.14", default-features = false, features = [
"samd21e",
"samd21e-rt",
"unproven",
"usb",
] }
bitbang-hal = "0.3.2"
cortex-m = "0.7.4"
cortex-m-rt = "0.7.1"
cortex-m-semihosting = "0.3.3"
embedded-hal = "0.2"
once_cell = { version = "1.7.2", default-features = false }
panic-halt = "0.2.0"
smart-leds = "0.3"
usb-device = "0.2"
usbd-serial = "0.1"
streamdeck-common = { version = "0.1.0", path = "./streamdeck-common" }
[build-dependencies]
anyhow = "1.0"
toml = "0.5.8"
# this lets you use `cargo fix`!
[[bin]]
name = "trinket-streamdeck"
test = false
bench = false
[workspace]
members = ["trinket", "common", "pico"]
[profile.release]
codegen-units = 1 # better optimizations
debug = true # symbols are nice and they don't increase the size on Flash
lto = true # better optimizations
[workspace]
members = ["./streamdeck-common"]

View file

@ -1,8 +1,8 @@
[package]
name = "streamdeck-common"
name = "common"
version = "0.1.0"
authors = ["asonix <asonix@asonix.dog>"]
edition = "2018"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

31
pico/Cargo.toml Normal file
View file

@ -0,0 +1,31 @@
[package]
name = "pico-streamdeck"
version = "0.1.0"
authors = ["asonix <asonix@asonix.dog>"]
readme = "../README.md"
edition = "2021"
build = "src/build.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
common = { version = "0.1.0", path = "../common" }
cortex-m = "0.7"
cortex-m-rt = { version = "0.7", features = ["device"] }
embedded-hal = "0.2"
once_cell = { version = "1.7.2", default-features = false }
panic-halt = "0.2.0"
rp2040-hal = { version = "0.3.0", features = ["rt"] }
# rp-pico = "0.2.0"
usb-device = "0.2"
usbd-serial = "0.1"
[build-dependencies]
anyhow = "1.0"
toml = "0.5.8"
# this lets you use `cargo fix`!
[[bin]]
name = "pico-streamdeck"
test = false
bench = false

47
pico/src/bsp.rs Normal file
View file

@ -0,0 +1,47 @@
//// The linker will place this boot block at the start of our program image. We
//// need this to help the ROM bootloader get our code up and running.
#[cfg(feature = "boot2")]
#[link_section = ".boot2"]
#[no_mangle]
#[used]
pub static BOOT2_FIRMWARE: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080;
pub use cortex_m_rt::entry;
pub use rp2040_hal::pac;
rp2040_hal::bsp_pins!(
Gpio0 { name: gpio0 },
Gpio1 { name: gpio1 },
Gpio2 { name: gpio2 },
Gpio3 { name: gpio3 },
Gpio4 { name: gpio4 },
Gpio5 { name: gpio5 },
Gpio6 { name: gpio6 },
Gpio7 { name: gpio7 },
Gpio8 { name: gpio8 },
Gpio9 { name: gpio9 },
Gpio10 { name: gpio10 },
Gpio11 { name: gpio11 },
Gpio12 { name: gpio12 },
Gpio13 { name: gpio13 },
Gpio14 { name: gpio14 },
Gpio15 { name: gpio15 },
Gpio16 { name: gpio16 },
Gpio17 { name: gpio17 },
Gpio18 { name: gpio18 },
Gpio19 { name: gpio19 },
Gpio20 { name: gpio20 },
Gpio21 { name: gpio21 },
Gpio22 { name: gpio22 },
Gpio23 { name: b_power_save },
Gpio24 { name: vbus_detect },
Gpio25 { name: led },
Gpio26 { name: gpio26 },
Gpio27 { name: gpio27 },
Gpio28 { name: gpio28 },
Gpio29 {
name: voltage_monitor
},
);
pub const XOSC_CRYSTAL_FREQ: u32 = 12_000_000;

41
pico/src/buttons.rs Normal file
View file

@ -0,0 +1,41 @@
use common::{ButtonPins, ButtonState};
use core::convert::Infallible;
use embedded_hal::digital::v2::InputPin;
use rp2040_hal::gpio::pin::{
bank0::{Gpio0, Gpio1, Gpio2, Gpio3, Gpio4},
Disabled, Input, Pin, PullDown, PullUp,
};
pub(crate) struct Buttons {
pub(crate) d0: Pin<Gpio0, Disabled<PullDown>>,
pub(crate) d1: Pin<Gpio1, Disabled<PullDown>>,
pub(crate) d2: Pin<Gpio2, Disabled<PullDown>>,
pub(crate) d3: Pin<Gpio3, Disabled<PullDown>>,
pub(crate) d4: Pin<Gpio4, Disabled<PullDown>>,
}
pub(crate) struct Pins {
d0: Pin<Gpio0, Input<PullUp>>,
d1: Pin<Gpio1, Input<PullUp>>,
d2: Pin<Gpio2, Input<PullUp>>,
d3: Pin<Gpio3, Input<PullUp>>,
d4: Pin<Gpio4, Input<PullUp>>,
}
impl<'a> ButtonPins<'a, 5> for Pins {
fn to_array(&'a self) -> [&'a dyn InputPin<Error = Infallible>; 5] {
[&self.d0, &self.d1, &self.d2, &self.d3, &self.d4]
}
}
impl Buttons {
pub(crate) fn init(self) -> ButtonState<Pins, 5> {
ButtonState::from_pins(Pins {
d0: self.d0.into_mode(),
d1: self.d1.into_mode(),
d2: self.d2.into_mode(),
d3: self.d3.into_mode(),
d4: self.d4.into_mode(),
})
}
}

View file

@ -0,0 +1,70 @@
//! Blatently stolen from https://github.com/korken89/pico-probe/blob/master/src/device_signature.rs
use rp2040_hal::pac::{IO_QSPI, XIP_SSI};
#[inline(always)]
#[link_section = ".data.ram_func"]
unsafe fn set_cs(level: bool) {
(*IO_QSPI::ptr()).gpio_qspiss.gpio_ctrl.modify(|_, w| {
if level {
w.outover().high()
} else {
w.outover().low()
}
});
}
#[link_section = ".data.ram_func"]
#[inline(never)]
unsafe fn do_flash_cmd(txrxbuf: &mut [u8]) {
// Load important addresses to the stack
let connect_internal_flash = rp2040_hal::rom_data::connect_internal_flash;
let flash_exit_xip = rp2040_hal::rom_data::flash_exit_xip;
let flash_flush_cache = rp2040_hal::rom_data::flash_flush_cache;
let mut boot2: core::mem::MaybeUninit<[u8; 256]> = core::mem::MaybeUninit::uninit();
let xip_base = 0x10000000 as *const u32;
rp2040_hal::rom_data::memcpy(boot2.as_mut_ptr() as _, xip_base as _, 256);
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
connect_internal_flash();
flash_exit_xip();
set_cs(false);
let ssi = &*XIP_SSI::ptr();
for b in txrxbuf {
while !ssi.sr.read().tfnf().bit_is_set() {}
ssi.dr0.write(|w| w.dr().bits(*b as _));
while !ssi.sr.read().rfne().bit_is_set() {}
*b = ssi.dr0.read().dr().bits() as _;
}
set_cs(true);
flash_flush_cache();
let ptr = (boot2.as_mut_ptr() as *const u8).add(1) as *const ();
let start: extern "C" fn() = core::mem::transmute(ptr);
start();
}
pub fn read_uid() -> [u8; 8] {
const FLASH_RUID_CMD: u8 = 0x4b;
const FLASH_RUID_DUMMY_BYTES: usize = 4;
const FLASH_RUID_DATA_BYTES: usize = 8;
const FLASH_RUID_TOTAL_BYTES: usize = 1 + FLASH_RUID_DUMMY_BYTES + FLASH_RUID_DATA_BYTES;
let mut buf = [0; FLASH_RUID_TOTAL_BYTES];
buf[0] = FLASH_RUID_CMD;
unsafe {
do_flash_cmd(&mut buf);
}
buf[FLASH_RUID_DUMMY_BYTES + 1..].try_into().unwrap()
}

216
pico/src/main.rs Normal file
View file

@ -0,0 +1,216 @@
#![no_std]
#![no_main]
use panic_halt as _;
use common::{handle_input, Handshake, UnsafeSync};
use core::cell::RefCell;
use cortex_m::{asm::delay as cycle_delay, interrupt::Mutex, peripheral::NVIC};
use once_cell::unsync::OnceCell;
use rp2040_hal::{
pac::{interrupt, Interrupt, Peripherals},
usb::UsbBus,
Sio, Watchdog,
};
use usb_device::{class_prelude::*, prelude::*};
use usbd_serial::{SerialPort, USB_CLASS_CDC};
mod bsp;
mod buttons;
mod device_signature;
use bsp::{entry, Pins};
use buttons::Buttons;
static USB_ALLOCATOR: UnsafeSync<OnceCell<UsbBusAllocator<UsbBus>>> =
UnsafeSync::new(OnceCell::new());
// static RTC_COUNT: Mutex<RefCell<Option<Rtc<Count32Mode>>>> = Mutex::new(RefCell::new(None));
static USB_BUS: Mutex<RefCell<Option<UsbDevice<UsbBus>>>> = Mutex::new(RefCell::new(None));
static USB_SERIAL: Mutex<RefCell<Option<SerialPort<UsbBus>>>> = Mutex::new(RefCell::new(None));
static HANDSHAKE: Mutex<RefCell<Handshake>> = Mutex::new(RefCell::new(Handshake::new()));
fn get_current_time() -> u32 {
unimplemented!()
}
fn is_handshake_complete() -> bool {
cortex_m::interrupt::free(|cs| HANDSHAKE.borrow(cs).borrow().is_complete())
}
#[entry]
fn main() -> ! {
let mut peripherals = Peripherals::take().unwrap();
let mut watchdog = Watchdog::new(peripherals.WATCHDOG);
let clocks = rp2040_hal::clocks::init_clocks_and_plls(
bsp::XOSC_CRYSTAL_FREQ,
peripherals.XOSC,
peripherals.CLOCKS,
peripherals.PLL_SYS,
peripherals.PLL_USB,
&mut peripherals.RESETS,
&mut watchdog,
)
.ok()
.unwrap();
let sio = Sio::new(peripherals.SIO);
let pins = Pins::new(
peripherals.IO_BANK0,
peripherals.PADS_BANK0,
sio.gpio_bank0,
&mut peripherals.RESETS,
);
let usb_allocator = UsbBusAllocator::new(UsbBus::new(
peripherals.USBCTRL_REGS,
peripherals.USBCTRL_DPRAM,
clocks.usb_clock,
true,
&mut peripherals.RESETS,
));
// SAFETY: No interrupt will access USB_ALLOCATOR before this point, since it is only
// referenced from inside the USB_SERIAL mutex and the USB_BUS mutex, which have not been set
// yet.
//
// SAFETY: This is the only line of the program that modifies USB_ALLOCATOR
let allocator = unsafe {
USB_ALLOCATOR.get().set(usb_allocator).ok().unwrap();
USB_ALLOCATOR.get().get().unwrap()
};
cortex_m::interrupt::free(|cs| {
let serial = USB_SERIAL.borrow(cs);
let mut serial = serial.borrow_mut();
*serial = Some(SerialPort::new(allocator));
});
cortex_m::interrupt::free(|cs| {
let bus = USB_BUS.borrow(cs);
let mut bus = bus.borrow_mut();
*bus = Some(
UsbDeviceBuilder::new(allocator, UsbVidPid(0x16c0, 0x27dd))
.manufacturer("Aode Lion")
.product("Pico Streamdeck")
.serial_number("AAAB")
.device_class(USB_CLASS_CDC)
.build(),
);
});
unsafe {
NVIC::unmask(Interrupt::USBCTRL_IRQ);
}
let mut buttons = Buttons {
d0: pins.gpio0,
d1: pins.gpio1,
d2: pins.gpio2,
d3: pins.gpio3,
d4: pins.gpio4,
}
.init();
loop {
let current_time = get_current_time();
if !is_handshake_complete() {
cycle_delay(1024 * 15);
continue;
}
if let Some(byte) = buttons.tick(current_time) {
let _ = cortex_m::interrupt::free(|cs| {
let mut serial = USB_SERIAL.borrow(cs).borrow_mut();
let serial = serial.as_mut().unwrap();
let _ = serial.write(&[byte])?;
serial.flush()
});
}
}
}
#[allow(non_snake_case)]
#[interrupt]
fn USBCTRL_IRQ() {
poll_usb();
}
fn poll_usb() -> Option<()> {
let ready = cortex_m::interrupt::free(|cs1| {
cortex_m::interrupt::free(|cs2| {
let mut usb_dev = USB_BUS.borrow(cs1).borrow_mut();
let usb_dev = usb_dev.as_mut()?;
let mut serial = USB_SERIAL.borrow(cs2).borrow_mut();
let serial = serial.as_mut()?;
Some(usb_dev.poll(&mut [&mut *serial]))
})
})?;
if !ready {
return Some(());
}
let mut input = [0u8; 32];
let count = cortex_m::interrupt::free(|cs| {
USB_SERIAL
.borrow(cs)
.borrow_mut()
.as_mut()?
.read(&mut input)
.ok()
})?;
let current_time = get_current_time();
cortex_m::interrupt::free(|cs| {
let mut handshake = HANDSHAKE.borrow(cs).borrow_mut();
if count == 32 && handshake.perform(&input, current_time, &mut Device)? {
return None;
}
let is_complete = handshake.is_complete();
if !is_complete {
return None;
}
Some(())
})?;
handle_input(&mut Device, &input, count)?;
Some(())
}
struct Device;
impl common::Device<8, 1> for Device {
fn serial_number(&mut self) -> [u8; 8] {
device_signature::read_uid()
}
fn write(&mut self, bytes: &[u8]) -> Option<()> {
cortex_m::interrupt::free(|cs| {
let mut serial = USB_SERIAL.borrow(cs).borrow_mut();
let serial = serial.as_mut()?;
serial.write(bytes).ok()?;
serial.flush().ok()
})
}
fn extras() -> [(&'static str, &'static str); 1] {
[("version", env!("FIRMWARE_VERSION"))]
}
fn reset(&mut self) {
cortex_m::interrupt::disable();
rp2040_hal::rom_data::reset_to_usb_boot(0, 0);
}
}

37
trinket/Cargo.toml Normal file
View file

@ -0,0 +1,37 @@
[package]
authors = ["asonix <asonix@asonix.dog>"]
edition = "2021"
readme = "../README.md"
name = "trinket-streamdeck"
version = "0.1.0"
build = "src/build.rs"
[dependencies]
apa102-spi = "0.3.2"
atsamd-hal = { version = "0.14", default-features = false, features = [
"samd21e",
"samd21e-rt",
"unproven",
"usb",
] }
bitbang-hal = "0.3.2"
common = { version = "0.1.0", path = "../common" }
cortex-m = "0.7.4"
cortex-m-rt = "0.7.1"
cortex-m-semihosting = "0.3.3"
embedded-hal = "0.2"
once_cell = { version = "1.7.2", default-features = false }
panic-halt = "0.2.0"
smart-leds = "0.3"
usb-device = "0.2"
usbd-serial = "0.1"
[build-dependencies]
anyhow = "1.0"
toml = "0.5.8"
# this lets you use `cargo fix`!
[[bin]]
name = "trinket-streamdeck"
test = false
bench = false

60
trinket/src/build.rs Normal file
View file

@ -0,0 +1,60 @@
use std::{fs::File, io::Read, path::Path, process::Command};
fn git_info() -> Option<String> {
let mut git_string: Option<String> = None;
if let Ok(output) = Command::new("git").args(["rev-parse", "HEAD"]).output() {
if output.status.success() {
let git_hash = String::from_utf8_lossy(&output.stdout);
git_string = Some(git_hash.to_string());
}
}
if let Ok(output) = Command::new("git")
.args(["rev-parse", "--abbrev-ref", "HEAD"])
.output()
{
if output.status.success() {
let git_branch = String::from_utf8_lossy(&output.stdout);
if let Some(ref mut hash) = &mut git_string {
hash.push('-');
hash.push_str(git_branch.as_ref());
}
}
}
git_string
}
fn version_info() -> Result<String, anyhow::Error> {
let cargo_toml = Path::new(&std::env::var("CARGO_MANIFEST_DIR")?).join("Cargo.toml");
let mut file = File::open(&cargo_toml)?;
let mut cargo_data = String::new();
file.read_to_string(&mut cargo_data)?;
let data: toml::Value = toml::from_str(&cargo_data)?;
let name = data["package"]["name"]
.as_str()
.ok_or_else(|| anyhow::anyhow!("Missing package name string"))?;
let version = data["package"]["version"]
.as_str()
.ok_or_else(|| anyhow::anyhow!("Missing version string"))?;
Ok(format!("{}-{}", name, version))
}
fn main() -> Result<(), anyhow::Error> {
let version = version_info()?;
let version = if let Some(git) = git_info() {
format!("{}-{}", version, git)
} else {
version
};
println!("cargo:rustc-env=FIRMWARE_VERSION={}", version);
Ok(())
}

View file

@ -1,9 +1,9 @@
use atsamd_hal::common::gpio::v2::{
Disabled, Floating, Input, Pin, PullUp, PA02, PA06, PA07, PA08, PA09,
};
use common::{ButtonPins, ButtonState};
use core::convert::Infallible;
use embedded_hal::digital::v2::InputPin;
use streamdeck_common::{ButtonPins, ButtonState};
pub(crate) struct Buttons {
pub(crate) d0: Pin<PA08, Disabled<Floating>>,

View file

@ -1,19 +1,17 @@
#![no_std]
#![no_main]
use atsamd_hal::{
clock::{ClockGenId, ClockSource},
rtc::{Count32Mode, Rtc},
timer::TimerCounter,
};
use panic_halt as _; // you can put a breakpoint on `rust_begin_unwind` to catch panics
use atsamd_hal::{
clock::GenericClockController,
clock::{ClockGenId, ClockSource, GenericClockController},
pac::{interrupt, CorePeripherals, Peripherals},
prelude::_atsamd_hal_embedded_hal_digital_v2_OutputPin,
rtc::{Count32Mode, Rtc},
timer::TimerCounter,
usb::UsbBus,
};
use common::{handle_input, Handshake, UnsafeSync};
use core::cell::RefCell;
use cortex_m::{
asm::delay as cycle_delay,
@ -22,15 +20,14 @@ use cortex_m::{
};
use once_cell::unsync::OnceCell;
use smart_leds::{SmartLedsWrite, RGB8};
use streamdeck_common::{handle_input, Handshake, UnsafeSync};
use usb_device::{bus::UsbBusAllocator, prelude::*};
use usbd_serial::{SerialPort, USB_CLASS_CDC};
mod bsp;
mod trinket_buttons;
mod buttons;
use bsp::{entry, Dotstar, Pins};
use trinket_buttons::Buttons;
use buttons::Buttons;
static USB_ALLOCATOR: UnsafeSync<OnceCell<UsbBusAllocator<UsbBus>>> =
UnsafeSync::new(OnceCell::new());
@ -103,7 +100,7 @@ fn main() -> ! {
*bus = Some(
UsbDeviceBuilder::new(allocator, UsbVidPid(0x16c0, 0x27dd))
.manufacturer("Aode Lion")
.product("Streamdeck")
.product("Trinket Streamdeck")
.serial_number("AAAA")
.device_class(USB_CLASS_CDC)
.build(),
@ -186,6 +183,7 @@ fn main() -> ! {
}
}
#[allow(non_snake_case)]
#[interrupt]
fn USB() {
poll_usb();
@ -240,7 +238,7 @@ fn poll_usb() -> Option<()> {
struct Device;
impl streamdeck_common::Device<16, 1> for Device {
impl common::Device<16, 1> for Device {
// section 10.3.3 of datasheet for SAMD21
fn serial_number(&mut self) -> [u8; 16] {
let generator: [*const [u8; 4]; 4] = [