From a5ce6742801bee89ff5e34e3032caa622dbb1bb6 Mon Sep 17 00:00:00 2001 From: Benjamin Koch <bbbsnowball@gmail.com> Date: Sat, 13 May 2023 01:06:25 +0200 Subject: [PATCH] add submodule for embassy-rs and copy their example --- .gitmodules | 3 + firmware/rust1/.cargo/config.toml | 8 + firmware/rust1/Cargo.toml | 50 +++ firmware/rust1/build.rs | 36 ++ firmware/rust1/embassy | 1 + firmware/rust1/memory.x | 5 + firmware/rust1/rust-toolchain.toml | 14 + firmware/rust1/src/bin/adc.rs | 38 +++ firmware/rust1/src/bin/blinky.rs | 26 ++ firmware/rust1/src/bin/button.rs | 22 ++ firmware/rust1/src/bin/flash.rs | 89 +++++ firmware/rust1/src/bin/gpio_async.rs | 39 +++ firmware/rust1/src/bin/gpout.rs | 34 ++ firmware/rust1/src/bin/i2c_async.rs | 102 ++++++ firmware/rust1/src/bin/i2c_blocking.rs | 70 ++++ firmware/rust1/src/bin/lora_lorawan.rs | 80 +++++ firmware/rust1/src/bin/lora_p2p_receive.rs | 115 +++++++ firmware/rust1/src/bin/lora_p2p_send.rs | 103 ++++++ .../rust1/src/bin/lora_p2p_send_multicore.rs | 139 ++++++++ firmware/rust1/src/bin/multicore.rs | 60 ++++ firmware/rust1/src/bin/multiprio.rs | 152 +++++++++ firmware/rust1/src/bin/pio_async.rs | 122 +++++++ firmware/rust1/src/bin/pio_dma.rs | 80 +++++ firmware/rust1/src/bin/pio_hd44780.rs | 235 +++++++++++++ firmware/rust1/src/bin/pwm.rs | 27 ++ firmware/rust1/src/bin/spi.rs | 43 +++ firmware/rust1/src/bin/spi_async.rs | 29 ++ firmware/rust1/src/bin/spi_display.rs | 308 ++++++++++++++++++ firmware/rust1/src/bin/uart.rs | 20 ++ firmware/rust1/src/bin/uart_buffered_split.rs | 57 ++++ firmware/rust1/src/bin/uart_unidir.rs | 49 +++ firmware/rust1/src/bin/usb_ethernet.rs | 151 +++++++++ firmware/rust1/src/bin/usb_logger.rs | 30 ++ firmware/rust1/src/bin/usb_serial.rs | 101 ++++++ firmware/rust1/src/bin/watchdog.rs | 48 +++ firmware/rust1/src/bin/ws2812-pio.rs | 129 ++++++++ 36 files changed, 2615 insertions(+) create mode 100644 .gitmodules create mode 100644 firmware/rust1/.cargo/config.toml create mode 100644 firmware/rust1/Cargo.toml create mode 100644 firmware/rust1/build.rs create mode 160000 firmware/rust1/embassy create mode 100644 firmware/rust1/memory.x create mode 100644 firmware/rust1/rust-toolchain.toml create mode 100644 firmware/rust1/src/bin/adc.rs create mode 100644 firmware/rust1/src/bin/blinky.rs create mode 100644 firmware/rust1/src/bin/button.rs create mode 100644 firmware/rust1/src/bin/flash.rs create mode 100644 firmware/rust1/src/bin/gpio_async.rs create mode 100644 firmware/rust1/src/bin/gpout.rs create mode 100644 firmware/rust1/src/bin/i2c_async.rs create mode 100644 firmware/rust1/src/bin/i2c_blocking.rs create mode 100644 firmware/rust1/src/bin/lora_lorawan.rs create mode 100644 firmware/rust1/src/bin/lora_p2p_receive.rs create mode 100644 firmware/rust1/src/bin/lora_p2p_send.rs create mode 100644 firmware/rust1/src/bin/lora_p2p_send_multicore.rs create mode 100644 firmware/rust1/src/bin/multicore.rs create mode 100644 firmware/rust1/src/bin/multiprio.rs create mode 100644 firmware/rust1/src/bin/pio_async.rs create mode 100644 firmware/rust1/src/bin/pio_dma.rs create mode 100644 firmware/rust1/src/bin/pio_hd44780.rs create mode 100644 firmware/rust1/src/bin/pwm.rs create mode 100644 firmware/rust1/src/bin/spi.rs create mode 100644 firmware/rust1/src/bin/spi_async.rs create mode 100644 firmware/rust1/src/bin/spi_display.rs create mode 100644 firmware/rust1/src/bin/uart.rs create mode 100644 firmware/rust1/src/bin/uart_buffered_split.rs create mode 100644 firmware/rust1/src/bin/uart_unidir.rs create mode 100644 firmware/rust1/src/bin/usb_ethernet.rs create mode 100644 firmware/rust1/src/bin/usb_logger.rs create mode 100644 firmware/rust1/src/bin/usb_serial.rs create mode 100644 firmware/rust1/src/bin/watchdog.rs create mode 100644 firmware/rust1/src/bin/ws2812-pio.rs diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..74ebf79 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "firmware/rust1/embassy"] + path = firmware/rust1/embassy + url = https://github.com/embassy-rs/embassy diff --git a/firmware/rust1/.cargo/config.toml b/firmware/rust1/.cargo/config.toml new file mode 100644 index 0000000..2ee6fcb --- /dev/null +++ b/firmware/rust1/.cargo/config.toml @@ -0,0 +1,8 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +runner = "probe-rs-cli run --chip RP2040" + +[build] +target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+ + +[env] +DEFMT_LOG = "debug" diff --git a/firmware/rust1/Cargo.toml b/firmware/rust1/Cargo.toml new file mode 100644 index 0000000..ffeb69f --- /dev/null +++ b/firmware/rust1/Cargo.toml @@ -0,0 +1,50 @@ +[package] +edition = "2021" +name = "embassy-rp-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + + +[dependencies] +embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal", features = ["defmt"] } +embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["nightly", "unstable-traits", "defmt", "defmt-timestamp-uptime"] } +embassy-rp = { version = "0.1.0", path = "../../embassy-rp", features = ["defmt", "unstable-traits", "nightly", "unstable-pac", "time-driver", "critical-section-impl"] } +embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } +embassy-usb-logger = { version = "0.1.0", path = "../../embassy-usb-logger" } +embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["time", "defmt"] } +lora-phy = { version = "1" } +lorawan-device = { version = "0.10.0", default-features = false, features = ["async", "external-lora-phy"] } +lorawan = { version = "0.7.3", default-features = false, features = ["default-crypto"] } + +defmt = "0.3" +defmt-rtt = "0.4" +fixed = "1.23.1" +fixed-macro = "1.2" + +#cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm"] } +cortex-m-rt = "0.7.0" +panic-probe = { version = "0.3", features = ["print-defmt"] } +futures = { version = "0.3.17", default-features = false, features = ["async-await", "cfg-target-has-atomic", "unstable"] } +display-interface-spi = "0.4.1" +embedded-graphics = "0.7.1" +st7789 = "0.6.1" +display-interface = "0.4.1" +byte-slice-cast = { version = "1.2.0", default-features = false } +smart-leds = "0.3.0" + +embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.10" } +embedded-hal-async = "0.2.0-alpha.1" +embedded-io = { version = "0.4.0", features = ["async", "defmt"] } +embedded-storage = { version = "0.3" } +static_cell = "1.0.0" +log = "0.4" +pio-proc = "0.2" +pio = "0.2.1" + +[profile.release] +debug = true diff --git a/firmware/rust1/build.rs b/firmware/rust1/build.rs new file mode 100644 index 0000000..3f915f9 --- /dev/null +++ b/firmware/rust1/build.rs @@ -0,0 +1,36 @@ +//! 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=-Tlink-rp.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); +} diff --git a/firmware/rust1/embassy b/firmware/rust1/embassy new file mode 160000 index 0000000..dec7547 --- /dev/null +++ b/firmware/rust1/embassy @@ -0,0 +1 @@ +Subproject commit dec75474d5fd82dd6abe25647f0e221c2266dda2 diff --git a/firmware/rust1/memory.x b/firmware/rust1/memory.x new file mode 100644 index 0000000..aba861a --- /dev/null +++ b/firmware/rust1/memory.x @@ -0,0 +1,5 @@ +MEMORY { + BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 + FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100 + RAM : ORIGIN = 0x20000000, LENGTH = 256K +} \ No newline at end of file diff --git a/firmware/rust1/rust-toolchain.toml b/firmware/rust1/rust-toolchain.toml new file mode 100644 index 0000000..2301ddc --- /dev/null +++ b/firmware/rust1/rust-toolchain.toml @@ -0,0 +1,14 @@ +# Before upgrading check that everything is available on all tier1 targets here: +# https://rust-lang.github.io/rustup-components-history +[toolchain] +channel = "nightly-2023-04-18" +components = [ "rust-src", "rustfmt", "llvm-tools-preview" ] +targets = [ + "thumbv7em-none-eabi", + "thumbv7m-none-eabi", + "thumbv6m-none-eabi", + "thumbv7em-none-eabihf", + "thumbv8m.main-none-eabihf", + "riscv32imac-unknown-none-elf", + "wasm32-unknown-unknown", +] diff --git a/firmware/rust1/src/bin/adc.rs b/firmware/rust1/src/bin/adc.rs new file mode 100644 index 0000000..4202fd3 --- /dev/null +++ b/firmware/rust1/src/bin/adc.rs @@ -0,0 +1,38 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::adc::{Adc, Config}; +use embassy_rp::interrupt; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let irq = interrupt::take!(ADC_IRQ_FIFO); + let mut adc = Adc::new(p.ADC, irq, Config::default()); + + let mut p26 = p.PIN_26; + let mut p27 = p.PIN_27; + let mut p28 = p.PIN_28; + + loop { + let level = adc.read(&mut p26).await; + info!("Pin 26 ADC: {}", level); + let level = adc.read(&mut p27).await; + info!("Pin 27 ADC: {}", level); + let level = adc.read(&mut p28).await; + info!("Pin 28 ADC: {}", level); + let temp = adc.read_temperature().await; + info!("Temp: {} degrees", convert_to_celsius(temp)); + Timer::after(Duration::from_secs(1)).await; + } +} + +fn convert_to_celsius(raw_temp: u16) -> f32 { + // According to chapter 4.9.5. Temperature Sensor in RP2040 datasheet + 27.0 - (raw_temp as f32 * 3.3 / 4096.0 - 0.706) / 0.001721 as f32 +} diff --git a/firmware/rust1/src/bin/blinky.rs b/firmware/rust1/src/bin/blinky.rs new file mode 100644 index 0000000..7aa36a1 --- /dev/null +++ b/firmware/rust1/src/bin/blinky.rs @@ -0,0 +1,26 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::gpio; +use embassy_time::{Duration, Timer}; +use gpio::{Level, Output}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut led = Output::new(p.PIN_25, Level::Low); + + loop { + info!("led on!"); + led.set_high(); + Timer::after(Duration::from_secs(1)).await; + + info!("led off!"); + led.set_low(); + Timer::after(Duration::from_secs(1)).await; + } +} diff --git a/firmware/rust1/src/bin/button.rs b/firmware/rust1/src/bin/button.rs new file mode 100644 index 0000000..c5422c6 --- /dev/null +++ b/firmware/rust1/src/bin/button.rs @@ -0,0 +1,22 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use embassy_executor::Spawner; +use embassy_rp::gpio::{Input, Level, Output, Pull}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let button = Input::new(p.PIN_28, Pull::Up); + let mut led = Output::new(p.PIN_25, Level::Low); + + loop { + if button.is_high() { + led.set_high(); + } else { + led.set_low(); + } + } +} diff --git a/firmware/rust1/src/bin/flash.rs b/firmware/rust1/src/bin/flash.rs new file mode 100644 index 0000000..8d6b379 --- /dev/null +++ b/firmware/rust1/src/bin/flash.rs @@ -0,0 +1,89 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::flash::{ERASE_SIZE, FLASH_BASE}; +use embassy_rp::peripherals::FLASH; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +const ADDR_OFFSET: u32 = 0x100000; +const FLASH_SIZE: usize = 2 * 1024 * 1024; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Hello World!"); + + // add some delay to give an attached debug probe time to parse the + // defmt RTT header. Reading that header might touch flash memory, which + // interferes with flash write operations. + // https://github.com/knurling-rs/defmt/pull/683 + Timer::after(Duration::from_millis(10)).await; + + let mut flash = embassy_rp::flash::Flash::<_, FLASH_SIZE>::new(p.FLASH); + erase_write_sector(&mut flash, 0x00); + + multiwrite_bytes(&mut flash, ERASE_SIZE as u32); + + loop {} +} + +fn multiwrite_bytes(flash: &mut embassy_rp::flash::Flash<'_, FLASH, FLASH_SIZE>, offset: u32) { + info!(">>>> [multiwrite_bytes]"); + let mut read_buf = [0u8; ERASE_SIZE]; + defmt::unwrap!(flash.read(ADDR_OFFSET + offset, &mut read_buf)); + + info!("Addr of flash block is {:x}", ADDR_OFFSET + offset + FLASH_BASE as u32); + info!("Contents start with {=[u8]}", read_buf[0..4]); + + defmt::unwrap!(flash.erase(ADDR_OFFSET + offset, ADDR_OFFSET + offset + ERASE_SIZE as u32)); + + defmt::unwrap!(flash.read(ADDR_OFFSET + offset, &mut read_buf)); + info!("Contents after erase starts with {=[u8]}", read_buf[0..4]); + if read_buf.iter().any(|x| *x != 0xFF) { + defmt::panic!("unexpected"); + } + + defmt::unwrap!(flash.write(ADDR_OFFSET + offset, &[0x01])); + defmt::unwrap!(flash.write(ADDR_OFFSET + offset + 1, &[0x02])); + defmt::unwrap!(flash.write(ADDR_OFFSET + offset + 2, &[0x03])); + defmt::unwrap!(flash.write(ADDR_OFFSET + offset + 3, &[0x04])); + + defmt::unwrap!(flash.read(ADDR_OFFSET + offset, &mut read_buf)); + info!("Contents after write starts with {=[u8]}", read_buf[0..4]); + if &read_buf[0..4] != &[0x01, 0x02, 0x03, 0x04] { + defmt::panic!("unexpected"); + } +} + +fn erase_write_sector(flash: &mut embassy_rp::flash::Flash<'_, FLASH, FLASH_SIZE>, offset: u32) { + info!(">>>> [erase_write_sector]"); + let mut buf = [0u8; ERASE_SIZE]; + defmt::unwrap!(flash.read(ADDR_OFFSET + offset, &mut buf)); + + info!("Addr of flash block is {:x}", ADDR_OFFSET + offset + FLASH_BASE as u32); + info!("Contents start with {=[u8]}", buf[0..4]); + + defmt::unwrap!(flash.erase(ADDR_OFFSET + offset, ADDR_OFFSET + offset + ERASE_SIZE as u32)); + + defmt::unwrap!(flash.read(ADDR_OFFSET + offset, &mut buf)); + info!("Contents after erase starts with {=[u8]}", buf[0..4]); + if buf.iter().any(|x| *x != 0xFF) { + defmt::panic!("unexpected"); + } + + for b in buf.iter_mut() { + *b = 0xDA; + } + + defmt::unwrap!(flash.write(ADDR_OFFSET + offset, &buf)); + + defmt::unwrap!(flash.read(ADDR_OFFSET + offset, &mut buf)); + info!("Contents after write starts with {=[u8]}", buf[0..4]); + if buf.iter().any(|x| *x != 0xDA) { + defmt::panic!("unexpected"); + } +} diff --git a/firmware/rust1/src/bin/gpio_async.rs b/firmware/rust1/src/bin/gpio_async.rs new file mode 100644 index 0000000..52d13a9 --- /dev/null +++ b/firmware/rust1/src/bin/gpio_async.rs @@ -0,0 +1,39 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::gpio; +use embassy_time::{Duration, Timer}; +use gpio::{Input, Level, Output, Pull}; +use {defmt_rtt as _, panic_probe as _}; + +/// This example shows how async gpio can be used with a RP2040. +/// +/// It requires an external signal to be manually triggered on PIN 16. For +/// example, this could be accomplished using an external power source with a +/// button so that it is possible to toggle the signal from low to high. +/// +/// This example will begin with turning on the LED on the board and wait for a +/// high signal on PIN 16. Once the high event/signal occurs the program will +/// continue and turn off the LED, and then wait for 2 seconds before completing +/// the loop and starting over again. +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut led = Output::new(p.PIN_25, Level::Low); + let mut async_input = Input::new(p.PIN_16, Pull::None); + + loop { + info!("wait_for_high. Turn on LED"); + led.set_high(); + + async_input.wait_for_high().await; + + info!("done wait_for_high. Turn off LED"); + led.set_low(); + + Timer::after(Duration::from_secs(2)).await; + } +} diff --git a/firmware/rust1/src/bin/gpout.rs b/firmware/rust1/src/bin/gpout.rs new file mode 100644 index 0000000..236a653 --- /dev/null +++ b/firmware/rust1/src/bin/gpout.rs @@ -0,0 +1,34 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::clocks; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + + let gpout3 = clocks::Gpout::new(p.PIN_25); + gpout3.set_div(1000, 0); + gpout3.enable(); + + loop { + gpout3.set_src(clocks::GpoutSrc::CLK_SYS); + info!( + "Pin 25 is now outputing CLK_SYS/1000, should be toggling at {}", + gpout3.get_freq() + ); + Timer::after(Duration::from_secs(2)).await; + + gpout3.set_src(clocks::GpoutSrc::CLK_REF); + info!( + "Pin 25 is now outputing CLK_REF/1000, should be toggling at {}", + gpout3.get_freq() + ); + Timer::after(Duration::from_secs(2)).await; + } +} diff --git a/firmware/rust1/src/bin/i2c_async.rs b/firmware/rust1/src/bin/i2c_async.rs new file mode 100644 index 0000000..d1a2e3c --- /dev/null +++ b/firmware/rust1/src/bin/i2c_async.rs @@ -0,0 +1,102 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::i2c::{self, Config}; +use embassy_rp::interrupt; +use embassy_time::{Duration, Timer}; +use embedded_hal_async::i2c::I2c; +use {defmt_rtt as _, panic_probe as _}; + +#[allow(dead_code)] +mod mcp23017 { + pub const ADDR: u8 = 0x20; // default addr + + macro_rules! mcpregs { + ($($name:ident : $val:expr),* $(,)?) => { + $( + pub const $name: u8 = $val; + )* + + pub fn regname(reg: u8) -> &'static str { + match reg { + $( + $val => stringify!($name), + )* + _ => panic!("bad reg"), + } + } + } + } + + // These are correct for IOCON.BANK=0 + mcpregs! { + IODIRA: 0x00, + IPOLA: 0x02, + GPINTENA: 0x04, + DEFVALA: 0x06, + INTCONA: 0x08, + IOCONA: 0x0A, + GPPUA: 0x0C, + INTFA: 0x0E, + INTCAPA: 0x10, + GPIOA: 0x12, + OLATA: 0x14, + IODIRB: 0x01, + IPOLB: 0x03, + GPINTENB: 0x05, + DEFVALB: 0x07, + INTCONB: 0x09, + IOCONB: 0x0B, + GPPUB: 0x0D, + INTFB: 0x0F, + INTCAPB: 0x11, + GPIOB: 0x13, + OLATB: 0x15, + } +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + + let sda = p.PIN_14; + let scl = p.PIN_15; + let irq = interrupt::take!(I2C1_IRQ); + + info!("set up i2c "); + let mut i2c = i2c::I2c::new_async(p.I2C1, scl, sda, irq, Config::default()); + + use mcp23017::*; + + info!("init mcp23017 config for IxpandO"); + // init - a outputs, b inputs + i2c.write(ADDR, &[IODIRA, 0x00]).await.unwrap(); + i2c.write(ADDR, &[IODIRB, 0xff]).await.unwrap(); + i2c.write(ADDR, &[GPPUB, 0xff]).await.unwrap(); // pullups + + let mut val = 1; + loop { + let mut portb = [0]; + + i2c.write_read(mcp23017::ADDR, &[GPIOB], &mut portb).await.unwrap(); + info!("portb = {:02x}", portb[0]); + i2c.write(mcp23017::ADDR, &[GPIOA, val | portb[0]]).await.unwrap(); + val = val.rotate_left(1); + + // get a register dump + info!("getting register dump"); + let mut regs = [0; 22]; + i2c.write_read(ADDR, &[0], &mut regs).await.unwrap(); + // always get the regdump but only display it if portb'0 is set + if portb[0] & 1 != 0 { + for (idx, reg) in regs.into_iter().enumerate() { + info!("{} => {:02x}", regname(idx as u8), reg); + } + } + + Timer::after(Duration::from_millis(100)).await; + } +} diff --git a/firmware/rust1/src/bin/i2c_blocking.rs b/firmware/rust1/src/bin/i2c_blocking.rs new file mode 100644 index 0000000..7623e33 --- /dev/null +++ b/firmware/rust1/src/bin/i2c_blocking.rs @@ -0,0 +1,70 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::i2c::{self, Config}; +use embassy_time::{Duration, Timer}; +use embedded_hal_1::i2c::I2c; +use {defmt_rtt as _, panic_probe as _}; + +#[allow(dead_code)] +mod mcp23017 { + pub const ADDR: u8 = 0x20; // default addr + + pub const IODIRA: u8 = 0x00; + pub const IPOLA: u8 = 0x02; + pub const GPINTENA: u8 = 0x04; + pub const DEFVALA: u8 = 0x06; + pub const INTCONA: u8 = 0x08; + pub const IOCONA: u8 = 0x0A; + pub const GPPUA: u8 = 0x0C; + pub const INTFA: u8 = 0x0E; + pub const INTCAPA: u8 = 0x10; + pub const GPIOA: u8 = 0x12; + pub const OLATA: u8 = 0x14; + pub const IODIRB: u8 = 0x01; + pub const IPOLB: u8 = 0x03; + pub const GPINTENB: u8 = 0x05; + pub const DEFVALB: u8 = 0x07; + pub const INTCONB: u8 = 0x09; + pub const IOCONB: u8 = 0x0B; + pub const GPPUB: u8 = 0x0D; + pub const INTFB: u8 = 0x0F; + pub const INTCAPB: u8 = 0x11; + pub const GPIOB: u8 = 0x13; + pub const OLATB: u8 = 0x15; +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + + let sda = p.PIN_14; + let scl = p.PIN_15; + + info!("set up i2c "); + let mut i2c = i2c::I2c::new_blocking(p.I2C1, scl, sda, Config::default()); + + use mcp23017::*; + + info!("init mcp23017 config for IxpandO"); + // init - a outputs, b inputs + i2c.write(ADDR, &[IODIRA, 0x00]).unwrap(); + i2c.write(ADDR, &[IODIRB, 0xff]).unwrap(); + i2c.write(ADDR, &[GPPUB, 0xff]).unwrap(); // pullups + + let mut val = 0xaa; + loop { + let mut portb = [0]; + + i2c.write(mcp23017::ADDR, &[GPIOA, val]).unwrap(); + i2c.write_read(mcp23017::ADDR, &[GPIOB], &mut portb).unwrap(); + + info!("portb = {:02x}", portb[0]); + val = !val; + + Timer::after(Duration::from_secs(1)).await; + } +} diff --git a/firmware/rust1/src/bin/lora_lorawan.rs b/firmware/rust1/src/bin/lora_lorawan.rs new file mode 100644 index 0000000..a9c84bf --- /dev/null +++ b/firmware/rust1/src/bin/lora_lorawan.rs @@ -0,0 +1,80 @@ +//! This example runs on the Raspberry Pi Pico with a Waveshare board containing a Semtech Sx1262 radio. +//! It demonstrates LoRaWAN join functionality. +#![no_std] +#![no_main] +#![macro_use] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_lora::iv::GenericSx126xInterfaceVariant; +use embassy_lora::LoraTimer; +use embassy_rp::gpio::{Input, Level, Output, Pin, Pull}; +use embassy_rp::spi::{Config, Spi}; +use embassy_time::Delay; +use lora_phy::mod_params::*; +use lora_phy::sx1261_2::SX1261_2; +use lora_phy::LoRa; +use lorawan::default_crypto::DefaultFactory as Crypto; +use lorawan_device::async_device::lora_radio::LoRaRadio; +use lorawan_device::async_device::{region, Device, JoinMode}; +use {defmt_rtt as _, panic_probe as _}; + +const LORAWAN_REGION: region::Region = region::Region::EU868; // warning: set this appropriately for the region + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + + let miso = p.PIN_12; + let mosi = p.PIN_11; + let clk = p.PIN_10; + let spi = Spi::new(p.SPI1, clk, mosi, miso, p.DMA_CH0, p.DMA_CH1, Config::default()); + + let nss = Output::new(p.PIN_3.degrade(), Level::High); + let reset = Output::new(p.PIN_15.degrade(), Level::High); + let dio1 = Input::new(p.PIN_20.degrade(), Pull::None); + let busy = Input::new(p.PIN_2.degrade(), Pull::None); + + let iv = GenericSx126xInterfaceVariant::new(nss, reset, dio1, busy, None, None).unwrap(); + + let mut delay = Delay; + + let lora = { + match LoRa::new( + SX1261_2::new(BoardType::RpPicoWaveshareSx1262, spi, iv), + true, + &mut delay, + ) + .await + { + Ok(l) => l, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + let radio = LoRaRadio::new(lora); + let region: region::Configuration = region::Configuration::new(LORAWAN_REGION); + let mut device: Device<_, Crypto, _, _> = Device::new(region, radio, LoraTimer::new(), embassy_rp::clocks::RoscRng); + + defmt::info!("Joining LoRaWAN network"); + + // TODO: Adjust the EUI and Keys according to your network credentials + match device + .join(&JoinMode::OTAA { + deveui: [0, 0, 0, 0, 0, 0, 0, 0], + appeui: [0, 0, 0, 0, 0, 0, 0, 0], + appkey: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + }) + .await + { + Ok(()) => defmt::info!("LoRaWAN network joined"), + Err(err) => { + info!("Radio error = {}", err); + return; + } + }; +} diff --git a/firmware/rust1/src/bin/lora_p2p_receive.rs b/firmware/rust1/src/bin/lora_p2p_receive.rs new file mode 100644 index 0000000..2504192 --- /dev/null +++ b/firmware/rust1/src/bin/lora_p2p_receive.rs @@ -0,0 +1,115 @@ +//! This example runs on the Raspberry Pi Pico with a Waveshare board containing a Semtech Sx1262 radio. +//! It demonstrates LORA P2P receive functionality in conjunction with the lora_p2p_send example. +#![no_std] +#![no_main] +#![macro_use] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_lora::iv::GenericSx126xInterfaceVariant; +use embassy_rp::gpio::{Input, Level, Output, Pin, Pull}; +use embassy_rp::spi::{Config, Spi}; +use embassy_time::{Delay, Duration, Timer}; +use lora_phy::mod_params::*; +use lora_phy::sx1261_2::SX1261_2; +use lora_phy::LoRa; +use {defmt_rtt as _, panic_probe as _}; + +const LORA_FREQUENCY_IN_HZ: u32 = 903_900_000; // warning: set this appropriately for the region + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + + let miso = p.PIN_12; + let mosi = p.PIN_11; + let clk = p.PIN_10; + let spi = Spi::new(p.SPI1, clk, mosi, miso, p.DMA_CH0, p.DMA_CH1, Config::default()); + + let nss = Output::new(p.PIN_3.degrade(), Level::High); + let reset = Output::new(p.PIN_15.degrade(), Level::High); + let dio1 = Input::new(p.PIN_20.degrade(), Pull::None); + let busy = Input::new(p.PIN_2.degrade(), Pull::None); + + let iv = GenericSx126xInterfaceVariant::new(nss, reset, dio1, busy, None, None).unwrap(); + + let mut delay = Delay; + + let mut lora = { + match LoRa::new( + SX1261_2::new(BoardType::RpPicoWaveshareSx1262, spi, iv), + false, + &mut delay, + ) + .await + { + Ok(l) => l, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + let mut debug_indicator = Output::new(p.PIN_25, Level::Low); + + let mut receiving_buffer = [00u8; 100]; + + let mdltn_params = { + match lora.create_modulation_params( + SpreadingFactor::_10, + Bandwidth::_250KHz, + CodingRate::_4_8, + LORA_FREQUENCY_IN_HZ, + ) { + Ok(mp) => mp, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + let rx_pkt_params = { + match lora.create_rx_packet_params(4, false, receiving_buffer.len() as u8, true, false, &mdltn_params) { + Ok(pp) => pp, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + match lora + .prepare_for_rx(&mdltn_params, &rx_pkt_params, None, true, false, 0, 0x00ffffffu32) + .await + { + Ok(()) => {} + Err(err) => { + info!("Radio error = {}", err); + return; + } + }; + + loop { + receiving_buffer = [00u8; 100]; + match lora.rx(&rx_pkt_params, &mut receiving_buffer).await { + Ok((received_len, _rx_pkt_status)) => { + if (received_len == 3) + && (receiving_buffer[0] == 0x01u8) + && (receiving_buffer[1] == 0x02u8) + && (receiving_buffer[2] == 0x03u8) + { + info!("rx successful"); + debug_indicator.set_high(); + Timer::after(Duration::from_secs(5)).await; + debug_indicator.set_low(); + } else { + info!("rx unknown packet"); + } + } + Err(err) => info!("rx unsuccessful = {}", err), + } + } +} diff --git a/firmware/rust1/src/bin/lora_p2p_send.rs b/firmware/rust1/src/bin/lora_p2p_send.rs new file mode 100644 index 0000000..3a0544b --- /dev/null +++ b/firmware/rust1/src/bin/lora_p2p_send.rs @@ -0,0 +1,103 @@ +//! This example runs on the Raspberry Pi Pico with a Waveshare board containing a Semtech Sx1262 radio. +//! It demonstrates LORA P2P send functionality. +#![no_std] +#![no_main] +#![macro_use] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_lora::iv::GenericSx126xInterfaceVariant; +use embassy_rp::gpio::{Input, Level, Output, Pin, Pull}; +use embassy_rp::spi::{Config, Spi}; +use embassy_time::Delay; +use lora_phy::mod_params::*; +use lora_phy::sx1261_2::SX1261_2; +use lora_phy::LoRa; +use {defmt_rtt as _, panic_probe as _}; + +const LORA_FREQUENCY_IN_HZ: u32 = 903_900_000; // warning: set this appropriately for the region + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + + let miso = p.PIN_12; + let mosi = p.PIN_11; + let clk = p.PIN_10; + let spi = Spi::new(p.SPI1, clk, mosi, miso, p.DMA_CH0, p.DMA_CH1, Config::default()); + + let nss = Output::new(p.PIN_3.degrade(), Level::High); + let reset = Output::new(p.PIN_15.degrade(), Level::High); + let dio1 = Input::new(p.PIN_20.degrade(), Pull::None); + let busy = Input::new(p.PIN_2.degrade(), Pull::None); + + let iv = GenericSx126xInterfaceVariant::new(nss, reset, dio1, busy, None, None).unwrap(); + + let mut delay = Delay; + + let mut lora = { + match LoRa::new( + SX1261_2::new(BoardType::RpPicoWaveshareSx1262, spi, iv), + false, + &mut delay, + ) + .await + { + Ok(l) => l, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + let mdltn_params = { + match lora.create_modulation_params( + SpreadingFactor::_10, + Bandwidth::_250KHz, + CodingRate::_4_8, + LORA_FREQUENCY_IN_HZ, + ) { + Ok(mp) => mp, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + let mut tx_pkt_params = { + match lora.create_tx_packet_params(4, false, true, false, &mdltn_params) { + Ok(pp) => pp, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + match lora.prepare_for_tx(&mdltn_params, 20, false).await { + Ok(()) => {} + Err(err) => { + info!("Radio error = {}", err); + return; + } + }; + + let buffer = [0x01u8, 0x02u8, 0x03u8]; + match lora.tx(&mdltn_params, &mut tx_pkt_params, &buffer, 0xffffff).await { + Ok(()) => { + info!("TX DONE"); + } + Err(err) => { + info!("Radio error = {}", err); + return; + } + }; + + match lora.sleep(&mut delay).await { + Ok(()) => info!("Sleep successful"), + Err(err) => info!("Sleep unsuccessful = {}", err), + } +} diff --git a/firmware/rust1/src/bin/lora_p2p_send_multicore.rs b/firmware/rust1/src/bin/lora_p2p_send_multicore.rs new file mode 100644 index 0000000..5585606 --- /dev/null +++ b/firmware/rust1/src/bin/lora_p2p_send_multicore.rs @@ -0,0 +1,139 @@ +//! This example runs on the Raspberry Pi Pico with a Waveshare board containing a Semtech Sx1262 radio. +//! It demonstrates LORA P2P send functionality using the second core, with data provided by the first core. +#![no_std] +#![no_main] +#![macro_use] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Executor; +use embassy_executor::_export::StaticCell; +use embassy_lora::iv::GenericSx126xInterfaceVariant; +use embassy_rp::gpio::{AnyPin, Input, Level, Output, Pin, Pull}; +use embassy_rp::multicore::{spawn_core1, Stack}; +use embassy_rp::peripherals::SPI1; +use embassy_rp::spi::{Async, Config, Spi}; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::channel::Channel; +use embassy_time::{Delay, Duration, Timer}; +use lora_phy::mod_params::*; +use lora_phy::sx1261_2::SX1261_2; +use lora_phy::LoRa; +use {defmt_rtt as _, panic_probe as _}; + +static mut CORE1_STACK: Stack<4096> = Stack::new(); +static EXECUTOR0: StaticCell<Executor> = StaticCell::new(); +static EXECUTOR1: StaticCell<Executor> = StaticCell::new(); +static CHANNEL: Channel<CriticalSectionRawMutex, [u8; 3], 1> = Channel::new(); + +const LORA_FREQUENCY_IN_HZ: u32 = 903_900_000; // warning: set this appropriately for the region + +#[cortex_m_rt::entry] +fn main() -> ! { + let p = embassy_rp::init(Default::default()); + + let miso = p.PIN_12; + let mosi = p.PIN_11; + let clk = p.PIN_10; + let spi = Spi::new(p.SPI1, clk, mosi, miso, p.DMA_CH0, p.DMA_CH1, Config::default()); + + let nss = Output::new(p.PIN_3.degrade(), Level::High); + let reset = Output::new(p.PIN_15.degrade(), Level::High); + let dio1 = Input::new(p.PIN_20.degrade(), Pull::None); + let busy = Input::new(p.PIN_2.degrade(), Pull::None); + + let iv = GenericSx126xInterfaceVariant::new(nss, reset, dio1, busy, None, None).unwrap(); + + spawn_core1(p.CORE1, unsafe { &mut CORE1_STACK }, move || { + let executor1 = EXECUTOR1.init(Executor::new()); + executor1.run(|spawner| unwrap!(spawner.spawn(core1_task(spi, iv)))); + }); + + let executor0 = EXECUTOR0.init(Executor::new()); + executor0.run(|spawner| unwrap!(spawner.spawn(core0_task()))); +} + +#[embassy_executor::task] +async fn core0_task() { + info!("Hello from core 0"); + loop { + CHANNEL.send([0x01u8, 0x02u8, 0x03u8]).await; + Timer::after(Duration::from_millis(60 * 1000)).await; + } +} + +#[embassy_executor::task] +async fn core1_task( + spi: Spi<'static, SPI1, Async>, + iv: GenericSx126xInterfaceVariant<Output<'static, AnyPin>, Input<'static, AnyPin>>, +) { + info!("Hello from core 1"); + let mut delay = Delay; + + let mut lora = { + match LoRa::new( + SX1261_2::new(BoardType::RpPicoWaveshareSx1262, spi, iv), + false, + &mut delay, + ) + .await + { + Ok(l) => l, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + let mdltn_params = { + match lora.create_modulation_params( + SpreadingFactor::_10, + Bandwidth::_250KHz, + CodingRate::_4_8, + LORA_FREQUENCY_IN_HZ, + ) { + Ok(mp) => mp, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + let mut tx_pkt_params = { + match lora.create_tx_packet_params(4, false, true, false, &mdltn_params) { + Ok(pp) => pp, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + loop { + let buffer: [u8; 3] = CHANNEL.recv().await; + match lora.prepare_for_tx(&mdltn_params, 20, false).await { + Ok(()) => {} + Err(err) => { + info!("Radio error = {}", err); + return; + } + }; + + match lora.tx(&mdltn_params, &mut tx_pkt_params, &buffer, 0xffffff).await { + Ok(()) => { + info!("TX DONE"); + } + Err(err) => { + info!("Radio error = {}", err); + return; + } + }; + + match lora.sleep(&mut delay).await { + Ok(()) => info!("Sleep successful"), + Err(err) => info!("Sleep unsuccessful = {}", err), + } + } +} diff --git a/firmware/rust1/src/bin/multicore.rs b/firmware/rust1/src/bin/multicore.rs new file mode 100644 index 0000000..376b2b6 --- /dev/null +++ b/firmware/rust1/src/bin/multicore.rs @@ -0,0 +1,60 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Executor; +use embassy_executor::_export::StaticCell; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::multicore::{spawn_core1, Stack}; +use embassy_rp::peripherals::PIN_25; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::channel::Channel; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +static mut CORE1_STACK: Stack<4096> = Stack::new(); +static EXECUTOR0: StaticCell<Executor> = StaticCell::new(); +static EXECUTOR1: StaticCell<Executor> = StaticCell::new(); +static CHANNEL: Channel<CriticalSectionRawMutex, LedState, 1> = Channel::new(); + +enum LedState { + On, + Off, +} + +#[cortex_m_rt::entry] +fn main() -> ! { + let p = embassy_rp::init(Default::default()); + let led = Output::new(p.PIN_25, Level::Low); + + spawn_core1(p.CORE1, unsafe { &mut CORE1_STACK }, move || { + let executor1 = EXECUTOR1.init(Executor::new()); + executor1.run(|spawner| unwrap!(spawner.spawn(core1_task(led)))); + }); + + let executor0 = EXECUTOR0.init(Executor::new()); + executor0.run(|spawner| unwrap!(spawner.spawn(core0_task()))); +} + +#[embassy_executor::task] +async fn core0_task() { + info!("Hello from core 0"); + loop { + CHANNEL.send(LedState::On).await; + Timer::after(Duration::from_millis(100)).await; + CHANNEL.send(LedState::Off).await; + Timer::after(Duration::from_millis(400)).await; + } +} + +#[embassy_executor::task] +async fn core1_task(mut led: Output<'static, PIN_25>) { + info!("Hello from core 1"); + loop { + match CHANNEL.recv().await { + LedState::On => led.set_high(), + LedState::Off => led.set_low(), + } + } +} diff --git a/firmware/rust1/src/bin/multiprio.rs b/firmware/rust1/src/bin/multiprio.rs new file mode 100644 index 0000000..2f79ba4 --- /dev/null +++ b/firmware/rust1/src/bin/multiprio.rs @@ -0,0 +1,152 @@ +//! This example showcases how to create multiple Executor instances to run tasks at +//! different priority levels. +//! +//! Low priority executor runs in thread mode (not interrupt), and uses `sev` for signaling +//! there's work in the queue, and `wfe` for waiting for work. +//! +//! Medium and high priority executors run in two interrupts with different priorities. +//! Signaling work is done by pending the interrupt. No "waiting" needs to be done explicitly, since +//! when there's work the interrupt will trigger and run the executor. +//! +//! Sample output below. Note that high priority ticks can interrupt everything else, and +//! medium priority computations can interrupt low priority computations, making them to appear +//! to take significantly longer time. +//! +//! ```not_rust +//! [med] Starting long computation +//! [med] done in 992 ms +//! [high] tick! +//! [low] Starting long computation +//! [med] Starting long computation +//! [high] tick! +//! [high] tick! +//! [med] done in 993 ms +//! [med] Starting long computation +//! [high] tick! +//! [high] tick! +//! [med] done in 993 ms +//! [low] done in 3972 ms +//! [med] Starting long computation +//! [high] tick! +//! [high] tick! +//! [med] done in 993 ms +//! ``` +//! +//! For comparison, try changing the code so all 3 tasks get spawned on the low priority executor. +//! You will get an output like the following. Note that no computation is ever interrupted. +//! +//! ```not_rust +//! [high] tick! +//! [med] Starting long computation +//! [med] done in 496 ms +//! [low] Starting long computation +//! [low] done in 992 ms +//! [med] Starting long computation +//! [med] done in 496 ms +//! [high] tick! +//! [low] Starting long computation +//! [low] done in 992 ms +//! [high] tick! +//! [med] Starting long computation +//! [med] done in 496 ms +//! [high] tick! +//! ``` +//! + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use core::mem; + +use cortex_m::peripheral::NVIC; +use cortex_m_rt::entry; +use defmt::{info, unwrap}; +use embassy_rp::executor::{Executor, InterruptExecutor}; +use embassy_rp::interrupt; +use embassy_rp::pac::Interrupt; +use embassy_time::{Duration, Instant, Timer, TICK_HZ}; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::task] +async fn run_high() { + loop { + info!(" [high] tick!"); + Timer::after(Duration::from_ticks(673740)).await; + } +} + +#[embassy_executor::task] +async fn run_med() { + loop { + let start = Instant::now(); + info!(" [med] Starting long computation"); + + // Spin-wait to simulate a long CPU computation + cortex_m::asm::delay(125_000_000); // ~1 second + + let end = Instant::now(); + let ms = end.duration_since(start).as_ticks() * 1000 / TICK_HZ; + info!(" [med] done in {} ms", ms); + + Timer::after(Duration::from_ticks(53421)).await; + } +} + +#[embassy_executor::task] +async fn run_low() { + loop { + let start = Instant::now(); + info!("[low] Starting long computation"); + + // Spin-wait to simulate a long CPU computation + cortex_m::asm::delay(250_000_000); // ~2 seconds + + let end = Instant::now(); + let ms = end.duration_since(start).as_ticks() * 1000 / TICK_HZ; + info!("[low] done in {} ms", ms); + + Timer::after(Duration::from_ticks(82983)).await; + } +} + +static EXECUTOR_HIGH: InterruptExecutor = InterruptExecutor::new(); +static EXECUTOR_MED: InterruptExecutor = InterruptExecutor::new(); +static EXECUTOR_LOW: StaticCell<Executor> = StaticCell::new(); + +#[interrupt] +unsafe fn SWI_IRQ_1() { + EXECUTOR_HIGH.on_interrupt() +} + +#[interrupt] +unsafe fn SWI_IRQ_0() { + EXECUTOR_MED.on_interrupt() +} + +#[entry] +fn main() -> ! { + info!("Hello World!"); + + let _p = embassy_rp::init(Default::default()); + let mut nvic: NVIC = unsafe { mem::transmute(()) }; + + // High-priority executor: SWI_IRQ_1, priority level 2 + unsafe { nvic.set_priority(Interrupt::SWI_IRQ_1, 2 << 6) }; + info!("bla: {}", NVIC::get_priority(Interrupt::SWI_IRQ_1)); + let spawner = EXECUTOR_HIGH.start(Interrupt::SWI_IRQ_1); + unwrap!(spawner.spawn(run_high())); + + // Medium-priority executor: SWI_IRQ_0, priority level 3 + unsafe { nvic.set_priority(Interrupt::SWI_IRQ_0, 3 << 6) }; + info!("bla: {}", NVIC::get_priority(Interrupt::SWI_IRQ_0)); + let spawner = EXECUTOR_MED.start(Interrupt::SWI_IRQ_0); + unwrap!(spawner.spawn(run_med())); + + // Low priority executor: runs in thread mode, using WFE/SEV + let executor = EXECUTOR_LOW.init(Executor::new()); + executor.run(|spawner| { + unwrap!(spawner.spawn(run_low())); + }); +} diff --git a/firmware/rust1/src/bin/pio_async.rs b/firmware/rust1/src/bin/pio_async.rs new file mode 100644 index 0000000..12484e8 --- /dev/null +++ b/firmware/rust1/src/bin/pio_async.rs @@ -0,0 +1,122 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +use defmt::info; +use embassy_embedded_hal::SetConfig; +use embassy_executor::Spawner; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{Common, Config, Irq, Pio, PioPin, ShiftDirection, StateMachine}; +use embassy_rp::relocate::RelocatedProgram; +use fixed::traits::ToFixed; +use fixed_macro::types::U56F8; +use {defmt_rtt as _, panic_probe as _}; + +fn setup_pio_task_sm0<'a>(pio: &mut Common<'a, PIO0>, sm: &mut StateMachine<'a, PIO0, 0>, pin: impl PioPin) { + // Setup sm0 + + // Send data serially to pin + let prg = pio_proc::pio_asm!( + ".origin 16", + "set pindirs, 1", + ".wrap_target", + "out pins,1 [19]", + ".wrap", + ); + + let relocated = RelocatedProgram::new(&prg.program); + let mut cfg = Config::default(); + cfg.use_program(&pio.load_program(&relocated), &[]); + let out_pin = pio.make_pio_pin(pin); + cfg.set_out_pins(&[&out_pin]); + cfg.set_set_pins(&[&out_pin]); + cfg.clock_divider = (U56F8!(125_000_000) / 20 / 200).to_fixed(); + cfg.shift_out.auto_fill = true; + sm.set_config(&cfg); +} + +#[embassy_executor::task] +async fn pio_task_sm0(mut sm: StateMachine<'static, PIO0, 0>) { + sm.set_enable(true); + + let mut v = 0x0f0caffa; + loop { + sm.tx().wait_push(v).await; + v ^= 0xffff; + info!("Pushed {:032b} to FIFO", v); + } +} + +fn setup_pio_task_sm1<'a>(pio: &mut Common<'a, PIO0>, sm: &mut StateMachine<'a, PIO0, 1>) { + // Setupm sm1 + + // Read 0b10101 repeatedly until ISR is full + let prg = pio_proc::pio_asm!(".origin 8", "set x, 0x15", ".wrap_target", "in x, 5 [31]", ".wrap",); + + let relocated = RelocatedProgram::new(&prg.program); + let mut cfg = Config::default(); + cfg.use_program(&pio.load_program(&relocated), &[]); + cfg.clock_divider = (U56F8!(125_000_000) / 2000).to_fixed(); + cfg.shift_in.auto_fill = true; + cfg.shift_in.direction = ShiftDirection::Right; + sm.set_config(&cfg); +} + +#[embassy_executor::task] +async fn pio_task_sm1(mut sm: StateMachine<'static, PIO0, 1>) { + sm.set_enable(true); + loop { + let rx = sm.rx().wait_pull().await; + info!("Pulled {:032b} from FIFO", rx); + } +} + +fn setup_pio_task_sm2<'a>(pio: &mut Common<'a, PIO0>, sm: &mut StateMachine<'a, PIO0, 2>) { + // Setup sm2 + + // Repeatedly trigger IRQ 3 + let prg = pio_proc::pio_asm!( + ".origin 0", + ".wrap_target", + "set x,10", + "delay:", + "jmp x-- delay [15]", + "irq 3 [15]", + ".wrap", + ); + let relocated = RelocatedProgram::new(&prg.program); + let mut cfg = Config::default(); + cfg.use_program(&pio.load_program(&relocated), &[]); + cfg.clock_divider = (U56F8!(125_000_000) / 2000).to_fixed(); + sm.set_config(&cfg); +} + +#[embassy_executor::task] +async fn pio_task_sm2(mut irq: Irq<'static, PIO0, 3>, mut sm: StateMachine<'static, PIO0, 2>) { + sm.set_enable(true); + loop { + irq.wait().await; + info!("IRQ trigged"); + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let pio = p.PIO0; + + let Pio { + mut common, + irq3, + mut sm0, + mut sm1, + mut sm2, + .. + } = Pio::new(pio); + + setup_pio_task_sm0(&mut common, &mut sm0, p.PIN_0); + setup_pio_task_sm1(&mut common, &mut sm1); + setup_pio_task_sm2(&mut common, &mut sm2); + spawner.spawn(pio_task_sm0(sm0)).unwrap(); + spawner.spawn(pio_task_sm1(sm1)).unwrap(); + spawner.spawn(pio_task_sm2(irq3, sm2)).unwrap(); +} diff --git a/firmware/rust1/src/bin/pio_dma.rs b/firmware/rust1/src/bin/pio_dma.rs new file mode 100644 index 0000000..7f85288 --- /dev/null +++ b/firmware/rust1/src/bin/pio_dma.rs @@ -0,0 +1,80 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +use defmt::info; +use embassy_embedded_hal::SetConfig; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_rp::pio::{Config, Pio, ShiftConfig, ShiftDirection}; +use embassy_rp::relocate::RelocatedProgram; +use embassy_rp::Peripheral; +use fixed::traits::ToFixed; +use fixed_macro::types::U56F8; +use {defmt_rtt as _, panic_probe as _}; + +fn swap_nibbles(v: u32) -> u32 { + let v = (v & 0x0f0f_0f0f) << 4 | (v & 0xf0f0_f0f0) >> 4; + let v = (v & 0x00ff_00ff) << 8 | (v & 0xff00_ff00) >> 8; + (v & 0x0000_ffff) << 16 | (v & 0xffff_0000) >> 16 +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let pio = p.PIO0; + let Pio { + mut common, + sm0: mut sm, + .. + } = Pio::new(pio); + + let prg = pio_proc::pio_asm!( + ".origin 0", + "set pindirs,1", + ".wrap_target", + "set y,7", + "loop:", + "out x,4", + "in x,4", + "jmp y--, loop", + ".wrap", + ); + + let relocated = RelocatedProgram::new(&prg.program); + let mut cfg = Config::default(); + cfg.use_program(&common.load_program(&relocated), &[]); + cfg.clock_divider = (U56F8!(125_000_000) / U56F8!(10_000)).to_fixed(); + cfg.shift_in = ShiftConfig { + auto_fill: true, + threshold: 32, + direction: ShiftDirection::Left, + }; + cfg.shift_out = ShiftConfig { + auto_fill: true, + threshold: 32, + direction: ShiftDirection::Right, + }; + + sm.set_config(&cfg); + sm.set_enable(true); + + let mut dma_out_ref = p.DMA_CH0.into_ref(); + let mut dma_in_ref = p.DMA_CH1.into_ref(); + let mut dout = [0x12345678u32; 29]; + for i in 1..dout.len() { + dout[i] = (dout[i - 1] & 0x0fff_ffff) * 13 + 7; + } + let mut din = [0u32; 29]; + loop { + let (rx, tx) = sm.rx_tx(); + join( + tx.dma_push(dma_out_ref.reborrow(), &dout), + rx.dma_pull(dma_in_ref.reborrow(), &mut din), + ) + .await; + for i in 0..din.len() { + assert_eq!(din[i], swap_nibbles(dout[i])); + } + info!("Swapped {} words", dout.len()); + } +} diff --git a/firmware/rust1/src/bin/pio_hd44780.rs b/firmware/rust1/src/bin/pio_hd44780.rs new file mode 100644 index 0000000..088fd56 --- /dev/null +++ b/firmware/rust1/src/bin/pio_hd44780.rs @@ -0,0 +1,235 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use core::fmt::Write; + +use embassy_embedded_hal::SetConfig; +use embassy_executor::Spawner; +use embassy_rp::dma::{AnyChannel, Channel}; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{Config, Direction, FifoJoin, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine}; +use embassy_rp::pwm::{self, Pwm}; +use embassy_rp::relocate::RelocatedProgram; +use embassy_rp::{into_ref, Peripheral, PeripheralRef}; +use embassy_time::{Duration, Instant, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // this test assumes a 2x16 HD44780 display attached as follow: + // rs = PIN0 + // rw = PIN1 + // e = PIN2 + // db4 = PIN3 + // db5 = PIN4 + // db6 = PIN5 + // db7 = PIN6 + // additionally a pwm signal for a bias voltage charge pump is provided on pin 15, + // allowing direct connection of the display to the RP2040 without level shifters. + let p = embassy_rp::init(Default::default()); + + let _pwm = Pwm::new_output_b(p.PWM_CH7, p.PIN_15, { + let mut c = pwm::Config::default(); + c.divider = 125.into(); + c.top = 100; + c.compare_b = 50; + c + }); + + let mut hd = HD44780::new( + p.PIO0, p.DMA_CH3, p.PIN_0, p.PIN_1, p.PIN_2, p.PIN_3, p.PIN_4, p.PIN_5, p.PIN_6, + ) + .await; + + loop { + struct Buf<const N: usize>([u8; N], usize); + impl<const N: usize> Write for Buf<N> { + fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> { + for b in s.as_bytes() { + if self.1 >= N { + return Err(core::fmt::Error); + } + self.0[self.1] = *b; + self.1 += 1; + } + Ok(()) + } + } + let mut buf = Buf([0; 16], 0); + write!(buf, "up {}s", Instant::now().as_micros() as f32 / 1e6).unwrap(); + hd.add_line(&buf.0[0..buf.1]).await; + Timer::after(Duration::from_secs(1)).await; + } +} + +pub struct HD44780<'l> { + dma: PeripheralRef<'l, AnyChannel>, + sm: StateMachine<'l, PIO0, 0>, + + buf: [u8; 40], +} + +impl<'l> HD44780<'l> { + pub async fn new( + pio: impl Peripheral<P = PIO0> + 'l, + dma: impl Peripheral<P = impl Channel> + 'l, + rs: impl PioPin, + rw: impl PioPin, + e: impl PioPin, + db4: impl PioPin, + db5: impl PioPin, + db6: impl PioPin, + db7: impl PioPin, + ) -> HD44780<'l> { + into_ref!(dma); + + let Pio { + mut common, + mut irq0, + mut sm0, + .. + } = Pio::new(pio); + + // takes command words (<wait:24> <command:4> <0:4>) + let prg = pio_proc::pio_asm!( + r#" + .side_set 1 opt + .origin 20 + + loop: + out x, 24 + delay: + jmp x--, delay + out pins, 4 side 1 + out null, 4 side 0 + jmp !osre, loop + irq 0 + "#, + ); + + let rs = common.make_pio_pin(rs); + let rw = common.make_pio_pin(rw); + let e = common.make_pio_pin(e); + let db4 = common.make_pio_pin(db4); + let db5 = common.make_pio_pin(db5); + let db6 = common.make_pio_pin(db6); + let db7 = common.make_pio_pin(db7); + + sm0.set_pin_dirs(Direction::Out, &[&rs, &rw, &e, &db4, &db5, &db6, &db7]); + + let relocated = RelocatedProgram::new(&prg.program); + let mut cfg = Config::default(); + cfg.use_program(&common.load_program(&relocated), &[&e]); + cfg.clock_divider = 125u8.into(); + cfg.set_out_pins(&[&db4, &db5, &db6, &db7]); + cfg.shift_out = ShiftConfig { + auto_fill: true, + direction: ShiftDirection::Left, + threshold: 32, + }; + cfg.fifo_join = FifoJoin::TxOnly; + sm0.set_config(&cfg); + + sm0.set_enable(true); + // init to 8 bit thrice + sm0.tx().push((50000 << 8) | 0x30); + sm0.tx().push((5000 << 8) | 0x30); + sm0.tx().push((200 << 8) | 0x30); + // init 4 bit + sm0.tx().push((200 << 8) | 0x20); + // set font and lines + sm0.tx().push((50 << 8) | 0x20); + sm0.tx().push(0b1100_0000); + + irq0.wait().await; + sm0.set_enable(false); + + // takes command sequences (<rs:1> <count:7>, data...) + // many side sets are only there to free up a delay bit! + let prg = pio_proc::pio_asm!( + r#" + .origin 27 + .side_set 1 + + .wrap_target + pull side 0 + out x 1 side 0 ; !rs + out y 7 side 0 ; #data - 1 + + ; rs/rw to e: >= 60ns + ; e high time: >= 500ns + ; e low time: >= 500ns + ; read data valid after e falling: ~5ns + ; write data hold after e falling: ~10ns + + loop: + pull side 0 + jmp !x data side 0 + command: + set pins 0b00 side 0 + jmp shift side 0 + data: + set pins 0b01 side 0 + shift: + out pins 4 side 1 [9] + nop side 0 [9] + out pins 4 side 1 [9] + mov osr null side 0 [7] + out pindirs 4 side 0 + set pins 0b10 side 0 + busy: + nop side 1 [9] + jmp pin more side 0 [9] + mov osr ~osr side 1 [9] + nop side 0 [4] + out pindirs 4 side 0 + jmp y-- loop side 0 + .wrap + more: + nop side 1 [9] + jmp busy side 0 [9] + "# + ); + + let relocated = RelocatedProgram::new(&prg.program); + let mut cfg = Config::default(); + cfg.use_program(&common.load_program(&relocated), &[&e]); + cfg.clock_divider = 8u8.into(); // ~64ns/insn + cfg.set_jmp_pin(&db7); + cfg.set_set_pins(&[&rs, &rw]); + cfg.set_out_pins(&[&db4, &db5, &db6, &db7]); + cfg.shift_out.direction = ShiftDirection::Left; + cfg.fifo_join = FifoJoin::TxOnly; + sm0.set_config(&cfg); + + sm0.set_enable(true); + + // display on and cursor on and blinking, reset display + sm0.tx().dma_push(dma.reborrow(), &[0x81u8, 0x0f, 1]).await; + + Self { + dma: dma.map_into(), + sm: sm0, + buf: [0x20; 40], + } + } + + pub async fn add_line(&mut self, s: &[u8]) { + // move cursor to 0:0, prepare 16 characters + self.buf[..3].copy_from_slice(&[0x80, 0x80, 15]); + // move line 2 up + self.buf.copy_within(22..38, 3); + // move cursor to 1:0, prepare 16 characters + self.buf[19..22].copy_from_slice(&[0x80, 0xc0, 15]); + // file line 2 with spaces + self.buf[22..38].fill(0x20); + // copy input line + let len = s.len().min(16); + self.buf[22..22 + len].copy_from_slice(&s[0..len]); + // set cursor to 1:15 + self.buf[38..].copy_from_slice(&[0x80, 0xcf]); + + self.sm.tx().dma_push(self.dma.reborrow(), &self.buf).await; + } +} diff --git a/firmware/rust1/src/bin/pwm.rs b/firmware/rust1/src/bin/pwm.rs new file mode 100644 index 0000000..69d3155 --- /dev/null +++ b/firmware/rust1/src/bin/pwm.rs @@ -0,0 +1,27 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_embedded_hal::SetConfig; +use embassy_executor::Spawner; +use embassy_rp::pwm::{Config, Pwm}; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + + let mut c: Config = Default::default(); + c.top = 0x8000; + c.compare_b = 8; + let mut pwm = Pwm::new_output_b(p.PWM_CH4, p.PIN_25, c.clone()); + + loop { + info!("current LED duty cycle: {}/32768", c.compare_b); + Timer::after(Duration::from_secs(1)).await; + c.compare_b = c.compare_b.rotate_left(4); + pwm.set_config(&c); + } +} diff --git a/firmware/rust1/src/bin/spi.rs b/firmware/rust1/src/bin/spi.rs new file mode 100644 index 0000000..a830a17 --- /dev/null +++ b/firmware/rust1/src/bin/spi.rs @@ -0,0 +1,43 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::spi::Spi; +use embassy_rp::{gpio, spi}; +use gpio::{Level, Output}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Hello World!"); + + // Example for resistive touch sensor in Waveshare Pico-ResTouch + + let miso = p.PIN_12; + let mosi = p.PIN_11; + let clk = p.PIN_10; + let touch_cs = p.PIN_16; + + // create SPI + let mut config = spi::Config::default(); + config.frequency = 2_000_000; + let mut spi = Spi::new_blocking(p.SPI1, clk, mosi, miso, config); + + // Configure CS + let mut cs = Output::new(touch_cs, Level::Low); + + loop { + cs.set_low(); + let mut buf = [0x90, 0x00, 0x00, 0xd0, 0x00, 0x00]; + spi.blocking_transfer_in_place(&mut buf).unwrap(); + cs.set_high(); + + let x = (buf[1] as u32) << 5 | (buf[2] as u32) >> 3; + let y = (buf[4] as u32) << 5 | (buf[5] as u32) >> 3; + + info!("touch: {=u32} {=u32}", x, y); + } +} diff --git a/firmware/rust1/src/bin/spi_async.rs b/firmware/rust1/src/bin/spi_async.rs new file mode 100644 index 0000000..671a9ca --- /dev/null +++ b/firmware/rust1/src/bin/spi_async.rs @@ -0,0 +1,29 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::spi::{Config, Spi}; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Hello World!"); + + let miso = p.PIN_12; + let mosi = p.PIN_11; + let clk = p.PIN_10; + + let mut spi = Spi::new(p.SPI1, clk, mosi, miso, p.DMA_CH0, p.DMA_CH1, Config::default()); + + loop { + let tx_buf = [1_u8, 2, 3, 4, 5, 6]; + let mut rx_buf = [0_u8; 6]; + spi.transfer(&mut rx_buf, &tx_buf).await.unwrap(); + info!("{:?}", rx_buf); + Timer::after(Duration::from_secs(1)).await; + } +} diff --git a/firmware/rust1/src/bin/spi_display.rs b/firmware/rust1/src/bin/spi_display.rs new file mode 100644 index 0000000..85a19ce --- /dev/null +++ b/firmware/rust1/src/bin/spi_display.rs @@ -0,0 +1,308 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use core::cell::RefCell; + +use defmt::*; +use embassy_embedded_hal::shared_bus::blocking::spi::SpiDeviceWithConfig; +use embassy_executor::Spawner; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::spi; +use embassy_rp::spi::{Blocking, Spi}; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::blocking_mutex::Mutex; +use embassy_time::Delay; +use embedded_graphics::image::{Image, ImageRawLE}; +use embedded_graphics::mono_font::ascii::FONT_10X20; +use embedded_graphics::mono_font::MonoTextStyle; +use embedded_graphics::pixelcolor::Rgb565; +use embedded_graphics::prelude::*; +use embedded_graphics::primitives::{PrimitiveStyleBuilder, Rectangle}; +use embedded_graphics::text::Text; +use st7789::{Orientation, ST7789}; +use {defmt_rtt as _, panic_probe as _}; + +use crate::my_display_interface::SPIDeviceInterface; +use crate::touch::Touch; + +const DISPLAY_FREQ: u32 = 64_000_000; +const TOUCH_FREQ: u32 = 200_000; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Hello World!"); + + let bl = p.PIN_13; + let rst = p.PIN_15; + let display_cs = p.PIN_9; + let dcx = p.PIN_8; + let miso = p.PIN_12; + let mosi = p.PIN_11; + let clk = p.PIN_10; + let touch_cs = p.PIN_16; + //let touch_irq = p.PIN_17; + + // create SPI + let mut display_config = spi::Config::default(); + display_config.frequency = DISPLAY_FREQ; + display_config.phase = spi::Phase::CaptureOnSecondTransition; + display_config.polarity = spi::Polarity::IdleHigh; + let mut touch_config = spi::Config::default(); + touch_config.frequency = TOUCH_FREQ; + touch_config.phase = spi::Phase::CaptureOnSecondTransition; + touch_config.polarity = spi::Polarity::IdleHigh; + + let spi: Spi<'_, _, Blocking> = Spi::new_blocking(p.SPI1, clk, mosi, miso, touch_config.clone()); + let spi_bus: Mutex<NoopRawMutex, _> = Mutex::new(RefCell::new(spi)); + + let display_spi = SpiDeviceWithConfig::new(&spi_bus, Output::new(display_cs, Level::High), display_config); + let touch_spi = SpiDeviceWithConfig::new(&spi_bus, Output::new(touch_cs, Level::High), touch_config); + + let mut touch = Touch::new(touch_spi); + + let dcx = Output::new(dcx, Level::Low); + let rst = Output::new(rst, Level::Low); + // dcx: 0 = command, 1 = data + + // Enable LCD backlight + let _bl = Output::new(bl, Level::High); + + // display interface abstraction from SPI and DC + let di = SPIDeviceInterface::new(display_spi, dcx); + + // create driver + let mut display = ST7789::new(di, rst, 240, 320); + + // initialize + display.init(&mut Delay).unwrap(); + + // set default orientation + display.set_orientation(Orientation::Landscape).unwrap(); + + display.clear(Rgb565::BLACK).unwrap(); + + let raw_image_data = ImageRawLE::new(include_bytes!("../../assets/ferris.raw"), 86); + let ferris = Image::new(&raw_image_data, Point::new(34, 68)); + + // Display the image + ferris.draw(&mut display).unwrap(); + + let style = MonoTextStyle::new(&FONT_10X20, Rgb565::GREEN); + Text::new( + "Hello embedded_graphics \n + embassy + RP2040!", + Point::new(20, 200), + style, + ) + .draw(&mut display) + .unwrap(); + + loop { + if let Some((x, y)) = touch.read() { + let style = PrimitiveStyleBuilder::new().fill_color(Rgb565::BLUE).build(); + + Rectangle::new(Point::new(x - 1, y - 1), Size::new(3, 3)) + .into_styled(style) + .draw(&mut display) + .unwrap(); + } + } +} + +/// Driver for the XPT2046 resistive touchscreen sensor +mod touch { + use embedded_hal_1::spi::{Operation, SpiDevice}; + + struct Calibration { + x1: i32, + x2: i32, + y1: i32, + y2: i32, + sx: i32, + sy: i32, + } + + const CALIBRATION: Calibration = Calibration { + x1: 3880, + x2: 340, + y1: 262, + y2: 3850, + sx: 320, + sy: 240, + }; + + pub struct Touch<SPI: SpiDevice> { + spi: SPI, + } + + impl<SPI> Touch<SPI> + where + SPI: SpiDevice, + { + pub fn new(spi: SPI) -> Self { + Self { spi } + } + + pub fn read(&mut self) -> Option<(i32, i32)> { + let mut x = [0; 2]; + let mut y = [0; 2]; + self.spi + .transaction(&mut [ + Operation::Write(&[0x90]), + Operation::Read(&mut x), + Operation::Write(&[0xd0]), + Operation::Read(&mut y), + ]) + .unwrap(); + + let x = (u16::from_be_bytes(x) >> 3) as i32; + let y = (u16::from_be_bytes(y) >> 3) as i32; + + let cal = &CALIBRATION; + + let x = ((x - cal.x1) * cal.sx / (cal.x2 - cal.x1)).clamp(0, cal.sx); + let y = ((y - cal.y1) * cal.sy / (cal.y2 - cal.y1)).clamp(0, cal.sy); + if x == 0 && y == 0 { + None + } else { + Some((x, y)) + } + } + } +} + +mod my_display_interface { + use display_interface::{DataFormat, DisplayError, WriteOnlyDataCommand}; + use embedded_hal_1::digital::OutputPin; + use embedded_hal_1::spi::SpiDeviceWrite; + + /// SPI display interface. + /// + /// This combines the SPI peripheral and a data/command pin + pub struct SPIDeviceInterface<SPI, DC> { + spi: SPI, + dc: DC, + } + + impl<SPI, DC> SPIDeviceInterface<SPI, DC> + where + SPI: SpiDeviceWrite, + DC: OutputPin, + { + /// Create new SPI interface for communciation with a display driver + pub fn new(spi: SPI, dc: DC) -> Self { + Self { spi, dc } + } + } + + impl<SPI, DC> WriteOnlyDataCommand for SPIDeviceInterface<SPI, DC> + where + SPI: SpiDeviceWrite, + DC: OutputPin, + { + fn send_commands(&mut self, cmds: DataFormat<'_>) -> Result<(), DisplayError> { + // 1 = data, 0 = command + self.dc.set_low().map_err(|_| DisplayError::DCError)?; + + send_u8(&mut self.spi, cmds).map_err(|_| DisplayError::BusWriteError)?; + Ok(()) + } + + fn send_data(&mut self, buf: DataFormat<'_>) -> Result<(), DisplayError> { + // 1 = data, 0 = command + self.dc.set_high().map_err(|_| DisplayError::DCError)?; + + send_u8(&mut self.spi, buf).map_err(|_| DisplayError::BusWriteError)?; + Ok(()) + } + } + + fn send_u8<T: SpiDeviceWrite>(spi: &mut T, words: DataFormat<'_>) -> Result<(), T::Error> { + match words { + DataFormat::U8(slice) => spi.write(slice), + DataFormat::U16(slice) => { + use byte_slice_cast::*; + spi.write(slice.as_byte_slice()) + } + DataFormat::U16LE(slice) => { + use byte_slice_cast::*; + for v in slice.as_mut() { + *v = v.to_le(); + } + spi.write(slice.as_byte_slice()) + } + DataFormat::U16BE(slice) => { + use byte_slice_cast::*; + for v in slice.as_mut() { + *v = v.to_be(); + } + spi.write(slice.as_byte_slice()) + } + DataFormat::U8Iter(iter) => { + let mut buf = [0; 32]; + let mut i = 0; + + for v in iter.into_iter() { + buf[i] = v; + i += 1; + + if i == buf.len() { + spi.write(&buf)?; + i = 0; + } + } + + if i > 0 { + spi.write(&buf[..i])?; + } + + Ok(()) + } + DataFormat::U16LEIter(iter) => { + use byte_slice_cast::*; + let mut buf = [0; 32]; + let mut i = 0; + + for v in iter.map(u16::to_le) { + buf[i] = v; + i += 1; + + if i == buf.len() { + spi.write(&buf.as_byte_slice())?; + i = 0; + } + } + + if i > 0 { + spi.write(&buf[..i].as_byte_slice())?; + } + + Ok(()) + } + DataFormat::U16BEIter(iter) => { + use byte_slice_cast::*; + let mut buf = [0; 64]; + let mut i = 0; + let len = buf.len(); + + for v in iter.map(u16::to_be) { + buf[i] = v; + i += 1; + + if i == len { + spi.write(&buf.as_byte_slice())?; + i = 0; + } + } + + if i > 0 { + spi.write(&buf[..i].as_byte_slice())?; + } + + Ok(()) + } + _ => unimplemented!(), + } + } +} diff --git a/firmware/rust1/src/bin/uart.rs b/firmware/rust1/src/bin/uart.rs new file mode 100644 index 0000000..05177a6 --- /dev/null +++ b/firmware/rust1/src/bin/uart.rs @@ -0,0 +1,20 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use embassy_executor::Spawner; +use embassy_rp::uart; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let config = uart::Config::default(); + let mut uart = uart::Uart::new_with_rtscts_blocking(p.UART0, p.PIN_0, p.PIN_1, p.PIN_3, p.PIN_2, config); + uart.blocking_write("Hello World!\r\n".as_bytes()).unwrap(); + + loop { + uart.blocking_write("hello there!\r\n".as_bytes()).unwrap(); + cortex_m::asm::delay(1_000_000); + } +} diff --git a/firmware/rust1/src/bin/uart_buffered_split.rs b/firmware/rust1/src/bin/uart_buffered_split.rs new file mode 100644 index 0000000..a8a6822 --- /dev/null +++ b/firmware/rust1/src/bin/uart_buffered_split.rs @@ -0,0 +1,57 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_executor::_export::StaticCell; +use embassy_rp::interrupt; +use embassy_rp::peripherals::UART0; +use embassy_rp::uart::{BufferedUart, BufferedUartRx, Config}; +use embassy_time::{Duration, Timer}; +use embedded_io::asynch::{Read, Write}; +use {defmt_rtt as _, panic_probe as _}; + +macro_rules! singleton { + ($val:expr) => {{ + type T = impl Sized; + static STATIC_CELL: StaticCell<T> = StaticCell::new(); + let (x,) = STATIC_CELL.init(($val,)); + x + }}; +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let (tx_pin, rx_pin, uart) = (p.PIN_0, p.PIN_1, p.UART0); + + let irq = interrupt::take!(UART0_IRQ); + let tx_buf = &mut singleton!([0u8; 16])[..]; + let rx_buf = &mut singleton!([0u8; 16])[..]; + let uart = BufferedUart::new(uart, irq, tx_pin, rx_pin, tx_buf, rx_buf, Config::default()); + let (rx, mut tx) = uart.split(); + + unwrap!(spawner.spawn(reader(rx))); + + info!("Writing..."); + loop { + let data = [ + 1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, + 29, 30, 31, + ]; + info!("TX {:?}", data); + tx.write_all(&data).await.unwrap(); + Timer::after(Duration::from_secs(1)).await; + } +} + +#[embassy_executor::task] +async fn reader(mut rx: BufferedUartRx<'static, UART0>) { + info!("Reading..."); + loop { + let mut buf = [0; 31]; + rx.read_exact(&mut buf).await.unwrap(); + info!("RX {:?}", buf); + } +} diff --git a/firmware/rust1/src/bin/uart_unidir.rs b/firmware/rust1/src/bin/uart_unidir.rs new file mode 100644 index 0000000..4119a30 --- /dev/null +++ b/firmware/rust1/src/bin/uart_unidir.rs @@ -0,0 +1,49 @@ +//! test TX-only and RX-only UARTs. You need to connect GPIO0 to GPIO5 for +//! this to work + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::interrupt; +use embassy_rp::peripherals::UART1; +use embassy_rp::uart::{Async, Config, UartRx, UartTx}; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + + let mut uart_tx = UartTx::new(p.UART0, p.PIN_0, p.DMA_CH0, Config::default()); + let uart_rx = UartRx::new( + p.UART1, + p.PIN_5, + interrupt::take!(UART1_IRQ), + p.DMA_CH1, + Config::default(), + ); + + unwrap!(spawner.spawn(reader(uart_rx))); + + info!("Writing..."); + loop { + let data = [1u8, 2, 3, 4, 5, 6, 7, 8]; + info!("TX {:?}", data); + uart_tx.write(&data).await.unwrap(); + Timer::after(Duration::from_secs(1)).await; + } +} + +#[embassy_executor::task] +async fn reader(mut rx: UartRx<'static, UART1, Async>) { + info!("Reading..."); + loop { + // read a total of 4 transmissions (32 / 8) and then print the result + let mut buf = [0; 32]; + rx.read(&mut buf).await.unwrap(); + info!("RX {:?}", buf); + } +} diff --git a/firmware/rust1/src/bin/usb_ethernet.rs b/firmware/rust1/src/bin/usb_ethernet.rs new file mode 100644 index 0000000..66a6ed4 --- /dev/null +++ b/firmware/rust1/src/bin/usb_ethernet.rs @@ -0,0 +1,151 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_net::tcp::TcpSocket; +use embassy_net::{Stack, StackResources}; +use embassy_rp::usb::Driver; +use embassy_rp::{interrupt, peripherals}; +use embassy_usb::class::cdc_ncm::embassy_net::{Device, Runner, State as NetState}; +use embassy_usb::class::cdc_ncm::{CdcNcmClass, State}; +use embassy_usb::{Builder, Config, UsbDevice}; +use embedded_io::asynch::Write; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +type MyDriver = Driver<'static, peripherals::USB>; + +macro_rules! singleton { + ($val:expr) => {{ + type T = impl Sized; + static STATIC_CELL: StaticCell<T> = StaticCell::new(); + let (x,) = STATIC_CELL.init(($val,)); + x + }}; +} + +const MTU: usize = 1514; + +#[embassy_executor::task] +async fn usb_task(mut device: UsbDevice<'static, MyDriver>) -> ! { + device.run().await +} + +#[embassy_executor::task] +async fn usb_ncm_task(class: Runner<'static, MyDriver, MTU>) -> ! { + class.run().await +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack<Device<'static, MTU>>) -> ! { + stack.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + + // Create the driver, from the HAL. + let irq = interrupt::take!(USBCTRL_IRQ); + let driver = Driver::new(p.USB, irq); + + // Create embassy-usb Config + let mut config = Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-Ethernet example"); + config.serial_number = Some("12345678"); + config.max_power = 100; + config.max_packet_size_0 = 64; + + // Required for Windows support. + config.composite_with_iads = true; + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + + // Create embassy-usb DeviceBuilder using the driver and config. + let mut builder = Builder::new( + driver, + config, + &mut singleton!([0; 256])[..], + &mut singleton!([0; 256])[..], + &mut singleton!([0; 256])[..], + &mut singleton!([0; 128])[..], + ); + + // Our MAC addr. + let our_mac_addr = [0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC]; + // Host's MAC addr. This is the MAC the host "thinks" its USB-to-ethernet adapter has. + let host_mac_addr = [0x88, 0x88, 0x88, 0x88, 0x88, 0x88]; + + // Create classes on the builder. + let class = CdcNcmClass::new(&mut builder, singleton!(State::new()), host_mac_addr, 64); + + // Build the builder. + let usb = builder.build(); + + unwrap!(spawner.spawn(usb_task(usb))); + + let (runner, device) = class.into_embassy_net_device::<MTU, 4, 4>(singleton!(NetState::new()), our_mac_addr); + unwrap!(spawner.spawn(usb_ncm_task(runner))); + + let config = embassy_net::Config::Dhcp(Default::default()); + //let config = embassy_net::Config::Static(embassy_net::StaticConfig { + // address: Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(10, 42, 0, 1)), + //}); + + // Generate random seed + let seed = 1234; // guaranteed random, chosen by a fair dice roll + + // Init network stack + let stack = &*singleton!(Stack::new(device, config, singleton!(StackResources::<2>::new()), seed)); + + unwrap!(spawner.spawn(net_task(stack))); + + // And now we can use it! + + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + let mut buf = [0; 4096]; + + loop { + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(embassy_net::SmolDuration::from_secs(10))); + + info!("Listening on TCP:1234..."); + if let Err(e) = socket.accept(1234).await { + warn!("accept error: {:?}", e); + continue; + } + + info!("Received connection from {:?}", socket.remote_endpoint()); + + loop { + let n = match socket.read(&mut buf).await { + Ok(0) => { + warn!("read EOF"); + break; + } + Ok(n) => n, + Err(e) => { + warn!("read error: {:?}", e); + break; + } + }; + + info!("rxd {:02x}", &buf[..n]); + + match socket.write_all(&buf[..n]).await { + Ok(()) => {} + Err(e) => { + warn!("write error: {:?}", e); + break; + } + }; + } + } +} diff --git a/firmware/rust1/src/bin/usb_logger.rs b/firmware/rust1/src/bin/usb_logger.rs new file mode 100644 index 0000000..52417a0 --- /dev/null +++ b/firmware/rust1/src/bin/usb_logger.rs @@ -0,0 +1,30 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use embassy_executor::Spawner; +use embassy_rp::interrupt; +use embassy_rp::peripherals::USB; +use embassy_rp::usb::Driver; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::task] +async fn logger_task(driver: Driver<'static, USB>) { + embassy_usb_logger::run!(1024, log::LevelFilter::Info, driver); +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let irq = interrupt::take!(USBCTRL_IRQ); + let driver = Driver::new(p.USB, irq); + spawner.spawn(logger_task(driver)).unwrap(); + + let mut counter = 0; + loop { + counter += 1; + log::info!("Tick {}", counter); + Timer::after(Duration::from_secs(1)).await; + } +} diff --git a/firmware/rust1/src/bin/usb_serial.rs b/firmware/rust1/src/bin/usb_serial.rs new file mode 100644 index 0000000..8160a18 --- /dev/null +++ b/firmware/rust1/src/bin/usb_serial.rs @@ -0,0 +1,101 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::{info, panic}; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_rp::interrupt; +use embassy_rp::usb::{Driver, Instance}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::{Builder, Config}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello there!"); + + let p = embassy_rp::init(Default::default()); + + // Create the driver, from the HAL. + let irq = interrupt::take!(USBCTRL_IRQ); + let driver = Driver::new(p.USB, irq); + + // Create embassy-usb Config + let mut config = Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-serial example"); + config.serial_number = Some("12345678"); + config.max_power = 100; + config.max_packet_size_0 = 64; + + // Required for windows compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut device_descriptor = [0; 256]; + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut device_descriptor, + &mut config_descriptor, + &mut bos_descriptor, + &mut control_buf, + ); + + // Create classes on the builder. + let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let echo_fut = async { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, echo_fut).await; +} + +struct Disconnected {} + +impl From<EndpointError> for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = class.read_packet(&mut buf).await?; + let data = &buf[..n]; + info!("data: {:x}", data); + class.write_packet(data).await?; + } +} diff --git a/firmware/rust1/src/bin/watchdog.rs b/firmware/rust1/src/bin/watchdog.rs new file mode 100644 index 0000000..ece5cfe --- /dev/null +++ b/firmware/rust1/src/bin/watchdog.rs @@ -0,0 +1,48 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_rp::gpio; +use embassy_rp::watchdog::*; +use embassy_time::{Duration, Timer}; +use gpio::{Level, Output}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Hello world!"); + + let mut watchdog = Watchdog::new(p.WATCHDOG); + let mut led = Output::new(p.PIN_25, Level::Low); + + // Set the LED high for 2 seconds so we know when we're about to start the watchdog + led.set_high(); + Timer::after(Duration::from_secs(2)).await; + + // Set to watchdog to reset if it's not fed within 1.05 seconds, and start it + watchdog.start(Duration::from_millis(1_050)); + info!("Started the watchdog timer"); + + // Blink once a second for 5 seconds, feed the watchdog timer once a second to avoid a reset + for _ in 1..=5 { + led.set_low(); + Timer::after(Duration::from_millis(500)).await; + led.set_high(); + Timer::after(Duration::from_millis(500)).await; + info!("Feeding watchdog"); + watchdog.feed(); + } + + info!("Stopped feeding, device will reset in 1.05 seconds"); + // Blink 10 times per second, not feeding the watchdog. + // The processor should reset in 1.05 seconds. + loop { + led.set_low(); + Timer::after(Duration::from_millis(100)).await; + led.set_high(); + Timer::after(Duration::from_millis(100)).await; + } +} diff --git a/firmware/rust1/src/bin/ws2812-pio.rs b/firmware/rust1/src/bin/ws2812-pio.rs new file mode 100644 index 0000000..d7c4742 --- /dev/null +++ b/firmware/rust1/src/bin/ws2812-pio.rs @@ -0,0 +1,129 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_embedded_hal::SetConfig; +use embassy_executor::Spawner; +use embassy_rp::pio::{Common, Config, FifoJoin, Instance, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine}; +use embassy_rp::relocate::RelocatedProgram; +use embassy_time::{Duration, Timer}; +use fixed_macro::fixed; +use smart_leds::RGB8; +use {defmt_rtt as _, panic_probe as _}; +pub struct Ws2812<'d, P: Instance, const S: usize> { + sm: StateMachine<'d, P, S>, +} + +impl<'d, P: Instance, const S: usize> Ws2812<'d, P, S> { + pub fn new(mut pio: Common<'d, P>, mut sm: StateMachine<'d, P, S>, pin: impl PioPin) -> Self { + // Setup sm0 + + // prepare the PIO program + let side_set = pio::SideSet::new(false, 1, false); + let mut a: pio::Assembler<32> = pio::Assembler::new_with_side_set(side_set); + + const T1: u8 = 2; // start bit + const T2: u8 = 5; // data bit + const T3: u8 = 3; // stop bit + const CYCLES_PER_BIT: u32 = (T1 + T2 + T3) as u32; + + let mut wrap_target = a.label(); + let mut wrap_source = a.label(); + let mut do_zero = a.label(); + a.set_with_side_set(pio::SetDestination::PINDIRS, 1, 0); + a.bind(&mut wrap_target); + // Do stop bit + a.out_with_delay_and_side_set(pio::OutDestination::X, 1, T3 - 1, 0); + // Do start bit + a.jmp_with_delay_and_side_set(pio::JmpCondition::XIsZero, &mut do_zero, T1 - 1, 1); + // Do data bit = 1 + a.jmp_with_delay_and_side_set(pio::JmpCondition::Always, &mut wrap_target, T2 - 1, 1); + a.bind(&mut do_zero); + // Do data bit = 0 + a.nop_with_delay_and_side_set(T2 - 1, 0); + a.bind(&mut wrap_source); + + let prg = a.assemble_with_wrap(wrap_source, wrap_target); + let mut cfg = Config::default(); + + // Pin config + let out_pin = pio.make_pio_pin(pin); + + let relocated = RelocatedProgram::new(&prg); + cfg.use_program(&pio.load_program(&relocated), &[&out_pin]); + + // Clock config, measured in kHz to avoid overflows + // TODO CLOCK_FREQ should come from embassy_rp + let clock_freq = fixed!(125_000: U24F8); + let ws2812_freq = fixed!(800: U24F8); + let bit_freq = ws2812_freq * CYCLES_PER_BIT; + cfg.clock_divider = clock_freq / bit_freq; + + // FIFO config + cfg.fifo_join = FifoJoin::TxOnly; + cfg.shift_out = ShiftConfig { + auto_fill: true, + threshold: 24, + direction: ShiftDirection::Left, + }; + + sm.set_config(&cfg); + sm.set_enable(true); + + Self { sm } + } + + pub async fn write(&mut self, colors: &[RGB8]) { + for color in colors { + let word = (u32::from(color.g) << 24) | (u32::from(color.r) << 16) | (u32::from(color.b) << 8); + self.sm.tx().wait_push(word).await; + } + } +} + +/// Input a value 0 to 255 to get a color value +/// The colours are a transition r - g - b - back to r. +fn wheel(mut wheel_pos: u8) -> RGB8 { + wheel_pos = 255 - wheel_pos; + if wheel_pos < 85 { + return (255 - wheel_pos * 3, 0, wheel_pos * 3).into(); + } + if wheel_pos < 170 { + wheel_pos -= 85; + return (0, wheel_pos * 3, 255 - wheel_pos * 3).into(); + } + wheel_pos -= 170; + (wheel_pos * 3, 255 - wheel_pos * 3, 0).into() +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Start"); + let p = embassy_rp::init(Default::default()); + + let Pio { common, sm0, .. } = Pio::new(p.PIO0); + + // This is the number of leds in the string. Helpfully, the sparkfun thing plus and adafruit + // feather boards for the 2040 both have one built in. + const NUM_LEDS: usize = 1; + let mut data = [RGB8::default(); NUM_LEDS]; + + // For the thing plus, use pin 8 + // For the feather, use pin 16 + let mut ws2812 = Ws2812::new(common, sm0, p.PIN_8); + + // Loop forever making RGB values and pushing them out to the WS2812. + loop { + for j in 0..(256 * 5) { + debug!("New Colors:"); + for i in 0..NUM_LEDS { + data[i] = wheel((((i * 256) as u16 / NUM_LEDS as u16 + j as u16) & 255) as u8); + debug!("R: {} G: {} B: {}", data[i].r, data[i].g, data[i].b); + } + ws2812.write(&data).await; + + Timer::after(Duration::from_micros(5)).await; + } + } +} -- GitLab