Very effective infinite signal gen up to ~300kHz, analog HW limited

This commit is contained in:
JP Stringham
2026-02-25 23:14:52 -05:00
parent 3465353ad1
commit 056940151c
3 changed files with 352 additions and 54 deletions

View File

@@ -35,5 +35,5 @@ rustflags = [
runner = "probe-rs run --chip RP2040 --protocol swd --speed 150" runner = "probe-rs run --chip RP2040 --protocol swd --speed 150"
[env] [env]
DEFMT_RTT_BUFFER_SIZE = { value = "8192", force = true } DEFMT_RTT_BUFFER_SIZE = { value = "1024", force = true }
DEFMT_LOG = { value = "info", force = true } DEFMT_LOG = { value = "info", force = true }

View File

@@ -4,34 +4,44 @@
#![no_std] #![no_std]
#![no_main] #![no_main]
mod rotary;
use cortex_m::singleton;
use defmt::println; use defmt::println;
use defmt_rtt as _; use defmt_rtt as _;
use embedded_hal::digital::InputPin;
use hal::Sio; use hal::Sio;
use hal::gpio::{FunctionPio0, Pin}; use hal::gpio::{FunctionPio0, Pin};
use hal::pac; use hal::pac;
use hal::pio::PIOExt; use hal::pio::PIOExt;
use panic_probe as _; use panic_probe as _;
use rp2040_hal as hal; use rp2040_hal as hal;
use rp2040_hal::clocks::ClockSource;
use rp2040_hal::dma::{DMAExt, ReadTarget, SingleChannel, WriteTarget, single_buffer};
use rp2040_hal::fugit::RateExtU32;
use rp2040_hal::pac::adc::cs::W;
use rp2040_hal::pll::PLLConfig;
use rp2040_hal::pll::common_configs::PLL_USB_48MHZ;
use crate::rotary::{RotaryEnc, RotaryResponse};
#[unsafe(link_section = ".boot_loader")] #[unsafe(link_section = ".boot_loader")]
#[used] #[used]
pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080; pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_W25Q080;
const WAVE_TABLE: [i8; 256] = [ // const WAVE_TABLE: [u8; 128] = [
0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51, 54, 57, 60, 62, 65, 68, 70, // 128, 134, 140, 146, 152, 159, 165, 171, 176, 182, 188, 193, 199, 204, 209, 213, 218, 222, 226,
73, 75, 78, 80, 83, 85, 87, 90, 92, 94, 96, 98, 100, 102, 104, 105, 107, 109, 110, 112, 113, // 230, 234, 237, 240, 243, 246, 248, 250, 252, 253, 254, 255, 255, 255, 255, 255, 254, 253, 252,
115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 124, 125, 125, 126, 126, 126, 126, 126, 126, // 250, 248, 246, 243, 240, 237, 234, 230, 227, 222, 218, 214, 209, 204, 199, 193, 188, 182, 177,
126, 126, 126, 126, 125, 125, 124, 124, 123, 123, 122, 121, 120, 119, 118, 117, 115, 114, 113, // 171, 165, 159, 153, 146, 140, 134, 128, 121, 115, 109, 103, 97, 91, 85, 79, 73, 67, 62, 57, 51,
111, 110, 108, 106, 105, 103, 101, 99, 97, 95, 93, 91, 89, 86, 84, 82, 79, 77, 74, 72, 69, 66, // 46, 42, 37, 33, 29, 25, 21, 18, 15, 12, 9, 7, 5, 3, 2, 1, 0, 0, 0, 0, 0, 1, 2, 3, 5, 7, 9, 12,
64, 61, 58, 56, 53, 50, 47, 44, 41, 38, 35, 32, 29, 26, 23, 20, 17, 14, 11, 8, 4, 1, -1, -4, // 14, 18, 21, 24, 28, 32, 37, 41, 46, 51, 56, 61, 67, 72, 78, 84, 90, 96, 102, 108, 115, 121,
-7, -10, -13, -16, -20, -23, -26, -29, -32, -35, -38, -41, -44, -47, -50, -52, -55, -58, -61, // ];
-63, -66, -69, -71, -74, -76, -79, -81, -84, -86, -88, -90, -93, -95, -97, -99, -101, -103,
-104, -106, -108, -109, -111, -112, -114, -115, -116, -118, -119, -120, -121, -122, -122, -123, const WAVE_TABLE: [u8; 64] = [
-124, -124, -125, -125, -126, -126, -126, -126, -126, -126, -126, -126, -126, -126, -125, -125, 128, 140, 152, 165, 176, 188, 199, 209, 218, 226, 234, 240, 246, 250, 253, 255, 255, 255, 253,
-124, -124, -123, -122, -121, -120, -119, -118, -117, -116, -115, -113, -112, -110, -109, -107, 250, 246, 240, 234, 227, 218, 209, 199, 188, 177, 165, 153, 140, 128, 115, 103, 91, 79, 67, 57,
-106, -104, -102, -100, -98, -96, -94, -92, -90, -88, -85, -83, -81, -78, -76, -73, -71, -68, 46, 37, 29, 21, 15, 9, 5, 2, 0, 0, 0, 2, 5, 9, 14, 21, 28, 37, 46, 56, 67, 78, 90, 102, 115,
-65, -63, -60, -57, -54, -52, -49, -46, -43, -40, -37, -34, -31, -28, -25, -22, -19, -16, -12,
-9, -6, -3, 0,
]; ];
/// Entry point to our bare-metal application. /// Entry point to our bare-metal application.
@@ -41,6 +51,56 @@ const WAVE_TABLE: [i8; 256] = [
#[rp2040_hal::entry] #[rp2040_hal::entry]
fn main() -> ! { fn main() -> ! {
let mut pac = pac::Peripherals::take().unwrap(); let mut pac = pac::Peripherals::take().unwrap();
let core = pac::CorePeripherals::take().unwrap();
// Set up the watchdog driver - needed by the clock setup code
// let mut watchdog = hal::Watchdog::new(pac.WATCHDOG);
hal::vreg::set_voltage(
&mut pac.VREG_AND_CHIP_RESET,
pac::vreg_and_chip_reset::vreg::VSEL_A::VOLTAGE1_30,
);
// settle
// cortex_m::asm::delay(3_000_000);
const XTAL_FREQ_HZ: u32 = 12_000_000;
let xosc = hal::xosc::setup_xosc_blocking_custom_delay(pac.XOSC, XTAL_FREQ_HZ.Hz(), 128)
.map_err(|_x| false)
.unwrap();
// watchdog.enable_tick_generation((XTAL_FREQ_HZ / 1_000_000) as u8);
// Configure the clocks
let mut clocks = hal::clocks::ClocksManager::new(pac.CLOCKS);
let pll_usb = rp2040_hal::pll::setup_pll_blocking(
pac.PLL_USB,
xosc.operating_frequency(),
PLL_USB_48MHZ,
&mut clocks,
&mut pac.RESETS,
)
.unwrap();
let pll_sys = hal::pll::setup_pll_blocking(
pac.PLL_SYS,
xosc.operating_frequency(),
PLLConfig {
vco_freq: 1500.MHz(),
refdiv: 1,
post_div1: 6,
post_div2: 1,
},
&mut clocks,
&mut pac.RESETS,
)
.unwrap();
clocks.init_default(&xosc, &pll_sys, &pll_usb).unwrap();
let pll_freq = pll_sys.get_freq();
println!("Booted at {}MHz", pll_freq.to_MHz());
let sio = Sio::new(pac.SIO); let sio = Sio::new(pac.SIO);
let pins = hal::gpio::Pins::new( let pins = hal::gpio::Pins::new(
@@ -56,14 +116,7 @@ fn main() -> ! {
let led_pin_id = led.id().num; let led_pin_id = led.id().num;
// Define a PIO program which reads data from the TX FIFO bit by bit // Define a PIO program which reads data from the TX FIFO bit by bit
let program = pio::pio_asm!( let program = pio::pio_asm!(".wrap_target", " out pins, 8", ".wrap");
".wrap_target",
" out pins, 8",
" out pins, 8",
" out pins, 8",
" out pins, 8",
".wrap"
);
let dyn_pin_array = [ let dyn_pin_array = [
pins.gpio3.into_function::<FunctionPio0>().into_dyn_pin(), pins.gpio3.into_function::<FunctionPio0>().into_dyn_pin(),
@@ -82,7 +135,7 @@ fn main() -> ! {
// Initialize and start PIO // Initialize and start PIO
let (mut pio, sm0, _, _, _) = pac.PIO0.split(&mut pac.RESETS); let (mut pio, sm0, _, _, _) = pac.PIO0.split(&mut pac.RESETS);
let installed = pio.install(&program.program).unwrap(); let installed = pio.install(&program.program).unwrap();
let (int, frac) = (1, 0); let (mut clock_div, mut frac) = (3, 0);
let (mut sm, _, mut tx) = rp2040_hal::pio::PIOBuilder::from_installed_program(installed) let (mut sm, _, mut tx) = rp2040_hal::pio::PIOBuilder::from_installed_program(installed)
.set_pins(led_pin_id, 1) .set_pins(led_pin_id, 1)
.out_pins(dyn_pin_array[3].id().num, 8) .out_pins(dyn_pin_array[3].id().num, 8)
@@ -90,7 +143,7 @@ fn main() -> ! {
.out_sticky(true) .out_sticky(true)
.autopull(true) .autopull(true)
.buffers(rp2040_hal::pio::Buffers::OnlyTx) .buffers(rp2040_hal::pio::Buffers::OnlyTx)
.clock_divisor_fixed_point(int, frac) .clock_divisor_fixed_point(clock_div, frac)
.build(sm0); .build(sm0);
// The GPIO pin needs to be configured as an output. // The GPIO pin needs to be configured as an output.
sm.set_pindirs([(led_pin_id, hal::pio::PinDir::Output)]); sm.set_pindirs([(led_pin_id, hal::pio::PinDir::Output)]);
@@ -101,44 +154,201 @@ fn main() -> ! {
pindirs[i] = (p.id().num, hal::pio::PinDir::Output); pindirs[i] = (p.id().num, hal::pio::PinDir::Output);
}); });
sm.set_pindirs(pindirs); sm.set_pindirs(pindirs);
tx.write(0b1111_1111_1111_1111_1111_1111_1111_1111); let mut sm = sm.start();
sm.start();
// let pgroup: PinGroup = PinGroup::new();
// let pgroup = pgroup.add_pin(pins.gpio3.into_push_pull_output()); const PROCESSED_TABLE_LEN: usize = WAVE_TABLE.len() * 64;
// let pgroup = pgroup.add_pin(pins.gpio4.into_push_pull_output()); println!("Processed wave table length: {}", PROCESSED_TABLE_LEN);
// let pgroup = pgroup.add_pin(pins.gpio5.into_push_pull_output()); let mut processed_wave_table = [0u32; PROCESSED_TABLE_LEN];
// let pgroup = pgroup.add_pin(pins.gpio6.into_push_pull_output());
// let pgroup = pgroup.add_pin(pins.gpio7.into_push_pull_output());
// let pgroup = pgroup.add_pin(pins.gpio8.into_push_pull_output());
// let pgroup = pgroup.add_pin(pins.gpio9.into_push_pull_output());
// let pgroup = pgroup.add_pin(pins.gpio10.into_push_pull_output());
// let pgroup = pgroup.add_pin(pins.gpio11.into_push_pull_output());
// let pgroup = pgroup.add_pin(pins.gpio12.into_push_pull_output());
// let mut pgroup = pgroup.add_pin(pins.gpio13.into_push_pull_output());
let mut processed_wave_table = [0u32; 64]; for i in 0..PROCESSED_TABLE_LEN {
for i in 0..256 {
let mut packed = 0u32; let mut packed = 0u32;
for j in 0..4 { for j in 0..4 {
let val = (WAVE_TABLE[i] as i32) + 127; let val = (WAVE_TABLE[i % WAVE_TABLE.len()] as u32) / 4 + 64;
let val = val as u32;
packed |= val.rotate_right(8 * j as u32); packed |= val.rotate_right(8 * j as u32);
} }
processed_wave_table[i / 4] = packed; processed_wave_table[i] = packed;
}
let dma = pac.DMA.split(&mut pac.RESETS);
let tx_buf = singleton!(: [u32; PROCESSED_TABLE_LEN] = processed_wave_table).unwrap();
let (rx_addr, rx_count) = tx_buf.rx_address_count();
let rx_addr = singleton!(: u32 = rx_addr).unwrap();
println!("rx addr count: {}", tx_buf.rx_address_count());
println!("rx inc: {}", tx_buf.rx_increment());
println!("tx addr count: {}", tx.tx_address_count());
println!("tx fifo addr: {}", tx.fifo_address());
println!("tx inc: {}", tx.tx_increment());
let (tx_addr, _) = tx.tx_address_count();
// trying to get around lack of ring buffer support in rp-hal
unsafe {
let pac = pac::Peripherals::steal();
println!("ctrl reg: {:b}", pac.DMA.ch(0).ch_al1_ctrl().read().bits());
pac.DMA.ch(0).ch_al1_ctrl().write(|w| {
w.chain_to().bits(1);
w.en().set_bit();
w.data_size().size_word();
w.incr_read().set_bit();
w
});
println!(
"\nCH0\n\nctrl reg: {:b}",
pac.DMA.ch(0).ch_ctrl_trig().read().bits()
);
pac.DMA.ch(0).ch_read_addr().write(|w| {
w.bits(*rx_addr);
w
});
pac.DMA.ch(0).ch_trans_count().write(|w| {
w.bits(rx_count);
w
});
pac.DMA.ch(0).ch_al2_write_addr_trig().write(|w| {
w.bits(tx_addr);
w
});
let read_addr = pac.DMA.ch(0).ch_read_addr().read().bits();
println!("ch0 ctrl read addr ptr: {}", read_addr);
let write_addr = pac.DMA.ch(0).ch_write_addr().read().bits();
println!("ch0 ctrl write addr ptr: {}", write_addr);
pac.DMA.ch(1).ch_ctrl_trig().write(|w| {
w.data_size().size_word();
w.en().set_bit();
w
});
let dma_ch0_read_addr_trig = pac.DMA.ch(0).ch_al3_read_addr_trig().as_ptr();
println!("{}", dma_ch0_read_addr_trig);
println!("{}", rx_addr.rx_address_count());
pac.DMA.ch(1).ch_read_addr().write(|w| {
w.bits(rx_addr.rx_address_count().0);
w
});
pac.DMA.ch(1).ch_trans_count().write(|w| {
w.bits(1);
w
});
pac.DMA.ch(1).ch_write_addr().write(|w| {
w.bits(dma_ch0_read_addr_trig as u32);
w
});
println!(
"\nCH1\n\nctrl reg: {:b}",
pac.DMA.ch(1).ch_ctrl_trig().read().bits()
);
let read_addr = pac.DMA.ch(1).ch_read_addr().read().bits();
println!("ctrl read addr ptr: {}", read_addr);
let write_addr = pac.DMA.ch(1).ch_write_addr().read().bits();
println!("ctrl write addr ptr: {}", write_addr);
} }
println!("alive"); // println!("{:?}", processed_wave_table);
println!("{:?}", processed_wave_table);
let mut tick = 0usize; let mut tick = 0u32;
// PIO runs in background, independently from CPU
let mut rotary = RotaryEnc::default();
let mut clk_pin = pins.gpio16.into_pull_up_input();
let mut dt_pin = pins.gpio17.into_pull_up_input();
let mut mode_switch = pins.gpio18.into_pull_up_input();
let mut mode = FreqSelectMode::Coarse;
let mut last_mode_chg = 0;
let mut mode_chg_allowed = true;
let mut last_rotary = 0;
let mut rotary_input_allowed = true;
const CYCLES_PER_ROTARY: u32 = 1_000;
const TICKS_PER_DMA_CHECK: u32 = (PROCESSED_TABLE_LEN / WAVE_TABLE.len() * 4 - 25) as u32;
println!("Ticks per dma check: {}", TICKS_PER_DMA_CHECK);
println!("Start main loop");
loop { loop {
while !tx.is_full() {
let out = processed_wave_table[tick % 64];
tick = tick.wrapping_add(1); tick = tick.wrapping_add(1);
tx.write(out);
// println!("tx ful");
if !rotary_input_allowed && tick.wrapping_sub(last_rotary) > CYCLES_PER_ROTARY {
rotary_input_allowed = true;
}
if !mode_chg_allowed && tick.wrapping_sub(last_mode_chg) > CYCLES_PER_ROTARY {
mode_chg_allowed = true;
}
if mode_chg_allowed {
if mode_switch.is_low().unwrap() {
mode = match mode {
FreqSelectMode::Coarse => FreqSelectMode::Fine,
FreqSelectMode::Fine => FreqSelectMode::Coarse,
};
last_mode_chg = tick;
mode_chg_allowed = false;
}
}
if rotary_input_allowed {
let clk_now = clk_pin.is_low().unwrap();
let dt_now = dt_pin.is_low().unwrap();
match rotary.update(clk_now, dt_now) {
RotaryResponse::NOTHING => {
continue;
}
RotaryResponse::UP => {
match mode {
FreqSelectMode::Coarse => clock_div = clock_div.saturating_add(1),
FreqSelectMode::Fine => frac = frac.saturating_add(1),
}
// println!("Rotary up");
}
RotaryResponse::DOWN => {
match mode {
FreqSelectMode::Coarse => clock_div = 3.max(clock_div - 1),
FreqSelectMode::Fine => frac = frac.saturating_sub(1),
}
// println!("Rotary dn");
}
}
sm.clock_divisor_fixed_point(clock_div, frac);
last_rotary = tick;
rotary_input_allowed = false;
let this_base_freq =
pll_freq.to_Hz() as usize / (clock_div as usize * WAVE_TABLE.len() * 4);
let next_freq =
pll_freq.to_Hz() as usize / ((clock_div + 1) as usize * WAVE_TABLE.len() * 4);
// this is pretty inaccurate as clock_div gets smaller
let calc_freq = this_base_freq - ((this_base_freq - next_freq) * (frac as usize)) / 255;
println!(
"mode {}\nclock_div {}, frac {}, freq {}",
mode, clock_div, frac, calc_freq
);
} }
} }
} }
#[derive(defmt::Format)]
enum FreqSelectMode {
Coarse,
Fine,
}

88
src/rotary.rs Normal file
View File

@@ -0,0 +1,88 @@
#[derive(Clone, Default)]
pub struct RotaryEnc {
state: RotaryEncState,
wait_for_reset: bool,
primed: bool
}
impl RotaryEnc {
pub fn update(&mut self, clk: bool, dt: bool) -> RotaryResponse {
let state_now = RotaryEncState::from_pin_states(clk, dt);
if state_now == self.state {
return RotaryResponse::NOTHING;
}
let response = match state_now {
RotaryEncState::BOTH_OFF => {
self.primed = false;
self.wait_for_reset = false;
RotaryResponse::NOTHING
}
RotaryEncState::CLK_ON | RotaryEncState::DT_ON => {
match (self.wait_for_reset, self.state) {
(false, RotaryEncState::BOTH_OFF) => {
self.primed = true;
}
_ => ()
}
RotaryResponse::NOTHING
},
RotaryEncState::BOTH_ON => {
match (self.primed, self.state) {
(true, RotaryEncState::CLK_ON) => {
self.primed = false;
RotaryResponse::UP
}
(true, RotaryEncState::DT_ON) => {
self.primed = false;
RotaryResponse::DOWN
}
_ => {
self.primed = false;
self.wait_for_reset = true;
RotaryResponse::NOTHING
}
}
}
};
self.state = state_now;
response
}
}
pub enum RotaryResponse {
NOTHING,
UP,
DOWN
}
#[derive(Clone, Copy, Default, PartialEq, Eq)]
enum RotaryEncState {
#[default]
BOTH_OFF,
CLK_ON,
DT_ON,
BOTH_ON
}
impl RotaryEncState {
pub fn from_pin_states(clk: bool, dt: bool) -> Self {
if clk && dt {
return Self::BOTH_ON;
}
if clk {
return Self::CLK_ON;
}
if dt {
return Self::DT_ON;
}
Self::BOTH_OFF
}
}