mirror of
https://github.com/embassy-rs/embassy.git
synced 2024-11-21 14:22:33 +00:00
Move pio programs into embassy-rp
This commit is contained in:
parent
456c226b29
commit
57c1fbf308
@ -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"] }
|
||||
|
@ -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;
|
||||
|
203
embassy-rp/src/pio_programs/hd44780.rs
Normal file
203
embassy-rp/src/pio_programs/hd44780.rs
Normal 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;
|
||||
}
|
||||
}
|
97
embassy-rp/src/pio_programs/i2s.rs
Normal file
97
embassy-rp/src/pio_programs/i2s.rs
Normal file
@ -0,0 +1,97 @@
|
||||
//! Pio backed I2s output
|
||||
|
||||
use crate::{
|
||||
dma::{AnyChannel, Channel, Transfer},
|
||||
into_ref,
|
||||
pio::{
|
||||
Common, Config, Direction, FifoJoin, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine,
|
||||
},
|
||||
Peripheral, PeripheralRef,
|
||||
};
|
||||
use fixed::traits::ToFixed;
|
||||
|
||||
/// 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)
|
||||
}
|
||||
}
|
10
embassy-rp/src/pio_programs/mod.rs
Normal file
10
embassy-rp/src/pio_programs/mod.rs
Normal 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;
|
109
embassy-rp/src/pio_programs/onewire.rs
Normal file
109
embassy-rp/src/pio_programs/onewire.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
114
embassy-rp/src/pio_programs/pwm.rs
Normal file
114
embassy-rp/src/pio_programs/pwm.rs
Normal file
@ -0,0 +1,114 @@
|
||||
//! PIO backed PWM driver
|
||||
|
||||
use core::time::Duration;
|
||||
|
||||
use crate::{
|
||||
clocks,
|
||||
gpio::Level,
|
||||
pio::{Common, Config, Direction, Instance, LoadedProgram, PioPin, StateMachine},
|
||||
};
|
||||
use pio::InstructionOperands;
|
||||
|
||||
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>,
|
||||
}
|
||||
|
||||
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 }
|
||||
}
|
||||
|
||||
/// Enable PWM output
|
||||
pub fn start(&mut self) {
|
||||
self.sm.set_enable(true);
|
||||
}
|
||||
|
||||
/// Disable PWM output
|
||||
pub fn stop(&mut self) {
|
||||
self.sm.set_enable(false);
|
||||
}
|
||||
|
||||
/// Set pwm period
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
72
embassy-rp/src/pio_programs/rotary_encoder.rs
Normal file
72
embassy-rp/src/pio_programs/rotary_encoder.rs
Normal file
@ -0,0 +1,72 @@
|
||||
//! PIO backed quadrature encoder
|
||||
|
||||
use crate::gpio::Pull;
|
||||
use crate::pio::{self, Common, Config, FifoJoin, Instance, LoadedProgram, PioPin, ShiftDirection, StateMachine};
|
||||
use fixed::traits::ToFixed;
|
||||
|
||||
/// 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,
|
||||
}
|
146
embassy-rp/src/pio_programs/stepper.rs
Normal file
146
embassy-rp/src/pio_programs/stepper.rs
Normal file
@ -0,0 +1,146 @@
|
||||
//! Pio Stepper Driver for 5-wire steppers
|
||||
|
||||
use core::mem::{self, MaybeUninit};
|
||||
|
||||
use crate::pio::{Common, Config, Direction, Instance, Irq, LoadedProgram, PioPin, StateMachine};
|
||||
use fixed::traits::ToFixed;
|
||||
use fixed::types::extra::U8;
|
||||
use fixed::FixedU32;
|
||||
|
||||
/// 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()() }
|
||||
}
|
||||
}
|
186
embassy-rp/src/pio_programs/uart.rs
Normal file
186
embassy-rp/src/pio_programs/uart.rs
Normal file
@ -0,0 +1,186 @@
|
||||
//! Pio backed uart drivers
|
||||
|
||||
use crate::{
|
||||
clocks::clk_sys_freq,
|
||||
gpio::Level,
|
||||
pio::{
|
||||
Common, Config, Direction as PioDirection, FifoJoin, Instance, LoadedProgram, PioPin, ShiftDirection,
|
||||
StateMachine,
|
||||
},
|
||||
};
|
||||
use core::convert::Infallible;
|
||||
use embedded_io_async::{ErrorType, Read, Write};
|
||||
use fixed::traits::ToFixed;
|
||||
|
||||
/// 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)
|
||||
}
|
||||
}
|
118
embassy-rp/src/pio_programs/ws2812.rs
Normal file
118
embassy-rp/src/pio_programs/ws2812.rs
Normal file
@ -0,0 +1,118 @@
|
||||
//! [ws2812](https://www.sparkfun.com/datasheets/LCD/HD44780.pdf)
|
||||
|
||||
use crate::{
|
||||
clocks::clk_sys_freq,
|
||||
dma::{AnyChannel, Channel},
|
||||
into_ref,
|
||||
pio::{Common, Config, FifoJoin, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine},
|
||||
Peripheral, PeripheralRef,
|
||||
};
|
||||
use embassy_time::Timer;
|
||||
use fixed::types::U24F8;
|
||||
use smart_leds::RGB8;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@ -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"
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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 };
|
||||
|
@ -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(()),
|
||||
|
@ -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();
|
||||
|
||||
|
@ -5,70 +5,20 @@
|
||||
|
||||
use defmt::info;
|
||||
use embassy_executor::Spawner;
|
||||
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::{
|
||||
bind_interrupts,
|
||||
pio::{InterruptHandler, Pio},
|
||||
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 +28,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));
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -15,7 +15,8 @@ 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;
|
||||
use embassy_rp::pio_programs::uart::{PioUartRx, PioUartRxProgram, PioUartTx, PioUartTxProgram};
|
||||
use embassy_rp::usb::{Driver, Instance, InterruptHandler};
|
||||
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
||||
use embassy_sync::pipe::Pipe;
|
||||
@ -25,13 +26,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 +84,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 +169,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 +186,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 +198,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -13,11 +13,11 @@
|
||||
use core::mem;
|
||||
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_rp::bind_interrupts;
|
||||
use embassy_rp::block::ImageDef;
|
||||
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,61 +30,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 p = embassy_rp::init(Default::default());
|
||||
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
|
||||
@ -95,20 +66,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 p.BOOTSEL.is_pressed() { i32::MAX } else { 0 };
|
||||
|
||||
// fill back buffer with fresh audio samples before awaiting the dma future
|
||||
for s in back_buffer.iter_mut() {
|
||||
|
88
examples/rp23/src/bin/pio_onewire.rs
Normal file
88
examples/rp23/src/bin/pio_onewire.rs
Normal 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(()),
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
||||
|
@ -6,11 +6,12 @@
|
||||
use defmt::info;
|
||||
use embassy_executor::Spawner;
|
||||
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::{
|
||||
bind_interrupts,
|
||||
pio::{InterruptHandler, Pio},
|
||||
pio_programs::rotary_encoder::{Direction, PioEncoder, PioEncoderProgram},
|
||||
};
|
||||
use {defmt_rtt as _, panic_probe as _};
|
||||
|
||||
#[link_section = ".start_block"]
|
||||
@ -21,59 +22,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 +33,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));
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
|
203
examples/rp23/src/bin/pio_uart.rs
Normal file
203
examples/rp23/src/bin/pio_uart.rs
Normal file
@ -0,0 +1,203 @@
|
||||
//! 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::bind_interrupts;
|
||||
use embassy_rp::block::ImageDef;
|
||||
use embassy_rp::peripherals::{PIO0, USB};
|
||||
use embassy_rp::pio;
|
||||
use embassy_rp::pio_programs::uart::{PioUartRx, PioUartRxProgram, PioUartTx, PioUartTxProgram};
|
||||
use embassy_rp::usb::{Driver, Instance, InterruptHandler};
|
||||
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;
|
||||
}
|
||||
}
|
@ -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));
|
||||
|
Loading…
Reference in New Issue
Block a user