mirror of
https://github.com/embassy-rs/embassy.git
synced 2024-11-21 22:32:29 +00:00
Merge pull request #3407 from CBJamo/pio_programs
rp: Move pio programs into embassy-rp
This commit is contained in:
commit
eea08d761d
@ -144,6 +144,7 @@ rp2040-boot2 = "0.3"
|
|||||||
document-features = "0.2.7"
|
document-features = "0.2.7"
|
||||||
sha2-const-stable = "0.1"
|
sha2-const-stable = "0.1"
|
||||||
rp-binary-info = { version = "0.1.0", optional = true }
|
rp-binary-info = { version = "0.1.0", optional = true }
|
||||||
|
smart-leds = "0.4.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
embassy-executor = { version = "0.6.0", path = "../embassy-executor", features = ["arch-std", "executor-thread"] }
|
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;
|
pub mod multicore;
|
||||||
#[cfg(feature = "_rp235x")]
|
#[cfg(feature = "_rp235x")]
|
||||||
pub mod otp;
|
pub mod otp;
|
||||||
|
pub mod pio_programs;
|
||||||
pub mod pwm;
|
pub mod pwm;
|
||||||
mod reset;
|
mod reset;
|
||||||
pub mod rom_data;
|
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;
|
||||||
|
}
|
||||||
|
}
|
95
embassy-rp/src/pio_programs/i2s.rs
Normal file
95
embassy-rp/src/pio_programs/i2s.rs
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
//! Pio backed I2s output
|
||||||
|
|
||||||
|
use fixed::traits::ToFixed;
|
||||||
|
|
||||||
|
use crate::dma::{AnyChannel, Channel, Transfer};
|
||||||
|
use crate::pio::{
|
||||||
|
Common, Config, Direction, FifoJoin, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine,
|
||||||
|
};
|
||||||
|
use crate::{into_ref, Peripheral, PeripheralRef};
|
||||||
|
|
||||||
|
/// This struct represents an i2s output driver program
|
||||||
|
pub struct PioI2sOutProgram<'a, PIO: Instance> {
|
||||||
|
prg: LoadedProgram<'a, PIO>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, PIO: Instance> PioI2sOutProgram<'a, PIO> {
|
||||||
|
/// Load the program into the given pio
|
||||||
|
pub fn new(common: &mut Common<'a, PIO>) -> Self {
|
||||||
|
let prg = pio_proc::pio_asm!(
|
||||||
|
".side_set 2",
|
||||||
|
" set x, 14 side 0b01", // side 0bWB - W = Word Clock, B = Bit Clock
|
||||||
|
"left_data:",
|
||||||
|
" out pins, 1 side 0b00",
|
||||||
|
" jmp x-- left_data side 0b01",
|
||||||
|
" out pins 1 side 0b10",
|
||||||
|
" set x, 14 side 0b11",
|
||||||
|
"right_data:",
|
||||||
|
" out pins 1 side 0b10",
|
||||||
|
" jmp x-- right_data side 0b11",
|
||||||
|
" out pins 1 side 0b00",
|
||||||
|
);
|
||||||
|
|
||||||
|
let prg = common.load_program(&prg.program);
|
||||||
|
|
||||||
|
Self { prg }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pio backed I2s output driver
|
||||||
|
pub struct PioI2sOut<'a, P: Instance, const S: usize> {
|
||||||
|
dma: PeripheralRef<'a, AnyChannel>,
|
||||||
|
sm: StateMachine<'a, P, S>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, P: Instance, const S: usize> PioI2sOut<'a, P, S> {
|
||||||
|
/// Configure a state machine to output I2s
|
||||||
|
pub fn new(
|
||||||
|
common: &mut Common<'a, P>,
|
||||||
|
mut sm: StateMachine<'a, P, S>,
|
||||||
|
dma: impl Peripheral<P = impl Channel> + 'a,
|
||||||
|
data_pin: impl PioPin,
|
||||||
|
bit_clock_pin: impl PioPin,
|
||||||
|
lr_clock_pin: impl PioPin,
|
||||||
|
sample_rate: u32,
|
||||||
|
bit_depth: u32,
|
||||||
|
channels: u32,
|
||||||
|
program: &PioI2sOutProgram<'a, P>,
|
||||||
|
) -> Self {
|
||||||
|
into_ref!(dma);
|
||||||
|
|
||||||
|
let data_pin = common.make_pio_pin(data_pin);
|
||||||
|
let bit_clock_pin = common.make_pio_pin(bit_clock_pin);
|
||||||
|
let left_right_clock_pin = common.make_pio_pin(lr_clock_pin);
|
||||||
|
|
||||||
|
let cfg = {
|
||||||
|
let mut cfg = Config::default();
|
||||||
|
cfg.use_program(&program.prg, &[&bit_clock_pin, &left_right_clock_pin]);
|
||||||
|
cfg.set_out_pins(&[&data_pin]);
|
||||||
|
let clock_frequency = sample_rate * bit_depth * channels;
|
||||||
|
cfg.clock_divider = (125_000_000. / clock_frequency as f64 / 2.).to_fixed();
|
||||||
|
cfg.shift_out = ShiftConfig {
|
||||||
|
threshold: 32,
|
||||||
|
direction: ShiftDirection::Left,
|
||||||
|
auto_fill: true,
|
||||||
|
};
|
||||||
|
// join fifos to have twice the time to start the next dma transfer
|
||||||
|
cfg.fifo_join = FifoJoin::TxOnly;
|
||||||
|
cfg
|
||||||
|
};
|
||||||
|
sm.set_config(&cfg);
|
||||||
|
sm.set_pin_dirs(Direction::Out, &[&data_pin, &left_right_clock_pin, &bit_clock_pin]);
|
||||||
|
|
||||||
|
sm.set_enable(true);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
dma: dma.map_into(),
|
||||||
|
sm,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return an in-prograss dma transfer future. Awaiting it will guarentee a complete transfer.
|
||||||
|
pub fn write<'b>(&'b mut self, buff: &'b [u32]) -> Transfer<'b, AnyChannel> {
|
||||||
|
self.sm.tx().dma_push(self.dma.reborrow(), buff)
|
||||||
|
}
|
||||||
|
}
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
121
embassy-rp/src/pio_programs/pwm.rs
Normal file
121
embassy-rp/src/pio_programs/pwm.rs
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
//! PIO backed PWM driver
|
||||||
|
|
||||||
|
use core::time::Duration;
|
||||||
|
|
||||||
|
use pio::InstructionOperands;
|
||||||
|
|
||||||
|
use crate::clocks;
|
||||||
|
use crate::gpio::Level;
|
||||||
|
use crate::pio::{Common, Config, Direction, Instance, LoadedProgram, Pin, PioPin, StateMachine};
|
||||||
|
|
||||||
|
/// This converts the duration provided into the number of cycles the PIO needs to run to make it take the same time
|
||||||
|
fn to_pio_cycles(duration: Duration) -> u32 {
|
||||||
|
(clocks::clk_sys_freq() / 1_000_000) / 3 * duration.as_micros() as u32 // parentheses are required to prevent overflow
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This struct represents a PWM program loaded into pio instruction memory.
|
||||||
|
pub struct PioPwmProgram<'a, PIO: Instance> {
|
||||||
|
prg: LoadedProgram<'a, PIO>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, PIO: Instance> PioPwmProgram<'a, PIO> {
|
||||||
|
/// Load the program into the given pio
|
||||||
|
pub fn new(common: &mut Common<'a, PIO>) -> Self {
|
||||||
|
let prg = pio_proc::pio_asm!(
|
||||||
|
".side_set 1 opt"
|
||||||
|
"pull noblock side 0"
|
||||||
|
"mov x, osr"
|
||||||
|
"mov y, isr"
|
||||||
|
"countloop:"
|
||||||
|
"jmp x!=y noset"
|
||||||
|
"jmp skip side 1"
|
||||||
|
"noset:"
|
||||||
|
"nop"
|
||||||
|
"skip:"
|
||||||
|
"jmp y-- countloop"
|
||||||
|
);
|
||||||
|
|
||||||
|
let prg = common.load_program(&prg.program);
|
||||||
|
|
||||||
|
Self { prg }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pio backed PWM output
|
||||||
|
pub struct PioPwm<'d, T: Instance, const SM: usize> {
|
||||||
|
sm: StateMachine<'d, T, SM>,
|
||||||
|
pin: Pin<'d, T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'d, T: Instance, const SM: usize> PioPwm<'d, T, SM> {
|
||||||
|
/// Configure a state machine as a PWM output
|
||||||
|
pub fn new(
|
||||||
|
pio: &mut Common<'d, T>,
|
||||||
|
mut sm: StateMachine<'d, T, SM>,
|
||||||
|
pin: impl PioPin,
|
||||||
|
program: &PioPwmProgram<'d, T>,
|
||||||
|
) -> Self {
|
||||||
|
let pin = pio.make_pio_pin(pin);
|
||||||
|
sm.set_pins(Level::High, &[&pin]);
|
||||||
|
sm.set_pin_dirs(Direction::Out, &[&pin]);
|
||||||
|
|
||||||
|
let mut cfg = Config::default();
|
||||||
|
cfg.use_program(&program.prg, &[&pin]);
|
||||||
|
|
||||||
|
sm.set_config(&cfg);
|
||||||
|
|
||||||
|
Self { sm, pin }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enable's the PIO program, continuing the wave generation from the PIO program.
|
||||||
|
pub fn start(&mut self) {
|
||||||
|
self.sm.set_enable(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stops the PIO program, ceasing all signals from the PIN that were generated via PIO.
|
||||||
|
pub fn stop(&mut self) {
|
||||||
|
self.sm.set_enable(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the pwm period, which is the length of time for each pio wave until reset.
|
||||||
|
pub fn set_period(&mut self, duration: Duration) {
|
||||||
|
let is_enabled = self.sm.is_enabled();
|
||||||
|
while !self.sm.tx().empty() {} // Make sure that the queue is empty
|
||||||
|
self.sm.set_enable(false);
|
||||||
|
self.sm.tx().push(to_pio_cycles(duration));
|
||||||
|
unsafe {
|
||||||
|
self.sm.exec_instr(
|
||||||
|
InstructionOperands::PULL {
|
||||||
|
if_empty: false,
|
||||||
|
block: false,
|
||||||
|
}
|
||||||
|
.encode(),
|
||||||
|
);
|
||||||
|
self.sm.exec_instr(
|
||||||
|
InstructionOperands::OUT {
|
||||||
|
destination: ::pio::OutDestination::ISR,
|
||||||
|
bit_count: 32,
|
||||||
|
}
|
||||||
|
.encode(),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
if is_enabled {
|
||||||
|
self.sm.set_enable(true) // Enable if previously enabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the number of pio cycles to set the wave on high to.
|
||||||
|
pub fn set_level(&mut self, level: u32) {
|
||||||
|
self.sm.tx().push(level);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the pulse width high time
|
||||||
|
pub fn write(&mut self, duration: Duration) {
|
||||||
|
self.set_level(to_pio_cycles(duration));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the state machine and pin.
|
||||||
|
pub fn release(self) -> (StateMachine<'d, T, SM>, Pin<'d, T>) {
|
||||||
|
(self.sm, self.pin)
|
||||||
|
}
|
||||||
|
}
|
73
embassy-rp/src/pio_programs/rotary_encoder.rs
Normal file
73
embassy-rp/src/pio_programs/rotary_encoder.rs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
//! PIO backed quadrature encoder
|
||||||
|
|
||||||
|
use fixed::traits::ToFixed;
|
||||||
|
|
||||||
|
use crate::gpio::Pull;
|
||||||
|
use crate::pio::{self, Common, Config, FifoJoin, Instance, LoadedProgram, PioPin, ShiftDirection, StateMachine};
|
||||||
|
|
||||||
|
/// This struct represents an Encoder program loaded into pio instruction memory.
|
||||||
|
pub struct PioEncoderProgram<'a, PIO: Instance> {
|
||||||
|
prg: LoadedProgram<'a, PIO>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, PIO: Instance> PioEncoderProgram<'a, PIO> {
|
||||||
|
/// Load the program into the given pio
|
||||||
|
pub fn new(common: &mut Common<'a, PIO>) -> Self {
|
||||||
|
let prg = pio_proc::pio_asm!("wait 1 pin 1", "wait 0 pin 1", "in pins, 2", "push",);
|
||||||
|
|
||||||
|
let prg = common.load_program(&prg.program);
|
||||||
|
|
||||||
|
Self { prg }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pio Backed quadrature encoder reader
|
||||||
|
pub struct PioEncoder<'d, T: Instance, const SM: usize> {
|
||||||
|
sm: StateMachine<'d, T, SM>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'d, T: Instance, const SM: usize> PioEncoder<'d, T, SM> {
|
||||||
|
/// Configure a state machine with the loaded [PioEncoderProgram]
|
||||||
|
pub fn new(
|
||||||
|
pio: &mut Common<'d, T>,
|
||||||
|
mut sm: StateMachine<'d, T, SM>,
|
||||||
|
pin_a: impl PioPin,
|
||||||
|
pin_b: impl PioPin,
|
||||||
|
program: &PioEncoderProgram<'d, T>,
|
||||||
|
) -> Self {
|
||||||
|
let mut pin_a = pio.make_pio_pin(pin_a);
|
||||||
|
let mut pin_b = pio.make_pio_pin(pin_b);
|
||||||
|
pin_a.set_pull(Pull::Up);
|
||||||
|
pin_b.set_pull(Pull::Up);
|
||||||
|
sm.set_pin_dirs(pio::Direction::In, &[&pin_a, &pin_b]);
|
||||||
|
|
||||||
|
let mut cfg = Config::default();
|
||||||
|
cfg.set_in_pins(&[&pin_a, &pin_b]);
|
||||||
|
cfg.fifo_join = FifoJoin::RxOnly;
|
||||||
|
cfg.shift_in.direction = ShiftDirection::Left;
|
||||||
|
cfg.clock_divider = 10_000.to_fixed();
|
||||||
|
cfg.use_program(&program.prg, &[]);
|
||||||
|
sm.set_config(&cfg);
|
||||||
|
sm.set_enable(true);
|
||||||
|
Self { sm }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read a single count from the encoder
|
||||||
|
pub async fn read(&mut self) -> Direction {
|
||||||
|
loop {
|
||||||
|
match self.sm.rx().wait_pull().await {
|
||||||
|
0 => return Direction::CounterClockwise,
|
||||||
|
1 => return Direction::Clockwise,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encoder Count Direction
|
||||||
|
pub enum Direction {
|
||||||
|
/// Encoder turned clockwise
|
||||||
|
Clockwise,
|
||||||
|
/// Encoder turned counter clockwise
|
||||||
|
CounterClockwise,
|
||||||
|
}
|
147
embassy-rp/src/pio_programs/stepper.rs
Normal file
147
embassy-rp/src/pio_programs/stepper.rs
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
//! Pio Stepper Driver for 5-wire steppers
|
||||||
|
|
||||||
|
use core::mem::{self, MaybeUninit};
|
||||||
|
|
||||||
|
use fixed::traits::ToFixed;
|
||||||
|
use fixed::types::extra::U8;
|
||||||
|
use fixed::FixedU32;
|
||||||
|
|
||||||
|
use crate::pio::{Common, Config, Direction, Instance, Irq, LoadedProgram, PioPin, StateMachine};
|
||||||
|
|
||||||
|
/// This struct represents a Stepper driver program loaded into pio instruction memory.
|
||||||
|
pub struct PioStepperProgram<'a, PIO: Instance> {
|
||||||
|
prg: LoadedProgram<'a, PIO>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, PIO: Instance> PioStepperProgram<'a, PIO> {
|
||||||
|
/// Load the program into the given pio
|
||||||
|
pub fn new(common: &mut Common<'a, PIO>) -> Self {
|
||||||
|
let prg = pio_proc::pio_asm!(
|
||||||
|
"pull block",
|
||||||
|
"mov x, osr",
|
||||||
|
"pull block",
|
||||||
|
"mov y, osr",
|
||||||
|
"jmp !x end",
|
||||||
|
"loop:",
|
||||||
|
"jmp !osre step",
|
||||||
|
"mov osr, y",
|
||||||
|
"step:",
|
||||||
|
"out pins, 4 [31]"
|
||||||
|
"jmp x-- loop",
|
||||||
|
"end:",
|
||||||
|
"irq 0 rel"
|
||||||
|
);
|
||||||
|
|
||||||
|
let prg = common.load_program(&prg.program);
|
||||||
|
|
||||||
|
Self { prg }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pio backed Stepper driver
|
||||||
|
pub struct PioStepper<'d, T: Instance, const SM: usize> {
|
||||||
|
irq: Irq<'d, T, SM>,
|
||||||
|
sm: StateMachine<'d, T, SM>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'d, T: Instance, const SM: usize> PioStepper<'d, T, SM> {
|
||||||
|
/// Configure a state machine to drive a stepper
|
||||||
|
pub fn new(
|
||||||
|
pio: &mut Common<'d, T>,
|
||||||
|
mut sm: StateMachine<'d, T, SM>,
|
||||||
|
irq: Irq<'d, T, SM>,
|
||||||
|
pin0: impl PioPin,
|
||||||
|
pin1: impl PioPin,
|
||||||
|
pin2: impl PioPin,
|
||||||
|
pin3: impl PioPin,
|
||||||
|
program: &PioStepperProgram<'d, T>,
|
||||||
|
) -> Self {
|
||||||
|
let pin0 = pio.make_pio_pin(pin0);
|
||||||
|
let pin1 = pio.make_pio_pin(pin1);
|
||||||
|
let pin2 = pio.make_pio_pin(pin2);
|
||||||
|
let pin3 = pio.make_pio_pin(pin3);
|
||||||
|
sm.set_pin_dirs(Direction::Out, &[&pin0, &pin1, &pin2, &pin3]);
|
||||||
|
let mut cfg = Config::default();
|
||||||
|
cfg.set_out_pins(&[&pin0, &pin1, &pin2, &pin3]);
|
||||||
|
cfg.clock_divider = (125_000_000 / (100 * 136)).to_fixed();
|
||||||
|
cfg.use_program(&program.prg, &[]);
|
||||||
|
sm.set_config(&cfg);
|
||||||
|
sm.set_enable(true);
|
||||||
|
Self { irq, sm }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set pulse frequency
|
||||||
|
pub fn set_frequency(&mut self, freq: u32) {
|
||||||
|
let clock_divider: FixedU32<U8> = (125_000_000 / (freq * 136)).to_fixed();
|
||||||
|
assert!(clock_divider <= 65536, "clkdiv must be <= 65536");
|
||||||
|
assert!(clock_divider >= 1, "clkdiv must be >= 1");
|
||||||
|
self.sm.set_clock_divider(clock_divider);
|
||||||
|
self.sm.clkdiv_restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Full step, one phase
|
||||||
|
pub async fn step(&mut self, steps: i32) {
|
||||||
|
if steps > 0 {
|
||||||
|
self.run(steps, 0b1000_0100_0010_0001_1000_0100_0010_0001).await
|
||||||
|
} else {
|
||||||
|
self.run(-steps, 0b0001_0010_0100_1000_0001_0010_0100_1000).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Full step, two phase
|
||||||
|
pub async fn step2(&mut self, steps: i32) {
|
||||||
|
if steps > 0 {
|
||||||
|
self.run(steps, 0b1001_1100_0110_0011_1001_1100_0110_0011).await
|
||||||
|
} else {
|
||||||
|
self.run(-steps, 0b0011_0110_1100_1001_0011_0110_1100_1001).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Half step
|
||||||
|
pub async fn step_half(&mut self, steps: i32) {
|
||||||
|
if steps > 0 {
|
||||||
|
self.run(steps, 0b1001_1000_1100_0100_0110_0010_0011_0001).await
|
||||||
|
} else {
|
||||||
|
self.run(-steps, 0b0001_0011_0010_0110_0100_1100_1000_1001).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(&mut self, steps: i32, pattern: u32) {
|
||||||
|
self.sm.tx().wait_push(steps as u32).await;
|
||||||
|
self.sm.tx().wait_push(pattern).await;
|
||||||
|
let drop = OnDrop::new(|| {
|
||||||
|
self.sm.clear_fifos();
|
||||||
|
unsafe {
|
||||||
|
self.sm.exec_instr(
|
||||||
|
pio::InstructionOperands::JMP {
|
||||||
|
address: 0,
|
||||||
|
condition: pio::JmpCondition::Always,
|
||||||
|
}
|
||||||
|
.encode(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
self.irq.wait().await;
|
||||||
|
drop.defuse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct OnDrop<F: FnOnce()> {
|
||||||
|
f: MaybeUninit<F>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F: FnOnce()> OnDrop<F> {
|
||||||
|
pub fn new(f: F) -> Self {
|
||||||
|
Self { f: MaybeUninit::new(f) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn defuse(self) {
|
||||||
|
mem::forget(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F: FnOnce()> Drop for OnDrop<F> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe { self.f.as_ptr().read()() }
|
||||||
|
}
|
||||||
|
}
|
185
embassy-rp/src/pio_programs/uart.rs
Normal file
185
embassy-rp/src/pio_programs/uart.rs
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
//! Pio backed uart drivers
|
||||||
|
|
||||||
|
use core::convert::Infallible;
|
||||||
|
|
||||||
|
use embedded_io_async::{ErrorType, Read, Write};
|
||||||
|
use fixed::traits::ToFixed;
|
||||||
|
|
||||||
|
use crate::clocks::clk_sys_freq;
|
||||||
|
use crate::gpio::Level;
|
||||||
|
use crate::pio::{
|
||||||
|
Common, Config, Direction as PioDirection, FifoJoin, Instance, LoadedProgram, PioPin, ShiftDirection, StateMachine,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// This struct represents a uart tx program loaded into pio instruction memory.
|
||||||
|
pub struct PioUartTxProgram<'a, PIO: Instance> {
|
||||||
|
prg: LoadedProgram<'a, PIO>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, PIO: Instance> PioUartTxProgram<'a, PIO> {
|
||||||
|
/// Load the uart tx program into the given pio
|
||||||
|
pub fn new(common: &mut Common<'a, PIO>) -> Self {
|
||||||
|
let prg = pio_proc::pio_asm!(
|
||||||
|
r#"
|
||||||
|
.side_set 1 opt
|
||||||
|
|
||||||
|
; An 8n1 UART transmit program.
|
||||||
|
; OUT pin 0 and side-set pin 0 are both mapped to UART TX pin.
|
||||||
|
|
||||||
|
pull side 1 [7] ; Assert stop bit, or stall with line in idle state
|
||||||
|
set x, 7 side 0 [7] ; Preload bit counter, assert start bit for 8 clocks
|
||||||
|
bitloop: ; This loop will run 8 times (8n1 UART)
|
||||||
|
out pins, 1 ; Shift 1 bit from OSR to the first OUT pin
|
||||||
|
jmp x-- bitloop [6] ; Each loop iteration is 8 cycles.
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
|
||||||
|
let prg = common.load_program(&prg.program);
|
||||||
|
|
||||||
|
Self { prg }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// PIO backed Uart transmitter
|
||||||
|
pub struct PioUartTx<'a, PIO: Instance, const SM: usize> {
|
||||||
|
sm_tx: StateMachine<'a, PIO, SM>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, PIO: Instance, const SM: usize> PioUartTx<'a, PIO, SM> {
|
||||||
|
/// Configure a pio state machine to use the loaded tx program.
|
||||||
|
pub fn new(
|
||||||
|
baud: u32,
|
||||||
|
common: &mut Common<'a, PIO>,
|
||||||
|
mut sm_tx: StateMachine<'a, PIO, SM>,
|
||||||
|
tx_pin: impl PioPin,
|
||||||
|
program: &PioUartTxProgram<'a, PIO>,
|
||||||
|
) -> Self {
|
||||||
|
let tx_pin = common.make_pio_pin(tx_pin);
|
||||||
|
sm_tx.set_pins(Level::High, &[&tx_pin]);
|
||||||
|
sm_tx.set_pin_dirs(PioDirection::Out, &[&tx_pin]);
|
||||||
|
|
||||||
|
let mut cfg = Config::default();
|
||||||
|
|
||||||
|
cfg.set_out_pins(&[&tx_pin]);
|
||||||
|
cfg.use_program(&program.prg, &[&tx_pin]);
|
||||||
|
cfg.shift_out.auto_fill = false;
|
||||||
|
cfg.shift_out.direction = ShiftDirection::Right;
|
||||||
|
cfg.fifo_join = FifoJoin::TxOnly;
|
||||||
|
cfg.clock_divider = (clk_sys_freq() / (8 * baud)).to_fixed();
|
||||||
|
sm_tx.set_config(&cfg);
|
||||||
|
sm_tx.set_enable(true);
|
||||||
|
|
||||||
|
Self { sm_tx }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a single u8
|
||||||
|
pub async fn write_u8(&mut self, data: u8) {
|
||||||
|
self.sm_tx.tx().wait_push(data as u32).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<PIO: Instance, const SM: usize> ErrorType for PioUartTx<'_, PIO, SM> {
|
||||||
|
type Error = Infallible;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<PIO: Instance, const SM: usize> Write for PioUartTx<'_, PIO, SM> {
|
||||||
|
async fn write(&mut self, buf: &[u8]) -> Result<usize, Infallible> {
|
||||||
|
for byte in buf {
|
||||||
|
self.write_u8(*byte).await;
|
||||||
|
}
|
||||||
|
Ok(buf.len())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This struct represents a Uart Rx program loaded into pio instruction memory.
|
||||||
|
pub struct PioUartRxProgram<'a, PIO: Instance> {
|
||||||
|
prg: LoadedProgram<'a, PIO>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, PIO: Instance> PioUartRxProgram<'a, PIO> {
|
||||||
|
/// Load the uart rx program into the given pio
|
||||||
|
pub fn new(common: &mut Common<'a, PIO>) -> Self {
|
||||||
|
let prg = pio_proc::pio_asm!(
|
||||||
|
r#"
|
||||||
|
; Slightly more fleshed-out 8n1 UART receiver which handles framing errors and
|
||||||
|
; break conditions more gracefully.
|
||||||
|
; IN pin 0 and JMP pin are both mapped to the GPIO used as UART RX.
|
||||||
|
|
||||||
|
start:
|
||||||
|
wait 0 pin 0 ; Stall until start bit is asserted
|
||||||
|
set x, 7 [10] ; Preload bit counter, then delay until halfway through
|
||||||
|
rx_bitloop: ; the first data bit (12 cycles incl wait, set).
|
||||||
|
in pins, 1 ; Shift data bit into ISR
|
||||||
|
jmp x-- rx_bitloop [6] ; Loop 8 times, each loop iteration is 8 cycles
|
||||||
|
jmp pin good_rx_stop ; Check stop bit (should be high)
|
||||||
|
|
||||||
|
irq 4 rel ; Either a framing error or a break. Set a sticky flag,
|
||||||
|
wait 1 pin 0 ; and wait for line to return to idle state.
|
||||||
|
jmp start ; Don't push data if we didn't see good framing.
|
||||||
|
|
||||||
|
good_rx_stop: ; No delay before returning to start; a little slack is
|
||||||
|
in null 24
|
||||||
|
push ; important in case the TX clock is slightly too fast.
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
|
||||||
|
let prg = common.load_program(&prg.program);
|
||||||
|
|
||||||
|
Self { prg }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// PIO backed Uart reciever
|
||||||
|
pub struct PioUartRx<'a, PIO: Instance, const SM: usize> {
|
||||||
|
sm_rx: StateMachine<'a, PIO, SM>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, PIO: Instance, const SM: usize> PioUartRx<'a, PIO, SM> {
|
||||||
|
/// Configure a pio state machine to use the loaded rx program.
|
||||||
|
pub fn new(
|
||||||
|
baud: u32,
|
||||||
|
common: &mut Common<'a, PIO>,
|
||||||
|
mut sm_rx: StateMachine<'a, PIO, SM>,
|
||||||
|
rx_pin: impl PioPin,
|
||||||
|
program: &PioUartRxProgram<'a, PIO>,
|
||||||
|
) -> Self {
|
||||||
|
let mut cfg = Config::default();
|
||||||
|
cfg.use_program(&program.prg, &[]);
|
||||||
|
|
||||||
|
let rx_pin = common.make_pio_pin(rx_pin);
|
||||||
|
sm_rx.set_pins(Level::High, &[&rx_pin]);
|
||||||
|
cfg.set_in_pins(&[&rx_pin]);
|
||||||
|
cfg.set_jmp_pin(&rx_pin);
|
||||||
|
sm_rx.set_pin_dirs(PioDirection::In, &[&rx_pin]);
|
||||||
|
|
||||||
|
cfg.clock_divider = (clk_sys_freq() / (8 * baud)).to_fixed();
|
||||||
|
cfg.shift_in.auto_fill = false;
|
||||||
|
cfg.shift_in.direction = ShiftDirection::Right;
|
||||||
|
cfg.shift_in.threshold = 32;
|
||||||
|
cfg.fifo_join = FifoJoin::RxOnly;
|
||||||
|
sm_rx.set_config(&cfg);
|
||||||
|
sm_rx.set_enable(true);
|
||||||
|
|
||||||
|
Self { sm_rx }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wait for a single u8
|
||||||
|
pub async fn read_u8(&mut self) -> u8 {
|
||||||
|
self.sm_rx.rx().wait_pull().await as u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<PIO: Instance, const SM: usize> ErrorType for PioUartRx<'_, PIO, SM> {
|
||||||
|
type Error = Infallible;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<PIO: Instance, const SM: usize> Read for PioUartRx<'_, PIO, SM> {
|
||||||
|
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Infallible> {
|
||||||
|
let mut i = 0;
|
||||||
|
while i < buf.len() {
|
||||||
|
buf[i] = self.read_u8().await;
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
Ok(i)
|
||||||
|
}
|
||||||
|
}
|
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 embassy_time::Timer;
|
||||||
|
use fixed::types::U24F8;
|
||||||
|
use smart_leds::RGB8;
|
||||||
|
|
||||||
|
use crate::clocks::clk_sys_freq;
|
||||||
|
use crate::dma::{AnyChannel, Channel};
|
||||||
|
use crate::pio::{
|
||||||
|
Common, Config, FifoJoin, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine,
|
||||||
|
};
|
||||||
|
use crate::{into_ref, Peripheral, PeripheralRef};
|
||||||
|
|
||||||
|
const T1: u8 = 2; // start bit
|
||||||
|
const T2: u8 = 5; // data bit
|
||||||
|
const T3: u8 = 3; // stop bit
|
||||||
|
const CYCLES_PER_BIT: u32 = (T1 + T2 + T3) as u32;
|
||||||
|
|
||||||
|
/// This struct represents a ws2812 program loaded into pio instruction memory.
|
||||||
|
pub struct PioWs2812Program<'a, PIO: Instance> {
|
||||||
|
prg: LoadedProgram<'a, PIO>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, PIO: Instance> PioWs2812Program<'a, PIO> {
|
||||||
|
/// Load the ws2812 program into the given pio
|
||||||
|
pub fn new(common: &mut Common<'a, PIO>) -> Self {
|
||||||
|
let side_set = pio::SideSet::new(false, 1, false);
|
||||||
|
let mut a: pio::Assembler<32> = pio::Assembler::new_with_side_set(side_set);
|
||||||
|
|
||||||
|
let mut wrap_target = a.label();
|
||||||
|
let mut wrap_source = a.label();
|
||||||
|
let mut do_zero = a.label();
|
||||||
|
a.set_with_side_set(pio::SetDestination::PINDIRS, 1, 0);
|
||||||
|
a.bind(&mut wrap_target);
|
||||||
|
// Do stop bit
|
||||||
|
a.out_with_delay_and_side_set(pio::OutDestination::X, 1, T3 - 1, 0);
|
||||||
|
// Do start bit
|
||||||
|
a.jmp_with_delay_and_side_set(pio::JmpCondition::XIsZero, &mut do_zero, T1 - 1, 1);
|
||||||
|
// Do data bit = 1
|
||||||
|
a.jmp_with_delay_and_side_set(pio::JmpCondition::Always, &mut wrap_target, T2 - 1, 1);
|
||||||
|
a.bind(&mut do_zero);
|
||||||
|
// Do data bit = 0
|
||||||
|
a.nop_with_delay_and_side_set(T2 - 1, 0);
|
||||||
|
a.bind(&mut wrap_source);
|
||||||
|
|
||||||
|
let prg = a.assemble_with_wrap(wrap_source, wrap_target);
|
||||||
|
let prg = common.load_program(&prg);
|
||||||
|
|
||||||
|
Self { prg }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pio backed ws2812 driver
|
||||||
|
/// Const N is the number of ws2812 leds attached to this pin
|
||||||
|
pub struct PioWs2812<'d, P: Instance, const S: usize, const N: usize> {
|
||||||
|
dma: PeripheralRef<'d, AnyChannel>,
|
||||||
|
sm: StateMachine<'d, P, S>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'d, P: Instance, const S: usize, const N: usize> PioWs2812<'d, P, S, N> {
|
||||||
|
/// Configure a pio state machine to use the loaded ws2812 program.
|
||||||
|
pub fn new(
|
||||||
|
pio: &mut Common<'d, P>,
|
||||||
|
mut sm: StateMachine<'d, P, S>,
|
||||||
|
dma: impl Peripheral<P = impl Channel> + 'd,
|
||||||
|
pin: impl PioPin,
|
||||||
|
program: &PioWs2812Program<'d, P>,
|
||||||
|
) -> Self {
|
||||||
|
into_ref!(dma);
|
||||||
|
|
||||||
|
// Setup sm0
|
||||||
|
let mut cfg = Config::default();
|
||||||
|
|
||||||
|
// Pin config
|
||||||
|
let out_pin = pio.make_pio_pin(pin);
|
||||||
|
cfg.set_out_pins(&[&out_pin]);
|
||||||
|
cfg.set_set_pins(&[&out_pin]);
|
||||||
|
|
||||||
|
cfg.use_program(&program.prg, &[&out_pin]);
|
||||||
|
|
||||||
|
// Clock config, measured in kHz to avoid overflows
|
||||||
|
let clock_freq = U24F8::from_num(clk_sys_freq() / 1000);
|
||||||
|
let ws2812_freq = U24F8::from_num(800);
|
||||||
|
let bit_freq = ws2812_freq * CYCLES_PER_BIT;
|
||||||
|
cfg.clock_divider = clock_freq / bit_freq;
|
||||||
|
|
||||||
|
// FIFO config
|
||||||
|
cfg.fifo_join = FifoJoin::TxOnly;
|
||||||
|
cfg.shift_out = ShiftConfig {
|
||||||
|
auto_fill: true,
|
||||||
|
threshold: 24,
|
||||||
|
direction: ShiftDirection::Left,
|
||||||
|
};
|
||||||
|
|
||||||
|
sm.set_config(&cfg);
|
||||||
|
sm.set_enable(true);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
dma: dma.map_into(),
|
||||||
|
sm,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a buffer of [smart_leds::RGB8] to the ws2812 string
|
||||||
|
pub async fn write(&mut self, colors: &[RGB8; N]) {
|
||||||
|
// Precompute the word bytes from the colors
|
||||||
|
let mut words = [0u32; N];
|
||||||
|
for i in 0..N {
|
||||||
|
let word = (u32::from(colors[i].g) << 24) | (u32::from(colors[i].r) << 16) | (u32::from(colors[i].b) << 8);
|
||||||
|
words[i] = word;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DMA transfer
|
||||||
|
self.sm.tx().dma_push(self.dma.reborrow(), &words).await;
|
||||||
|
|
||||||
|
Timer::after_micros(55).await;
|
||||||
|
}
|
||||||
|
}
|
@ -42,7 +42,7 @@ embedded-graphics = "0.7.1"
|
|||||||
st7789 = "0.6.1"
|
st7789 = "0.6.1"
|
||||||
display-interface = "0.4.1"
|
display-interface = "0.4.1"
|
||||||
byte-slice-cast = { version = "1.2.0", default-features = false }
|
byte-slice-cast = { version = "1.2.0", default-features = false }
|
||||||
smart-leds = "0.3.0"
|
smart-leds = "0.4.0"
|
||||||
heapless = "0.8"
|
heapless = "0.8"
|
||||||
usbd-hid = "0.8.1"
|
usbd-hid = "0.8.1"
|
||||||
|
|
||||||
|
@ -7,13 +7,11 @@
|
|||||||
use core::fmt::Write;
|
use core::fmt::Write;
|
||||||
|
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
use embassy_rp::dma::{AnyChannel, Channel};
|
use embassy_rp::bind_interrupts;
|
||||||
use embassy_rp::peripherals::PIO0;
|
use embassy_rp::peripherals::PIO0;
|
||||||
use embassy_rp::pio::{
|
use embassy_rp::pio::{InterruptHandler, Pio};
|
||||||
Config, Direction, FifoJoin, InterruptHandler, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine,
|
use embassy_rp::pio_programs::hd44780::{PioHD44780, PioHD44780CommandSequenceProgram, PioHD44780CommandWordProgram};
|
||||||
};
|
|
||||||
use embassy_rp::pwm::{self, Pwm};
|
use embassy_rp::pwm::{self, Pwm};
|
||||||
use embassy_rp::{bind_interrupts, into_ref, Peripheral, PeripheralRef};
|
|
||||||
use embassy_time::{Instant, Timer};
|
use embassy_time::{Instant, Timer};
|
||||||
use {defmt_rtt as _, panic_probe as _};
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
|
|
||||||
@ -43,8 +41,27 @@ async fn main(_spawner: Spawner) {
|
|||||||
c
|
c
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut hd = HD44780::new(
|
let Pio {
|
||||||
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,
|
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;
|
.await;
|
||||||
|
|
||||||
@ -68,173 +85,3 @@ async fn main(_spawner: Spawner) {
|
|||||||
Timer::after_secs(1).await;
|
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 core::mem;
|
||||||
|
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
|
use embassy_rp::bind_interrupts;
|
||||||
use embassy_rp::peripherals::PIO0;
|
use embassy_rp::peripherals::PIO0;
|
||||||
use embassy_rp::pio::{Config, FifoJoin, InterruptHandler, Pio, ShiftConfig, ShiftDirection};
|
use embassy_rp::pio::{InterruptHandler, Pio};
|
||||||
use embassy_rp::{bind_interrupts, Peripheral};
|
use embassy_rp::pio_programs::i2s::{PioI2sOut, PioI2sOutProgram};
|
||||||
use fixed::traits::ToFixed;
|
|
||||||
use static_cell::StaticCell;
|
use static_cell::StaticCell;
|
||||||
use {defmt_rtt as _, panic_probe as _};
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
|
|
||||||
@ -25,61 +25,32 @@ bind_interrupts!(struct Irqs {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const SAMPLE_RATE: u32 = 48_000;
|
const SAMPLE_RATE: u32 = 48_000;
|
||||||
|
const BIT_DEPTH: u32 = 16;
|
||||||
|
const CHANNELS: u32 = 2;
|
||||||
|
|
||||||
#[embassy_executor::main]
|
#[embassy_executor::main]
|
||||||
async fn main(_spawner: Spawner) {
|
async fn main(_spawner: Spawner) {
|
||||||
let mut p = embassy_rp::init(Default::default());
|
let mut p = embassy_rp::init(Default::default());
|
||||||
|
|
||||||
// Setup pio state machine for i2s output
|
// Setup pio state machine for i2s output
|
||||||
let mut pio = Pio::new(p.PIO0, Irqs);
|
let Pio { mut common, sm0, .. } = 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 bit_clock_pin = p.PIN_18;
|
let bit_clock_pin = p.PIN_18;
|
||||||
let left_right_clock_pin = p.PIN_19;
|
let left_right_clock_pin = p.PIN_19;
|
||||||
let data_pin = p.PIN_20;
|
let data_pin = p.PIN_20;
|
||||||
|
|
||||||
let data_pin = pio.common.make_pio_pin(data_pin);
|
let program = PioI2sOutProgram::new(&mut common);
|
||||||
let bit_clock_pin = pio.common.make_pio_pin(bit_clock_pin);
|
let mut i2s = PioI2sOut::new(
|
||||||
let left_right_clock_pin = pio.common.make_pio_pin(left_right_clock_pin);
|
&mut common,
|
||||||
|
sm0,
|
||||||
let cfg = {
|
p.DMA_CH0,
|
||||||
let mut cfg = Config::default();
|
data_pin,
|
||||||
cfg.use_program(
|
bit_clock_pin,
|
||||||
&pio.common.load_program(&pio_program.program),
|
left_right_clock_pin,
|
||||||
&[&bit_clock_pin, &left_right_clock_pin],
|
SAMPLE_RATE,
|
||||||
);
|
BIT_DEPTH,
|
||||||
cfg.set_out_pins(&[&data_pin]);
|
CHANNELS,
|
||||||
const BIT_DEPTH: u32 = 16;
|
&program,
|
||||||
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],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// create two audio buffers (back and front) which will take turns being
|
// 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);
|
let (mut back_buffer, mut front_buffer) = dma_buffer.split_at_mut(BUFFER_SIZE);
|
||||||
|
|
||||||
// start pio state machine
|
// 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 fade_value: i32 = 0;
|
||||||
let mut phase: i32 = 0;
|
let mut phase: i32 = 0;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// trigger transfer of front buffer data to the pio fifo
|
// trigger transfer of front buffer data to the pio fifo
|
||||||
// but don't await the returned future, yet
|
// 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
|
// fade in audio when bootsel is pressed
|
||||||
let fade_target = if p.BOOTSEL.is_pressed() { i32::MAX } else { 0 };
|
let fade_target = if p.BOOTSEL.is_pressed() { i32::MAX } else { 0 };
|
||||||
|
@ -6,7 +6,8 @@ use defmt::*;
|
|||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
use embassy_rp::bind_interrupts;
|
use embassy_rp::bind_interrupts;
|
||||||
use embassy_rp::peripherals::PIO0;
|
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 embassy_time::Timer;
|
||||||
use {defmt_rtt as _, panic_probe as _};
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
|
|
||||||
@ -18,7 +19,11 @@ bind_interrupts!(struct Irqs {
|
|||||||
async fn main(_spawner: Spawner) {
|
async fn main(_spawner: Spawner) {
|
||||||
let p = embassy_rp::init(Default::default());
|
let p = embassy_rp::init(Default::default());
|
||||||
let mut pio = Pio::new(p.PIO0, Irqs);
|
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 {
|
loop {
|
||||||
sensor.start().await; // Start a new measurement
|
sensor.start().await; // Start a new measurement
|
||||||
@ -33,89 +38,12 @@ async fn main(_spawner: Spawner) {
|
|||||||
|
|
||||||
/// DS18B20 temperature sensor driver
|
/// DS18B20 temperature sensor driver
|
||||||
pub struct Ds18b20<'d, PIO: pio::Instance, const SM: usize> {
|
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> {
|
impl<'d, PIO: pio::Instance, const SM: usize> Ds18b20<'d, PIO, SM> {
|
||||||
/// Create a new instance the driver
|
pub fn new(wire: PioOneWire<'d, PIO, SM>) -> Self {
|
||||||
pub fn new(common: &mut Common<'d, PIO>, mut sm: StateMachine<'d, PIO, SM>, pin: impl PioPin) -> Self {
|
Self { wire }
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate CRC8 of the data
|
/// 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`.
|
/// Start a new measurement. Allow at least 1000ms before getting `temperature`.
|
||||||
pub async fn start(&mut self) {
|
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.
|
/// Read the temperature. Ensure >1000ms has passed since `start` before calling this.
|
||||||
pub async fn temperature(&mut self) -> Result<f32, ()> {
|
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];
|
let mut data = [0; 9];
|
||||||
self.read_bytes(&mut data).await;
|
self.wire.read_bytes(&mut data).await;
|
||||||
match Self::crc8(&data) == 0 {
|
match Self::crc8(&data) == 0 {
|
||||||
true => Ok(((data[1] as u32) << 8 | data[0] as u32) as f32 / 16.),
|
true => Ok(((data[1] as u32) << 8 | data[0] as u32) as f32 / 16.),
|
||||||
false => Err(()),
|
false => Err(()),
|
||||||
|
@ -5,12 +5,11 @@
|
|||||||
use core::time::Duration;
|
use core::time::Duration;
|
||||||
|
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
use embassy_rp::gpio::Level;
|
use embassy_rp::bind_interrupts;
|
||||||
use embassy_rp::peripherals::PIO0;
|
use embassy_rp::peripherals::PIO0;
|
||||||
use embassy_rp::pio::{Common, Config, Direction, Instance, InterruptHandler, Pio, PioPin, StateMachine};
|
use embassy_rp::pio::{InterruptHandler, Pio};
|
||||||
use embassy_rp::{bind_interrupts, clocks};
|
use embassy_rp::pio_programs::pwm::{PioPwm, PioPwmProgram};
|
||||||
use embassy_time::Timer;
|
use embassy_time::Timer;
|
||||||
use pio::InstructionOperands;
|
|
||||||
use {defmt_rtt as _, panic_probe as _};
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
|
|
||||||
const REFRESH_INTERVAL: u64 = 20000;
|
const REFRESH_INTERVAL: u64 = 20000;
|
||||||
@ -19,93 +18,14 @@ bind_interrupts!(struct Irqs {
|
|||||||
PIO0_IRQ_0 => InterruptHandler<PIO0>;
|
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]
|
#[embassy_executor::main]
|
||||||
async fn main(_spawner: Spawner) {
|
async fn main(_spawner: Spawner) {
|
||||||
let p = embassy_rp::init(Default::default());
|
let p = embassy_rp::init(Default::default());
|
||||||
let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs);
|
let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs);
|
||||||
|
|
||||||
// Note that PIN_25 is the led pin on the Pico
|
// 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.set_period(Duration::from_micros(REFRESH_INTERVAL));
|
||||||
pwm_pio.start();
|
pwm_pio.start();
|
||||||
|
|
||||||
|
@ -5,70 +5,18 @@
|
|||||||
|
|
||||||
use defmt::info;
|
use defmt::info;
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
use embassy_rp::gpio::Pull;
|
use embassy_rp::bind_interrupts;
|
||||||
use embassy_rp::peripherals::PIO0;
|
use embassy_rp::peripherals::PIO0;
|
||||||
use embassy_rp::{bind_interrupts, pio};
|
use embassy_rp::pio::{InterruptHandler, Pio};
|
||||||
use fixed::traits::ToFixed;
|
use embassy_rp::pio_programs::rotary_encoder::{Direction, PioEncoder, PioEncoderProgram};
|
||||||
use pio::{Common, Config, FifoJoin, Instance, InterruptHandler, Pio, PioPin, ShiftDirection, StateMachine};
|
|
||||||
use {defmt_rtt as _, panic_probe as _};
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
|
|
||||||
bind_interrupts!(struct Irqs {
|
bind_interrupts!(struct Irqs {
|
||||||
PIO0_IRQ_0 => InterruptHandler<PIO0>;
|
PIO0_IRQ_0 => InterruptHandler<PIO0>;
|
||||||
});
|
});
|
||||||
|
|
||||||
pub struct PioEncoder<'d, T: Instance, const SM: usize> {
|
#[embassy_executor::task]
|
||||||
sm: StateMachine<'d, T, SM>,
|
async fn encoder_0(mut encoder: PioEncoder<'static, PIO0, 0>) {
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
loop {
|
loop {
|
||||||
info!("Count: {}", count);
|
info!("Count: {}", count);
|
||||||
@ -78,3 +26,30 @@ async fn main(_spawner: Spawner) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[embassy_executor::task]
|
||||||
|
async fn encoder_1(mut encoder: PioEncoder<'static, PIO0, 1>) {
|
||||||
|
let mut count = 0;
|
||||||
|
loop {
|
||||||
|
info!("Count: {}", count);
|
||||||
|
count += match encoder.read().await {
|
||||||
|
Direction::Clockwise => 1,
|
||||||
|
Direction::CounterClockwise => -1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[embassy_executor::main]
|
||||||
|
async fn main(spawner: Spawner) {
|
||||||
|
let p = embassy_rp::init(Default::default());
|
||||||
|
let Pio {
|
||||||
|
mut common, sm0, sm1, ..
|
||||||
|
} = Pio::new(p.PIO0, Irqs);
|
||||||
|
|
||||||
|
let prg = PioEncoderProgram::new(&mut common);
|
||||||
|
let encoder0 = PioEncoder::new(&mut common, sm0, p.PIN_4, p.PIN_5, &prg);
|
||||||
|
let encoder1 = PioEncoder::new(&mut common, sm1, p.PIN_6, p.PIN_7, &prg);
|
||||||
|
|
||||||
|
spawner.must_spawn(encoder_0(encoder0));
|
||||||
|
spawner.must_spawn(encoder_1(encoder1));
|
||||||
|
}
|
||||||
|
@ -5,12 +5,11 @@
|
|||||||
use core::time::Duration;
|
use core::time::Duration;
|
||||||
|
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
use embassy_rp::gpio::Level;
|
use embassy_rp::bind_interrupts;
|
||||||
use embassy_rp::peripherals::PIO0;
|
use embassy_rp::peripherals::PIO0;
|
||||||
use embassy_rp::pio::{Common, Config, Direction, Instance, InterruptHandler, Pio, PioPin, StateMachine};
|
use embassy_rp::pio::{Instance, InterruptHandler, Pio};
|
||||||
use embassy_rp::{bind_interrupts, clocks};
|
use embassy_rp::pio_programs::pwm::{PioPwm, PioPwmProgram};
|
||||||
use embassy_time::Timer;
|
use embassy_time::Timer;
|
||||||
use pio::InstructionOperands;
|
|
||||||
use {defmt_rtt as _, panic_probe as _};
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
|
|
||||||
const DEFAULT_MIN_PULSE_WIDTH: u64 = 1000; // uncalibrated default, the shortest duty cycle sent to a servo
|
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>;
|
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> {
|
pub struct ServoBuilder<'d, T: Instance, const SM: usize> {
|
||||||
pwm: PwmPio<'d, T, SM>,
|
pwm: PioPwm<'d, T, SM>,
|
||||||
period: Duration,
|
period: Duration,
|
||||||
min_pulse_width: Duration,
|
min_pulse_width: Duration,
|
||||||
max_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> {
|
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 {
|
Self {
|
||||||
pwm,
|
pwm,
|
||||||
period: Duration::from_micros(REFRESH_INTERVAL),
|
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> {
|
pub struct Servo<'d, T: Instance, const SM: usize> {
|
||||||
pwm: PwmPio<'d, T, SM>,
|
pwm: PioPwm<'d, T, SM>,
|
||||||
min_pulse_width: Duration,
|
min_pulse_width: Duration,
|
||||||
max_pulse_width: Duration,
|
max_pulse_width: Duration,
|
||||||
max_degree_rotation: u64,
|
max_degree_rotation: u64,
|
||||||
@ -190,7 +109,8 @@ async fn main(_spawner: Spawner) {
|
|||||||
let p = embassy_rp::init(Default::default());
|
let p = embassy_rp::init(Default::default());
|
||||||
let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs);
|
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)
|
let mut servo = ServoBuilder::new(pwm_pio)
|
||||||
.set_max_degree_rotation(120) // Example of adjusting values for MG996R servo
|
.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.
|
.set_min_pulse_width(Duration::from_micros(350)) // This value was detemined by a rough experiment.
|
||||||
|
@ -3,143 +3,20 @@
|
|||||||
|
|
||||||
#![no_std]
|
#![no_std]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
use core::mem::{self, MaybeUninit};
|
|
||||||
|
|
||||||
use defmt::info;
|
use defmt::info;
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
use embassy_rp::bind_interrupts;
|
use embassy_rp::bind_interrupts;
|
||||||
use embassy_rp::peripherals::PIO0;
|
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 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 _};
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
|
|
||||||
bind_interrupts!(struct Irqs {
|
bind_interrupts!(struct Irqs {
|
||||||
PIO0_IRQ_0 => InterruptHandler<PIO0>;
|
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]
|
#[embassy_executor::main]
|
||||||
async fn main(_spawner: Spawner) {
|
async fn main(_spawner: Spawner) {
|
||||||
let p = embassy_rp::init(Default::default());
|
let p = embassy_rp::init(Default::default());
|
||||||
@ -147,14 +24,18 @@ async fn main(_spawner: Spawner) {
|
|||||||
mut common, irq0, sm0, ..
|
mut common, irq0, sm0, ..
|
||||||
} = Pio::new(p.PIO0, Irqs);
|
} = 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);
|
stepper.set_frequency(120);
|
||||||
loop {
|
loop {
|
||||||
info!("CW full steps");
|
info!("CW full steps");
|
||||||
stepper.step(1000).await;
|
stepper.step(1000).await;
|
||||||
|
|
||||||
info!("CCW full steps, drop after 1 sec");
|
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!");
|
info!("Time's up!");
|
||||||
Timer::after(Duration::from_secs(1)).await;
|
Timer::after(Duration::from_secs(1)).await;
|
||||||
}
|
}
|
||||||
|
@ -13,10 +13,10 @@
|
|||||||
use defmt::{info, panic, trace};
|
use defmt::{info, panic, trace};
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
use embassy_futures::join::{join, join3};
|
use embassy_futures::join::{join, join3};
|
||||||
use embassy_rp::bind_interrupts;
|
|
||||||
use embassy_rp::peripherals::{PIO0, USB};
|
use embassy_rp::peripherals::{PIO0, USB};
|
||||||
use embassy_rp::pio::InterruptHandler as PioInterruptHandler;
|
use embassy_rp::pio_programs::uart::{PioUartRx, PioUartRxProgram, PioUartTx, PioUartTxProgram};
|
||||||
use embassy_rp::usb::{Driver, Instance, InterruptHandler};
|
use embassy_rp::usb::{Driver, Instance, InterruptHandler};
|
||||||
|
use embassy_rp::{bind_interrupts, pio};
|
||||||
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
||||||
use embassy_sync::pipe::Pipe;
|
use embassy_sync::pipe::Pipe;
|
||||||
use embassy_usb::class::cdc_acm::{CdcAcmClass, Receiver, Sender, State};
|
use embassy_usb::class::cdc_acm::{CdcAcmClass, Receiver, Sender, State};
|
||||||
@ -25,13 +25,11 @@ use embassy_usb::{Builder, Config};
|
|||||||
use embedded_io_async::{Read, Write};
|
use embedded_io_async::{Read, Write};
|
||||||
use {defmt_rtt as _, panic_probe as _};
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
|
|
||||||
use crate::uart::PioUart;
|
//use crate::uart::PioUart;
|
||||||
use crate::uart_rx::PioUartRx;
|
|
||||||
use crate::uart_tx::PioUartTx;
|
|
||||||
|
|
||||||
bind_interrupts!(struct Irqs {
|
bind_interrupts!(struct Irqs {
|
||||||
USBCTRL_IRQ => InterruptHandler<USB>;
|
USBCTRL_IRQ => InterruptHandler<USB>;
|
||||||
PIO0_IRQ_0 => PioInterruptHandler<PIO0>;
|
PIO0_IRQ_0 => pio::InterruptHandler<PIO0>;
|
||||||
});
|
});
|
||||||
|
|
||||||
#[embassy_executor::main]
|
#[embassy_executor::main]
|
||||||
@ -85,8 +83,15 @@ async fn main(_spawner: Spawner) {
|
|||||||
let usb_fut = usb.run();
|
let usb_fut = usb.run();
|
||||||
|
|
||||||
// PIO UART setup
|
// PIO UART setup
|
||||||
let uart = PioUart::new(9600, p.PIO0, p.PIN_4, p.PIN_5);
|
let pio::Pio {
|
||||||
let (mut uart_tx, mut uart_rx) = uart.split();
|
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
|
// Pipe setup
|
||||||
let mut usb_pipe: Pipe<NoopRawMutex, 20> = Pipe::new();
|
let mut usb_pipe: Pipe<NoopRawMutex, 20> = Pipe::new();
|
||||||
@ -163,8 +168,8 @@ async fn usb_write<'d, T: Instance + 'd>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Read from the UART and write it to the USB TX pipe
|
/// Read from the UART and write it to the USB TX pipe
|
||||||
async fn uart_read(
|
async fn uart_read<PIO: pio::Instance, const SM: usize>(
|
||||||
uart_rx: &mut PioUartRx<'_>,
|
uart_rx: &mut PioUartRx<'_, PIO, SM>,
|
||||||
usb_pipe_writer: &mut embassy_sync::pipe::Writer<'_, NoopRawMutex, 20>,
|
usb_pipe_writer: &mut embassy_sync::pipe::Writer<'_, NoopRawMutex, 20>,
|
||||||
) -> ! {
|
) -> ! {
|
||||||
let mut buf = [0; 64];
|
let mut buf = [0; 64];
|
||||||
@ -180,8 +185,8 @@ async fn uart_read(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Read from the UART TX pipe and write it to the UART
|
/// Read from the UART TX pipe and write it to the UART
|
||||||
async fn uart_write(
|
async fn uart_write<PIO: pio::Instance, const SM: usize>(
|
||||||
uart_tx: &mut PioUartTx<'_>,
|
uart_tx: &mut PioUartTx<'_, PIO, SM>,
|
||||||
uart_pipe_reader: &mut embassy_sync::pipe::Reader<'_, NoopRawMutex, 20>,
|
uart_pipe_reader: &mut embassy_sync::pipe::Reader<'_, NoopRawMutex, 20>,
|
||||||
) -> ! {
|
) -> ! {
|
||||||
let mut buf = [0; 64];
|
let mut buf = [0; 64];
|
||||||
@ -192,197 +197,3 @@ async fn uart_write(
|
|||||||
let _ = uart_tx.write(&data).await;
|
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 defmt::*;
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
use embassy_rp::dma::{AnyChannel, Channel};
|
use embassy_rp::bind_interrupts;
|
||||||
use embassy_rp::peripherals::PIO0;
|
use embassy_rp::peripherals::PIO0;
|
||||||
use embassy_rp::pio::{
|
use embassy_rp::pio::{InterruptHandler, Pio};
|
||||||
Common, Config, FifoJoin, Instance, InterruptHandler, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine,
|
use embassy_rp::pio_programs::ws2812::{PioWs2812, PioWs2812Program};
|
||||||
};
|
use embassy_time::{Duration, Ticker};
|
||||||
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 smart_leds::RGB8;
|
use smart_leds::RGB8;
|
||||||
use {defmt_rtt as _, panic_probe as _};
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
|
|
||||||
@ -22,96 +18,6 @@ bind_interrupts!(struct Irqs {
|
|||||||
PIO0_IRQ_0 => InterruptHandler<PIO0>;
|
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
|
/// Input a value 0 to 255 to get a color value
|
||||||
/// The colours are a transition r - g - b - back to r.
|
/// The colours are a transition r - g - b - back to r.
|
||||||
fn wheel(mut wheel_pos: u8) -> RGB8 {
|
fn wheel(mut wheel_pos: u8) -> RGB8 {
|
||||||
@ -142,7 +48,8 @@ async fn main(_spawner: Spawner) {
|
|||||||
// Common neopixel pins:
|
// Common neopixel pins:
|
||||||
// Thing plus: 8
|
// Thing plus: 8
|
||||||
// Adafruit Feather: 16; Adafruit Feather+RFM95: 4
|
// 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.
|
// Loop forever making RGB values and pushing them out to the WS2812.
|
||||||
let mut ticker = Ticker::every(Duration::from_millis(10));
|
let mut ticker = Ticker::every(Duration::from_millis(10));
|
||||||
|
@ -7,14 +7,12 @@
|
|||||||
use core::fmt::Write;
|
use core::fmt::Write;
|
||||||
|
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
|
use embassy_rp::bind_interrupts;
|
||||||
use embassy_rp::block::ImageDef;
|
use embassy_rp::block::ImageDef;
|
||||||
use embassy_rp::dma::{AnyChannel, Channel};
|
|
||||||
use embassy_rp::peripherals::PIO0;
|
use embassy_rp::peripherals::PIO0;
|
||||||
use embassy_rp::pio::{
|
use embassy_rp::pio::{InterruptHandler, Pio};
|
||||||
Config, Direction, FifoJoin, InterruptHandler, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine,
|
use embassy_rp::pio_programs::hd44780::{PioHD44780, PioHD44780CommandSequenceProgram, PioHD44780CommandWordProgram};
|
||||||
};
|
|
||||||
use embassy_rp::pwm::{self, Pwm};
|
use embassy_rp::pwm::{self, Pwm};
|
||||||
use embassy_rp::{bind_interrupts, into_ref, Peripheral, PeripheralRef};
|
|
||||||
use embassy_time::{Instant, Timer};
|
use embassy_time::{Instant, Timer};
|
||||||
use {defmt_rtt as _, panic_probe as _};
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
|
|
||||||
@ -48,8 +46,27 @@ async fn main(_spawner: Spawner) {
|
|||||||
c
|
c
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut hd = HD44780::new(
|
let Pio {
|
||||||
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,
|
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;
|
.await;
|
||||||
|
|
||||||
@ -73,173 +90,3 @@ async fn main(_spawner: Spawner) {
|
|||||||
Timer::after_secs(1).await;
|
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,12 @@
|
|||||||
use core::mem;
|
use core::mem;
|
||||||
|
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
|
use embassy_rp::bind_interrupts;
|
||||||
use embassy_rp::block::ImageDef;
|
use embassy_rp::block::ImageDef;
|
||||||
|
use embassy_rp::gpio::{Input, Pull};
|
||||||
use embassy_rp::peripherals::PIO0;
|
use embassy_rp::peripherals::PIO0;
|
||||||
use embassy_rp::pio::{Config, FifoJoin, InterruptHandler, Pio, ShiftConfig, ShiftDirection};
|
use embassy_rp::pio::{InterruptHandler, Pio};
|
||||||
use embassy_rp::{bind_interrupts, Peripheral};
|
use embassy_rp::pio_programs::i2s::{PioI2sOut, PioI2sOutProgram};
|
||||||
use fixed::traits::ToFixed;
|
|
||||||
use static_cell::StaticCell;
|
use static_cell::StaticCell;
|
||||||
use {defmt_rtt as _, panic_probe as _};
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
|
|
||||||
@ -30,63 +31,36 @@ bind_interrupts!(struct Irqs {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const SAMPLE_RATE: u32 = 48_000;
|
const SAMPLE_RATE: u32 = 48_000;
|
||||||
|
const BIT_DEPTH: u32 = 16;
|
||||||
|
const CHANNELS: u32 = 2;
|
||||||
|
|
||||||
#[embassy_executor::main]
|
#[embassy_executor::main]
|
||||||
async fn main(_spawner: Spawner) {
|
async fn main(_spawner: Spawner) {
|
||||||
let p = embassy_rp::init(Default::default());
|
let p = embassy_rp::init(Default::default());
|
||||||
|
|
||||||
// Setup pio state machine for i2s output
|
// Setup pio state machine for i2s output
|
||||||
let mut pio = Pio::new(p.PIO0, Irqs);
|
let Pio { mut common, sm0, .. } = 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 bit_clock_pin = p.PIN_18;
|
let bit_clock_pin = p.PIN_18;
|
||||||
let left_right_clock_pin = p.PIN_19;
|
let left_right_clock_pin = p.PIN_19;
|
||||||
let data_pin = p.PIN_20;
|
let data_pin = p.PIN_20;
|
||||||
|
|
||||||
let data_pin = pio.common.make_pio_pin(data_pin);
|
let program = PioI2sOutProgram::new(&mut common);
|
||||||
let bit_clock_pin = pio.common.make_pio_pin(bit_clock_pin);
|
let mut i2s = PioI2sOut::new(
|
||||||
let left_right_clock_pin = pio.common.make_pio_pin(left_right_clock_pin);
|
&mut common,
|
||||||
|
sm0,
|
||||||
let cfg = {
|
p.DMA_CH0,
|
||||||
let mut cfg = Config::default();
|
data_pin,
|
||||||
cfg.use_program(
|
bit_clock_pin,
|
||||||
&pio.common.load_program(&pio_program.program),
|
left_right_clock_pin,
|
||||||
&[&bit_clock_pin, &left_right_clock_pin],
|
SAMPLE_RATE,
|
||||||
);
|
BIT_DEPTH,
|
||||||
cfg.set_out_pins(&[&data_pin]);
|
CHANNELS,
|
||||||
const BIT_DEPTH: u32 = 16;
|
&program,
|
||||||
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 fade_input = Input::new(p.PIN_0, Pull::Up);
|
||||||
|
|
||||||
// create two audio buffers (back and front) which will take turns being
|
// create two audio buffers (back and front) which will take turns being
|
||||||
// filled with new audio data and being sent to the pio fifo using dma
|
// filled with new audio data and being sent to the pio fifo using dma
|
||||||
const BUFFER_SIZE: usize = 960;
|
const BUFFER_SIZE: usize = 960;
|
||||||
@ -95,20 +69,16 @@ async fn main(_spawner: Spawner) {
|
|||||||
let (mut back_buffer, mut front_buffer) = dma_buffer.split_at_mut(BUFFER_SIZE);
|
let (mut back_buffer, mut front_buffer) = dma_buffer.split_at_mut(BUFFER_SIZE);
|
||||||
|
|
||||||
// start pio state machine
|
// 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 fade_value: i32 = 0;
|
||||||
let mut phase: i32 = 0;
|
let mut phase: i32 = 0;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// trigger transfer of front buffer data to the pio fifo
|
// trigger transfer of front buffer data to the pio fifo
|
||||||
// but don't await the returned future, yet
|
// 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
|
// fade in audio when bootsel is pressed
|
||||||
let fade_target = i32::MAX;
|
let fade_target = if fade_input.is_low() { i32::MAX } else { 0 };
|
||||||
|
|
||||||
// fill back buffer with fresh audio samples before awaiting the dma future
|
// fill back buffer with fresh audio samples before awaiting the dma future
|
||||||
for s in back_buffer.iter_mut() {
|
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 core::time::Duration;
|
||||||
|
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
|
use embassy_rp::bind_interrupts;
|
||||||
use embassy_rp::block::ImageDef;
|
use embassy_rp::block::ImageDef;
|
||||||
use embassy_rp::gpio::Level;
|
|
||||||
use embassy_rp::peripherals::PIO0;
|
use embassy_rp::peripherals::PIO0;
|
||||||
use embassy_rp::pio::{Common, Config, Direction, Instance, InterruptHandler, Pio, PioPin, StateMachine};
|
use embassy_rp::pio::{InterruptHandler, Pio};
|
||||||
use embassy_rp::{bind_interrupts, clocks};
|
use embassy_rp::pio_programs::pwm::{PioPwm, PioPwmProgram};
|
||||||
use embassy_time::Timer;
|
use embassy_time::Timer;
|
||||||
use pio::InstructionOperands;
|
|
||||||
use {defmt_rtt as _, panic_probe as _};
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
|
|
||||||
#[link_section = ".start_block"]
|
#[link_section = ".start_block"]
|
||||||
@ -24,93 +23,14 @@ bind_interrupts!(struct Irqs {
|
|||||||
PIO0_IRQ_0 => InterruptHandler<PIO0>;
|
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]
|
#[embassy_executor::main]
|
||||||
async fn main(_spawner: Spawner) {
|
async fn main(_spawner: Spawner) {
|
||||||
let p = embassy_rp::init(Default::default());
|
let p = embassy_rp::init(Default::default());
|
||||||
let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs);
|
let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs);
|
||||||
|
|
||||||
// Note that PIN_25 is the led pin on the Pico
|
// 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.set_period(Duration::from_micros(REFRESH_INTERVAL));
|
||||||
pwm_pio.start();
|
pwm_pio.start();
|
||||||
|
|
||||||
|
@ -5,12 +5,11 @@
|
|||||||
|
|
||||||
use defmt::info;
|
use defmt::info;
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
|
use embassy_rp::bind_interrupts;
|
||||||
use embassy_rp::block::ImageDef;
|
use embassy_rp::block::ImageDef;
|
||||||
use embassy_rp::gpio::Pull;
|
|
||||||
use embassy_rp::peripherals::PIO0;
|
use embassy_rp::peripherals::PIO0;
|
||||||
use embassy_rp::{bind_interrupts, pio};
|
use embassy_rp::pio::{InterruptHandler, Pio};
|
||||||
use fixed::traits::ToFixed;
|
use embassy_rp::pio_programs::rotary_encoder::{Direction, PioEncoder, PioEncoderProgram};
|
||||||
use pio::{Common, Config, FifoJoin, Instance, InterruptHandler, Pio, PioPin, ShiftDirection, StateMachine};
|
|
||||||
use {defmt_rtt as _, panic_probe as _};
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
|
|
||||||
#[link_section = ".start_block"]
|
#[link_section = ".start_block"]
|
||||||
@ -21,59 +20,8 @@ bind_interrupts!(struct Irqs {
|
|||||||
PIO0_IRQ_0 => InterruptHandler<PIO0>;
|
PIO0_IRQ_0 => InterruptHandler<PIO0>;
|
||||||
});
|
});
|
||||||
|
|
||||||
pub struct PioEncoder<'d, T: Instance, const SM: usize> {
|
#[embassy_executor::task]
|
||||||
sm: StateMachine<'d, T, SM>,
|
async fn encoder_0(mut encoder: PioEncoder<'static, PIO0, 0>) {
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
loop {
|
loop {
|
||||||
info!("Count: {}", count);
|
info!("Count: {}", count);
|
||||||
@ -83,3 +31,30 @@ async fn main(_spawner: Spawner) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[embassy_executor::task]
|
||||||
|
async fn encoder_1(mut encoder: PioEncoder<'static, PIO0, 1>) {
|
||||||
|
let mut count = 0;
|
||||||
|
loop {
|
||||||
|
info!("Count: {}", count);
|
||||||
|
count += match encoder.read().await {
|
||||||
|
Direction::Clockwise => 1,
|
||||||
|
Direction::CounterClockwise => -1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[embassy_executor::main]
|
||||||
|
async fn main(spawner: Spawner) {
|
||||||
|
let p = embassy_rp::init(Default::default());
|
||||||
|
let Pio {
|
||||||
|
mut common, sm0, sm1, ..
|
||||||
|
} = Pio::new(p.PIO0, Irqs);
|
||||||
|
|
||||||
|
let prg = PioEncoderProgram::new(&mut common);
|
||||||
|
let encoder0 = PioEncoder::new(&mut common, sm0, p.PIN_4, p.PIN_5, &prg);
|
||||||
|
let encoder1 = PioEncoder::new(&mut common, sm1, p.PIN_6, p.PIN_7, &prg);
|
||||||
|
|
||||||
|
spawner.must_spawn(encoder_0(encoder0));
|
||||||
|
spawner.must_spawn(encoder_1(encoder1));
|
||||||
|
}
|
||||||
|
@ -5,13 +5,12 @@
|
|||||||
use core::time::Duration;
|
use core::time::Duration;
|
||||||
|
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
|
use embassy_rp::bind_interrupts;
|
||||||
use embassy_rp::block::ImageDef;
|
use embassy_rp::block::ImageDef;
|
||||||
use embassy_rp::gpio::Level;
|
|
||||||
use embassy_rp::peripherals::PIO0;
|
use embassy_rp::peripherals::PIO0;
|
||||||
use embassy_rp::pio::{Common, Config, Direction, Instance, InterruptHandler, Pio, PioPin, StateMachine};
|
use embassy_rp::pio::{Instance, InterruptHandler, Pio};
|
||||||
use embassy_rp::{bind_interrupts, clocks};
|
use embassy_rp::pio_programs::pwm::{PioPwm, PioPwmProgram};
|
||||||
use embassy_time::Timer;
|
use embassy_time::Timer;
|
||||||
use pio::InstructionOperands;
|
|
||||||
use {defmt_rtt as _, panic_probe as _};
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
|
|
||||||
#[link_section = ".start_block"]
|
#[link_section = ".start_block"]
|
||||||
@ -27,88 +26,8 @@ bind_interrupts!(struct Irqs {
|
|||||||
PIO0_IRQ_0 => InterruptHandler<PIO0>;
|
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> {
|
pub struct ServoBuilder<'d, T: Instance, const SM: usize> {
|
||||||
pwm: PwmPio<'d, T, SM>,
|
pwm: PioPwm<'d, T, SM>,
|
||||||
period: Duration,
|
period: Duration,
|
||||||
min_pulse_width: Duration,
|
min_pulse_width: Duration,
|
||||||
max_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> {
|
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 {
|
Self {
|
||||||
pwm,
|
pwm,
|
||||||
period: Duration::from_micros(REFRESH_INTERVAL),
|
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> {
|
pub struct Servo<'d, T: Instance, const SM: usize> {
|
||||||
pwm: PwmPio<'d, T, SM>,
|
pwm: PioPwm<'d, T, SM>,
|
||||||
min_pulse_width: Duration,
|
min_pulse_width: Duration,
|
||||||
max_pulse_width: Duration,
|
max_pulse_width: Duration,
|
||||||
max_degree_rotation: u64,
|
max_degree_rotation: u64,
|
||||||
@ -195,7 +114,8 @@ async fn main(_spawner: Spawner) {
|
|||||||
let p = embassy_rp::init(Default::default());
|
let p = embassy_rp::init(Default::default());
|
||||||
let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs);
|
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)
|
let mut servo = ServoBuilder::new(pwm_pio)
|
||||||
.set_max_degree_rotation(120) // Example of adjusting values for MG996R servo
|
.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.
|
.set_min_pulse_width(Duration::from_micros(350)) // This value was detemined by a rough experiment.
|
||||||
|
@ -3,18 +3,15 @@
|
|||||||
|
|
||||||
#![no_std]
|
#![no_std]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
use core::mem::{self, MaybeUninit};
|
|
||||||
|
|
||||||
use defmt::info;
|
use defmt::info;
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
use embassy_rp::bind_interrupts;
|
use embassy_rp::bind_interrupts;
|
||||||
use embassy_rp::block::ImageDef;
|
use embassy_rp::block::ImageDef;
|
||||||
use embassy_rp::peripherals::PIO0;
|
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 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 _};
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
|
|
||||||
#[link_section = ".start_block"]
|
#[link_section = ".start_block"]
|
||||||
@ -25,126 +22,6 @@ bind_interrupts!(struct Irqs {
|
|||||||
PIO0_IRQ_0 => InterruptHandler<PIO0>;
|
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]
|
#[embassy_executor::main]
|
||||||
async fn main(_spawner: Spawner) {
|
async fn main(_spawner: Spawner) {
|
||||||
let p = embassy_rp::init(Default::default());
|
let p = embassy_rp::init(Default::default());
|
||||||
@ -152,14 +29,18 @@ async fn main(_spawner: Spawner) {
|
|||||||
mut common, irq0, sm0, ..
|
mut common, irq0, sm0, ..
|
||||||
} = Pio::new(p.PIO0, Irqs);
|
} = 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);
|
stepper.set_frequency(120);
|
||||||
loop {
|
loop {
|
||||||
info!("CW full steps");
|
info!("CW full steps");
|
||||||
stepper.step(1000).await;
|
stepper.step(1000).await;
|
||||||
|
|
||||||
info!("CCW full steps, drop after 1 sec");
|
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!");
|
info!("Time's up!");
|
||||||
Timer::after(Duration::from_secs(1)).await;
|
Timer::after(Duration::from_secs(1)).await;
|
||||||
}
|
}
|
||||||
|
202
examples/rp23/src/bin/pio_uart.rs
Normal file
202
examples/rp23/src/bin/pio_uart.rs
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
//! This example shows how to use the PIO module in the RP2040 chip to implement a duplex UART.
|
||||||
|
//! The PIO module is a very powerful peripheral that can be used to implement many different
|
||||||
|
//! protocols. It is a very flexible state machine that can be programmed to do almost anything.
|
||||||
|
//!
|
||||||
|
//! This example opens up a USB device that implements a CDC ACM serial port. It then uses the
|
||||||
|
//! PIO module to implement a UART that is connected to the USB serial port. This allows you to
|
||||||
|
//! communicate with a device connected to the RP2040 over USB serial.
|
||||||
|
|
||||||
|
#![no_std]
|
||||||
|
#![no_main]
|
||||||
|
#![allow(async_fn_in_trait)]
|
||||||
|
|
||||||
|
use defmt::{info, panic, trace};
|
||||||
|
use embassy_executor::Spawner;
|
||||||
|
use embassy_futures::join::{join, join3};
|
||||||
|
use embassy_rp::block::ImageDef;
|
||||||
|
use embassy_rp::peripherals::{PIO0, USB};
|
||||||
|
use embassy_rp::pio_programs::uart::{PioUartRx, PioUartRxProgram, PioUartTx, PioUartTxProgram};
|
||||||
|
use embassy_rp::usb::{Driver, Instance, InterruptHandler};
|
||||||
|
use embassy_rp::{bind_interrupts, pio};
|
||||||
|
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
||||||
|
use embassy_sync::pipe::Pipe;
|
||||||
|
use embassy_usb::class::cdc_acm::{CdcAcmClass, Receiver, Sender, State};
|
||||||
|
use embassy_usb::driver::EndpointError;
|
||||||
|
use embassy_usb::{Builder, Config};
|
||||||
|
use embedded_io_async::{Read, Write};
|
||||||
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
|
|
||||||
|
#[link_section = ".start_block"]
|
||||||
|
#[used]
|
||||||
|
pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe();
|
||||||
|
|
||||||
|
bind_interrupts!(struct Irqs {
|
||||||
|
USBCTRL_IRQ => InterruptHandler<USB>;
|
||||||
|
PIO0_IRQ_0 => pio::InterruptHandler<PIO0>;
|
||||||
|
});
|
||||||
|
|
||||||
|
#[embassy_executor::main]
|
||||||
|
async fn main(_spawner: Spawner) {
|
||||||
|
info!("Hello there!");
|
||||||
|
|
||||||
|
let p = embassy_rp::init(Default::default());
|
||||||
|
|
||||||
|
// Create the driver, from the HAL.
|
||||||
|
let driver = Driver::new(p.USB, Irqs);
|
||||||
|
|
||||||
|
// Create embassy-usb Config
|
||||||
|
let mut config = Config::new(0xc0de, 0xcafe);
|
||||||
|
config.manufacturer = Some("Embassy");
|
||||||
|
config.product = Some("PIO UART example");
|
||||||
|
config.serial_number = Some("12345678");
|
||||||
|
config.max_power = 100;
|
||||||
|
config.max_packet_size_0 = 64;
|
||||||
|
|
||||||
|
// Required for windows compatibility.
|
||||||
|
// https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help
|
||||||
|
config.device_class = 0xEF;
|
||||||
|
config.device_sub_class = 0x02;
|
||||||
|
config.device_protocol = 0x01;
|
||||||
|
config.composite_with_iads = true;
|
||||||
|
|
||||||
|
// Create embassy-usb DeviceBuilder using the driver and config.
|
||||||
|
// It needs some buffers for building the descriptors.
|
||||||
|
let mut config_descriptor = [0; 256];
|
||||||
|
let mut bos_descriptor = [0; 256];
|
||||||
|
let mut control_buf = [0; 64];
|
||||||
|
|
||||||
|
let mut state = State::new();
|
||||||
|
|
||||||
|
let mut builder = Builder::new(
|
||||||
|
driver,
|
||||||
|
config,
|
||||||
|
&mut config_descriptor,
|
||||||
|
&mut bos_descriptor,
|
||||||
|
&mut [], // no msos descriptors
|
||||||
|
&mut control_buf,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create classes on the builder.
|
||||||
|
let class = CdcAcmClass::new(&mut builder, &mut state, 64);
|
||||||
|
|
||||||
|
// Build the builder.
|
||||||
|
let mut usb = builder.build();
|
||||||
|
|
||||||
|
// Run the USB device.
|
||||||
|
let usb_fut = usb.run();
|
||||||
|
|
||||||
|
// PIO UART setup
|
||||||
|
let pio::Pio {
|
||||||
|
mut common, sm0, sm1, ..
|
||||||
|
} = pio::Pio::new(p.PIO0, Irqs);
|
||||||
|
|
||||||
|
let tx_program = PioUartTxProgram::new(&mut common);
|
||||||
|
let mut uart_tx = PioUartTx::new(9600, &mut common, sm0, p.PIN_4, &tx_program);
|
||||||
|
|
||||||
|
let rx_program = PioUartRxProgram::new(&mut common);
|
||||||
|
let mut uart_rx = PioUartRx::new(9600, &mut common, sm1, p.PIN_5, &rx_program);
|
||||||
|
|
||||||
|
// Pipe setup
|
||||||
|
let mut usb_pipe: Pipe<NoopRawMutex, 20> = Pipe::new();
|
||||||
|
let (mut usb_pipe_reader, mut usb_pipe_writer) = usb_pipe.split();
|
||||||
|
|
||||||
|
let mut uart_pipe: Pipe<NoopRawMutex, 20> = Pipe::new();
|
||||||
|
let (mut uart_pipe_reader, mut uart_pipe_writer) = uart_pipe.split();
|
||||||
|
|
||||||
|
let (mut usb_tx, mut usb_rx) = class.split();
|
||||||
|
|
||||||
|
// Read + write from USB
|
||||||
|
let usb_future = async {
|
||||||
|
loop {
|
||||||
|
info!("Wait for USB connection");
|
||||||
|
usb_rx.wait_connection().await;
|
||||||
|
info!("Connected");
|
||||||
|
let _ = join(
|
||||||
|
usb_read(&mut usb_rx, &mut uart_pipe_writer),
|
||||||
|
usb_write(&mut usb_tx, &mut usb_pipe_reader),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
info!("Disconnected");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Read + write from UART
|
||||||
|
let uart_future = join(
|
||||||
|
uart_read(&mut uart_rx, &mut usb_pipe_writer),
|
||||||
|
uart_write(&mut uart_tx, &mut uart_pipe_reader),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Run everything concurrently.
|
||||||
|
// If we had made everything `'static` above instead, we could do this using separate tasks instead.
|
||||||
|
join3(usb_fut, usb_future, uart_future).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Disconnected {}
|
||||||
|
|
||||||
|
impl From<EndpointError> for Disconnected {
|
||||||
|
fn from(val: EndpointError) -> Self {
|
||||||
|
match val {
|
||||||
|
EndpointError::BufferOverflow => panic!("Buffer overflow"),
|
||||||
|
EndpointError::Disabled => Disconnected {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read from the USB and write it to the UART TX pipe
|
||||||
|
async fn usb_read<'d, T: Instance + 'd>(
|
||||||
|
usb_rx: &mut Receiver<'d, Driver<'d, T>>,
|
||||||
|
uart_pipe_writer: &mut embassy_sync::pipe::Writer<'_, NoopRawMutex, 20>,
|
||||||
|
) -> Result<(), Disconnected> {
|
||||||
|
let mut buf = [0; 64];
|
||||||
|
loop {
|
||||||
|
let n = usb_rx.read_packet(&mut buf).await?;
|
||||||
|
let data = &buf[..n];
|
||||||
|
trace!("USB IN: {:x}", data);
|
||||||
|
(*uart_pipe_writer).write(data).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read from the USB TX pipe and write it to the USB
|
||||||
|
async fn usb_write<'d, T: Instance + 'd>(
|
||||||
|
usb_tx: &mut Sender<'d, Driver<'d, T>>,
|
||||||
|
usb_pipe_reader: &mut embassy_sync::pipe::Reader<'_, NoopRawMutex, 20>,
|
||||||
|
) -> Result<(), Disconnected> {
|
||||||
|
let mut buf = [0; 64];
|
||||||
|
loop {
|
||||||
|
let n = (*usb_pipe_reader).read(&mut buf).await;
|
||||||
|
let data = &buf[..n];
|
||||||
|
trace!("USB OUT: {:x}", data);
|
||||||
|
usb_tx.write_packet(&data).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read from the UART and write it to the USB TX pipe
|
||||||
|
async fn uart_read<PIO: pio::Instance, const SM: usize>(
|
||||||
|
uart_rx: &mut PioUartRx<'_, PIO, SM>,
|
||||||
|
usb_pipe_writer: &mut embassy_sync::pipe::Writer<'_, NoopRawMutex, 20>,
|
||||||
|
) -> ! {
|
||||||
|
let mut buf = [0; 64];
|
||||||
|
loop {
|
||||||
|
let n = uart_rx.read(&mut buf).await.expect("UART read error");
|
||||||
|
if n == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let data = &buf[..n];
|
||||||
|
trace!("UART IN: {:x}", buf);
|
||||||
|
(*usb_pipe_writer).write(data).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read from the UART TX pipe and write it to the UART
|
||||||
|
async fn uart_write<PIO: pio::Instance, const SM: usize>(
|
||||||
|
uart_tx: &mut PioUartTx<'_, PIO, SM>,
|
||||||
|
uart_pipe_reader: &mut embassy_sync::pipe::Reader<'_, NoopRawMutex, 20>,
|
||||||
|
) -> ! {
|
||||||
|
let mut buf = [0; 64];
|
||||||
|
loop {
|
||||||
|
let n = (*uart_pipe_reader).read(&mut buf).await;
|
||||||
|
let data = &buf[..n];
|
||||||
|
trace!("UART OUT: {:x}", data);
|
||||||
|
let _ = uart_tx.write(&data).await;
|
||||||
|
}
|
||||||
|
}
|
@ -6,16 +6,12 @@
|
|||||||
|
|
||||||
use defmt::*;
|
use defmt::*;
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
|
use embassy_rp::bind_interrupts;
|
||||||
use embassy_rp::block::ImageDef;
|
use embassy_rp::block::ImageDef;
|
||||||
use embassy_rp::dma::{AnyChannel, Channel};
|
|
||||||
use embassy_rp::peripherals::PIO0;
|
use embassy_rp::peripherals::PIO0;
|
||||||
use embassy_rp::pio::{
|
use embassy_rp::pio::{InterruptHandler, Pio};
|
||||||
Common, Config, FifoJoin, Instance, InterruptHandler, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine,
|
use embassy_rp::pio_programs::ws2812::{PioWs2812, PioWs2812Program};
|
||||||
};
|
use embassy_time::{Duration, Ticker};
|
||||||
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 smart_leds::RGB8;
|
use smart_leds::RGB8;
|
||||||
use {defmt_rtt as _, panic_probe as _};
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
|
|
||||||
@ -27,96 +23,6 @@ bind_interrupts!(struct Irqs {
|
|||||||
PIO0_IRQ_0 => InterruptHandler<PIO0>;
|
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
|
/// Input a value 0 to 255 to get a color value
|
||||||
/// The colours are a transition r - g - b - back to r.
|
/// The colours are a transition r - g - b - back to r.
|
||||||
fn wheel(mut wheel_pos: u8) -> RGB8 {
|
fn wheel(mut wheel_pos: u8) -> RGB8 {
|
||||||
@ -147,7 +53,8 @@ async fn main(_spawner: Spawner) {
|
|||||||
// Common neopixel pins:
|
// Common neopixel pins:
|
||||||
// Thing plus: 8
|
// Thing plus: 8
|
||||||
// Adafruit Feather: 16; Adafruit Feather+RFM95: 4
|
// 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.
|
// Loop forever making RGB values and pushing them out to the WS2812.
|
||||||
let mut ticker = Ticker::every(Duration::from_millis(10));
|
let mut ticker = Ticker::every(Duration::from_millis(10));
|
||||||
|
Loading…
Reference in New Issue
Block a user