time to do final merge

This commit is contained in:
Priec
2025-11-19 21:46:07 +01:00
parent e569fbc39d
commit 8e1c2ec29f
26 changed files with 2940 additions and 2 deletions

View File

@@ -0,0 +1,43 @@
// src/software_uart/debug.rs
use defmt::info;
pub fn dump_tim6_regs() {
use embassy_stm32::pac::timer::TimBasic;
let tim = unsafe { TimBasic::from_ptr(0x4000_1000usize as _) };
let sr = tim.sr().read();
let dier = tim.dier().read();
let cr1 = tim.cr1().read();
let arr = tim.arr().read().arr();
let psc = tim.psc().read();
info!(
"TIM6: CR1.CEN={} DIER.UDE={} SR.UIF={} PSC={} ARR={}",
cr1.cen(),
dier.ude(),
sr.uif(),
psc,
arr
);
}
pub fn dump_dma_ch0_regs() {
use embassy_stm32::pac::gpdma::Gpdma;
let dma = unsafe { Gpdma::from_ptr(0x4002_0000usize as _) };
let ch = dma.ch(0);
let cr = ch.cr().read();
let tr1 = ch.tr1().read();
let tr2 = ch.tr2().read();
let br1 = ch.br1().read();
info!(
"GPDMA1_CH0: EN={} PRIO={} SDW={} DDW={} SINC={} DINC={} REQSEL={} SWREQ={} DREQ={} BNDT={}",
cr.en(),
cr.prio(),
tr1.sdw(),
tr1.ddw(),
tr1.sinc(),
tr1.dinc(),
tr2.reqsel(),
tr2.swreq(),
tr2.dreq(),
br1.bndt()
);
}

View File

@@ -0,0 +1,66 @@
// src/dma_timer.rs
use embassy_stm32::{
peripherals::{TIM6, TIM7},
rcc,
timer::low_level::Timer,
Peri,
};
use core::mem;
use embassy_stm32::timer::BasicInstance;
use embassy_stm32::pac::timer::vals::Urs;
/// Initializes TIM6 to tick at `baud * oversample` frequency.
/// Each TIM6 update event triggers one DMA beat.
pub fn init_tim6_for_uart<'d>(tim6: Peri<'d, TIM6>, baud: u32, oversample: u16) {
rcc::enable_and_reset::<TIM6>();
let ll = Timer::new(tim6);
configure_basic_timer(&ll, baud, oversample);
mem::forget(ll);
}
/// Initializes TIM7 to tick at `baud * oversample` frequency.
/// Each TIM7 update event triggers one DMA beat.
pub fn init_tim7_for_uart<'d>(tim7: Peri<'d, TIM7>, baud: u32, oversample: u16) {
rcc::enable_and_reset::<TIM7>();
let ll = Timer::new(tim7);
configure_basic_timer(&ll, baud, oversample);
// Enable Update Interrupt (UIE)
ll.regs_basic().dier().modify(|w| {
w.set_ude(true);
w.set_uie(false);
});
mem::forget(ll);
}
// Shared internal helper — identical CR1/ARR setup
fn configure_basic_timer<T: BasicInstance>(ll: &Timer<'_, T>, baud: u32, oversample: u16) {
let f_timer = rcc::frequency::<T>().0;
let target = baud.saturating_mul(oversample.max(1) as u32).max(1);
// Compute ARR (prescaler = 0)
let mut arr = (f_timer / target).saturating_sub(1) as u16;
if arr == 0 { arr = 1; }
ll.regs_basic().cr1().write(|w| {
w.set_cen(false);
w.set_opm(false);
w.set_udis(false);
w.set_urs(Urs::ANY_EVENT);
});
ll.regs_basic().psc().write_value(0u16);
ll.regs_basic().arr().write(|w| w.set_arr(arr));
ll.regs_basic().dier().modify(|w| w.set_ude(true));
ll.regs_basic().egr().write(|w| w.set_ug(true));
// Clear spurious UIF from UG trigger
ll.regs_basic().sr().modify(|w| w.set_uif(false));
ll.regs_basic().cr1().write(|w| {
w.set_opm(false);
w.set_cen(true);
w.set_udis(false);
w.set_urs(Urs::ANY_EVENT);
});
}

View File

@@ -0,0 +1,96 @@
// src/software_uart/runtime.rs
use embassy_executor::task;
use embassy_stm32::{
dma::Request,
peripherals::GPDMA1_CH1,
Peri,
};
use crate::config::RX_PIN_BIT;
use embassy_stm32::dma::{
ReadableRingBuffer,
TransferOptions,
};
use crate::config::{RX_OVERSAMPLE, UART_CFG};
use crate::software_uart::decode_uart_samples;
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, pipe::Pipe};
use embassy_futures::yield_now;
use defmt::info;
// datasheet tabulka 137
pub const TIM7_UP_REQ: Request = 5;
/// RX DMA task: reads GPIO samples paced by TIM7 and fills PIPE_RX
#[task]
pub async fn rx_dma_task(
ch: Peri<'static, GPDMA1_CH1>,
register: *mut u8,
ring: &'static mut [u8],
pipe_rx: &'static Pipe<CriticalSectionRawMutex, 4096>,
) {
let mut opts = TransferOptions::default();
opts.half_transfer_ir = true;
opts.complete_transfer_ir = true;
// SAFETY: ring is exclusive to this task
let mut rx = unsafe { ReadableRingBuffer::new(ch, TIM7_UP_REQ, register, ring, opts) };
rx.start();
// We read into the second half of a buffer, keeping "leftovers" in the first half.
const CHUNK_SIZE: usize = 4096;
const HISTORY_SIZE: usize = 512; // Enough to hold a potential split frame
const TOTAL_BUF_SIZE: usize = HISTORY_SIZE + CHUNK_SIZE;
// Logic level buffer
let mut level_buf = [0u8; TOTAL_BUF_SIZE];
let mut valid_len = 0usize;
let mut raw_chunk = [0u8; CHUNK_SIZE];
loop {
let _ = rx.read_exact(&mut raw_chunk).await;
for (i, b) in raw_chunk.iter().enumerate() {
level_buf[valid_len + i] = ((*b >> RX_PIN_BIT) & 1) as u8;
}
let current_end = valid_len + CHUNK_SIZE;
// 3. Decode everything we have
let (decoded, consumed) = decode_uart_samples(
&level_buf[..current_end],
RX_OVERSAMPLE,
&UART_CFG
);
if !decoded.is_empty() {
pipe_rx.write(decoded.as_slice()).await;
for byte in decoded.as_slice() {
// info!("DMA BUFFER CHAR: {} (ASCII: {})", *byte, *byte as char);
}
}
// 4. Shift remaining data to front
// We processed 'consumed' samples.
// We keep everything from 'consumed' up to 'current_end'.
let remaining = current_end - consumed;
// Safety check: if remaining > HISTORY_SIZE, we are in trouble (buffer too small / decoder stuck).
// But for now, just shift.
if remaining > 0 {
level_buf.copy_within(consumed..current_end, 0);
}
valid_len = remaining;
// If valid_len grows too large (decoder not consuming), we must discard to avoid panic on next write
if valid_len >= HISTORY_SIZE {
// Discard oldest to make space
// logic: we move the last (HISTORY_SIZE/2) to 0.
// This effectively "skips" garbage data.
let keep = HISTORY_SIZE / 2;
level_buf.copy_within(valid_len - keep..valid_len, 0);
valid_len = keep;
}
yield_now().await;
}
}

View File

@@ -0,0 +1,84 @@
// src/software_uart/gpio_dma_uart_tx.rs
use embassy_executor::task;
use embassy_stm32::{
dma::{Request, TransferOptions, WritableRingBuffer},
peripherals::GPDMA1_CH0,
Peri,
};
use embassy_futures::yield_now;
use defmt::info;
use embassy_sync::pipe::Pipe;
use crate::config::{TX_PIN_BIT, UART_CFG};
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use crate::software_uart::uart_emulation::encode_uart_byte_cfg;
pub const TIM6_UP_REQ: Request = 4;
pub async fn encode_uart_frames<'a>(
pin_bit: u8,
bytes: &[u8],
out_buf: &'a mut [u32],
) -> usize {
let mut offset = 0;
for &b in bytes {
let mut frame = [0u32; 12];
let used = encode_uart_byte_cfg(pin_bit, b, &UART_CFG, &mut frame);
if offset + used <= out_buf.len() {
out_buf[offset..offset + used].copy_from_slice(&frame[..used]);
offset += used;
} else {
break;
}
// cooperative async yield
yield_now().await;
}
offset
}
/// TX DMA task: encodes UART frames and sends them via DMA at TIM6 rate
#[task]
pub async fn tx_dma_task(
ch: Peri<'static, GPDMA1_CH0>,
register: *mut u32, // Either odr or bsrr
tx_ring_mem: &'static mut [u32],
pipe_rx: &'static Pipe<CriticalSectionRawMutex, 1024>,
) {
let mut tx_opts = TransferOptions::default();
tx_opts.half_transfer_ir = true;
tx_opts.complete_transfer_ir = true;
// SAFETY: tx_ring is exclusive to this task
let mut tx_ring = unsafe {
WritableRingBuffer::new(
ch,
TIM6_UP_REQ,
register,
tx_ring_mem,
tx_opts,
)
};
tx_ring.start();
info!("TX DMA ring started");
let mut frame_buf = [0u32; 4096];
let mut rx_buf = [0u8; 256];
loop {
let n = pipe_rx.read(&mut rx_buf).await;
if n == 0 {
yield_now().await;
continue;
}
let used = encode_uart_frames(TX_PIN_BIT, &rx_buf[..n], &mut frame_buf).await;
if used > 0 {
let _ = tx_ring.write_exact(&frame_buf[..used]).await;
}
info!("tx_dma_task wrote {} words", used);
yield_now().await;
}
}

View File

@@ -0,0 +1,13 @@
// src/software_uart/mod.rs
pub mod gpio_dma_uart_tx;
pub mod gpio_dma_uart_rx;
pub mod dma_timer;
pub mod uart_emulation;
pub mod debug;
pub use gpio_dma_uart_tx::*;
pub use gpio_dma_uart_rx::*;
pub use dma_timer::*;
pub use uart_emulation::*;
pub use debug::*;

View File

@@ -0,0 +1,204 @@
// src/software_uart/uart_emulation.rs
use heapless::Vec;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Parity {
None,
Even,
Odd,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum StopBits {
One,
Two,
}
#[derive(Clone, Copy, Debug)]
pub struct UartConfig {
pub data_bits: u8,
pub parity: Parity,
pub stop_bits: StopBits,
}
impl Default for UartConfig {
fn default() -> Self {
Self {
data_bits: 8,
parity: Parity::None,
stop_bits: StopBits::One,
}
}
}
/// Encodes one byte into a sequence of GPIO BSRR words
pub fn encode_uart_byte_cfg(
pin_bit: u8,
data: u8,
cfg: &UartConfig,
out: &mut [u32; 12],
) -> usize {
// GPIOx_BSRR register str. 636 kap. 13.4.7
let set_high = |bit: u8| -> u32 { 1u32 << bit };
// let set_low = |bit: u8| -> u32 { 0 }; // ODR
let set_low = |bit: u8| -> u32 { 1u32 << (bit as u32 + 16) }; // BSRR
let mut idx = 0usize;
// START bit (LOW)
out[idx] = set_low(pin_bit);
idx += 1;
// Data bits, LSB-first
let nbits = cfg.data_bits.clamp(5, 8);
for i in 0..nbits {
let one = ((data >> i) & 1) != 0;
out[idx] = if one { set_high(pin_bit) } else { set_low(pin_bit) };
idx += 1;
}
// Parity
match cfg.parity {
Parity::None => {}
Parity::Even | Parity::Odd => {
let mask: u8 = if nbits == 8 { 0xFF } else { (1u16 << nbits) as u8 - 1 };
let ones = (data & mask).count_ones() & 1;
let par_bit_is_one = match cfg.parity {
Parity::Even => ones == 1,
Parity::Odd => ones == 0,
_ => false,
};
out[idx] = if par_bit_is_one {
set_high(pin_bit)
} else {
set_low(pin_bit)
};
idx += 1;
}
}
// STOP bits (HIGH)
let stop_ticks = match cfg.stop_bits {
StopBits::One => 1usize,
StopBits::Two => 2usize,
};
for _ in 0..stop_ticks {
out[idx] = set_high(pin_bit);
idx += 1;
}
idx
}
/// Decode an oversampled stream of logic levels into UART bytes.
/// Returns (decoded bytes, number of samples consumed/processed).
pub fn decode_uart_samples(
samples: &[u8],
oversample: u16,
cfg: &UartConfig,
) -> (heapless::Vec<u8, 256>, usize) {
let mut out = Vec::<u8, 256>::new();
let mut idx = 0usize;
let nbits = cfg.data_bits as usize;
let ovs = oversample as usize;
// Calculate total frame width in samples to ensure we have enough data
// 1 start + n data + parity? + stops
let parity_bits = match cfg.parity {
Parity::None => 0,
_ => 1,
};
let stop_bits_count = match cfg.stop_bits {
StopBits::One => 1,
StopBits::Two => 2,
};
let frame_bits = 1 + nbits + parity_bits + stop_bits_count;
let frame_len = frame_bits * ovs;
// Helper: Majority vote over 3 samples centered at `i`
let get_bit = |i: usize| -> u8 {
let mut votes = 0;
// Check i-1, i, i+1. Saturating sub/add handles boundaries roughly.
if i > 0 && samples.get(i - 1).map_or(true, |&x| x != 0) {
votes += 1;
}
if samples.get(i).map_or(true, |&x| x != 0) {
votes += 1;
}
if samples.get(i + 1).map_or(true, |&x| x != 0) {
votes += 1;
}
if votes >= 2 {
1
} else {
0
}
};
// We loop while we have enough remaining samples for a full frame
while idx + frame_len <= samples.len() {
// Wait for falling edge (High -> Low)
// samples[idx] == 1 (Idle/Stop) && samples[idx+1] == 0 (Start)
if samples[idx] != 0 && samples[idx + 1] == 0 {
// Align to center of START bit
// Start bit begins at idx+1. Center is at idx + 1 + (ovs/2)
let center_offset = 1 + (ovs / 2);
let mut scan_idx = idx + center_offset;
// 1. Validate Start Bit (Must be 0)
if get_bit(scan_idx) != 0 {
idx += 1; // False start (noise), move on
continue;
}
// Move to center of first data bit
scan_idx += ovs;
// 2. Read Data Bits
let mut data: u8 = 0;
for bit in 0..nbits {
if get_bit(scan_idx) == 1 {
data |= 1 << bit;
}
scan_idx += ovs;
}
// 3. Skip Parity (if any)
if cfg.parity != Parity::None {
scan_idx += ovs;
}
// 4. Validate Stop Bit (Must be 1)
// If stop bit is 0, it's a framing error. We reject the whole byte.
if get_bit(scan_idx) == 0 {
idx += 1; // Try to find a real start bit on the next sample
continue;
}
// 5. Byte is valid
let _ = out.push(data);
// 6. Active Resync: Fast-forward through the stop bit(s) and idle time
// scan_idx is currently at the center of the Stop bit.
idx = scan_idx;
// Advance while we are reading High (1).
// As soon as we see Low (0), we stop. That 0 is the beginning of the NEXT start bit.
// The outer loop expects `idx` to be the High *before* the start bit, so we will handle that.
while idx < samples.len() && samples[idx] != 0 {
idx += 1;
}
// Back up one step.
// The outer loop logic is: `if samples[idx] != 0 && samples[idx+1] == 0`.
// If we stopped at `idx` because it was 0, then `idx-1` was the last 1 (Idle).
if idx > 0 {
idx -= 1;
}
} else {
// No start bit detected here, move to next sample
idx += 1;
}
}
(out, idx)
}