Skip to content
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
4d094e8
Port Gen3 Read Logic
Dec 10, 2025
e7413d1
Add new_with_rtscts_buffer
Dec 11, 2025
b168b71
Remove potential panic and change error type
Dec 12, 2025
c667ec1
Add time feature for new_with_rtscts_buffer
Dec 12, 2025
54fb33e
Merge branch main into personal/v-neilxu/portGen3ReadLogic
Dec 12, 2025
5a7481c
Update to avoid complie fail
Dec 12, 2025
f7a079f
Update commit for review
Dec 19, 2025
f2ad65c
Merge branch 'main' into personal/v-neilxu/portGen3ReadLogic
Dec 19, 2025
a6b66cd
Add time feature check back
Dec 19, 2025
c32df29
Add transfer import
Dec 19, 2025
ed62121
Use BBBuffer to implement circular buffer and add example
Dec 24, 2025
96c88d1
Merge branch main into personal/v-neilxu/portGen3ReadLogic
Dec 24, 2025
b1ce341
Update to avoid compiling fail
Dec 24, 2025
3f05448
Remove unused code
Dec 24, 2025
1b99e13
Remove redundant code in channel.rs and enlarge BBQueue size the add …
Dec 30, 2025
729215e
Merge branch 'main' into personal/v-neilxu/portGen3ReadLogic
Jan 7, 2026
a226078
Use ping pong buffer to implement continuous DMA transfer
Jan 13, 2026
5dcd4b5
Update to avoid building fail
Jan 13, 2026
d091d2f
Update Cargo.toml
Neil-alter Jan 13, 2026
95bb668
Remove unuse dependency
Jan 13, 2026
bf9e8c5
Remove unuse dependency
Jan 13, 2026
23e82c2
Update src/uart.rs to avoid panic
Neil-alter Jan 13, 2026
c2d3041
Update based on suggestion
Neil-alter Jan 13, 2026
2c5b37c
Update rename, overrun record and xfercount count for index
Jan 15, 2026
717c77c
Remove unnecessary import
Jan 15, 2026
4e5a9dd
Update based on copilot suggestion
Jan 15, 2026
d97ff2c
Fix UART async read error handling
jerrysxie Jan 16, 2026
fad5483
Merge branch 'main' into personal/v-neilxu/portGen3ReadLogic
jerrysxie Jan 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions examples/rt685s-evk/src/bin/uart-async_with_buffer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#![no_std]
#![no_main]

use defmt::info;
use embassy_executor::Spawner;
use embassy_imxrt::uart::{Async, Uart, UartRx};
use embassy_imxrt::{bind_interrupts, peripherals, uart};
use embassy_time::Timer;
use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _};

const BUFLEN: usize = 2048;
const POLLING_RATE_US: u64 = 1000;

bind_interrupts!(struct Irqs {
FLEXCOMM4 => uart::InterruptHandler<peripherals::FLEXCOMM4>;
});

#[embassy_executor::task]
async fn reader(mut rx: UartRx<'static, Async>) {
info!("Reading...");
let mut expected_counter = 0;
loop {
let mut buf = [0; BUFLEN / 2];
rx.read(&mut buf).await.unwrap();
info!("RX {:?}", buf);

for &b in buf.iter() {
assert_eq!(b, expected_counter);
}
expected_counter = expected_counter.wrapping_add(1);

Timer::after_millis(1).await;
}
}

#[embassy_executor::main]
async fn main(spawner: Spawner) {
let p = embassy_imxrt::init(Default::default());

info!("UART test start");

static mut RX_BUF: [u8; BUFLEN] = [0; BUFLEN];
let uart = Uart::new_async_with_buffer(
p.FLEXCOMM4,
p.PIO0_29,
p.PIO0_30,
Irqs,
p.DMA0_CH9,
p.DMA0_CH8,
Default::default(),
unsafe { &mut *core::ptr::addr_of_mut!(RX_BUF) },
POLLING_RATE_US,
)
.unwrap();
let (mut tx, rx) = uart.split();
spawner.must_spawn(reader(rx));

info!("Writing...");

let mut counter = 1;
loop {
let mut data: [u8; BUFLEN / 2] = [0; BUFLEN / 2];
for b in data.iter_mut() {
*b = counter;
}
tx.write(&data).await.unwrap();

counter = counter.wrapping_add(1);
}
}
92 changes: 92 additions & 0 deletions examples/rt685s-evk/src/bin/uart-async_with_rtscts_buffer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#![no_std]
#![no_main]

use defmt::info;
use embassy_executor::Spawner;
use embassy_imxrt::uart::{Async, Uart};
use embassy_imxrt::{bind_interrupts, peripherals, uart};
use embassy_time::Timer;
use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _};

bind_interrupts!(struct Irqs {
FLEXCOMM2 => uart::InterruptHandler<peripherals::FLEXCOMM2>;
FLEXCOMM4 => uart::InterruptHandler<peripherals::FLEXCOMM4>;
});

const BUFLEN: usize = 2048;
const POLLING_RATE_US: u64 = 1000;

#[embassy_executor::task]
async fn usart4_task(mut uart: Uart<'static, Async>) {
loop {
let mut rx_buf = [0; BUFLEN / 2];
uart.read(&mut rx_buf).await.unwrap();
assert!(rx_buf.iter().all(|b| *b == 0x55));
info!("usart4_task read");

Timer::after_millis(10).await;

let tx_buf = [0xaa; BUFLEN / 2];
uart.write(&tx_buf).await.unwrap();
info!("usart4_task write");
}
}

#[embassy_executor::task]
async fn usart2_task(mut uart: Uart<'static, Async>) {
loop {
let tx_buf = [0x55; BUFLEN / 2];
uart.write(&tx_buf).await.unwrap();
info!("usart2_task write");

Timer::after_millis(10).await;

let mut rx_buf = [0x00; BUFLEN / 2];
uart.read(&mut rx_buf).await.unwrap();
assert!(rx_buf.iter().all(|b| *b == 0xaa));
info!("usart2_task read");
}
}

/* WARNING: to enable HW flow control, a 0 ohm resistor on the EVK needs to moved to the neighboring pad.
*/
#[embassy_executor::main]
async fn main(spawner: Spawner) {
let p = embassy_imxrt::init(Default::default());

info!("UART test start");

static mut RX_BUF4: [u8; BUFLEN] = [0; BUFLEN];
let usart4: Uart<'_, Async> = Uart::new_async_with_rtscts_buffer(
p.FLEXCOMM4,
p.PIO0_29,
p.PIO0_30,
p.PIO1_0,
p.PIO0_31,
Irqs,
p.DMA0_CH9,
p.DMA0_CH8,
Default::default(),
unsafe { &mut *core::ptr::addr_of_mut!(RX_BUF4) },
POLLING_RATE_US,
)
.unwrap();
spawner.must_spawn(usart4_task(usart4));

static mut RX_BUF2: [u8; BUFLEN] = [0; BUFLEN];
let usart2 = Uart::new_async_with_rtscts_buffer(
p.FLEXCOMM2,
p.PIO0_15,
p.PIO0_16,
p.PIO0_18,
p.PIO0_17,
Irqs,
p.DMA0_CH5,
p.DMA0_CH4,
Default::default(),
unsafe { &mut *core::ptr::addr_of_mut!(RX_BUF2) },
POLLING_RATE_US,
)
.unwrap();
spawner.must_spawn(usart2_task(usart2));
}
142 changes: 141 additions & 1 deletion src/dma/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use core::marker::PhantomData;

use embassy_sync::waitqueue::AtomicWaker;

use super::DESCRIPTORS;
use super::{BufferConsumeStatus, DESCRIPTORS, PING_DESCRIPTORS, PING_PONG_STATUS, PONG_DESCRIPTORS, PingPongSelector};
use crate::dma::DmaInfo;
use crate::dma::transfer::{Direction, Transfer, TransferOptions};

Expand Down Expand Up @@ -47,6 +47,11 @@ impl<'d> Channel<'d> {
self.info.waker
}

/// Return the channel number
pub fn get_channel_number(&self) -> usize {
self.info.ch_num
}

/// Check whether DMA is active
pub fn is_active(&self) -> bool {
let channel = self.info.ch_num;
Expand Down Expand Up @@ -155,6 +160,102 @@ impl<'d> Channel<'d> {
});
}

/// Configure the DMA channel for ping-pong (double buffer) transfer
///
/// # Note
///
/// `mem_len` should be a multiple of the transfer width, otherwise transfer count will be rounded down
pub fn configure_channel_ping_pong(
&self,
dir: Direction,
srcbase: *const u32,
dstbase_a: *mut u32,
dstbase_b: *mut u32,
mem_len: usize,
options: TransferOptions,
) {
debug_assert!(mem_len.is_multiple_of(options.width.byte_width()));

let xferwidth: usize = options.width.byte_width();
let xfercount = (mem_len / xferwidth) - 1;
let channel = self.info.ch_num;

// Configure for transfer type, no hardware triggering (we'll trigger via software), high priority
// SAFETY: unsafe due to .bits usage
self.info.regs.channel(channel).cfg().write(|w| unsafe {
if dir == Direction::MemoryToMemory {
w.periphreqen().clear_bit();
} else {
w.periphreqen().set_bit();
}
w.hwtrigen().clear_bit();
w.chpriority().bits(0)
});

// Enable the interrupt on this channel
self.info
.regs
.intenset0()
.write(|w| unsafe { w.inten().bits(1 << channel) });

// Mark configuration valid, clear trigger on complete, width is 1 byte, source & destination increments are width x 1 (1 byte)
// SAFETY: unsafe due to .bits usage
self.info.regs.channel(channel).xfercfg().write(|w| unsafe {
w.cfgvalid().set_bit();
// Descriptor is exhausted and we need to manually hit SWTRIG to trigger the next one.
w.clrtrig().set_bit();
// Set reload to enable continuous ping-pong operation
w.reload().set_bit();
w.setinta().set_bit();
w.width().bits(options.width.into());
if dir == Direction::PeripheralToMemory {
w.srcinc().bits(0);
} else {
w.srcinc().bits(1);
}
if dir == Direction::MemoryToPeripheral {
w.dstinc().bits(0);
} else {
w.dstinc().bits(1);
}
w.xfercount().bits(xfercount as u16)
});

#[allow(clippy::indexing_slicing)]
let descriptor_initial = unsafe { &mut DESCRIPTORS.list[channel] };
#[allow(clippy::indexing_slicing)]
let descriptor_a = unsafe { &mut PING_DESCRIPTORS.list[channel] };
#[allow(clippy::indexing_slicing)]
let descriptor_b = unsafe { &mut PONG_DESCRIPTORS.list[channel] };

// Configure the channel descriptor
// NOTE: the DMA controller expects the memory buffer end address but peripheral address is actual
let xfer_cfg = self.info.regs.channel(channel).xfercfg().read();
descriptor_initial.reserved = 0;
descriptor_a.reserved = xfer_cfg.bits();
descriptor_b.reserved = xfer_cfg.bits();

descriptor_initial.src_data_end_addr = srcbase as u32;
descriptor_initial.dst_data_end_addr = dstbase_a as u32 + (xfercount * xferwidth) as u32;
descriptor_initial.nxt_desc_link_addr = descriptor_b as *const _ as u32;

descriptor_b.src_data_end_addr = srcbase as u32;
descriptor_b.dst_data_end_addr = dstbase_b as u32 + (xfercount * xferwidth) as u32;
descriptor_b.nxt_desc_link_addr = descriptor_a as *const _ as u32;

descriptor_a.src_data_end_addr = srcbase as u32;
descriptor_a.dst_data_end_addr = dstbase_a as u32 + (xfercount * xferwidth) as u32;
descriptor_a.nxt_desc_link_addr = descriptor_b as *const _ as u32;

#[allow(clippy::indexing_slicing)]
let ping_pong_status = unsafe { &mut PING_PONG_STATUS[channel] };
ping_pong_status.current = PingPongSelector::BufferA;
ping_pong_status.buffer_a_status = BufferConsumeStatus::Committed;
ping_pong_status.buffer_b_status = BufferConsumeStatus::Committed;

info!("DMA Ping-Pong Descriptors set up on channel {}", channel);
}

/// Enable the DMA channel (only after configuring)
// SAFETY: unsafe due to .bits usage
pub fn enable_channel(&self) {
Expand Down Expand Up @@ -182,4 +283,43 @@ impl<'d> Channel<'d> {
.xfercfg()
.modify(|_, w| w.swtrig().set_bit());
}

/// Return the current ping-pong buffer being used by the DMA channel
pub fn current_buffer(&self) -> PingPongSelector {
let channel = self.info.ch_num;
#[allow(clippy::indexing_slicing)]
let ping_pong_status = unsafe { &PING_PONG_STATUS[channel] };
ping_pong_status.current
}

/// Mark the specified ping-pong buffer as committed (ready for DMA to use)
///
/// # Safety
///
/// The caller must ensure that the buffer being committed is not currently being accessed
/// by the DMA controller or other parts of the system.
pub unsafe fn commit_buffer(&self, selector: PingPongSelector) {
let channel = self.info.ch_num;
#[allow(clippy::indexing_slicing)]
let ping_pong_status = unsafe { &mut PING_PONG_STATUS[channel] };
match selector {
PingPongSelector::BufferA => {
ping_pong_status.buffer_a_status = BufferConsumeStatus::Committed;
}
PingPongSelector::BufferB => {
ping_pong_status.buffer_b_status = BufferConsumeStatus::Committed;
}
}
}

/// Get the status of the specified ping-pong buffer
pub fn buffer_status(&self, selector: PingPongSelector) -> BufferConsumeStatus {
let channel = self.info.ch_num;
#[allow(clippy::indexing_slicing)]
let ping_pong_status = unsafe { &PING_PONG_STATUS[channel] };
match selector {
PingPongSelector::BufferA => ping_pong_status.buffer_a_status,
PingPongSelector::BufferB => ping_pong_status.buffer_b_status,
}
}
}
Loading