From 30bdb70b164b71284f6c9679a286510b8057faae Mon Sep 17 00:00:00 2001
From: Univa <41708691+Univa@users.noreply.github.com>
Date: Wed, 13 Mar 2024 13:05:45 -0400
Subject: [PATCH] rp2040 support
---
README.md | 5 +-
.../content/docs/features/feature-storage.md | 33 ++++-
docs/src/content/docs/index.md | 5 +-
rumcake-macros/Cargo.toml | 1 +
rumcake-macros/src/hw/rp.rs | 137 ++++++++++++++++++
rumcake-macros/src/keyboard.rs | 37 ++++-
rumcake-macros/src/lib.rs | 10 +-
rumcake/Cargo.toml | 6 +
rumcake/src/hw/mcu/rp.rs | 98 +++++++++++++
rumcake/src/hw/mod.rs | 9 +-
10 files changed, 325 insertions(+), 16 deletions(-)
create mode 100644 rumcake-macros/src/hw/rp.rs
create mode 100644 rumcake/src/hw/mcu/rp.rs
diff --git a/README.md b/README.md
index 3aacf6a..d3cf583 100644
--- a/README.md
+++ b/README.md
@@ -48,10 +48,7 @@ and flashing instructions for some common setups.
- STM32F072CBx
- STM32F303CBx
- nRF52840 (tested with nice!nano v2)
-
-### Planned MCUs for the future
-
-- RP-based chips (I don't have access to an RP-based keyboard at the moment)
+- RP2040
## Features
diff --git a/docs/src/content/docs/features/feature-storage.md b/docs/src/content/docs/features/feature-storage.md
index c5143b8..bc96c59 100644
--- a/docs/src/content/docs/features/feature-storage.md
+++ b/docs/src/content/docs/features/feature-storage.md
@@ -69,25 +69,52 @@ __config_end = __config_start + LENGTH(CONFIG); /* add this */
Finally, you can add `storage(driver = "internal")` to your `#[keyboard]` macro invocation, and make sure to implement
`StorageDevice` for your keyboard:
-```rust ins={5,7,11-12}
+```rust ins={5,7-11,16-23}
#[keyboard(
// somewhere in your keyboard macro invocation ...
underglow(
driver = "ws2812_bitbang",
use_storage // This underglow feature uses storage
),
- storage(driver = "internal")
+ storage(
+ driver = "internal",
+ // `flash_size` below is required for RP2040, omit if you are not using an RP2040.
+ // Should be equal to the total size of the flash chip (not the size of your CONFIG partition)
+ flash_size = 2097152
+ )
)]
struct MyKeyboard;
use rumcake::storage::StorageDevice;
impl StorageDevice for MyKeyboard {}
+
+// Required for RP2040, omit if you are not using an RP2040
+use rumcake::hw::mcu::setup_dma_channel;
+impl RP2040FlashSettings for MyKeyboard {
+ setup_dma_channel! { DMA_CH0 }
+}
```
+:::note
+**For RP2040 users**: You must implement the `RP2040FlashSettings` trait (generated by the
+`#[keyboard]` macro), and the `#[keyboard]` macro invocation must also include `flash_size` as
+shown in the example above. If you are not using RP2040, these things can be omitted.
+:::
+
:::tip
By default, the `setup_storage_buffer()` function in the `StorageDevice` trait creates a buffer
with a size of 1024 bytes. You can override the implementation to increase the size of the
-buffer to store values that may be larger, or you can decrease the size to save memory.
+buffer to store values that may be larger, or you can decrease the size to save memory:
+
+```rust del={3} ins={4}
+impl StorageDevice for MyKeyboard {
+ fn get_storage_buffer() -> &'static mut [u8] {
+ static mut STORAGE_BUFFER: [u8; 1024] = [0; 1024];
+ static mut STORAGE_BUFFER: [u8; 32] = [0; 32];
+ unsafe { &mut STORAGE_BUFFER }
+ }
+}
+```
Keep in mind, that the size of this buffer must be large enough to store the largest possible value
that you will be reading, or writing from the storage peripheral.
diff --git a/docs/src/content/docs/index.md b/docs/src/content/docs/index.md
index ec51f17..437a709 100644
--- a/docs/src/content/docs/index.md
+++ b/docs/src/content/docs/index.md
@@ -45,10 +45,7 @@ and flashing instructions for some common setups.
- STM32F072CBx
- STM32F303CBx
- nRF52840 (tested with nice!nano v2)
-
-## Planned MCUs for the future
-
-- RP-based chips (I don't have access to an RP-based keyboard at the moment)
+- RP2040
# Features
diff --git a/rumcake-macros/Cargo.toml b/rumcake-macros/Cargo.toml
index 378cc7f..c4ed431 100644
--- a/rumcake-macros/Cargo.toml
+++ b/rumcake-macros/Cargo.toml
@@ -21,6 +21,7 @@ embassy-stm32 = { git = "https://github.com/embassy-rs/embassy", features = ["de
[features]
stm32 = []
nrf = []
+rp = []
storage = []
diff --git a/rumcake-macros/src/hw/rp.rs b/rumcake-macros/src/hw/rp.rs
new file mode 100644
index 0000000..d91c934
--- /dev/null
+++ b/rumcake-macros/src/hw/rp.rs
@@ -0,0 +1,137 @@
+use proc_macro2::{Ident, TokenStream};
+use proc_macro_error::OptionExt;
+use quote::quote;
+use syn::punctuated::Punctuated;
+use syn::Token;
+
+pub fn input_pin(ident: Ident) -> TokenStream {
+ quote! {
+ unsafe {
+ ::rumcake::hw::mcu::embassy_rp::gpio::Input::new(
+ ::rumcake::hw::mcu::embassy_rp::gpio::Pin::degrade(
+ ::rumcake::hw::mcu::embassy_rp::peripherals::#ident::steal(),
+ ),
+ ::rumcake::hw::mcu::embassy_rp::gpio::Pull::Up,
+ )
+ }
+ }
+}
+
+pub fn output_pin(ident: Ident) -> TokenStream {
+ quote! {
+ unsafe {
+ ::rumcake::hw::mcu::embassy_rp::gpio::Output::new(
+ ::rumcake::hw::mcu::embassy_rp::gpio::Pin::degrade(
+ ::rumcake::hw::mcu::embassy_rp::peripherals::#ident::steal(),
+ ),
+ ::rumcake::hw::mcu::embassy_rp::gpio::Level::High,
+ )
+ }
+ }
+}
+
+pub fn internal_storage_trait() -> TokenStream {
+ quote! {
+ /// A trait that must be implemented to use the flash chip connected to your RP2040 for storage..
+ pub(crate) trait RP2040FlashSettings {
+ /// Get the DMA channel used for flash operations.
+ fn setup_dma_channel(
+ ) -> impl ::rumcake::hw::mcu::embassy_rp::Peripheral
;
+ }
+ }
+}
+
+pub fn setup_dma_channel(dma: Ident) -> TokenStream {
+ quote! {
+ fn setup_dma_channel(
+ ) -> impl ::rumcake::hw::mcu::embassy_rp::Peripheral
{
+ unsafe {
+ ::rumcake::hw::mcu::embassy_rp::peripherals::#dma::steal()
+ }
+ }
+ }
+}
+
+fn setup_i2c_inner(args: Punctuated) -> TokenStream {
+ let mut args = args.iter();
+
+ let interrupt = args.next().expect_or_abort("Missing interrupt argument.");
+ let i2c = args
+ .next()
+ .expect_or_abort("Missing I2C peripheral argument.");
+ let scl = args
+ .next()
+ .expect_or_abort("Missing SCL peripheral argument.");
+ let sda = args
+ .next()
+ .expect_or_abort("Missing SDA peripheral argument.");
+
+ quote! {
+ unsafe {
+ ::rumcake::hw::mcu::embassy_rp::bind_interrupts! {
+ struct Irqs {
+ #interrupt => ::rumcake::hw::mcu::embassy_rp::i2c::InterruptHandler<::rumcake::hw::mcu::embassy_rp::peripherals::#i2c>;
+ }
+ };
+ let i2c = ::rumcake::hw::mcu::embassy_rp::peripherals::#i2c::steal();
+ let scl = ::rumcake::hw::mcu::embassy_rp::peripherals::#scl::steal();
+ let sda = ::rumcake::hw::mcu::embassy_rp::peripherals::#sda::steal();
+ ::rumcake::hw::mcu::embassy_rp::i2c::I2c::new_async(i2c, scl, sda, Irqs, Default::default())
+ }
+ }
+}
+
+pub fn setup_i2c(args: Punctuated) -> TokenStream {
+ let inner = setup_i2c_inner(args);
+ quote! {
+ fn setup_i2c() -> impl ::rumcake::embedded_hal_async::i2c::I2c {
+ #inner
+ }
+ }
+}
+
+fn setup_buffered_uart_inner(args: Punctuated) -> TokenStream {
+ let mut args = args.iter();
+
+ let interrupt = args.next().expect_or_abort("Missing interrupt argument.");
+ let uart = args
+ .next()
+ .expect_or_abort("Missing UART peripheral argument.");
+ let rx_pin = args.next().expect_or_abort("Missing RX pin argument.");
+ let tx_pin = args.next().expect_or_abort("Missing TX pin argument.");
+
+ quote! {
+ unsafe {
+ static mut RBUF: [u8; 64] = [0; 64];
+ static mut TBUF: [u8; 64] = [0; 64];
+ ::rumcake::hw::mcu::embassy_rp::bind_interrupts! {
+ struct Irqs {
+ #interrupt => ::rumcake::hw::mcu::embassy_rp::uart::BufferedInterruptHandler<::rumcake::hw::mcu::embassy_rp::peripherals::#uart>;
+ }
+ };
+ let uart = ::rumcake::hw::mcu::embassy_rp::peripherals::#uart::steal();
+ let rx = ::rumcake::hw::mcu::embassy_rp::peripherals::#rx_pin::steal();
+ let tx = ::rumcake::hw::mcu::embassy_rp::peripherals::#tx_pin::steal();
+ ::rumcake::hw::mcu::embassy_rp::uart::BufferedUart::new(
+ uart,
+ Irqs,
+ rx,
+ tx,
+ &mut TBUF,
+ &mut RBUF,
+ Default::default(),
+ )
+ }
+ }
+}
+
+pub fn setup_buffered_uart(args: Punctuated) -> TokenStream {
+ let inner = setup_buffered_uart_inner(args);
+
+ quote! {
+ fn setup_serial(
+ ) -> impl ::rumcake::embedded_io_async::Write + ::rumcake::embedded_io_async::Read {
+ #inner
+ }
+ }
+}
diff --git a/rumcake-macros/src/keyboard.rs b/rumcake-macros/src/keyboard.rs
index 60e8162..725115e 100644
--- a/rumcake-macros/src/keyboard.rs
+++ b/rumcake-macros/src/keyboard.rs
@@ -62,6 +62,7 @@ pub(crate) struct ViaSettings {
#[darling(default)]
pub(crate) struct StorageSettings {
driver: String,
+ flash_size: usize,
}
enum SplitSettings<'a> {
@@ -256,6 +257,8 @@ fn setup_display_driver(
fn setup_storage_driver(
initialization: &mut TokenStream,
+ traits: &mut HashMap,
+ kb_name: &Ident,
config: &StorageSettings,
uses_bluetooth: bool,
) {
@@ -273,7 +276,7 @@ fn setup_storage_driver(
static DATABASE: ::rumcake::storage::StorageService<'static, ::rumcake::hw::mcu::nrf_softdevice::Flash> = ::rumcake::storage::StorageService::new();
unsafe { DATABASE.setup(flash, config_start, config_end, &mut READ_BUF, &mut OP_BUF).await; }
})
- } else {
+ } else if cfg!(any(feature = "stm32", feature = "nrf")) {
initialization.extend(quote! {
use ::rumcake::storage::FlashStorage;
let flash = ::rumcake::hw::mcu::setup_internal_flash();
@@ -284,6 +287,30 @@ fn setup_storage_driver(
static DATABASE: ::rumcake::storage::StorageService<'static, ::rumcake::hw::mcu::Flash> = ::rumcake::storage::StorageService::new();
unsafe { DATABASE.setup(flash, config_start, config_end, &mut READ_BUF, &mut OP_BUF).await; }
})
+ } else if cfg!(feature = "rp") {
+ #[cfg(feature = "rp")]
+ traits.insert(config.driver.clone(), crate::hw::internal_storage_trait());
+ if config.flash_size == 0 {
+ initialization.extend(quote_spanned! {
+ config.driver.span() => compile_error!("You must specify a non-zero size for your flash chip.");
+ });
+ } else {
+ let size = config.flash_size;
+ initialization.extend(quote! {
+ use ::rumcake::storage::FlashStorage;
+ let flash = ::rumcake::hw::mcu::setup_internal_flash::<#size>(<#kb_name as RP2040FlashSettings>::setup_dma_channel());
+ let config_start = unsafe { &::rumcake::hw::__config_start as *const u32 as usize };
+ let config_end = unsafe { &::rumcake::hw::__config_end as *const u32 as usize };
+ static mut READ_BUF: [u8; ::rumcake::hw::mcu::embassy_rp::flash::ERASE_SIZE] = [0; ::rumcake::hw::mcu::embassy_rp::flash::ERASE_SIZE];
+ static mut OP_BUF: [u8; ::rumcake::hw::mcu::embassy_rp::flash::ERASE_SIZE] = [0; ::rumcake::hw::mcu::embassy_rp::flash::ERASE_SIZE];
+ static DATABASE: ::rumcake::storage::StorageService<'static, ::rumcake::hw::mcu::Flash<#size>> = ::rumcake::storage::StorageService::new();
+ unsafe { DATABASE.setup(flash, config_start, config_end, &mut READ_BUF, &mut OP_BUF).await; }
+ })
+ }
+ } else {
+ initialization.extend(quote_spanned! {
+ config.driver.span() => compile_error!("Internal storage driver is not available for your platform.");
+ });
};
}
_ => (),
@@ -354,7 +381,13 @@ pub(crate) fn keyboard_main(
driver.driver.span() => compile_error!("Storage driver was specified, but rumcake's `storage` feature flag is not enabled. Please enable the feature.");
});
} else {
- setup_storage_driver(&mut initialization, driver, uses_bluetooth);
+ setup_storage_driver(
+ &mut initialization,
+ &mut traits,
+ &kb_name,
+ driver,
+ uses_bluetooth,
+ );
}
};
diff --git a/rumcake-macros/src/lib.rs b/rumcake-macros/src/lib.rs
index 1c1484d..35cedf0 100644
--- a/rumcake-macros/src/lib.rs
+++ b/rumcake-macros/src/lib.rs
@@ -210,6 +210,7 @@ pub fn is31fl3731_get_led_from_rgb_matrix_coordinates(
#[cfg_attr(feature = "stm32", path = "hw/stm32.rs")]
#[cfg_attr(feature = "nrf", path = "hw/nrf.rs")]
+#[cfg_attr(feature = "rp", path = "hw/rp.rs")]
mod hw;
#[proc_macro]
@@ -244,13 +245,20 @@ pub fn setup_buffered_uarte(input: proc_macro::TokenStream) -> proc_macro::Token
hw::setup_buffered_uarte(ident).into()
}
-#[cfg(feature = "stm32")]
+#[cfg(any(feature = "stm32", feature = "rp"))]
#[proc_macro]
pub fn setup_buffered_uart(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let ident = parse_macro_input!(input with Punctuated::parse_terminated);
hw::setup_buffered_uart(ident).into()
}
+#[cfg(feature = "rp")]
+#[proc_macro]
+pub fn setup_dma_channel(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+ let args = parse_macro_input!(input as Ident);
+ hw::setup_dma_channel(args).into()
+}
+
mod via;
#[proc_macro]
diff --git a/rumcake/Cargo.toml b/rumcake/Cargo.toml
index f030c6c..eae2ab7 100644
--- a/rumcake/Cargo.toml
+++ b/rumcake/Cargo.toml
@@ -25,6 +25,7 @@ features = [
flavours = [
{ feature = "nrf52840", triple = "thumbv7em-none-eabihf", extra_features = ["nrf-ble", "bluetooth"] },
+ { feature = "rp2040", triple = "thumbv6m-none-eabi", extra_features = [] },
{ feature = "stm32f072cb", triple = "thumbv6m-none-eabi", extra_features = [] },
{ feature = "stm32f303cb", triple = "thumbv7em-none-eabihf", extra_features = [] },
]
@@ -45,6 +46,7 @@ embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "b8be12
embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "b8be126", features = ["defmt", "integrated-timers", "executor-thread"] }
embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "b8be126", features = ["defmt", "defmt-timestamp-uptime"] }
embassy-usb = { git = "https://github.com/embassy-rs/embassy", rev = "b8be126", features = ["defmt"] }
+embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "b8be126", features = ["defmt", "unstable-pac"], optional = true }
embassy-stm32 = { git = "https://github.com/embassy-rs/embassy", rev = "b8be126", features = ["defmt", "unstable-pac"], optional = true }
embassy-nrf = { git = "https://github.com/embassy-rs/embassy", rev = "b8be126", features = ["defmt", "nfc-pins-as-gpio", "time-driver-rtc1"], optional = true }
nrf-softdevice = { git = "https://github.com/embassy-rs/nrf-softdevice", rev = "487f98e", optional = true }
@@ -86,6 +88,10 @@ drivers = []
# Chips
#
+# RP
+rp = ["dep:cortex-m", "embassy-executor/arch-cortex-m", "dep:embassy-rp", "rumcake-macros/rp"]
+rp2040 = ["rp", "embassy-rp/time-driver"]
+
# STM32
stm32 = ["dep:cortex-m", "embassy-executor/arch-cortex-m", "dep:embassy-stm32", "rumcake-macros/stm32"]
stm32f072cb = ["stm32", "embassy-stm32/stm32f072cb", "embassy-stm32/time-driver-any"]
diff --git a/rumcake/src/hw/mcu/rp.rs b/rumcake/src/hw/mcu/rp.rs
new file mode 100644
index 0000000..25d3782
--- /dev/null
+++ b/rumcake/src/hw/mcu/rp.rs
@@ -0,0 +1,98 @@
+//! Utilities for interfacing with the hardware, specific to RP-based MCUs.
+//!
+//! Note that the contents of this RP-version of `mcu` module may share some of the same members
+//! of other versions of the `mcu` module. This is the case so that parts of `rumcake` can remain
+//! hardware-agnostic.
+
+use defmt::assert;
+use embassy_rp::bind_interrupts;
+use embassy_rp::config::Config;
+use embassy_rp::flash::Async;
+use embassy_rp::flash::Flash as HALFlash;
+use embassy_rp::peripherals::{FLASH, USB};
+use embassy_rp::usb::Driver;
+use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex;
+
+pub use rumcake_macros::{
+ input_pin, output_pin, setup_buffered_uart, setup_dma_channel, setup_i2c,
+};
+
+pub use embassy_rp;
+
+pub const SYSCLK: u32 = 125_000_000;
+
+pub type RawMutex = ThreadModeRawMutex;
+
+/// A function that allows you to jump to the bootloader, usually for re-flashing the firmware.
+pub fn jump_to_bootloader() {
+ // TODO
+}
+
+/// Initialize the MCU's internal clocks.
+pub fn initialize_rcc() {
+ let conf = Config::default();
+ embassy_rp::init(conf);
+ assert!(
+ SYSCLK == embassy_rp::clocks::clk_sys_freq(),
+ "SYSCLK is not correct."
+ );
+}
+
+#[cfg(feature = "usb")]
+/// Setup the USB driver. The output of this function usually needs to be passed to another
+/// function that sets up the HID readers or writers to be used with a task. For example, you may
+/// need to pass this to [`crate::usb::setup_usb_hid_nkro_writer`] to set up a keyboard that
+/// communicates with a host device over USB.
+pub fn setup_usb_driver(
+) -> embassy_usb::Builder<'static, Driver<'static, USB>> {
+ unsafe {
+ #[cfg(feature = "rp2040")]
+ bind_interrupts!(
+ struct Irqs {
+ USBCTRL_IRQ => embassy_rp::usb::InterruptHandler;
+ }
+ );
+
+ let mut config = embassy_usb::Config::new(K::USB_VID, K::USB_PID);
+ config.manufacturer.replace(K::MANUFACTURER);
+ config.product.replace(K::PRODUCT);
+ config.serial_number.replace(K::SERIAL_NUMBER);
+ config.max_power = 500;
+
+ let usb_driver = Driver::new(USB::steal(), Irqs);
+
+ static DEVICE_DESCRIPTOR: static_cell::StaticCell<[u8; 256]> =
+ static_cell::StaticCell::new();
+ let device_descriptor = DEVICE_DESCRIPTOR.init([0; 256]);
+ static CONFIG_DESCRIPTOR: static_cell::StaticCell<[u8; 256]> =
+ static_cell::StaticCell::new();
+ let config_descriptor = CONFIG_DESCRIPTOR.init([0; 256]);
+ static BOS_DESCRIPTOR: static_cell::StaticCell<[u8; 256]> = static_cell::StaticCell::new();
+ let bos_descriptor = BOS_DESCRIPTOR.init([0; 256]);
+ static MSOS_DESCRIPTOR: static_cell::StaticCell<[u8; 256]> = static_cell::StaticCell::new();
+ let msos_descriptor = MSOS_DESCRIPTOR.init([0; 256]);
+ static CONTROL_BUF: static_cell::StaticCell<[u8; 128]> = static_cell::StaticCell::new();
+ let control_buf = CONTROL_BUF.init([0; 128]);
+
+ embassy_usb::Builder::new(
+ usb_driver,
+ config,
+ device_descriptor,
+ config_descriptor,
+ bos_descriptor,
+ msos_descriptor,
+ control_buf,
+ )
+ }
+}
+
+pub type Flash<'a, const FLASH_SIZE: usize> = HALFlash<'a, FLASH, Async, FLASH_SIZE>;
+
+/// Construct an instance of [`Flash`]. This usually needs to be passed to
+/// [`crate::storage::Database::setup`], so that your device can use storage features.
+pub fn setup_internal_flash<'a, const FLASH_SIZE: usize>(
+ channel: impl crate::hw::mcu::embassy_rp::Peripheral
+ + 'a,
+) -> Flash<'a, FLASH_SIZE> {
+ unsafe { Flash::new(FLASH::steal(), channel) }
+}
diff --git a/rumcake/src/hw/mod.rs b/rumcake/src/hw/mod.rs
index db614ae..08f3cf4 100644
--- a/rumcake/src/hw/mod.rs
+++ b/rumcake/src/hw/mod.rs
@@ -1,13 +1,18 @@
//! Utilities for interfacing with hardware.
-#[cfg(all(not(feature = "stm32"), not(feature = "nrf")))]
+#[cfg(all(not(feature = "stm32"), not(feature = "nrf"), not(feature = "rp")))]
compile_error!("Please enable the appropriate feature flag for the chip you're using.");
-#[cfg(all(feature = "stm32", feature = "nrf"))]
+#[cfg(any(
+ all(feature = "stm32", feature = "nrf"),
+ all(feature = "nrf", feature = "rp"),
+ all(feature = "rp", feature = "stm32")
+))]
compile_error!("Please enable only one chip feature flag.");
#[cfg_attr(feature = "stm32", path = "mcu/stm32.rs")]
#[cfg_attr(feature = "nrf", path = "mcu/nrf.rs")]
+#[cfg_attr(feature = "rp", path = "mcu/rp.rs")]
pub mod mcu;
use crate::State;