Merge pull request #3407 from CBJamo/pio_programs

rp: Move pio programs into embassy-rp
This commit is contained in:
Dario Nieuwenhuis 2024-10-13 19:41:57 +00:00 committed by GitHub
commit eea08d761d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 1593 additions and 1664 deletions

View File

@ -144,6 +144,7 @@ rp2040-boot2 = "0.3"
document-features = "0.2.7"
sha2-const-stable = "0.1"
rp-binary-info = { version = "0.1.0", optional = true }
smart-leds = "0.4.0"
[dev-dependencies]
embassy-executor = { version = "0.6.0", path = "../embassy-executor", features = ["arch-std", "executor-thread"] }

View File

@ -34,6 +34,7 @@ pub mod i2c_slave;
pub mod multicore;
#[cfg(feature = "_rp235x")]
pub mod otp;
pub mod pio_programs;
pub mod pwm;
mod reset;
pub mod rom_data;

View File

@ -0,0 +1,203 @@
//! [HD44780 display driver](https://www.sparkfun.com/datasheets/LCD/HD44780.pdf)
use crate::dma::{AnyChannel, Channel};
use crate::pio::{
Common, Config, Direction, FifoJoin, Instance, Irq, LoadedProgram, PioPin, ShiftConfig, ShiftDirection,
StateMachine,
};
use crate::{into_ref, Peripheral, PeripheralRef};
/// This struct represents a HD44780 program that takes command words (<wait:24> <command:4> <0:4>)
pub struct PioHD44780CommandWordProgram<'a, PIO: Instance> {
prg: LoadedProgram<'a, PIO>,
}
impl<'a, PIO: Instance> PioHD44780CommandWordProgram<'a, PIO> {
/// Load the program into the given pio
pub fn new(common: &mut Common<'a, PIO>) -> Self {
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 prg = common.load_program(&prg.program);
Self { prg }
}
}
/// This struct represents a HD44780 program that takes command sequences (<rs:1> <count:7>, data...)
pub struct PioHD44780CommandSequenceProgram<'a, PIO: Instance> {
prg: LoadedProgram<'a, PIO>,
}
impl<'a, PIO: Instance> PioHD44780CommandSequenceProgram<'a, PIO> {
/// Load the program into the given pio
pub fn new(common: &mut Common<'a, PIO>) -> Self {
// 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 prg = common.load_program(&prg.program);
Self { prg }
}
}
/// Pio backed HD44780 driver
pub struct PioHD44780<'l, P: Instance, const S: usize> {
dma: PeripheralRef<'l, AnyChannel>,
sm: StateMachine<'l, P, S>,
buf: [u8; 40],
}
impl<'l, P: Instance, const S: usize> PioHD44780<'l, P, S> {
/// Configure the given state machine to first init, then write data to, a HD44780 display.
pub async fn new(
common: &mut Common<'l, P>,
mut sm: StateMachine<'l, P, S>,
mut irq: Irq<'l, P, S>,
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,
word_prg: &PioHD44780CommandWordProgram<'l, P>,
seq_prg: &PioHD44780CommandSequenceProgram<'l, P>,
) -> PioHD44780<'l, P, S> {
into_ref!(dma);
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);
sm.set_pin_dirs(Direction::Out, &[&rs, &rw, &e, &db4, &db5, &db6, &db7]);
let mut cfg = Config::default();
cfg.use_program(&word_prg.prg, &[&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;
sm.set_config(&cfg);
sm.set_enable(true);
// init to 8 bit thrice
sm.tx().push((50000 << 8) | 0x30);
sm.tx().push((5000 << 8) | 0x30);
sm.tx().push((200 << 8) | 0x30);
// init 4 bit
sm.tx().push((200 << 8) | 0x20);
// set font and lines
sm.tx().push((50 << 8) | 0x20);
sm.tx().push(0b1100_0000);
irq.wait().await;
sm.set_enable(false);
let mut cfg = Config::default();
cfg.use_program(&seq_prg.prg, &[&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;
sm.set_config(&cfg);
sm.set_enable(true);
// display on and cursor on and blinking, reset display
sm.tx().dma_push(dma.reborrow(), &[0x81u8, 0x0f, 1]).await;
Self {
dma: dma.map_into(),
sm,
buf: [0x20; 40],
}
}
/// Write a line to the display
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;
}
}

View File

@ -0,0 +1,95 @@
//! Pio backed I2s output
use fixed::traits::ToFixed;
use crate::dma::{AnyChannel, Channel, Transfer};
use crate::pio::{
Common, Config, Direction, FifoJoin, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine,
};
use crate::{into_ref, Peripheral, PeripheralRef};
/// This struct represents an i2s output driver program
pub struct PioI2sOutProgram<'a, PIO: Instance> {
prg: LoadedProgram<'a, PIO>,
}
impl<'a, PIO: Instance> PioI2sOutProgram<'a, PIO> {
/// Load the program into the given pio
pub fn new(common: &mut Common<'a, PIO>) -> Self {
let prg = pio_proc::pio_asm!(
".side_set 2",
" set x, 14 side 0b01", // side 0bWB - W = Word Clock, B = Bit Clock
"left_data:",
" out pins, 1 side 0b00",
" jmp x-- left_data side 0b01",
" out pins 1 side 0b10",
" set x, 14 side 0b11",
"right_data:",
" out pins 1 side 0b10",
" jmp x-- right_data side 0b11",
" out pins 1 side 0b00",
);
let prg = common.load_program(&prg.program);
Self { prg }
}
}
/// Pio backed I2s output driver
pub struct PioI2sOut<'a, P: Instance, const S: usize> {
dma: PeripheralRef<'a, AnyChannel>,
sm: StateMachine<'a, P, S>,
}
impl<'a, P: Instance, const S: usize> PioI2sOut<'a, P, S> {
/// Configure a state machine to output I2s
pub fn new(
common: &mut Common<'a, P>,
mut sm: StateMachine<'a, P, S>,
dma: impl Peripheral<P = impl Channel> + 'a,
data_pin: impl PioPin,
bit_clock_pin: impl PioPin,
lr_clock_pin: impl PioPin,
sample_rate: u32,
bit_depth: u32,
channels: u32,
program: &PioI2sOutProgram<'a, P>,
) -> Self {
into_ref!(dma);
let data_pin = common.make_pio_pin(data_pin);
let bit_clock_pin = common.make_pio_pin(bit_clock_pin);
let left_right_clock_pin = common.make_pio_pin(lr_clock_pin);
let cfg = {
let mut cfg = Config::default();
cfg.use_program(&program.prg, &[&bit_clock_pin, &left_right_clock_pin]);
cfg.set_out_pins(&[&data_pin]);
let clock_frequency = sample_rate * bit_depth * channels;
cfg.clock_divider = (125_000_000. / clock_frequency as f64 / 2.).to_fixed();
cfg.shift_out = ShiftConfig {
threshold: 32,
direction: ShiftDirection::Left,
auto_fill: true,
};
// join fifos to have twice the time to start the next dma transfer
cfg.fifo_join = FifoJoin::TxOnly;
cfg
};
sm.set_config(&cfg);
sm.set_pin_dirs(Direction::Out, &[&data_pin, &left_right_clock_pin, &bit_clock_pin]);
sm.set_enable(true);
Self {
dma: dma.map_into(),
sm,
}
}
/// Return an in-prograss dma transfer future. Awaiting it will guarentee a complete transfer.
pub fn write<'b>(&'b mut self, buff: &'b [u32]) -> Transfer<'b, AnyChannel> {
self.sm.tx().dma_push(self.dma.reborrow(), buff)
}
}

View File

@ -0,0 +1,10 @@
//! Pre-built pio programs for common interfaces
pub mod hd44780;
pub mod i2s;
pub mod onewire;
pub mod pwm;
pub mod rotary_encoder;
pub mod stepper;
pub mod uart;
pub mod ws2812;

View File

@ -0,0 +1,109 @@
//! OneWire pio driver
use crate::pio::{self, Common, Config, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine};
/// This struct represents an onewire driver program
pub struct PioOneWireProgram<'a, PIO: Instance> {
prg: LoadedProgram<'a, PIO>,
}
impl<'a, PIO: Instance> PioOneWireProgram<'a, PIO> {
/// Load the program into the given pio
pub fn new(common: &mut Common<'a, PIO>) -> Self {
let prg = pio_proc::pio_asm!(
r#"
.wrap_target
again:
pull block
mov x, osr
jmp !x, read
write:
set pindirs, 1
set pins, 0
loop1:
jmp x--,loop1
set pindirs, 0 [31]
wait 1 pin 0 [31]
pull block
mov x, osr
bytes1:
pull block
set y, 7
set pindirs, 1
bit1:
set pins, 0 [1]
out pins,1 [31]
set pins, 1 [20]
jmp y--,bit1
jmp x--,bytes1
set pindirs, 0 [31]
jmp again
read:
pull block
mov x, osr
bytes2:
set y, 7
bit2:
set pindirs, 1
set pins, 0 [1]
set pindirs, 0 [5]
in pins,1 [10]
jmp y--,bit2
jmp x--,bytes2
.wrap
"#,
);
let prg = common.load_program(&prg.program);
Self { prg }
}
}
/// Pio backed OneWire driver
pub struct PioOneWire<'d, PIO: pio::Instance, const SM: usize> {
sm: StateMachine<'d, PIO, SM>,
}
impl<'d, PIO: pio::Instance, const SM: usize> PioOneWire<'d, PIO, SM> {
/// Create a new instance the driver
pub fn new(
common: &mut Common<'d, PIO>,
mut sm: StateMachine<'d, PIO, SM>,
pin: impl PioPin,
program: &PioOneWireProgram<'d, PIO>,
) -> Self {
let pin = common.make_pio_pin(pin);
let mut cfg = Config::default();
cfg.use_program(&program.prg, &[]);
cfg.set_out_pins(&[&pin]);
cfg.set_in_pins(&[&pin]);
cfg.set_set_pins(&[&pin]);
cfg.shift_in = ShiftConfig {
auto_fill: true,
direction: ShiftDirection::Right,
threshold: 8,
};
cfg.clock_divider = 255_u8.into();
sm.set_config(&cfg);
sm.set_enable(true);
Self { sm }
}
/// Write bytes over the wire
pub async fn write_bytes(&mut self, bytes: &[u8]) {
self.sm.tx().wait_push(250).await;
self.sm.tx().wait_push(bytes.len() as u32 - 1).await;
for b in bytes {
self.sm.tx().wait_push(*b as u32).await;
}
}
/// Read bytes from the wire
pub async fn read_bytes(&mut self, bytes: &mut [u8]) {
self.sm.tx().wait_push(0).await;
self.sm.tx().wait_push(bytes.len() as u32 - 1).await;
for b in bytes.iter_mut() {
*b = (self.sm.rx().wait_pull().await >> 24) as u8;
}
}
}

View File

@ -0,0 +1,121 @@
//! PIO backed PWM driver
use core::time::Duration;
use pio::InstructionOperands;
use crate::clocks;
use crate::gpio::Level;
use crate::pio::{Common, Config, Direction, Instance, LoadedProgram, Pin, PioPin, StateMachine};
/// This converts the duration provided into the number of cycles the PIO needs to run to make it take the same time
fn to_pio_cycles(duration: Duration) -> u32 {
(clocks::clk_sys_freq() / 1_000_000) / 3 * duration.as_micros() as u32 // parentheses are required to prevent overflow
}
/// This struct represents a PWM program loaded into pio instruction memory.
pub struct PioPwmProgram<'a, PIO: Instance> {
prg: LoadedProgram<'a, PIO>,
}
impl<'a, PIO: Instance> PioPwmProgram<'a, PIO> {
/// Load the program into the given pio
pub fn new(common: &mut Common<'a, PIO>) -> Self {
let prg = pio_proc::pio_asm!(
".side_set 1 opt"
"pull noblock side 0"
"mov x, osr"
"mov y, isr"
"countloop:"
"jmp x!=y noset"
"jmp skip side 1"
"noset:"
"nop"
"skip:"
"jmp y-- countloop"
);
let prg = common.load_program(&prg.program);
Self { prg }
}
}
/// Pio backed PWM output
pub struct PioPwm<'d, T: Instance, const SM: usize> {
sm: StateMachine<'d, T, SM>,
pin: Pin<'d, T>,
}
impl<'d, T: Instance, const SM: usize> PioPwm<'d, T, SM> {
/// Configure a state machine as a PWM output
pub fn new(
pio: &mut Common<'d, T>,
mut sm: StateMachine<'d, T, SM>,
pin: impl PioPin,
program: &PioPwmProgram<'d, T>,
) -> Self {
let pin = pio.make_pio_pin(pin);
sm.set_pins(Level::High, &[&pin]);
sm.set_pin_dirs(Direction::Out, &[&pin]);
let mut cfg = Config::default();
cfg.use_program(&program.prg, &[&pin]);
sm.set_config(&cfg);
Self { sm, pin }
}
/// Enable's the PIO program, continuing the wave generation from the PIO program.
pub fn start(&mut self) {
self.sm.set_enable(true);
}
/// Stops the PIO program, ceasing all signals from the PIN that were generated via PIO.
pub fn stop(&mut self) {
self.sm.set_enable(false);
}
/// Sets the pwm period, which is the length of time for each pio wave until reset.
pub fn set_period(&mut self, duration: Duration) {
let is_enabled = self.sm.is_enabled();
while !self.sm.tx().empty() {} // Make sure that the queue is empty
self.sm.set_enable(false);
self.sm.tx().push(to_pio_cycles(duration));
unsafe {
self.sm.exec_instr(
InstructionOperands::PULL {
if_empty: false,
block: false,
}
.encode(),
);
self.sm.exec_instr(
InstructionOperands::OUT {
destination: ::pio::OutDestination::ISR,
bit_count: 32,
}
.encode(),
);
};
if is_enabled {
self.sm.set_enable(true) // Enable if previously enabled
}
}
/// Set the number of pio cycles to set the wave on high to.
pub fn set_level(&mut self, level: u32) {
self.sm.tx().push(level);
}
/// Set the pulse width high time
pub fn write(&mut self, duration: Duration) {
self.set_level(to_pio_cycles(duration));
}
/// Return the state machine and pin.
pub fn release(self) -> (StateMachine<'d, T, SM>, Pin<'d, T>) {
(self.sm, self.pin)
}
}

View File

@ -0,0 +1,73 @@
//! PIO backed quadrature encoder
use fixed::traits::ToFixed;
use crate::gpio::Pull;
use crate::pio::{self, Common, Config, FifoJoin, Instance, LoadedProgram, PioPin, ShiftDirection, StateMachine};
/// This struct represents an Encoder program loaded into pio instruction memory.
pub struct PioEncoderProgram<'a, PIO: Instance> {
prg: LoadedProgram<'a, PIO>,
}
impl<'a, PIO: Instance> PioEncoderProgram<'a, PIO> {
/// Load the program into the given pio
pub fn new(common: &mut Common<'a, PIO>) -> Self {
let prg = pio_proc::pio_asm!("wait 1 pin 1", "wait 0 pin 1", "in pins, 2", "push",);
let prg = common.load_program(&prg.program);
Self { prg }
}
}
/// Pio Backed quadrature encoder reader
pub struct PioEncoder<'d, T: Instance, const SM: usize> {
sm: StateMachine<'d, T, SM>,
}
impl<'d, T: Instance, const SM: usize> PioEncoder<'d, T, SM> {
/// Configure a state machine with the loaded [PioEncoderProgram]
pub fn new(
pio: &mut Common<'d, T>,
mut sm: StateMachine<'d, T, SM>,
pin_a: impl PioPin,
pin_b: impl PioPin,
program: &PioEncoderProgram<'d, T>,
) -> Self {
let mut pin_a = pio.make_pio_pin(pin_a);
let mut pin_b = pio.make_pio_pin(pin_b);
pin_a.set_pull(Pull::Up);
pin_b.set_pull(Pull::Up);
sm.set_pin_dirs(pio::Direction::In, &[&pin_a, &pin_b]);
let mut cfg = Config::default();
cfg.set_in_pins(&[&pin_a, &pin_b]);
cfg.fifo_join = FifoJoin::RxOnly;
cfg.shift_in.direction = ShiftDirection::Left;
cfg.clock_divider = 10_000.to_fixed();
cfg.use_program(&program.prg, &[]);
sm.set_config(&cfg);
sm.set_enable(true);
Self { sm }
}
/// Read a single count from the encoder
pub async fn read(&mut self) -> Direction {
loop {
match self.sm.rx().wait_pull().await {
0 => return Direction::CounterClockwise,
1 => return Direction::Clockwise,
_ => {}
}
}
}
}
/// Encoder Count Direction
pub enum Direction {
/// Encoder turned clockwise
Clockwise,
/// Encoder turned counter clockwise
CounterClockwise,
}

View File

@ -0,0 +1,147 @@
//! Pio Stepper Driver for 5-wire steppers
use core::mem::{self, MaybeUninit};
use fixed::traits::ToFixed;
use fixed::types::extra::U8;
use fixed::FixedU32;
use crate::pio::{Common, Config, Direction, Instance, Irq, LoadedProgram, PioPin, StateMachine};
/// This struct represents a Stepper driver program loaded into pio instruction memory.
pub struct PioStepperProgram<'a, PIO: Instance> {
prg: LoadedProgram<'a, PIO>,
}
impl<'a, PIO: Instance> PioStepperProgram<'a, PIO> {
/// Load the program into the given pio
pub fn new(common: &mut Common<'a, PIO>) -> Self {
let prg = pio_proc::pio_asm!(
"pull block",
"mov x, osr",
"pull block",
"mov y, osr",
"jmp !x end",
"loop:",
"jmp !osre step",
"mov osr, y",
"step:",
"out pins, 4 [31]"
"jmp x-- loop",
"end:",
"irq 0 rel"
);
let prg = common.load_program(&prg.program);
Self { prg }
}
}
/// Pio backed Stepper driver
pub struct PioStepper<'d, T: Instance, const SM: usize> {
irq: Irq<'d, T, SM>,
sm: StateMachine<'d, T, SM>,
}
impl<'d, T: Instance, const SM: usize> PioStepper<'d, T, SM> {
/// Configure a state machine to drive a stepper
pub fn new(
pio: &mut Common<'d, T>,
mut sm: StateMachine<'d, T, SM>,
irq: Irq<'d, T, SM>,
pin0: impl PioPin,
pin1: impl PioPin,
pin2: impl PioPin,
pin3: impl PioPin,
program: &PioStepperProgram<'d, T>,
) -> Self {
let pin0 = pio.make_pio_pin(pin0);
let pin1 = pio.make_pio_pin(pin1);
let pin2 = pio.make_pio_pin(pin2);
let pin3 = pio.make_pio_pin(pin3);
sm.set_pin_dirs(Direction::Out, &[&pin0, &pin1, &pin2, &pin3]);
let mut cfg = Config::default();
cfg.set_out_pins(&[&pin0, &pin1, &pin2, &pin3]);
cfg.clock_divider = (125_000_000 / (100 * 136)).to_fixed();
cfg.use_program(&program.prg, &[]);
sm.set_config(&cfg);
sm.set_enable(true);
Self { irq, sm }
}
/// Set pulse frequency
pub fn set_frequency(&mut self, freq: u32) {
let clock_divider: FixedU32<U8> = (125_000_000 / (freq * 136)).to_fixed();
assert!(clock_divider <= 65536, "clkdiv must be <= 65536");
assert!(clock_divider >= 1, "clkdiv must be >= 1");
self.sm.set_clock_divider(clock_divider);
self.sm.clkdiv_restart();
}
/// Full step, one phase
pub async fn step(&mut self, steps: i32) {
if steps > 0 {
self.run(steps, 0b1000_0100_0010_0001_1000_0100_0010_0001).await
} else {
self.run(-steps, 0b0001_0010_0100_1000_0001_0010_0100_1000).await
}
}
/// Full step, two phase
pub async fn step2(&mut self, steps: i32) {
if steps > 0 {
self.run(steps, 0b1001_1100_0110_0011_1001_1100_0110_0011).await
} else {
self.run(-steps, 0b0011_0110_1100_1001_0011_0110_1100_1001).await
}
}
/// Half step
pub async fn step_half(&mut self, steps: i32) {
if steps > 0 {
self.run(steps, 0b1001_1000_1100_0100_0110_0010_0011_0001).await
} else {
self.run(-steps, 0b0001_0011_0010_0110_0100_1100_1000_1001).await
}
}
async fn run(&mut self, steps: i32, pattern: u32) {
self.sm.tx().wait_push(steps as u32).await;
self.sm.tx().wait_push(pattern).await;
let drop = OnDrop::new(|| {
self.sm.clear_fifos();
unsafe {
self.sm.exec_instr(
pio::InstructionOperands::JMP {
address: 0,
condition: pio::JmpCondition::Always,
}
.encode(),
);
}
});
self.irq.wait().await;
drop.defuse();
}
}
struct OnDrop<F: FnOnce()> {
f: MaybeUninit<F>,
}
impl<F: FnOnce()> OnDrop<F> {
pub fn new(f: F) -> Self {
Self { f: MaybeUninit::new(f) }
}
pub fn defuse(self) {
mem::forget(self)
}
}
impl<F: FnOnce()> Drop for OnDrop<F> {
fn drop(&mut self) {
unsafe { self.f.as_ptr().read()() }
}
}

View File

@ -0,0 +1,185 @@
//! Pio backed uart drivers
use core::convert::Infallible;
use embedded_io_async::{ErrorType, Read, Write};
use fixed::traits::ToFixed;
use crate::clocks::clk_sys_freq;
use crate::gpio::Level;
use crate::pio::{
Common, Config, Direction as PioDirection, FifoJoin, Instance, LoadedProgram, PioPin, ShiftDirection, StateMachine,
};
/// This struct represents a uart tx program loaded into pio instruction memory.
pub struct PioUartTxProgram<'a, PIO: Instance> {
prg: LoadedProgram<'a, PIO>,
}
impl<'a, PIO: Instance> PioUartTxProgram<'a, PIO> {
/// Load the uart tx program into the given pio
pub fn new(common: &mut Common<'a, PIO>) -> Self {
let prg = pio_proc::pio_asm!(
r#"
.side_set 1 opt
; An 8n1 UART transmit program.
; OUT pin 0 and side-set pin 0 are both mapped to UART TX pin.
pull side 1 [7] ; Assert stop bit, or stall with line in idle state
set x, 7 side 0 [7] ; Preload bit counter, assert start bit for 8 clocks
bitloop: ; This loop will run 8 times (8n1 UART)
out pins, 1 ; Shift 1 bit from OSR to the first OUT pin
jmp x-- bitloop [6] ; Each loop iteration is 8 cycles.
"#
);
let prg = common.load_program(&prg.program);
Self { prg }
}
}
/// PIO backed Uart transmitter
pub struct PioUartTx<'a, PIO: Instance, const SM: usize> {
sm_tx: StateMachine<'a, PIO, SM>,
}
impl<'a, PIO: Instance, const SM: usize> PioUartTx<'a, PIO, SM> {
/// Configure a pio state machine to use the loaded tx program.
pub fn new(
baud: u32,
common: &mut Common<'a, PIO>,
mut sm_tx: StateMachine<'a, PIO, SM>,
tx_pin: impl PioPin,
program: &PioUartTxProgram<'a, PIO>,
) -> Self {
let tx_pin = common.make_pio_pin(tx_pin);
sm_tx.set_pins(Level::High, &[&tx_pin]);
sm_tx.set_pin_dirs(PioDirection::Out, &[&tx_pin]);
let mut cfg = Config::default();
cfg.set_out_pins(&[&tx_pin]);
cfg.use_program(&program.prg, &[&tx_pin]);
cfg.shift_out.auto_fill = false;
cfg.shift_out.direction = ShiftDirection::Right;
cfg.fifo_join = FifoJoin::TxOnly;
cfg.clock_divider = (clk_sys_freq() / (8 * baud)).to_fixed();
sm_tx.set_config(&cfg);
sm_tx.set_enable(true);
Self { sm_tx }
}
/// Write a single u8
pub async fn write_u8(&mut self, data: u8) {
self.sm_tx.tx().wait_push(data as u32).await;
}
}
impl<PIO: Instance, const SM: usize> ErrorType for PioUartTx<'_, PIO, SM> {
type Error = Infallible;
}
impl<PIO: Instance, const SM: usize> Write for PioUartTx<'_, PIO, SM> {
async fn write(&mut self, buf: &[u8]) -> Result<usize, Infallible> {
for byte in buf {
self.write_u8(*byte).await;
}
Ok(buf.len())
}
}
/// This struct represents a Uart Rx program loaded into pio instruction memory.
pub struct PioUartRxProgram<'a, PIO: Instance> {
prg: LoadedProgram<'a, PIO>,
}
impl<'a, PIO: Instance> PioUartRxProgram<'a, PIO> {
/// Load the uart rx program into the given pio
pub fn new(common: &mut Common<'a, PIO>) -> Self {
let prg = pio_proc::pio_asm!(
r#"
; Slightly more fleshed-out 8n1 UART receiver which handles framing errors and
; break conditions more gracefully.
; IN pin 0 and JMP pin are both mapped to the GPIO used as UART RX.
start:
wait 0 pin 0 ; Stall until start bit is asserted
set x, 7 [10] ; Preload bit counter, then delay until halfway through
rx_bitloop: ; the first data bit (12 cycles incl wait, set).
in pins, 1 ; Shift data bit into ISR
jmp x-- rx_bitloop [6] ; Loop 8 times, each loop iteration is 8 cycles
jmp pin good_rx_stop ; Check stop bit (should be high)
irq 4 rel ; Either a framing error or a break. Set a sticky flag,
wait 1 pin 0 ; and wait for line to return to idle state.
jmp start ; Don't push data if we didn't see good framing.
good_rx_stop: ; No delay before returning to start; a little slack is
in null 24
push ; important in case the TX clock is slightly too fast.
"#
);
let prg = common.load_program(&prg.program);
Self { prg }
}
}
/// PIO backed Uart reciever
pub struct PioUartRx<'a, PIO: Instance, const SM: usize> {
sm_rx: StateMachine<'a, PIO, SM>,
}
impl<'a, PIO: Instance, const SM: usize> PioUartRx<'a, PIO, SM> {
/// Configure a pio state machine to use the loaded rx program.
pub fn new(
baud: u32,
common: &mut Common<'a, PIO>,
mut sm_rx: StateMachine<'a, PIO, SM>,
rx_pin: impl PioPin,
program: &PioUartRxProgram<'a, PIO>,
) -> Self {
let mut cfg = Config::default();
cfg.use_program(&program.prg, &[]);
let rx_pin = common.make_pio_pin(rx_pin);
sm_rx.set_pins(Level::High, &[&rx_pin]);
cfg.set_in_pins(&[&rx_pin]);
cfg.set_jmp_pin(&rx_pin);
sm_rx.set_pin_dirs(PioDirection::In, &[&rx_pin]);
cfg.clock_divider = (clk_sys_freq() / (8 * baud)).to_fixed();
cfg.shift_in.auto_fill = false;
cfg.shift_in.direction = ShiftDirection::Right;
cfg.shift_in.threshold = 32;
cfg.fifo_join = FifoJoin::RxOnly;
sm_rx.set_config(&cfg);
sm_rx.set_enable(true);
Self { sm_rx }
}
/// Wait for a single u8
pub async fn read_u8(&mut self) -> u8 {
self.sm_rx.rx().wait_pull().await as u8
}
}
impl<PIO: Instance, const SM: usize> ErrorType for PioUartRx<'_, PIO, SM> {
type Error = Infallible;
}
impl<PIO: Instance, const SM: usize> Read for PioUartRx<'_, PIO, SM> {
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Infallible> {
let mut i = 0;
while i < buf.len() {
buf[i] = self.read_u8().await;
i += 1;
}
Ok(i)
}
}

View File

@ -0,0 +1,118 @@
//! [ws2812](https://www.sparkfun.com/datasheets/LCD/HD44780.pdf)
use embassy_time::Timer;
use fixed::types::U24F8;
use smart_leds::RGB8;
use crate::clocks::clk_sys_freq;
use crate::dma::{AnyChannel, Channel};
use crate::pio::{
Common, Config, FifoJoin, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine,
};
use crate::{into_ref, Peripheral, PeripheralRef};
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;
/// This struct represents a ws2812 program loaded into pio instruction memory.
pub struct PioWs2812Program<'a, PIO: Instance> {
prg: LoadedProgram<'a, PIO>,
}
impl<'a, PIO: Instance> PioWs2812Program<'a, PIO> {
/// Load the ws2812 program into the given pio
pub fn new(common: &mut Common<'a, PIO>) -> Self {
let side_set = pio::SideSet::new(false, 1, false);
let mut a: pio::Assembler<32> = pio::Assembler::new_with_side_set(side_set);
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 prg = common.load_program(&prg);
Self { prg }
}
}
/// Pio backed ws2812 driver
/// Const N is the number of ws2812 leds attached to this pin
pub struct PioWs2812<'d, P: Instance, const S: usize, const N: usize> {
dma: PeripheralRef<'d, AnyChannel>,
sm: StateMachine<'d, P, S>,
}
impl<'d, P: Instance, const S: usize, const N: usize> PioWs2812<'d, P, S, N> {
/// Configure a pio state machine to use the loaded ws2812 program.
pub fn new(
pio: &mut Common<'d, P>,
mut sm: StateMachine<'d, P, S>,
dma: impl Peripheral<P = impl Channel> + 'd,
pin: impl PioPin,
program: &PioWs2812Program<'d, P>,
) -> Self {
into_ref!(dma);
// Setup sm0
let mut cfg = Config::default();
// Pin config
let out_pin = pio.make_pio_pin(pin);
cfg.set_out_pins(&[&out_pin]);
cfg.set_set_pins(&[&out_pin]);
cfg.use_program(&program.prg, &[&out_pin]);
// Clock config, measured in kHz to avoid overflows
let clock_freq = U24F8::from_num(clk_sys_freq() / 1000);
let ws2812_freq = U24F8::from_num(800);
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 {
dma: dma.map_into(),
sm,
}
}
/// Write a buffer of [smart_leds::RGB8] to the ws2812 string
pub async fn write(&mut self, colors: &[RGB8; N]) {
// Precompute the word bytes from the colors
let mut words = [0u32; N];
for i in 0..N {
let word = (u32::from(colors[i].g) << 24) | (u32::from(colors[i].r) << 16) | (u32::from(colors[i].b) << 8);
words[i] = word;
}
// DMA transfer
self.sm.tx().dma_push(self.dma.reborrow(), &words).await;
Timer::after_micros(55).await;
}
}

View File

@ -42,7 +42,7 @@ 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"
smart-leds = "0.4.0"
heapless = "0.8"
usbd-hid = "0.8.1"

View File

@ -7,13 +7,11 @@
use core::fmt::Write;
use embassy_executor::Spawner;
use embassy_rp::dma::{AnyChannel, Channel};
use embassy_rp::bind_interrupts;
use embassy_rp::peripherals::PIO0;
use embassy_rp::pio::{
Config, Direction, FifoJoin, InterruptHandler, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine,
};
use embassy_rp::pio::{InterruptHandler, Pio};
use embassy_rp::pio_programs::hd44780::{PioHD44780, PioHD44780CommandSequenceProgram, PioHD44780CommandWordProgram};
use embassy_rp::pwm::{self, Pwm};
use embassy_rp::{bind_interrupts, into_ref, Peripheral, PeripheralRef};
use embassy_time::{Instant, Timer};
use {defmt_rtt as _, panic_probe as _};
@ -43,8 +41,27 @@ async fn main(_spawner: Spawner) {
c
});
let mut hd = HD44780::new(
p.PIO0, Irqs, p.DMA_CH3, p.PIN_0, p.PIN_1, p.PIN_2, p.PIN_3, p.PIN_4, p.PIN_5, p.PIN_6,
let Pio {
mut common, sm0, irq0, ..
} = Pio::new(p.PIO0, Irqs);
let word_prg = PioHD44780CommandWordProgram::new(&mut common);
let seq_prg = PioHD44780CommandSequenceProgram::new(&mut common);
let mut hd = PioHD44780::new(
&mut common,
sm0,
irq0,
p.DMA_CH3,
p.PIN_0,
p.PIN_1,
p.PIN_2,
p.PIN_3,
p.PIN_4,
p.PIN_5,
p.PIN_6,
&word_prg,
&seq_prg,
)
.await;
@ -68,173 +85,3 @@ async fn main(_spawner: Spawner) {
Timer::after_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,
irq: Irqs,
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, irq);
// 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 mut cfg = Config::default();
cfg.use_program(&common.load_program(&prg.program), &[&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 mut cfg = Config::default();
cfg.use_program(&common.load_program(&prg.program), &[&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;
}
}

View File

@ -13,10 +13,10 @@
use core::mem;
use embassy_executor::Spawner;
use embassy_rp::bind_interrupts;
use embassy_rp::peripherals::PIO0;
use embassy_rp::pio::{Config, FifoJoin, InterruptHandler, Pio, ShiftConfig, ShiftDirection};
use embassy_rp::{bind_interrupts, Peripheral};
use fixed::traits::ToFixed;
use embassy_rp::pio::{InterruptHandler, Pio};
use embassy_rp::pio_programs::i2s::{PioI2sOut, PioI2sOutProgram};
use static_cell::StaticCell;
use {defmt_rtt as _, panic_probe as _};
@ -25,61 +25,32 @@ bind_interrupts!(struct Irqs {
});
const SAMPLE_RATE: u32 = 48_000;
const BIT_DEPTH: u32 = 16;
const CHANNELS: u32 = 2;
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let mut p = embassy_rp::init(Default::default());
// Setup pio state machine for i2s output
let mut pio = Pio::new(p.PIO0, Irqs);
#[rustfmt::skip]
let pio_program = pio_proc::pio_asm!(
".side_set 2",
" set x, 14 side 0b01", // side 0bWB - W = Word Clock, B = Bit Clock
"left_data:",
" out pins, 1 side 0b00",
" jmp x-- left_data side 0b01",
" out pins 1 side 0b10",
" set x, 14 side 0b11",
"right_data:",
" out pins 1 side 0b10",
" jmp x-- right_data side 0b11",
" out pins 1 side 0b00",
);
let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs);
let bit_clock_pin = p.PIN_18;
let left_right_clock_pin = p.PIN_19;
let data_pin = p.PIN_20;
let data_pin = pio.common.make_pio_pin(data_pin);
let bit_clock_pin = pio.common.make_pio_pin(bit_clock_pin);
let left_right_clock_pin = pio.common.make_pio_pin(left_right_clock_pin);
let cfg = {
let mut cfg = Config::default();
cfg.use_program(
&pio.common.load_program(&pio_program.program),
&[&bit_clock_pin, &left_right_clock_pin],
);
cfg.set_out_pins(&[&data_pin]);
const BIT_DEPTH: u32 = 16;
const CHANNELS: u32 = 2;
let clock_frequency = SAMPLE_RATE * BIT_DEPTH * CHANNELS;
cfg.clock_divider = (125_000_000. / clock_frequency as f64 / 2.).to_fixed();
cfg.shift_out = ShiftConfig {
threshold: 32,
direction: ShiftDirection::Left,
auto_fill: true,
};
// join fifos to have twice the time to start the next dma transfer
cfg.fifo_join = FifoJoin::TxOnly;
cfg
};
pio.sm0.set_config(&cfg);
pio.sm0.set_pin_dirs(
embassy_rp::pio::Direction::Out,
&[&data_pin, &left_right_clock_pin, &bit_clock_pin],
let program = PioI2sOutProgram::new(&mut common);
let mut i2s = PioI2sOut::new(
&mut common,
sm0,
p.DMA_CH0,
data_pin,
bit_clock_pin,
left_right_clock_pin,
SAMPLE_RATE,
BIT_DEPTH,
CHANNELS,
&program,
);
// create two audio buffers (back and front) which will take turns being
@ -90,17 +61,13 @@ async fn main(_spawner: Spawner) {
let (mut back_buffer, mut front_buffer) = dma_buffer.split_at_mut(BUFFER_SIZE);
// start pio state machine
pio.sm0.set_enable(true);
let tx = pio.sm0.tx();
let mut dma_ref = p.DMA_CH0.into_ref();
let mut fade_value: i32 = 0;
let mut phase: i32 = 0;
loop {
// trigger transfer of front buffer data to the pio fifo
// but don't await the returned future, yet
let dma_future = tx.dma_push(dma_ref.reborrow(), front_buffer);
let dma_future = i2s.write(front_buffer);
// fade in audio when bootsel is pressed
let fade_target = if p.BOOTSEL.is_pressed() { i32::MAX } else { 0 };

View File

@ -6,7 +6,8 @@ use defmt::*;
use embassy_executor::Spawner;
use embassy_rp::bind_interrupts;
use embassy_rp::peripherals::PIO0;
use embassy_rp::pio::{self, Common, Config, InterruptHandler, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine};
use embassy_rp::pio::{self, InterruptHandler, Pio};
use embassy_rp::pio_programs::onewire::{PioOneWire, PioOneWireProgram};
use embassy_time::Timer;
use {defmt_rtt as _, panic_probe as _};
@ -18,7 +19,11 @@ bind_interrupts!(struct Irqs {
async fn main(_spawner: Spawner) {
let p = embassy_rp::init(Default::default());
let mut pio = Pio::new(p.PIO0, Irqs);
let mut sensor = Ds18b20::new(&mut pio.common, pio.sm0, p.PIN_2);
let prg = PioOneWireProgram::new(&mut pio.common);
let onewire = PioOneWire::new(&mut pio.common, pio.sm0, p.PIN_2, &prg);
let mut sensor = Ds18b20::new(onewire);
loop {
sensor.start().await; // Start a new measurement
@ -33,89 +38,12 @@ async fn main(_spawner: Spawner) {
/// DS18B20 temperature sensor driver
pub struct Ds18b20<'d, PIO: pio::Instance, const SM: usize> {
sm: StateMachine<'d, PIO, SM>,
wire: PioOneWire<'d, PIO, SM>,
}
impl<'d, PIO: pio::Instance, const SM: usize> Ds18b20<'d, PIO, SM> {
/// Create a new instance the driver
pub fn new(common: &mut Common<'d, PIO>, mut sm: StateMachine<'d, PIO, SM>, pin: impl PioPin) -> Self {
let prg = pio_proc::pio_asm!(
r#"
.wrap_target
again:
pull block
mov x, osr
jmp !x, read
write:
set pindirs, 1
set pins, 0
loop1:
jmp x--,loop1
set pindirs, 0 [31]
wait 1 pin 0 [31]
pull block
mov x, osr
bytes1:
pull block
set y, 7
set pindirs, 1
bit1:
set pins, 0 [1]
out pins,1 [31]
set pins, 1 [20]
jmp y--,bit1
jmp x--,bytes1
set pindirs, 0 [31]
jmp again
read:
pull block
mov x, osr
bytes2:
set y, 7
bit2:
set pindirs, 1
set pins, 0 [1]
set pindirs, 0 [5]
in pins,1 [10]
jmp y--,bit2
jmp x--,bytes2
.wrap
"#,
);
let pin = common.make_pio_pin(pin);
let mut cfg = Config::default();
cfg.use_program(&common.load_program(&prg.program), &[]);
cfg.set_out_pins(&[&pin]);
cfg.set_in_pins(&[&pin]);
cfg.set_set_pins(&[&pin]);
cfg.shift_in = ShiftConfig {
auto_fill: true,
direction: ShiftDirection::Right,
threshold: 8,
};
cfg.clock_divider = 255_u8.into();
sm.set_config(&cfg);
sm.set_enable(true);
Self { sm }
}
/// Write bytes over the wire
async fn write_bytes(&mut self, bytes: &[u8]) {
self.sm.tx().wait_push(250).await;
self.sm.tx().wait_push(bytes.len() as u32 - 1).await;
for b in bytes {
self.sm.tx().wait_push(*b as u32).await;
}
}
/// Read bytes from the wire
async fn read_bytes(&mut self, bytes: &mut [u8]) {
self.sm.tx().wait_push(0).await;
self.sm.tx().wait_push(bytes.len() as u32 - 1).await;
for b in bytes.iter_mut() {
*b = (self.sm.rx().wait_pull().await >> 24) as u8;
}
pub fn new(wire: PioOneWire<'d, PIO, SM>) -> Self {
Self { wire }
}
/// Calculate CRC8 of the data
@ -139,14 +67,14 @@ impl<'d, PIO: pio::Instance, const SM: usize> Ds18b20<'d, PIO, SM> {
/// Start a new measurement. Allow at least 1000ms before getting `temperature`.
pub async fn start(&mut self) {
self.write_bytes(&[0xCC, 0x44]).await;
self.wire.write_bytes(&[0xCC, 0x44]).await;
}
/// Read the temperature. Ensure >1000ms has passed since `start` before calling this.
pub async fn temperature(&mut self) -> Result<f32, ()> {
self.write_bytes(&[0xCC, 0xBE]).await;
self.wire.write_bytes(&[0xCC, 0xBE]).await;
let mut data = [0; 9];
self.read_bytes(&mut data).await;
self.wire.read_bytes(&mut data).await;
match Self::crc8(&data) == 0 {
true => Ok(((data[1] as u32) << 8 | data[0] as u32) as f32 / 16.),
false => Err(()),

View File

@ -5,12 +5,11 @@
use core::time::Duration;
use embassy_executor::Spawner;
use embassy_rp::gpio::Level;
use embassy_rp::bind_interrupts;
use embassy_rp::peripherals::PIO0;
use embassy_rp::pio::{Common, Config, Direction, Instance, InterruptHandler, Pio, PioPin, StateMachine};
use embassy_rp::{bind_interrupts, clocks};
use embassy_rp::pio::{InterruptHandler, Pio};
use embassy_rp::pio_programs::pwm::{PioPwm, PioPwmProgram};
use embassy_time::Timer;
use pio::InstructionOperands;
use {defmt_rtt as _, panic_probe as _};
const REFRESH_INTERVAL: u64 = 20000;
@ -19,93 +18,14 @@ bind_interrupts!(struct Irqs {
PIO0_IRQ_0 => InterruptHandler<PIO0>;
});
pub fn to_pio_cycles(duration: Duration) -> u32 {
(clocks::clk_sys_freq() / 1_000_000) / 3 * duration.as_micros() as u32 // parentheses are required to prevent overflow
}
pub struct PwmPio<'d, T: Instance, const SM: usize> {
sm: StateMachine<'d, T, SM>,
}
impl<'d, T: Instance, const SM: usize> PwmPio<'d, T, SM> {
pub fn new(pio: &mut Common<'d, T>, mut sm: StateMachine<'d, T, SM>, pin: impl PioPin) -> Self {
let prg = pio_proc::pio_asm!(
".side_set 1 opt"
"pull noblock side 0"
"mov x, osr"
"mov y, isr"
"countloop:"
"jmp x!=y noset"
"jmp skip side 1"
"noset:"
"nop"
"skip:"
"jmp y-- countloop"
);
pio.load_program(&prg.program);
let pin = pio.make_pio_pin(pin);
sm.set_pins(Level::High, &[&pin]);
sm.set_pin_dirs(Direction::Out, &[&pin]);
let mut cfg = Config::default();
cfg.use_program(&pio.load_program(&prg.program), &[&pin]);
sm.set_config(&cfg);
Self { sm }
}
pub fn start(&mut self) {
self.sm.set_enable(true);
}
pub fn stop(&mut self) {
self.sm.set_enable(false);
}
pub fn set_period(&mut self, duration: Duration) {
let is_enabled = self.sm.is_enabled();
while !self.sm.tx().empty() {} // Make sure that the queue is empty
self.sm.set_enable(false);
self.sm.tx().push(to_pio_cycles(duration));
unsafe {
self.sm.exec_instr(
InstructionOperands::PULL {
if_empty: false,
block: false,
}
.encode(),
);
self.sm.exec_instr(
InstructionOperands::OUT {
destination: ::pio::OutDestination::ISR,
bit_count: 32,
}
.encode(),
);
};
if is_enabled {
self.sm.set_enable(true) // Enable if previously enabled
}
}
pub fn set_level(&mut self, level: u32) {
self.sm.tx().push(level);
}
pub fn write(&mut self, duration: Duration) {
self.set_level(to_pio_cycles(duration));
}
}
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let p = embassy_rp::init(Default::default());
let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs);
// Note that PIN_25 is the led pin on the Pico
let mut pwm_pio = PwmPio::new(&mut common, sm0, p.PIN_25);
let prg = PioPwmProgram::new(&mut common);
let mut pwm_pio = PioPwm::new(&mut common, sm0, p.PIN_25, &prg);
pwm_pio.set_period(Duration::from_micros(REFRESH_INTERVAL));
pwm_pio.start();

View File

@ -5,70 +5,18 @@
use defmt::info;
use embassy_executor::Spawner;
use embassy_rp::gpio::Pull;
use embassy_rp::bind_interrupts;
use embassy_rp::peripherals::PIO0;
use embassy_rp::{bind_interrupts, pio};
use fixed::traits::ToFixed;
use pio::{Common, Config, FifoJoin, Instance, InterruptHandler, Pio, PioPin, ShiftDirection, StateMachine};
use embassy_rp::pio::{InterruptHandler, Pio};
use embassy_rp::pio_programs::rotary_encoder::{Direction, PioEncoder, PioEncoderProgram};
use {defmt_rtt as _, panic_probe as _};
bind_interrupts!(struct Irqs {
PIO0_IRQ_0 => InterruptHandler<PIO0>;
});
pub struct PioEncoder<'d, T: Instance, const SM: usize> {
sm: StateMachine<'d, T, SM>,
}
impl<'d, T: Instance, const SM: usize> PioEncoder<'d, T, SM> {
pub fn new(
pio: &mut Common<'d, T>,
mut sm: StateMachine<'d, T, SM>,
pin_a: impl PioPin,
pin_b: impl PioPin,
) -> Self {
let mut pin_a = pio.make_pio_pin(pin_a);
let mut pin_b = pio.make_pio_pin(pin_b);
pin_a.set_pull(Pull::Up);
pin_b.set_pull(Pull::Up);
sm.set_pin_dirs(pio::Direction::In, &[&pin_a, &pin_b]);
let prg = pio_proc::pio_asm!("wait 1 pin 1", "wait 0 pin 1", "in pins, 2", "push",);
let mut cfg = Config::default();
cfg.set_in_pins(&[&pin_a, &pin_b]);
cfg.fifo_join = FifoJoin::RxOnly;
cfg.shift_in.direction = ShiftDirection::Left;
cfg.clock_divider = 10_000.to_fixed();
cfg.use_program(&pio.load_program(&prg.program), &[]);
sm.set_config(&cfg);
sm.set_enable(true);
Self { sm }
}
pub async fn read(&mut self) -> Direction {
loop {
match self.sm.rx().wait_pull().await {
0 => return Direction::CounterClockwise,
1 => return Direction::Clockwise,
_ => {}
}
}
}
}
pub enum Direction {
Clockwise,
CounterClockwise,
}
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let p = embassy_rp::init(Default::default());
let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs);
let mut encoder = PioEncoder::new(&mut common, sm0, p.PIN_4, p.PIN_5);
#[embassy_executor::task]
async fn encoder_0(mut encoder: PioEncoder<'static, PIO0, 0>) {
let mut count = 0;
loop {
info!("Count: {}", count);
@ -78,3 +26,30 @@ async fn main(_spawner: Spawner) {
};
}
}
#[embassy_executor::task]
async fn encoder_1(mut encoder: PioEncoder<'static, PIO0, 1>) {
let mut count = 0;
loop {
info!("Count: {}", count);
count += match encoder.read().await {
Direction::Clockwise => 1,
Direction::CounterClockwise => -1,
};
}
}
#[embassy_executor::main]
async fn main(spawner: Spawner) {
let p = embassy_rp::init(Default::default());
let Pio {
mut common, sm0, sm1, ..
} = Pio::new(p.PIO0, Irqs);
let prg = PioEncoderProgram::new(&mut common);
let encoder0 = PioEncoder::new(&mut common, sm0, p.PIN_4, p.PIN_5, &prg);
let encoder1 = PioEncoder::new(&mut common, sm1, p.PIN_6, p.PIN_7, &prg);
spawner.must_spawn(encoder_0(encoder0));
spawner.must_spawn(encoder_1(encoder1));
}

View File

@ -5,12 +5,11 @@
use core::time::Duration;
use embassy_executor::Spawner;
use embassy_rp::gpio::Level;
use embassy_rp::bind_interrupts;
use embassy_rp::peripherals::PIO0;
use embassy_rp::pio::{Common, Config, Direction, Instance, InterruptHandler, Pio, PioPin, StateMachine};
use embassy_rp::{bind_interrupts, clocks};
use embassy_rp::pio::{Instance, InterruptHandler, Pio};
use embassy_rp::pio_programs::pwm::{PioPwm, PioPwmProgram};
use embassy_time::Timer;
use pio::InstructionOperands;
use {defmt_rtt as _, panic_probe as _};
const DEFAULT_MIN_PULSE_WIDTH: u64 = 1000; // uncalibrated default, the shortest duty cycle sent to a servo
@ -22,88 +21,8 @@ bind_interrupts!(struct Irqs {
PIO0_IRQ_0 => InterruptHandler<PIO0>;
});
pub fn to_pio_cycles(duration: Duration) -> u32 {
(clocks::clk_sys_freq() / 1_000_000) / 3 * duration.as_micros() as u32 // parentheses are required to prevent overflow
}
pub struct PwmPio<'d, T: Instance, const SM: usize> {
sm: StateMachine<'d, T, SM>,
}
impl<'d, T: Instance, const SM: usize> PwmPio<'d, T, SM> {
pub fn new(pio: &mut Common<'d, T>, mut sm: StateMachine<'d, T, SM>, pin: impl PioPin) -> Self {
let prg = pio_proc::pio_asm!(
".side_set 1 opt"
"pull noblock side 0"
"mov x, osr"
"mov y, isr"
"countloop:"
"jmp x!=y noset"
"jmp skip side 1"
"noset:"
"nop"
"skip:"
"jmp y-- countloop"
);
pio.load_program(&prg.program);
let pin = pio.make_pio_pin(pin);
sm.set_pins(Level::High, &[&pin]);
sm.set_pin_dirs(Direction::Out, &[&pin]);
let mut cfg = Config::default();
cfg.use_program(&pio.load_program(&prg.program), &[&pin]);
sm.set_config(&cfg);
Self { sm }
}
pub fn start(&mut self) {
self.sm.set_enable(true);
}
pub fn stop(&mut self) {
self.sm.set_enable(false);
}
pub fn set_period(&mut self, duration: Duration) {
let is_enabled = self.sm.is_enabled();
while !self.sm.tx().empty() {} // Make sure that the queue is empty
self.sm.set_enable(false);
self.sm.tx().push(to_pio_cycles(duration));
unsafe {
self.sm.exec_instr(
InstructionOperands::PULL {
if_empty: false,
block: false,
}
.encode(),
);
self.sm.exec_instr(
InstructionOperands::OUT {
destination: ::pio::OutDestination::ISR,
bit_count: 32,
}
.encode(),
);
};
if is_enabled {
self.sm.set_enable(true) // Enable if previously enabled
}
}
pub fn set_level(&mut self, level: u32) {
self.sm.tx().push(level);
}
pub fn write(&mut self, duration: Duration) {
self.set_level(to_pio_cycles(duration));
}
}
pub struct ServoBuilder<'d, T: Instance, const SM: usize> {
pwm: PwmPio<'d, T, SM>,
pwm: PioPwm<'d, T, SM>,
period: Duration,
min_pulse_width: Duration,
max_pulse_width: Duration,
@ -111,7 +30,7 @@ pub struct ServoBuilder<'d, T: Instance, const SM: usize> {
}
impl<'d, T: Instance, const SM: usize> ServoBuilder<'d, T, SM> {
pub fn new(pwm: PwmPio<'d, T, SM>) -> Self {
pub fn new(pwm: PioPwm<'d, T, SM>) -> Self {
Self {
pwm,
period: Duration::from_micros(REFRESH_INTERVAL),
@ -153,7 +72,7 @@ impl<'d, T: Instance, const SM: usize> ServoBuilder<'d, T, SM> {
}
pub struct Servo<'d, T: Instance, const SM: usize> {
pwm: PwmPio<'d, T, SM>,
pwm: PioPwm<'d, T, SM>,
min_pulse_width: Duration,
max_pulse_width: Duration,
max_degree_rotation: u64,
@ -190,7 +109,8 @@ async fn main(_spawner: Spawner) {
let p = embassy_rp::init(Default::default());
let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs);
let pwm_pio = PwmPio::new(&mut common, sm0, p.PIN_1);
let prg = PioPwmProgram::new(&mut common);
let pwm_pio = PioPwm::new(&mut common, sm0, p.PIN_1, &prg);
let mut servo = ServoBuilder::new(pwm_pio)
.set_max_degree_rotation(120) // Example of adjusting values for MG996R servo
.set_min_pulse_width(Duration::from_micros(350)) // This value was detemined by a rough experiment.

View File

@ -3,143 +3,20 @@
#![no_std]
#![no_main]
use core::mem::{self, MaybeUninit};
use defmt::info;
use embassy_executor::Spawner;
use embassy_rp::bind_interrupts;
use embassy_rp::peripherals::PIO0;
use embassy_rp::pio::{Common, Config, Direction, Instance, InterruptHandler, Irq, Pio, PioPin, StateMachine};
use embassy_rp::pio::{InterruptHandler, Pio};
use embassy_rp::pio_programs::stepper::{PioStepper, PioStepperProgram};
use embassy_time::{with_timeout, Duration, Timer};
use fixed::traits::ToFixed;
use fixed::types::extra::U8;
use fixed::FixedU32;
use {defmt_rtt as _, panic_probe as _};
bind_interrupts!(struct Irqs {
PIO0_IRQ_0 => InterruptHandler<PIO0>;
});
pub struct PioStepper<'d, T: Instance, const SM: usize> {
irq: Irq<'d, T, SM>,
sm: StateMachine<'d, T, SM>,
}
impl<'d, T: Instance, const SM: usize> PioStepper<'d, T, SM> {
pub fn new(
pio: &mut Common<'d, T>,
mut sm: StateMachine<'d, T, SM>,
irq: Irq<'d, T, SM>,
pin0: impl PioPin,
pin1: impl PioPin,
pin2: impl PioPin,
pin3: impl PioPin,
) -> Self {
let prg = pio_proc::pio_asm!(
"pull block",
"mov x, osr",
"pull block",
"mov y, osr",
"jmp !x end",
"loop:",
"jmp !osre step",
"mov osr, y",
"step:",
"out pins, 4 [31]"
"jmp x-- loop",
"end:",
"irq 0 rel"
);
let pin0 = pio.make_pio_pin(pin0);
let pin1 = pio.make_pio_pin(pin1);
let pin2 = pio.make_pio_pin(pin2);
let pin3 = pio.make_pio_pin(pin3);
sm.set_pin_dirs(Direction::Out, &[&pin0, &pin1, &pin2, &pin3]);
let mut cfg = Config::default();
cfg.set_out_pins(&[&pin0, &pin1, &pin2, &pin3]);
cfg.clock_divider = (125_000_000 / (100 * 136)).to_fixed();
cfg.use_program(&pio.load_program(&prg.program), &[]);
sm.set_config(&cfg);
sm.set_enable(true);
Self { irq, sm }
}
// Set pulse frequency
pub fn set_frequency(&mut self, freq: u32) {
let clock_divider: FixedU32<U8> = (125_000_000 / (freq * 136)).to_fixed();
assert!(clock_divider <= 65536, "clkdiv must be <= 65536");
assert!(clock_divider >= 1, "clkdiv must be >= 1");
self.sm.set_clock_divider(clock_divider);
self.sm.clkdiv_restart();
}
// Full step, one phase
pub async fn step(&mut self, steps: i32) {
if steps > 0 {
self.run(steps, 0b1000_0100_0010_0001_1000_0100_0010_0001).await
} else {
self.run(-steps, 0b0001_0010_0100_1000_0001_0010_0100_1000).await
}
}
// Full step, two phase
pub async fn step2(&mut self, steps: i32) {
if steps > 0 {
self.run(steps, 0b1001_1100_0110_0011_1001_1100_0110_0011).await
} else {
self.run(-steps, 0b0011_0110_1100_1001_0011_0110_1100_1001).await
}
}
// Half step
pub async fn step_half(&mut self, steps: i32) {
if steps > 0 {
self.run(steps, 0b1001_1000_1100_0100_0110_0010_0011_0001).await
} else {
self.run(-steps, 0b0001_0011_0010_0110_0100_1100_1000_1001).await
}
}
async fn run(&mut self, steps: i32, pattern: u32) {
self.sm.tx().wait_push(steps as u32).await;
self.sm.tx().wait_push(pattern).await;
let drop = OnDrop::new(|| {
self.sm.clear_fifos();
unsafe {
self.sm.exec_instr(
pio::InstructionOperands::JMP {
address: 0,
condition: pio::JmpCondition::Always,
}
.encode(),
);
}
});
self.irq.wait().await;
drop.defuse();
}
}
struct OnDrop<F: FnOnce()> {
f: MaybeUninit<F>,
}
impl<F: FnOnce()> OnDrop<F> {
pub fn new(f: F) -> Self {
Self { f: MaybeUninit::new(f) }
}
pub fn defuse(self) {
mem::forget(self)
}
}
impl<F: FnOnce()> Drop for OnDrop<F> {
fn drop(&mut self) {
unsafe { self.f.as_ptr().read()() }
}
}
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let p = embassy_rp::init(Default::default());
@ -147,14 +24,18 @@ async fn main(_spawner: Spawner) {
mut common, irq0, sm0, ..
} = Pio::new(p.PIO0, Irqs);
let mut stepper = PioStepper::new(&mut common, sm0, irq0, p.PIN_4, p.PIN_5, p.PIN_6, p.PIN_7);
let prg = PioStepperProgram::new(&mut common);
let mut stepper = PioStepper::new(&mut common, sm0, irq0, p.PIN_4, p.PIN_5, p.PIN_6, p.PIN_7, &prg);
stepper.set_frequency(120);
loop {
info!("CW full steps");
stepper.step(1000).await;
info!("CCW full steps, drop after 1 sec");
if let Err(_) = with_timeout(Duration::from_secs(1), stepper.step(-i32::MAX)).await {
if with_timeout(Duration::from_secs(1), stepper.step(-i32::MAX))
.await
.is_err()
{
info!("Time's up!");
Timer::after(Duration::from_secs(1)).await;
}

View File

@ -13,10 +13,10 @@
use defmt::{info, panic, trace};
use embassy_executor::Spawner;
use embassy_futures::join::{join, join3};
use embassy_rp::bind_interrupts;
use embassy_rp::peripherals::{PIO0, USB};
use embassy_rp::pio::InterruptHandler as PioInterruptHandler;
use embassy_rp::pio_programs::uart::{PioUartRx, PioUartRxProgram, PioUartTx, PioUartTxProgram};
use embassy_rp::usb::{Driver, Instance, InterruptHandler};
use embassy_rp::{bind_interrupts, pio};
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
use embassy_sync::pipe::Pipe;
use embassy_usb::class::cdc_acm::{CdcAcmClass, Receiver, Sender, State};
@ -25,13 +25,11 @@ use embassy_usb::{Builder, Config};
use embedded_io_async::{Read, Write};
use {defmt_rtt as _, panic_probe as _};
use crate::uart::PioUart;
use crate::uart_rx::PioUartRx;
use crate::uart_tx::PioUartTx;
//use crate::uart::PioUart;
bind_interrupts!(struct Irqs {
USBCTRL_IRQ => InterruptHandler<USB>;
PIO0_IRQ_0 => PioInterruptHandler<PIO0>;
PIO0_IRQ_0 => pio::InterruptHandler<PIO0>;
});
#[embassy_executor::main]
@ -85,8 +83,15 @@ async fn main(_spawner: Spawner) {
let usb_fut = usb.run();
// PIO UART setup
let uart = PioUart::new(9600, p.PIO0, p.PIN_4, p.PIN_5);
let (mut uart_tx, mut uart_rx) = uart.split();
let pio::Pio {
mut common, sm0, sm1, ..
} = pio::Pio::new(p.PIO0, Irqs);
let tx_program = PioUartTxProgram::new(&mut common);
let mut uart_tx = PioUartTx::new(9600, &mut common, sm0, p.PIN_4, &tx_program);
let rx_program = PioUartRxProgram::new(&mut common);
let mut uart_rx = PioUartRx::new(9600, &mut common, sm1, p.PIN_5, &rx_program);
// Pipe setup
let mut usb_pipe: Pipe<NoopRawMutex, 20> = Pipe::new();
@ -163,8 +168,8 @@ async fn usb_write<'d, T: Instance + 'd>(
}
/// Read from the UART and write it to the USB TX pipe
async fn uart_read(
uart_rx: &mut PioUartRx<'_>,
async fn uart_read<PIO: pio::Instance, const SM: usize>(
uart_rx: &mut PioUartRx<'_, PIO, SM>,
usb_pipe_writer: &mut embassy_sync::pipe::Writer<'_, NoopRawMutex, 20>,
) -> ! {
let mut buf = [0; 64];
@ -180,8 +185,8 @@ async fn uart_read(
}
/// Read from the UART TX pipe and write it to the UART
async fn uart_write(
uart_tx: &mut PioUartTx<'_>,
async fn uart_write<PIO: pio::Instance, const SM: usize>(
uart_tx: &mut PioUartTx<'_, PIO, SM>,
uart_pipe_reader: &mut embassy_sync::pipe::Reader<'_, NoopRawMutex, 20>,
) -> ! {
let mut buf = [0; 64];
@ -192,197 +197,3 @@ async fn uart_write(
let _ = uart_tx.write(&data).await;
}
}
mod uart {
use embassy_rp::peripherals::PIO0;
use embassy_rp::pio::{Pio, PioPin};
use embassy_rp::Peripheral;
use crate::uart_rx::PioUartRx;
use crate::uart_tx::PioUartTx;
use crate::Irqs;
pub struct PioUart<'a> {
tx: PioUartTx<'a>,
rx: PioUartRx<'a>,
}
impl<'a> PioUart<'a> {
pub fn new(
baud: u64,
pio: impl Peripheral<P = PIO0> + 'a,
tx_pin: impl PioPin,
rx_pin: impl PioPin,
) -> PioUart<'a> {
let Pio {
mut common, sm0, sm1, ..
} = Pio::new(pio, Irqs);
let tx = PioUartTx::new(&mut common, sm0, tx_pin, baud);
let rx = PioUartRx::new(&mut common, sm1, rx_pin, baud);
PioUart { tx, rx }
}
pub fn split(self) -> (PioUartTx<'a>, PioUartRx<'a>) {
(self.tx, self.rx)
}
}
}
mod uart_tx {
use core::convert::Infallible;
use embassy_rp::gpio::Level;
use embassy_rp::peripherals::PIO0;
use embassy_rp::pio::{Common, Config, Direction, FifoJoin, PioPin, ShiftDirection, StateMachine};
use embedded_io_async::{ErrorType, Write};
use fixed::traits::ToFixed;
use fixed_macro::types::U56F8;
pub struct PioUartTx<'a> {
sm_tx: StateMachine<'a, PIO0, 0>,
}
impl<'a> PioUartTx<'a> {
pub fn new(
common: &mut Common<'a, PIO0>,
mut sm_tx: StateMachine<'a, PIO0, 0>,
tx_pin: impl PioPin,
baud: u64,
) -> Self {
let prg = pio_proc::pio_asm!(
r#"
.side_set 1 opt
; An 8n1 UART transmit program.
; OUT pin 0 and side-set pin 0 are both mapped to UART TX pin.
pull side 1 [7] ; Assert stop bit, or stall with line in idle state
set x, 7 side 0 [7] ; Preload bit counter, assert start bit for 8 clocks
bitloop: ; This loop will run 8 times (8n1 UART)
out pins, 1 ; Shift 1 bit from OSR to the first OUT pin
jmp x-- bitloop [6] ; Each loop iteration is 8 cycles.
"#
);
let tx_pin = common.make_pio_pin(tx_pin);
sm_tx.set_pins(Level::High, &[&tx_pin]);
sm_tx.set_pin_dirs(Direction::Out, &[&tx_pin]);
let mut cfg = Config::default();
cfg.set_out_pins(&[&tx_pin]);
cfg.use_program(&common.load_program(&prg.program), &[&tx_pin]);
cfg.shift_out.auto_fill = false;
cfg.shift_out.direction = ShiftDirection::Right;
cfg.fifo_join = FifoJoin::TxOnly;
cfg.clock_divider = (U56F8!(125_000_000) / (8 * baud)).to_fixed();
sm_tx.set_config(&cfg);
sm_tx.set_enable(true);
Self { sm_tx }
}
pub async fn write_u8(&mut self, data: u8) {
self.sm_tx.tx().wait_push(data as u32).await;
}
}
impl ErrorType for PioUartTx<'_> {
type Error = Infallible;
}
impl Write for PioUartTx<'_> {
async fn write(&mut self, buf: &[u8]) -> Result<usize, Infallible> {
for byte in buf {
self.write_u8(*byte).await;
}
Ok(buf.len())
}
}
}
mod uart_rx {
use core::convert::Infallible;
use embassy_rp::gpio::Level;
use embassy_rp::peripherals::PIO0;
use embassy_rp::pio::{Common, Config, Direction, FifoJoin, PioPin, ShiftDirection, StateMachine};
use embedded_io_async::{ErrorType, Read};
use fixed::traits::ToFixed;
use fixed_macro::types::U56F8;
pub struct PioUartRx<'a> {
sm_rx: StateMachine<'a, PIO0, 1>,
}
impl<'a> PioUartRx<'a> {
pub fn new(
common: &mut Common<'a, PIO0>,
mut sm_rx: StateMachine<'a, PIO0, 1>,
rx_pin: impl PioPin,
baud: u64,
) -> Self {
let prg = pio_proc::pio_asm!(
r#"
; Slightly more fleshed-out 8n1 UART receiver which handles framing errors and
; break conditions more gracefully.
; IN pin 0 and JMP pin are both mapped to the GPIO used as UART RX.
start:
wait 0 pin 0 ; Stall until start bit is asserted
set x, 7 [10] ; Preload bit counter, then delay until halfway through
rx_bitloop: ; the first data bit (12 cycles incl wait, set).
in pins, 1 ; Shift data bit into ISR
jmp x-- rx_bitloop [6] ; Loop 8 times, each loop iteration is 8 cycles
jmp pin good_rx_stop ; Check stop bit (should be high)
irq 4 rel ; Either a framing error or a break. Set a sticky flag,
wait 1 pin 0 ; and wait for line to return to idle state.
jmp start ; Don't push data if we didn't see good framing.
good_rx_stop: ; No delay before returning to start; a little slack is
in null 24
push ; important in case the TX clock is slightly too fast.
"#
);
let mut cfg = Config::default();
cfg.use_program(&common.load_program(&prg.program), &[]);
let rx_pin = common.make_pio_pin(rx_pin);
sm_rx.set_pins(Level::High, &[&rx_pin]);
cfg.set_in_pins(&[&rx_pin]);
cfg.set_jmp_pin(&rx_pin);
sm_rx.set_pin_dirs(Direction::In, &[&rx_pin]);
cfg.clock_divider = (U56F8!(125_000_000) / (8 * baud)).to_fixed();
cfg.shift_in.auto_fill = false;
cfg.shift_in.direction = ShiftDirection::Right;
cfg.shift_in.threshold = 32;
cfg.fifo_join = FifoJoin::RxOnly;
sm_rx.set_config(&cfg);
sm_rx.set_enable(true);
Self { sm_rx }
}
pub async fn read_u8(&mut self) -> u8 {
self.sm_rx.rx().wait_pull().await as u8
}
}
impl ErrorType for PioUartRx<'_> {
type Error = Infallible;
}
impl Read for PioUartRx<'_> {
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Infallible> {
let mut i = 0;
while i < buf.len() {
buf[i] = self.read_u8().await;
i += 1;
}
Ok(i)
}
}
}

View File

@ -6,15 +6,11 @@
use defmt::*;
use embassy_executor::Spawner;
use embassy_rp::dma::{AnyChannel, Channel};
use embassy_rp::bind_interrupts;
use embassy_rp::peripherals::PIO0;
use embassy_rp::pio::{
Common, Config, FifoJoin, Instance, InterruptHandler, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine,
};
use embassy_rp::{bind_interrupts, clocks, into_ref, Peripheral, PeripheralRef};
use embassy_time::{Duration, Ticker, Timer};
use fixed::types::U24F8;
use fixed_macro::fixed;
use embassy_rp::pio::{InterruptHandler, Pio};
use embassy_rp::pio_programs::ws2812::{PioWs2812, PioWs2812Program};
use embassy_time::{Duration, Ticker};
use smart_leds::RGB8;
use {defmt_rtt as _, panic_probe as _};
@ -22,96 +18,6 @@ bind_interrupts!(struct Irqs {
PIO0_IRQ_0 => InterruptHandler<PIO0>;
});
pub struct Ws2812<'d, P: Instance, const S: usize, const N: usize> {
dma: PeripheralRef<'d, AnyChannel>,
sm: StateMachine<'d, P, S>,
}
impl<'d, P: Instance, const S: usize, const N: usize> Ws2812<'d, P, S, N> {
pub fn new(
pio: &mut Common<'d, P>,
mut sm: StateMachine<'d, P, S>,
dma: impl Peripheral<P = impl Channel> + 'd,
pin: impl PioPin,
) -> Self {
into_ref!(dma);
// 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);
cfg.set_out_pins(&[&out_pin]);
cfg.set_set_pins(&[&out_pin]);
cfg.use_program(&pio.load_program(&prg), &[&out_pin]);
// Clock config, measured in kHz to avoid overflows
// TODO CLOCK_FREQ should come from embassy_rp
let clock_freq = U24F8::from_num(clocks::clk_sys_freq() / 1000);
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 {
dma: dma.map_into(),
sm,
}
}
pub async fn write(&mut self, colors: &[RGB8; N]) {
// Precompute the word bytes from the colors
let mut words = [0u32; N];
for i in 0..N {
let word = (u32::from(colors[i].g) << 24) | (u32::from(colors[i].r) << 16) | (u32::from(colors[i].b) << 8);
words[i] = word;
}
// DMA transfer
self.sm.tx().dma_push(self.dma.reborrow(), &words).await;
Timer::after_micros(55).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 {
@ -142,7 +48,8 @@ async fn main(_spawner: Spawner) {
// Common neopixel pins:
// Thing plus: 8
// Adafruit Feather: 16; Adafruit Feather+RFM95: 4
let mut ws2812 = Ws2812::new(&mut common, sm0, p.DMA_CH0, p.PIN_16);
let program = PioWs2812Program::new(&mut common);
let mut ws2812 = PioWs2812::new(&mut common, sm0, p.DMA_CH0, p.PIN_16, &program);
// Loop forever making RGB values and pushing them out to the WS2812.
let mut ticker = Ticker::every(Duration::from_millis(10));

View File

@ -7,14 +7,12 @@
use core::fmt::Write;
use embassy_executor::Spawner;
use embassy_rp::bind_interrupts;
use embassy_rp::block::ImageDef;
use embassy_rp::dma::{AnyChannel, Channel};
use embassy_rp::peripherals::PIO0;
use embassy_rp::pio::{
Config, Direction, FifoJoin, InterruptHandler, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine,
};
use embassy_rp::pio::{InterruptHandler, Pio};
use embassy_rp::pio_programs::hd44780::{PioHD44780, PioHD44780CommandSequenceProgram, PioHD44780CommandWordProgram};
use embassy_rp::pwm::{self, Pwm};
use embassy_rp::{bind_interrupts, into_ref, Peripheral, PeripheralRef};
use embassy_time::{Instant, Timer};
use {defmt_rtt as _, panic_probe as _};
@ -48,8 +46,27 @@ async fn main(_spawner: Spawner) {
c
});
let mut hd = HD44780::new(
p.PIO0, Irqs, p.DMA_CH3, p.PIN_0, p.PIN_1, p.PIN_2, p.PIN_3, p.PIN_4, p.PIN_5, p.PIN_6,
let Pio {
mut common, sm0, irq0, ..
} = Pio::new(p.PIO0, Irqs);
let word_prg = PioHD44780CommandWordProgram::new(&mut common);
let seq_prg = PioHD44780CommandSequenceProgram::new(&mut common);
let mut hd = PioHD44780::new(
&mut common,
sm0,
irq0,
p.DMA_CH3,
p.PIN_0,
p.PIN_1,
p.PIN_2,
p.PIN_3,
p.PIN_4,
p.PIN_5,
p.PIN_6,
&word_prg,
&seq_prg,
)
.await;
@ -73,173 +90,3 @@ async fn main(_spawner: Spawner) {
Timer::after_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,
irq: Irqs,
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, irq);
// 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 mut cfg = Config::default();
cfg.use_program(&common.load_program(&prg.program), &[&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 mut cfg = Config::default();
cfg.use_program(&common.load_program(&prg.program), &[&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;
}
}

View File

@ -13,11 +13,12 @@
use core::mem;
use embassy_executor::Spawner;
use embassy_rp::bind_interrupts;
use embassy_rp::block::ImageDef;
use embassy_rp::gpio::{Input, Pull};
use embassy_rp::peripherals::PIO0;
use embassy_rp::pio::{Config, FifoJoin, InterruptHandler, Pio, ShiftConfig, ShiftDirection};
use embassy_rp::{bind_interrupts, Peripheral};
use fixed::traits::ToFixed;
use embassy_rp::pio::{InterruptHandler, Pio};
use embassy_rp::pio_programs::i2s::{PioI2sOut, PioI2sOutProgram};
use static_cell::StaticCell;
use {defmt_rtt as _, panic_probe as _};
@ -30,63 +31,36 @@ bind_interrupts!(struct Irqs {
});
const SAMPLE_RATE: u32 = 48_000;
const BIT_DEPTH: u32 = 16;
const CHANNELS: u32 = 2;
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let p = embassy_rp::init(Default::default());
// Setup pio state machine for i2s output
let mut pio = Pio::new(p.PIO0, Irqs);
#[rustfmt::skip]
let pio_program = pio_proc::pio_asm!(
".side_set 2",
" set x, 14 side 0b01", // side 0bWB - W = Word Clock, B = Bit Clock
"left_data:",
" out pins, 1 side 0b00",
" jmp x-- left_data side 0b01",
" out pins 1 side 0b10",
" set x, 14 side 0b11",
"right_data:",
" out pins 1 side 0b10",
" jmp x-- right_data side 0b11",
" out pins 1 side 0b00",
);
let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs);
let bit_clock_pin = p.PIN_18;
let left_right_clock_pin = p.PIN_19;
let data_pin = p.PIN_20;
let data_pin = pio.common.make_pio_pin(data_pin);
let bit_clock_pin = pio.common.make_pio_pin(bit_clock_pin);
let left_right_clock_pin = pio.common.make_pio_pin(left_right_clock_pin);
let cfg = {
let mut cfg = Config::default();
cfg.use_program(
&pio.common.load_program(&pio_program.program),
&[&bit_clock_pin, &left_right_clock_pin],
);
cfg.set_out_pins(&[&data_pin]);
const BIT_DEPTH: u32 = 16;
const CHANNELS: u32 = 2;
let clock_frequency = SAMPLE_RATE * BIT_DEPTH * CHANNELS;
cfg.clock_divider = (125_000_000. / clock_frequency as f64 / 2.).to_fixed();
cfg.shift_out = ShiftConfig {
threshold: 32,
direction: ShiftDirection::Left,
auto_fill: true,
};
// join fifos to have twice the time to start the next dma transfer
cfg.fifo_join = FifoJoin::TxOnly;
cfg
};
pio.sm0.set_config(&cfg);
pio.sm0.set_pin_dirs(
embassy_rp::pio::Direction::Out,
&[&data_pin, &left_right_clock_pin, &bit_clock_pin],
let program = PioI2sOutProgram::new(&mut common);
let mut i2s = PioI2sOut::new(
&mut common,
sm0,
p.DMA_CH0,
data_pin,
bit_clock_pin,
left_right_clock_pin,
SAMPLE_RATE,
BIT_DEPTH,
CHANNELS,
&program,
);
let fade_input = Input::new(p.PIN_0, Pull::Up);
// create two audio buffers (back and front) which will take turns being
// filled with new audio data and being sent to the pio fifo using dma
const BUFFER_SIZE: usize = 960;
@ -95,20 +69,16 @@ async fn main(_spawner: Spawner) {
let (mut back_buffer, mut front_buffer) = dma_buffer.split_at_mut(BUFFER_SIZE);
// start pio state machine
pio.sm0.set_enable(true);
let tx = pio.sm0.tx();
let mut dma_ref = p.DMA_CH0.into_ref();
let mut fade_value: i32 = 0;
let mut phase: i32 = 0;
loop {
// trigger transfer of front buffer data to the pio fifo
// but don't await the returned future, yet
let dma_future = tx.dma_push(dma_ref.reborrow(), front_buffer);
let dma_future = i2s.write(front_buffer);
// fade in audio
let fade_target = i32::MAX;
// fade in audio when bootsel is pressed
let fade_target = if fade_input.is_low() { i32::MAX } else { 0 };
// fill back buffer with fresh audio samples before awaiting the dma future
for s in back_buffer.iter_mut() {

View File

@ -0,0 +1,88 @@
//! This example shows how you can use PIO to read a `DS18B20` one-wire temperature sensor.
#![no_std]
#![no_main]
use defmt::*;
use embassy_executor::Spawner;
use embassy_rp::bind_interrupts;
use embassy_rp::block::ImageDef;
use embassy_rp::peripherals::PIO0;
use embassy_rp::pio::{self, InterruptHandler, Pio};
use embassy_rp::pio_programs::onewire::{PioOneWire, PioOneWireProgram};
use embassy_time::Timer;
use {defmt_rtt as _, panic_probe as _};
#[link_section = ".start_block"]
#[used]
pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe();
bind_interrupts!(struct Irqs {
PIO0_IRQ_0 => InterruptHandler<PIO0>;
});
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let p = embassy_rp::init(Default::default());
let mut pio = Pio::new(p.PIO0, Irqs);
let prg = PioOneWireProgram::new(&mut pio.common);
let onewire = PioOneWire::new(&mut pio.common, pio.sm0, p.PIN_2, &prg);
let mut sensor = Ds18b20::new(onewire);
loop {
sensor.start().await; // Start a new measurement
Timer::after_secs(1).await; // Allow 1s for the measurement to finish
match sensor.temperature().await {
Ok(temp) => info!("temp = {:?} deg C", temp),
_ => error!("sensor error"),
}
Timer::after_secs(1).await;
}
}
/// DS18B20 temperature sensor driver
pub struct Ds18b20<'d, PIO: pio::Instance, const SM: usize> {
wire: PioOneWire<'d, PIO, SM>,
}
impl<'d, PIO: pio::Instance, const SM: usize> Ds18b20<'d, PIO, SM> {
pub fn new(wire: PioOneWire<'d, PIO, SM>) -> Self {
Self { wire }
}
/// Calculate CRC8 of the data
fn crc8(data: &[u8]) -> u8 {
let mut temp;
let mut data_byte;
let mut crc = 0;
for b in data {
data_byte = *b;
for _ in 0..8 {
temp = (crc ^ data_byte) & 0x01;
crc >>= 1;
if temp != 0 {
crc ^= 0x8C;
}
data_byte >>= 1;
}
}
crc
}
/// Start a new measurement. Allow at least 1000ms before getting `temperature`.
pub async fn start(&mut self) {
self.wire.write_bytes(&[0xCC, 0x44]).await;
}
/// Read the temperature. Ensure >1000ms has passed since `start` before calling this.
pub async fn temperature(&mut self) -> Result<f32, ()> {
self.wire.write_bytes(&[0xCC, 0xBE]).await;
let mut data = [0; 9];
self.wire.read_bytes(&mut data).await;
match Self::crc8(&data) == 0 {
true => Ok(((data[1] as u32) << 8 | data[0] as u32) as f32 / 16.),
false => Err(()),
}
}
}

View File

@ -5,13 +5,12 @@
use core::time::Duration;
use embassy_executor::Spawner;
use embassy_rp::bind_interrupts;
use embassy_rp::block::ImageDef;
use embassy_rp::gpio::Level;
use embassy_rp::peripherals::PIO0;
use embassy_rp::pio::{Common, Config, Direction, Instance, InterruptHandler, Pio, PioPin, StateMachine};
use embassy_rp::{bind_interrupts, clocks};
use embassy_rp::pio::{InterruptHandler, Pio};
use embassy_rp::pio_programs::pwm::{PioPwm, PioPwmProgram};
use embassy_time::Timer;
use pio::InstructionOperands;
use {defmt_rtt as _, panic_probe as _};
#[link_section = ".start_block"]
@ -24,93 +23,14 @@ bind_interrupts!(struct Irqs {
PIO0_IRQ_0 => InterruptHandler<PIO0>;
});
pub fn to_pio_cycles(duration: Duration) -> u32 {
(clocks::clk_sys_freq() / 1_000_000) / 3 * duration.as_micros() as u32 // parentheses are required to prevent overflow
}
pub struct PwmPio<'d, T: Instance, const SM: usize> {
sm: StateMachine<'d, T, SM>,
}
impl<'d, T: Instance, const SM: usize> PwmPio<'d, T, SM> {
pub fn new(pio: &mut Common<'d, T>, mut sm: StateMachine<'d, T, SM>, pin: impl PioPin) -> Self {
let prg = pio_proc::pio_asm!(
".side_set 1 opt"
"pull noblock side 0"
"mov x, osr"
"mov y, isr"
"countloop:"
"jmp x!=y noset"
"jmp skip side 1"
"noset:"
"nop"
"skip:"
"jmp y-- countloop"
);
pio.load_program(&prg.program);
let pin = pio.make_pio_pin(pin);
sm.set_pins(Level::High, &[&pin]);
sm.set_pin_dirs(Direction::Out, &[&pin]);
let mut cfg = Config::default();
cfg.use_program(&pio.load_program(&prg.program), &[&pin]);
sm.set_config(&cfg);
Self { sm }
}
pub fn start(&mut self) {
self.sm.set_enable(true);
}
pub fn stop(&mut self) {
self.sm.set_enable(false);
}
pub fn set_period(&mut self, duration: Duration) {
let is_enabled = self.sm.is_enabled();
while !self.sm.tx().empty() {} // Make sure that the queue is empty
self.sm.set_enable(false);
self.sm.tx().push(to_pio_cycles(duration));
unsafe {
self.sm.exec_instr(
InstructionOperands::PULL {
if_empty: false,
block: false,
}
.encode(),
);
self.sm.exec_instr(
InstructionOperands::OUT {
destination: ::pio::OutDestination::ISR,
bit_count: 32,
}
.encode(),
);
};
if is_enabled {
self.sm.set_enable(true) // Enable if previously enabled
}
}
pub fn set_level(&mut self, level: u32) {
self.sm.tx().push(level);
}
pub fn write(&mut self, duration: Duration) {
self.set_level(to_pio_cycles(duration));
}
}
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let p = embassy_rp::init(Default::default());
let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs);
// Note that PIN_25 is the led pin on the Pico
let mut pwm_pio = PwmPio::new(&mut common, sm0, p.PIN_25);
let prg = PioPwmProgram::new(&mut common);
let mut pwm_pio = PioPwm::new(&mut common, sm0, p.PIN_25, &prg);
pwm_pio.set_period(Duration::from_micros(REFRESH_INTERVAL));
pwm_pio.start();

View File

@ -5,12 +5,11 @@
use defmt::info;
use embassy_executor::Spawner;
use embassy_rp::bind_interrupts;
use embassy_rp::block::ImageDef;
use embassy_rp::gpio::Pull;
use embassy_rp::peripherals::PIO0;
use embassy_rp::{bind_interrupts, pio};
use fixed::traits::ToFixed;
use pio::{Common, Config, FifoJoin, Instance, InterruptHandler, Pio, PioPin, ShiftDirection, StateMachine};
use embassy_rp::pio::{InterruptHandler, Pio};
use embassy_rp::pio_programs::rotary_encoder::{Direction, PioEncoder, PioEncoderProgram};
use {defmt_rtt as _, panic_probe as _};
#[link_section = ".start_block"]
@ -21,59 +20,8 @@ bind_interrupts!(struct Irqs {
PIO0_IRQ_0 => InterruptHandler<PIO0>;
});
pub struct PioEncoder<'d, T: Instance, const SM: usize> {
sm: StateMachine<'d, T, SM>,
}
impl<'d, T: Instance, const SM: usize> PioEncoder<'d, T, SM> {
pub fn new(
pio: &mut Common<'d, T>,
mut sm: StateMachine<'d, T, SM>,
pin_a: impl PioPin,
pin_b: impl PioPin,
) -> Self {
let mut pin_a = pio.make_pio_pin(pin_a);
let mut pin_b = pio.make_pio_pin(pin_b);
pin_a.set_pull(Pull::Up);
pin_b.set_pull(Pull::Up);
sm.set_pin_dirs(pio::Direction::In, &[&pin_a, &pin_b]);
let prg = pio_proc::pio_asm!("wait 1 pin 1", "wait 0 pin 1", "in pins, 2", "push",);
let mut cfg = Config::default();
cfg.set_in_pins(&[&pin_a, &pin_b]);
cfg.fifo_join = FifoJoin::RxOnly;
cfg.shift_in.direction = ShiftDirection::Left;
cfg.clock_divider = 10_000.to_fixed();
cfg.use_program(&pio.load_program(&prg.program), &[]);
sm.set_config(&cfg);
sm.set_enable(true);
Self { sm }
}
pub async fn read(&mut self) -> Direction {
loop {
match self.sm.rx().wait_pull().await {
0 => return Direction::CounterClockwise,
1 => return Direction::Clockwise,
_ => {}
}
}
}
}
pub enum Direction {
Clockwise,
CounterClockwise,
}
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let p = embassy_rp::init(Default::default());
let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs);
let mut encoder = PioEncoder::new(&mut common, sm0, p.PIN_4, p.PIN_5);
#[embassy_executor::task]
async fn encoder_0(mut encoder: PioEncoder<'static, PIO0, 0>) {
let mut count = 0;
loop {
info!("Count: {}", count);
@ -83,3 +31,30 @@ async fn main(_spawner: Spawner) {
};
}
}
#[embassy_executor::task]
async fn encoder_1(mut encoder: PioEncoder<'static, PIO0, 1>) {
let mut count = 0;
loop {
info!("Count: {}", count);
count += match encoder.read().await {
Direction::Clockwise => 1,
Direction::CounterClockwise => -1,
};
}
}
#[embassy_executor::main]
async fn main(spawner: Spawner) {
let p = embassy_rp::init(Default::default());
let Pio {
mut common, sm0, sm1, ..
} = Pio::new(p.PIO0, Irqs);
let prg = PioEncoderProgram::new(&mut common);
let encoder0 = PioEncoder::new(&mut common, sm0, p.PIN_4, p.PIN_5, &prg);
let encoder1 = PioEncoder::new(&mut common, sm1, p.PIN_6, p.PIN_7, &prg);
spawner.must_spawn(encoder_0(encoder0));
spawner.must_spawn(encoder_1(encoder1));
}

View File

@ -5,13 +5,12 @@
use core::time::Duration;
use embassy_executor::Spawner;
use embassy_rp::bind_interrupts;
use embassy_rp::block::ImageDef;
use embassy_rp::gpio::Level;
use embassy_rp::peripherals::PIO0;
use embassy_rp::pio::{Common, Config, Direction, Instance, InterruptHandler, Pio, PioPin, StateMachine};
use embassy_rp::{bind_interrupts, clocks};
use embassy_rp::pio::{Instance, InterruptHandler, Pio};
use embassy_rp::pio_programs::pwm::{PioPwm, PioPwmProgram};
use embassy_time::Timer;
use pio::InstructionOperands;
use {defmt_rtt as _, panic_probe as _};
#[link_section = ".start_block"]
@ -27,88 +26,8 @@ bind_interrupts!(struct Irqs {
PIO0_IRQ_0 => InterruptHandler<PIO0>;
});
pub fn to_pio_cycles(duration: Duration) -> u32 {
(clocks::clk_sys_freq() / 1_000_000) / 3 * duration.as_micros() as u32 // parentheses are required to prevent overflow
}
pub struct PwmPio<'d, T: Instance, const SM: usize> {
sm: StateMachine<'d, T, SM>,
}
impl<'d, T: Instance, const SM: usize> PwmPio<'d, T, SM> {
pub fn new(pio: &mut Common<'d, T>, mut sm: StateMachine<'d, T, SM>, pin: impl PioPin) -> Self {
let prg = pio_proc::pio_asm!(
".side_set 1 opt"
"pull noblock side 0"
"mov x, osr"
"mov y, isr"
"countloop:"
"jmp x!=y noset"
"jmp skip side 1"
"noset:"
"nop"
"skip:"
"jmp y-- countloop"
);
pio.load_program(&prg.program);
let pin = pio.make_pio_pin(pin);
sm.set_pins(Level::High, &[&pin]);
sm.set_pin_dirs(Direction::Out, &[&pin]);
let mut cfg = Config::default();
cfg.use_program(&pio.load_program(&prg.program), &[&pin]);
sm.set_config(&cfg);
Self { sm }
}
pub fn start(&mut self) {
self.sm.set_enable(true);
}
pub fn stop(&mut self) {
self.sm.set_enable(false);
}
pub fn set_period(&mut self, duration: Duration) {
let is_enabled = self.sm.is_enabled();
while !self.sm.tx().empty() {} // Make sure that the queue is empty
self.sm.set_enable(false);
self.sm.tx().push(to_pio_cycles(duration));
unsafe {
self.sm.exec_instr(
InstructionOperands::PULL {
if_empty: false,
block: false,
}
.encode(),
);
self.sm.exec_instr(
InstructionOperands::OUT {
destination: ::pio::OutDestination::ISR,
bit_count: 32,
}
.encode(),
);
};
if is_enabled {
self.sm.set_enable(true) // Enable if previously enabled
}
}
pub fn set_level(&mut self, level: u32) {
self.sm.tx().push(level);
}
pub fn write(&mut self, duration: Duration) {
self.set_level(to_pio_cycles(duration));
}
}
pub struct ServoBuilder<'d, T: Instance, const SM: usize> {
pwm: PwmPio<'d, T, SM>,
pwm: PioPwm<'d, T, SM>,
period: Duration,
min_pulse_width: Duration,
max_pulse_width: Duration,
@ -116,7 +35,7 @@ pub struct ServoBuilder<'d, T: Instance, const SM: usize> {
}
impl<'d, T: Instance, const SM: usize> ServoBuilder<'d, T, SM> {
pub fn new(pwm: PwmPio<'d, T, SM>) -> Self {
pub fn new(pwm: PioPwm<'d, T, SM>) -> Self {
Self {
pwm,
period: Duration::from_micros(REFRESH_INTERVAL),
@ -158,7 +77,7 @@ impl<'d, T: Instance, const SM: usize> ServoBuilder<'d, T, SM> {
}
pub struct Servo<'d, T: Instance, const SM: usize> {
pwm: PwmPio<'d, T, SM>,
pwm: PioPwm<'d, T, SM>,
min_pulse_width: Duration,
max_pulse_width: Duration,
max_degree_rotation: u64,
@ -195,7 +114,8 @@ async fn main(_spawner: Spawner) {
let p = embassy_rp::init(Default::default());
let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs);
let pwm_pio = PwmPio::new(&mut common, sm0, p.PIN_1);
let prg = PioPwmProgram::new(&mut common);
let pwm_pio = PioPwm::new(&mut common, sm0, p.PIN_1, &prg);
let mut servo = ServoBuilder::new(pwm_pio)
.set_max_degree_rotation(120) // Example of adjusting values for MG996R servo
.set_min_pulse_width(Duration::from_micros(350)) // This value was detemined by a rough experiment.

View File

@ -3,18 +3,15 @@
#![no_std]
#![no_main]
use core::mem::{self, MaybeUninit};
use defmt::info;
use embassy_executor::Spawner;
use embassy_rp::bind_interrupts;
use embassy_rp::block::ImageDef;
use embassy_rp::peripherals::PIO0;
use embassy_rp::pio::{Common, Config, Direction, Instance, InterruptHandler, Irq, Pio, PioPin, StateMachine};
use embassy_rp::pio::{InterruptHandler, Pio};
use embassy_rp::pio_programs::stepper::{PioStepper, PioStepperProgram};
use embassy_time::{with_timeout, Duration, Timer};
use fixed::traits::ToFixed;
use fixed::types::extra::U8;
use fixed::FixedU32;
use {defmt_rtt as _, panic_probe as _};
#[link_section = ".start_block"]
@ -25,126 +22,6 @@ bind_interrupts!(struct Irqs {
PIO0_IRQ_0 => InterruptHandler<PIO0>;
});
pub struct PioStepper<'d, T: Instance, const SM: usize> {
irq: Irq<'d, T, SM>,
sm: StateMachine<'d, T, SM>,
}
impl<'d, T: Instance, const SM: usize> PioStepper<'d, T, SM> {
pub fn new(
pio: &mut Common<'d, T>,
mut sm: StateMachine<'d, T, SM>,
irq: Irq<'d, T, SM>,
pin0: impl PioPin,
pin1: impl PioPin,
pin2: impl PioPin,
pin3: impl PioPin,
) -> Self {
let prg = pio_proc::pio_asm!(
"pull block",
"mov x, osr",
"pull block",
"mov y, osr",
"jmp !x end",
"loop:",
"jmp !osre step",
"mov osr, y",
"step:",
"out pins, 4 [31]"
"jmp x-- loop",
"end:",
"irq 0 rel"
);
let pin0 = pio.make_pio_pin(pin0);
let pin1 = pio.make_pio_pin(pin1);
let pin2 = pio.make_pio_pin(pin2);
let pin3 = pio.make_pio_pin(pin3);
sm.set_pin_dirs(Direction::Out, &[&pin0, &pin1, &pin2, &pin3]);
let mut cfg = Config::default();
cfg.set_out_pins(&[&pin0, &pin1, &pin2, &pin3]);
cfg.clock_divider = (125_000_000 / (100 * 136)).to_fixed();
cfg.use_program(&pio.load_program(&prg.program), &[]);
sm.set_config(&cfg);
sm.set_enable(true);
Self { irq, sm }
}
// Set pulse frequency
pub fn set_frequency(&mut self, freq: u32) {
let clock_divider: FixedU32<U8> = (125_000_000 / (freq * 136)).to_fixed();
assert!(clock_divider <= 65536, "clkdiv must be <= 65536");
assert!(clock_divider >= 1, "clkdiv must be >= 1");
self.sm.set_clock_divider(clock_divider);
self.sm.clkdiv_restart();
}
// Full step, one phase
pub async fn step(&mut self, steps: i32) {
if steps > 0 {
self.run(steps, 0b1000_0100_0010_0001_1000_0100_0010_0001).await
} else {
self.run(-steps, 0b0001_0010_0100_1000_0001_0010_0100_1000).await
}
}
// Full step, two phase
pub async fn step2(&mut self, steps: i32) {
if steps > 0 {
self.run(steps, 0b1001_1100_0110_0011_1001_1100_0110_0011).await
} else {
self.run(-steps, 0b0011_0110_1100_1001_0011_0110_1100_1001).await
}
}
// Half step
pub async fn step_half(&mut self, steps: i32) {
if steps > 0 {
self.run(steps, 0b1001_1000_1100_0100_0110_0010_0011_0001).await
} else {
self.run(-steps, 0b0001_0011_0010_0110_0100_1100_1000_1001).await
}
}
async fn run(&mut self, steps: i32, pattern: u32) {
self.sm.tx().wait_push(steps as u32).await;
self.sm.tx().wait_push(pattern).await;
let drop = OnDrop::new(|| {
self.sm.clear_fifos();
unsafe {
self.sm.exec_instr(
pio::InstructionOperands::JMP {
address: 0,
condition: pio::JmpCondition::Always,
}
.encode(),
);
}
});
self.irq.wait().await;
drop.defuse();
}
}
struct OnDrop<F: FnOnce()> {
f: MaybeUninit<F>,
}
impl<F: FnOnce()> OnDrop<F> {
pub fn new(f: F) -> Self {
Self { f: MaybeUninit::new(f) }
}
pub fn defuse(self) {
mem::forget(self)
}
}
impl<F: FnOnce()> Drop for OnDrop<F> {
fn drop(&mut self) {
unsafe { self.f.as_ptr().read()() }
}
}
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let p = embassy_rp::init(Default::default());
@ -152,14 +29,18 @@ async fn main(_spawner: Spawner) {
mut common, irq0, sm0, ..
} = Pio::new(p.PIO0, Irqs);
let mut stepper = PioStepper::new(&mut common, sm0, irq0, p.PIN_4, p.PIN_5, p.PIN_6, p.PIN_7);
let prg = PioStepperProgram::new(&mut common);
let mut stepper = PioStepper::new(&mut common, sm0, irq0, p.PIN_4, p.PIN_5, p.PIN_6, p.PIN_7, &prg);
stepper.set_frequency(120);
loop {
info!("CW full steps");
stepper.step(1000).await;
info!("CCW full steps, drop after 1 sec");
if let Err(_) = with_timeout(Duration::from_secs(1), stepper.step(i32::MIN)).await {
if with_timeout(Duration::from_secs(1), stepper.step(-i32::MAX))
.await
.is_err()
{
info!("Time's up!");
Timer::after(Duration::from_secs(1)).await;
}

View File

@ -0,0 +1,202 @@
//! This example shows how to use the PIO module in the RP2040 chip to implement a duplex UART.
//! The PIO module is a very powerful peripheral that can be used to implement many different
//! protocols. It is a very flexible state machine that can be programmed to do almost anything.
//!
//! This example opens up a USB device that implements a CDC ACM serial port. It then uses the
//! PIO module to implement a UART that is connected to the USB serial port. This allows you to
//! communicate with a device connected to the RP2040 over USB serial.
#![no_std]
#![no_main]
#![allow(async_fn_in_trait)]
use defmt::{info, panic, trace};
use embassy_executor::Spawner;
use embassy_futures::join::{join, join3};
use embassy_rp::block::ImageDef;
use embassy_rp::peripherals::{PIO0, USB};
use embassy_rp::pio_programs::uart::{PioUartRx, PioUartRxProgram, PioUartTx, PioUartTxProgram};
use embassy_rp::usb::{Driver, Instance, InterruptHandler};
use embassy_rp::{bind_interrupts, pio};
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
use embassy_sync::pipe::Pipe;
use embassy_usb::class::cdc_acm::{CdcAcmClass, Receiver, Sender, State};
use embassy_usb::driver::EndpointError;
use embassy_usb::{Builder, Config};
use embedded_io_async::{Read, Write};
use {defmt_rtt as _, panic_probe as _};
#[link_section = ".start_block"]
#[used]
pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe();
bind_interrupts!(struct Irqs {
USBCTRL_IRQ => InterruptHandler<USB>;
PIO0_IRQ_0 => pio::InterruptHandler<PIO0>;
});
#[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 driver = Driver::new(p.USB, Irqs);
// Create embassy-usb Config
let mut config = Config::new(0xc0de, 0xcafe);
config.manufacturer = Some("Embassy");
config.product = Some("PIO UART 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 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 config_descriptor,
&mut bos_descriptor,
&mut [], // no msos descriptors
&mut control_buf,
);
// Create classes on the builder.
let 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();
// PIO UART setup
let pio::Pio {
mut common, sm0, sm1, ..
} = pio::Pio::new(p.PIO0, Irqs);
let tx_program = PioUartTxProgram::new(&mut common);
let mut uart_tx = PioUartTx::new(9600, &mut common, sm0, p.PIN_4, &tx_program);
let rx_program = PioUartRxProgram::new(&mut common);
let mut uart_rx = PioUartRx::new(9600, &mut common, sm1, p.PIN_5, &rx_program);
// Pipe setup
let mut usb_pipe: Pipe<NoopRawMutex, 20> = Pipe::new();
let (mut usb_pipe_reader, mut usb_pipe_writer) = usb_pipe.split();
let mut uart_pipe: Pipe<NoopRawMutex, 20> = Pipe::new();
let (mut uart_pipe_reader, mut uart_pipe_writer) = uart_pipe.split();
let (mut usb_tx, mut usb_rx) = class.split();
// Read + write from USB
let usb_future = async {
loop {
info!("Wait for USB connection");
usb_rx.wait_connection().await;
info!("Connected");
let _ = join(
usb_read(&mut usb_rx, &mut uart_pipe_writer),
usb_write(&mut usb_tx, &mut usb_pipe_reader),
)
.await;
info!("Disconnected");
}
};
// Read + write from UART
let uart_future = join(
uart_read(&mut uart_rx, &mut usb_pipe_writer),
uart_write(&mut uart_tx, &mut uart_pipe_reader),
);
// Run everything concurrently.
// If we had made everything `'static` above instead, we could do this using separate tasks instead.
join3(usb_fut, usb_future, uart_future).await;
}
struct Disconnected {}
impl From<EndpointError> for Disconnected {
fn from(val: EndpointError) -> Self {
match val {
EndpointError::BufferOverflow => panic!("Buffer overflow"),
EndpointError::Disabled => Disconnected {},
}
}
}
/// Read from the USB and write it to the UART TX pipe
async fn usb_read<'d, T: Instance + 'd>(
usb_rx: &mut Receiver<'d, Driver<'d, T>>,
uart_pipe_writer: &mut embassy_sync::pipe::Writer<'_, NoopRawMutex, 20>,
) -> Result<(), Disconnected> {
let mut buf = [0; 64];
loop {
let n = usb_rx.read_packet(&mut buf).await?;
let data = &buf[..n];
trace!("USB IN: {:x}", data);
(*uart_pipe_writer).write(data).await;
}
}
/// Read from the USB TX pipe and write it to the USB
async fn usb_write<'d, T: Instance + 'd>(
usb_tx: &mut Sender<'d, Driver<'d, T>>,
usb_pipe_reader: &mut embassy_sync::pipe::Reader<'_, NoopRawMutex, 20>,
) -> Result<(), Disconnected> {
let mut buf = [0; 64];
loop {
let n = (*usb_pipe_reader).read(&mut buf).await;
let data = &buf[..n];
trace!("USB OUT: {:x}", data);
usb_tx.write_packet(&data).await?;
}
}
/// Read from the UART and write it to the USB TX pipe
async fn uart_read<PIO: pio::Instance, const SM: usize>(
uart_rx: &mut PioUartRx<'_, PIO, SM>,
usb_pipe_writer: &mut embassy_sync::pipe::Writer<'_, NoopRawMutex, 20>,
) -> ! {
let mut buf = [0; 64];
loop {
let n = uart_rx.read(&mut buf).await.expect("UART read error");
if n == 0 {
continue;
}
let data = &buf[..n];
trace!("UART IN: {:x}", buf);
(*usb_pipe_writer).write(data).await;
}
}
/// Read from the UART TX pipe and write it to the UART
async fn uart_write<PIO: pio::Instance, const SM: usize>(
uart_tx: &mut PioUartTx<'_, PIO, SM>,
uart_pipe_reader: &mut embassy_sync::pipe::Reader<'_, NoopRawMutex, 20>,
) -> ! {
let mut buf = [0; 64];
loop {
let n = (*uart_pipe_reader).read(&mut buf).await;
let data = &buf[..n];
trace!("UART OUT: {:x}", data);
let _ = uart_tx.write(&data).await;
}
}

View File

@ -6,16 +6,12 @@
use defmt::*;
use embassy_executor::Spawner;
use embassy_rp::bind_interrupts;
use embassy_rp::block::ImageDef;
use embassy_rp::dma::{AnyChannel, Channel};
use embassy_rp::peripherals::PIO0;
use embassy_rp::pio::{
Common, Config, FifoJoin, Instance, InterruptHandler, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine,
};
use embassy_rp::{bind_interrupts, clocks, into_ref, Peripheral, PeripheralRef};
use embassy_time::{Duration, Ticker, Timer};
use fixed::types::U24F8;
use fixed_macro::fixed;
use embassy_rp::pio::{InterruptHandler, Pio};
use embassy_rp::pio_programs::ws2812::{PioWs2812, PioWs2812Program};
use embassy_time::{Duration, Ticker};
use smart_leds::RGB8;
use {defmt_rtt as _, panic_probe as _};
@ -27,96 +23,6 @@ bind_interrupts!(struct Irqs {
PIO0_IRQ_0 => InterruptHandler<PIO0>;
});
pub struct Ws2812<'d, P: Instance, const S: usize, const N: usize> {
dma: PeripheralRef<'d, AnyChannel>,
sm: StateMachine<'d, P, S>,
}
impl<'d, P: Instance, const S: usize, const N: usize> Ws2812<'d, P, S, N> {
pub fn new(
pio: &mut Common<'d, P>,
mut sm: StateMachine<'d, P, S>,
dma: impl Peripheral<P = impl Channel> + 'd,
pin: impl PioPin,
) -> Self {
into_ref!(dma);
// 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);
cfg.set_out_pins(&[&out_pin]);
cfg.set_set_pins(&[&out_pin]);
cfg.use_program(&pio.load_program(&prg), &[&out_pin]);
// Clock config, measured in kHz to avoid overflows
// TODO CLOCK_FREQ should come from embassy_rp
let clock_freq = U24F8::from_num(clocks::clk_sys_freq() / 1000);
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 {
dma: dma.map_into(),
sm,
}
}
pub async fn write(&mut self, colors: &[RGB8; N]) {
// Precompute the word bytes from the colors
let mut words = [0u32; N];
for i in 0..N {
let word = (u32::from(colors[i].g) << 24) | (u32::from(colors[i].r) << 16) | (u32::from(colors[i].b) << 8);
words[i] = word;
}
// DMA transfer
self.sm.tx().dma_push(self.dma.reborrow(), &words).await;
Timer::after_micros(55).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 {
@ -147,7 +53,8 @@ async fn main(_spawner: Spawner) {
// Common neopixel pins:
// Thing plus: 8
// Adafruit Feather: 16; Adafruit Feather+RFM95: 4
let mut ws2812 = Ws2812::new(&mut common, sm0, p.DMA_CH0, p.PIN_16);
let program = PioWs2812Program::new(&mut common);
let mut ws2812 = PioWs2812::new(&mut common, sm0, p.DMA_CH0, p.PIN_16, &program);
// Loop forever making RGB values and pushing them out to the WS2812.
let mut ticker = Ticker::every(Duration::from_millis(10));