Skip to content

Instantly share code, notes, and snippets.

@justacec
Created August 27, 2023 12:21
Show Gist options
  • Save justacec/91c4a2b9f529b861f5431b2bead06c8f to your computer and use it in GitHub Desktop.
Save justacec/91c4a2b9f529b861f5431b2bead06c8f to your computer and use it in GitHub Desktop.
Rust Embedded Mores Code Example
[package]
edition = "2021"
name = "rp2040-pong"
version = "0.1.0"
[dependencies]
cortex-m = "0.7"
cortex-m-rt = "0.7"
#rtic = { git = "https://github.com/rtic-rs/rtic", features = ["thumbv6-backend"]}
#rtic-monotonics = { git = "https://github.com/rtic-rs/rtic", features = ["rp2040"] }
rtic = { version = "2", features = ["thumbv6-backend"] }
rtic-monotonics = { version = "1", features = ["rp2040"] }
embedded-hal = { version = "0.2", features = ["unproven"] }
heapless = "0.7"
fugit = "0.3"
usb-device = "0.2"
usbd-serial = "0.1"
defmt = "0.3"
defmt-rtt = "0.3"
panic-probe = { version = "0.3", features = ["print-defmt"] }
# We're using a Pico by default on this template
rp-pico = { version = "0.7", features = ["rt"] }
pio-proc = "0.2"
pio = "0.2"
# but you can use any BSP. Uncomment this to use the pro_micro_rp2040 BSP instead
# sparkfun-pro-micro-rp2040 = "0.3"
# If you're not going to use a Board Support Package you'll need these:
# rp2040-hal = { version="0.6", features=["rt"] }
# rp2040-boot2 = "0.2"
# cargo build/run
[profile.dev]
codegen-units = 1
debug = 2
debug-assertions = true
incremental = false
opt-level = 0
overflow-checks = true
# cargo build/run --release
[profile.release]
codegen-units = 1
debug = 2
debug-assertions = false
incremental = false
lto = 'fat'
opt-level = 0
overflow-checks = false
# do not optimize proc-macro crates = faster builds from scratch
[profile.dev.build-override]
codegen-units = 8
debug = false
debug-assertions = false
opt-level = 0
overflow-checks = false
[profile.release.build-override]
codegen-units = 8
debug = false
debug-assertions = false
opt-level = 0
overflow-checks = false
# cargo test
[profile.test]
codegen-units = 1
debug = 2
debug-assertions = true
incremental = false
opt-level = 3
overflow-checks = true
# cargo test --release
[profile.bench]
codegen-units = 1
debug = 2
debug-assertions = false
incremental = false
lto = 'fat'
opt-level = 3
#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]
mod morseencoder;
#[rtic::app(
device = rp_pico::hal::pac,
dispatchers = [TIMER_IRQ_1, TIMER_IRQ_2]
)]
mod app {
use rp_pico::hal::{
clocks, gpio,
gpio::{pin::bank0::Gpio25, FunctionPio0, Pin},
pac,
pac::PIO0,
sio::Sio,
usb,
watchdog::Watchdog,
};
use rp_pico::XOSC_CRYSTAL_FREQ;
// use core::mem::MaybeUninit;
// use embedded_hal::digital::v2::{OutputPin, ToggleableOutputPin};
// use fugit::RateExtU32;
use rtic_monotonics::rp2040::*;
use usb_device::{class_prelude::*, prelude::*};
use usbd_serial::SerialPort;
use defmt::*;
use defmt_rtt as _;
use panic_probe as _;
use crate::morseencoder::MorseEncoder;
// rtic_monotonics::make_rp2040_monotonic_handler!();
#[shared]
struct Shared {
usb_dev: UsbDevice<'static, usb::UsbBus>,
serial: SerialPort<'static, usb::UsbBus>,
me: MorseEncoder<PIO0>,
}
#[local]
struct Local {
led: gpio::Pin<Gpio25, FunctionPio0>,
// USB_BUS: &'static UsbBusAllocator<usb::UsbBus>,
// USB_DEVICE: &'static UsbDevice<'static, usb::UsbBus>,
// USB_SERIAL: Option<SerialPort<'static, usb::UsbBus>>,
}
#[init]
fn init(mut ctx: init::Context) -> (Shared, Local) {
let rp2040_timer_token = rtic_monotonics::create_rp2040_monotonic_token!();
// Configure the clocks, watchdog - The default is to generate a 125 MHz system clock
Timer::start(ctx.device.TIMER, &mut ctx.device.RESETS, rp2040_timer_token); // default rp2040 clock-rate is 125MHz
let mut watchdog = Watchdog::new(ctx.device.WATCHDOG);
let clocks = clocks::init_clocks_and_plls(
XOSC_CRYSTAL_FREQ,
ctx.device.XOSC,
ctx.device.CLOCKS,
ctx.device.PLL_SYS,
ctx.device.PLL_USB,
&mut ctx.device.RESETS,
&mut watchdog,
)
.ok()
.unwrap();
// Set up the USB driver
static mut USB_BUS: Option<usb_device::bus::UsbBusAllocator<usb::UsbBus>> = None;
unsafe {
USB_BUS.replace(UsbBusAllocator::new(usb::UsbBus::new(
ctx.device.USBCTRL_REGS,
ctx.device.USBCTRL_DPRAM,
clocks.usb_clock,
true,
&mut ctx.device.RESETS,
)));
}
let serial = SerialPort::new(unsafe { USB_BUS.as_ref().unwrap() });
let usb_dev = UsbDeviceBuilder::new(
unsafe { USB_BUS.as_ref().unwrap() },
UsbVidPid(0x16c0, 0x27dd),
)
.manufacturer("JustTech")
.product("Justace Pong")
.serial_number("0001")
.device_class(usbd_serial::USB_CLASS_CDC)
.build();
// unsafe {
// pac::NVIC::unmask(pac::Interrupt::USBCTRL_IRQ);
// };
// Init LED pin
let sio = Sio::new(ctx.device.SIO);
let gpioa = rp_pico::Pins::new(
ctx.device.IO_BANK0,
ctx.device.PADS_BANK0,
sio.gpio_bank0,
&mut ctx.device.RESETS,
);
let led: Pin<_, FunctionPio0> = gpioa.led.into_mode();
// led.set_low().unwrap();
// Create the encoder
let me = MorseEncoder::init(ctx.device.PIO0, 25, &mut ctx.device.RESETS);
// Spawn heartbeat task
heartbeat::spawn().ok();
// Return resources and timer
(
Shared {
me,
serial,
usb_dev: usb_dev,
},
Local { led },
)
}
#[task(binds = USBCTRL_IRQ, priority = 1, shared = [me, usb_dev, serial])]
fn usb_stuff(ctx: usb_stuff::Context) {
use core::sync::atomic::{AtomicBool, Ordering};
info!("Got USB");
// Clear the USB IRQ
pac::NVIC::unpend(pac::Interrupt::USBCTRL_IRQ);
/// Note whether we've already printed the "hello" message.
static SAID_HELLO: AtomicBool = AtomicBool::new(false);
// Grab the global objects. This is OK as we only access them under interrupt.
let usb_dev = ctx.shared.usb_dev;
let serial = ctx.shared.serial;
(usb_dev, serial).lock(|usb_dev, serial| {
// Say hello exactly once on start-up
if !SAID_HELLO.load(Ordering::Relaxed) {
SAID_HELLO.store(true, Ordering::Relaxed);
let _ = serial.write(b"Hello, World!\r\n");
}
info!("Polling");
// Poll the USB driver with all of our supported USB Classes
if usb_dev.poll(&mut [serial]) {
let mut buf = [0u8; 64];
match serial.read(&mut buf) {
Err(_e) => {
// Do nothing
info!("USB: Error");
}
Ok(0) => {
// Do nothing
info!("USB Zero");
}
Ok(count) => {
info!("USB Got Something");
// Convert to upper case
buf.iter_mut().take(count).for_each(|b| {
b.make_ascii_uppercase();
});
// Send back to the host
let mut wr_ptr = &buf[..count];
while !wr_ptr.is_empty() {
let _ = serial.write(wr_ptr).map(|len| {
wr_ptr = &wr_ptr[len..];
});
}
}
}
}
});
info!("Finished USB");
}
#[task(priority = 2, local = [led], shared = [me])]
async fn heartbeat(mut ctx: heartbeat::Context) {
// Flicker the built-in LED
// _ = ctx.local.led.toggle();
info!("Sending Character");
ctx.shared.me.lock(|a| a.send_str("Hello World").unwrap());
// Re-spawn this task after 1 second
Timer::delay(5000.millis()).await;
}
}
use bsp::hal::{pac::RESETS, pio, pio::PIOExt, pio::ShiftDirection, pio::PIO, pio::SM0};
use defmt::*;
use defmt_rtt as _;
use heapless::Vec;
use pio_proc;
use rp_pico as bsp;
const LOOKUP: [u8; 128] = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0b0000000, // Space
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0b10111111, // 0
0b10101111, // 1
0b10100111, // 2
0b10100011, // 3
0b10100001, // 4
0b10100000, // 5
0b10110000, // 6
0b10111000, // 7
0b10111100, // 8
0b10111110, // 9
0, 0, 0, 0, 0, 0, 0, 0b01001000, // A
0b10010000, // B
0b10010100, // C
0b01110000, // D
0b00100000, // E
0b10000100, // F
0b01111000, // G
0b10000000, // H
0b01000000, // I
0b10001110, // J
0b01110100, // K
0b10001000, // L
0b01011000, // M
0b01010000, // N
0b01111100, // O
0b10001100, // P
0b10011010, // Q
0b01101000, // R
0b01100000, // S
0b00110000, // T
0b01100100, // U
0b10000010, // V
0b01101100, // W
0b10010010, // X
0b10010110, // Y
0b10011000, // Z
0, 0, 0, 0, 0, 0, 0b01001000, // a
0b10010000, // b
0b10010100, // c
0b01110000, // d
0b00100000, // e
0b10000100, // f
0b01111000, // g
0b10000000, // h
0b01000000, // i
0b10001110, // j
0b01110100, // k
0b10001000, // l
0b01011000, // m
0b01010000, // n
0b01111100, // o
0b10001100, // p
0b10011010, // q
0b01101000, // r
0b01100000, // s
0b00110000, // t
0b01100100, // u
0b10000010, // v
0b01101100, // w
0b10010010, // x
0b10010110, // y
0b10011000, // z
0, 0, 0, 0, 0,
];
pub struct MorseEncoder<P>
where
P: PIOExt,
{
pio: PIO<P>,
tx: pio::Tx<(P, SM0)>,
}
impl<P> MorseEncoder<P>
where
P: PIOExt,
{
pub fn init(pio: P, pin_number: i32, reset: &mut RESETS) -> Self {
let led_pin_id = pin_number;
// Initialize and start PIO
let (mut pio, sm0, sm1, _, _) = pio.split(reset);
let program_sm0 = pio_proc::pio_file!("src/pio0.pio", select_program("morse"));
let installed_sm0 = pio.install(&program_sm0.program).unwrap();
info!(
"sm0 offset: {} wrap_target: {}",
installed_sm0.offset(),
installed_sm0.wrap_target()
);
let (mut sm_0, _rx, tx) = pio::PIOBuilder::from_program(installed_sm0)
.set_pins(led_pin_id as u8, 1)
.clock_divisor_fixed_point(50000, 0)
.out_shift_direction(ShiftDirection::Left)
.build(sm0);
// The GPIO pin needs to be configured as an output.
sm_0.set_pindirs([(led_pin_id as u8, pio::PinDir::Output)]);
let program_sm1 = pio_proc::pio_file!("src/pio0.pio", select_program("delay"));
let installed_sm1 = pio.install(&program_sm1.program).unwrap();
info!(
"sm1 offset: {} wrap_target: {}",
installed_sm1.offset(),
installed_sm1.wrap_target()
);
let (mut sm_1, _, _) = pio::PIOBuilder::from_program(installed_sm1)
.clock_divisor_fixed_point(50000, 0)
.build(sm1);
let sm = sm_0.with(sm_1);
sm.sync().start();
MorseEncoder { pio: pio, tx: tx }
}
pub fn send_char(&mut self, input: char) -> Result<(), ()> {
if self.tx.is_full() {
return Err(());
}
let val = Self::lookup(input as u8);
while self.tx.is_full() {}
match self.tx.write(val as u32) {
true => Ok(()),
false => Err(()),
}
}
pub fn send_str(&mut self, input: &str) -> Result<(), ()> {
let vals = Self::lookup_str(input);
for val in vals {
while self.tx.is_full() {}
match self.tx.write(val as u32) {
true => {}
false => return Err(()),
}
}
Ok(())
}
pub fn lookup_str(s: &str) -> Vec<u8, 256> {
s.as_bytes().iter().map(|c| LOOKUP[*c as usize]).collect()
}
pub fn lookup(x: u8) -> u8 {
LOOKUP[x as usize]
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment