Basic DMA HAL #19
274
examples/simple/examples/dma.rs
Normal file
274
examples/simple/examples/dma.rs
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
//! Simple DMA example
|
||||||
|
#![no_main]
|
||||||
|
#![no_std]
|
||||||
|
|
||||||
|
use core::cell::Cell;
|
||||||
|
|
||||||
|
use cortex_m::interrupt::Mutex;
|
||||||
|
use cortex_m_rt::entry;
|
||||||
|
use embedded_hal::delay::DelayNs;
|
||||||
|
use panic_rtt_target as _;
|
||||||
|
use rtt_target::{rprintln, rtt_init_print};
|
||||||
|
use simple_examples::peb1;
|
||||||
|
use va416xx_hal::dma::{Dma, DmaCfg, DmaChannel, DmaCtrlBlock};
|
||||||
|
use va416xx_hal::pwm::CountdownTimer;
|
||||||
|
use va416xx_hal::{
|
||||||
|
pac::{self, interrupt},
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
|
|
||||||
|
static DMA_DONE_FLAG: Mutex<Cell<bool>> = Mutex::new(Cell::new(false));
|
||||||
|
static DMA_ACTIVE_FLAG: Mutex<Cell<bool>> = Mutex::new(Cell::new(false));
|
||||||
|
|
||||||
|
// Place the DMA control block into SRAM1 statically. This section needs to be defined in
|
||||||
|
// memory.x
|
||||||
|
#[link_section = ".sram1"]
|
||||||
|
static mut DMA_CTRL_BLOCK: DmaCtrlBlock = DmaCtrlBlock::new();
|
||||||
|
|
||||||
|
// We can use statically allocated buffers for DMA transfers as well.
|
||||||
|
#[link_section = ".sram1"]
|
||||||
|
static mut DMA_SRC_BUF: [u16; 36] = [0; 36];
|
||||||
|
#[link_section = ".sram1"]
|
||||||
|
static mut DMA_DEST_BUF: [u16; 36] = [0; 36];
|
||||||
|
|
||||||
|
#[entry]
|
||||||
|
fn main() -> ! {
|
||||||
|
rtt_init_print!();
|
||||||
|
rprintln!("VA416xx DMA example");
|
||||||
|
|
||||||
|
let mut dp = pac::Peripherals::take().unwrap();
|
||||||
|
// Use the external clock connected to XTAL_N.
|
||||||
|
let clocks = dp
|
||||||
|
.clkgen
|
||||||
|
.constrain()
|
||||||
|
.xtal_n_clk_with_src_freq(peb1::EXTCLK_FREQ)
|
||||||
|
.freeze(&mut dp.sysconfig)
|
||||||
|
.unwrap();
|
||||||
|
// Safety: The DMA control block has an alignment rule of 128 and we constructed it directly
|
||||||
|
// statically.
|
||||||
|
let dma = Dma::new(&mut dp.sysconfig, dp.dma, DmaCfg::default(), unsafe {
|
||||||
|
core::ptr::addr_of_mut!(DMA_CTRL_BLOCK)
|
||||||
|
})
|
||||||
|
.expect("error creating DMA");
|
||||||
|
let (mut dma0, _, _, _) = dma.split();
|
||||||
|
let mut delay_ms = CountdownTimer::new(&mut dp.sysconfig, dp.tim0, &clocks);
|
||||||
|
let mut src_buf_8_bit: [u8; 65] = [0; 65];
|
||||||
|
let mut dest_buf_8_bit: [u8; 65] = [0; 65];
|
||||||
|
let mut src_buf_32_bit: [u32; 17] = [0; 17];
|
||||||
|
let mut dest_buf_32_bit: [u32; 17] = [0; 17];
|
||||||
|
loop {
|
||||||
|
// This example uses stack-allocated buffers.
|
||||||
|
transfer_example_8_bit(
|
||||||
|
&mut src_buf_8_bit,
|
||||||
|
&mut dest_buf_8_bit,
|
||||||
|
&mut dma0,
|
||||||
|
&mut delay_ms,
|
||||||
|
);
|
||||||
|
delay_ms.delay_ms(500);
|
||||||
|
// This example uses statically allocated buffers.
|
||||||
|
transfer_example_16_bit(&mut dma0, &mut delay_ms);
|
||||||
|
delay_ms.delay_ms(500);
|
||||||
|
transfer_example_32_bit(
|
||||||
|
&mut src_buf_32_bit,
|
||||||
|
&mut dest_buf_32_bit,
|
||||||
|
&mut dma0,
|
||||||
|
&mut delay_ms,
|
||||||
|
);
|
||||||
|
delay_ms.delay_ms(500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transfer_example_8_bit(
|
||||||
|
src_buf: &mut [u8; 65],
|
||||||
|
dest_buf: &mut [u8; 65],
|
||||||
|
dma0: &mut DmaChannel,
|
||||||
|
delay_ms: &mut CountdownTimer<pac::Tim0>,
|
||||||
|
) {
|
||||||
|
(0..64).for_each(|i| {
|
||||||
|
src_buf[i] = i as u8;
|
||||||
|
});
|
||||||
|
cortex_m::interrupt::free(|cs| {
|
||||||
|
DMA_DONE_FLAG.borrow(cs).set(false);
|
||||||
|
});
|
||||||
|
cortex_m::interrupt::free(|cs| {
|
||||||
|
DMA_ACTIVE_FLAG.borrow(cs).set(false);
|
||||||
|
});
|
||||||
|
// Safety: The source and destination buffer are valid for the duration of the DMA transfer.
|
||||||
|
unsafe {
|
||||||
|
dma0.prepare_mem_to_mem_transfer_8_bit(src_buf, dest_buf)
|
||||||
|
.expect("error preparing transfer");
|
||||||
|
}
|
||||||
|
// Enable all interrupts.
|
||||||
|
// Safety: Not using mask based critical sections.
|
||||||
|
unsafe {
|
||||||
|
dma0.enable_done_interrupt();
|
||||||
|
dma0.enable_active_interrupt();
|
||||||
|
};
|
||||||
|
// Enable the individual channel.
|
||||||
|
dma0.enable();
|
||||||
|
// We still need to manually trigger the DMA request.
|
||||||
|
dma0.trigger_with_sw_request();
|
||||||
|
// Use polling for completion status.
|
||||||
|
loop {
|
||||||
|
let mut dma_done = false;
|
||||||
|
cortex_m::interrupt::free(|cs| {
|
||||||
|
if DMA_ACTIVE_FLAG.borrow(cs).get() {
|
||||||
|
rprintln!("DMA0 is active with 8 bit transfer");
|
||||||
|
DMA_ACTIVE_FLAG.borrow(cs).set(false);
|
||||||
|
}
|
||||||
|
if DMA_DONE_FLAG.borrow(cs).get() {
|
||||||
|
dma_done = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if dma_done {
|
||||||
|
rprintln!("8-bit transfer done");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
delay_ms.delay_ms(1);
|
||||||
|
}
|
||||||
|
(0..64).for_each(|i| {
|
||||||
|
assert_eq!(dest_buf[i], i as u8);
|
||||||
|
});
|
||||||
|
// Sentinel value, should be 0.
|
||||||
|
assert_eq!(dest_buf[64], 0);
|
||||||
|
dest_buf.fill(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transfer_example_16_bit(dma0: &mut DmaChannel, delay_ms: &mut CountdownTimer<pac::Tim0>) {
|
||||||
|
unsafe {
|
||||||
|
// Set values scaled from 0 to 65535 to verify this is really a 16-bit transfer.
|
||||||
|
(0..32).for_each(|i| {
|
||||||
|
DMA_SRC_BUF[i] = (i as u32 * u16::MAX as u32 / (DMA_SRC_BUF.len() - 1) as u32) as u16;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
cortex_m::interrupt::free(|cs| {
|
||||||
|
DMA_DONE_FLAG.borrow(cs).set(false);
|
||||||
|
});
|
||||||
|
cortex_m::interrupt::free(|cs| {
|
||||||
|
DMA_ACTIVE_FLAG.borrow(cs).set(false);
|
||||||
|
});
|
||||||
|
let dest_buf_ref = unsafe { &mut *core::ptr::addr_of_mut!(DMA_DEST_BUF[0..32]) };
|
||||||
|
// Safety: The source and destination buffer are valid for the duration of the DMA transfer.
|
||||||
|
unsafe {
|
||||||
|
dma0.prepare_mem_to_mem_transfer_16_bit(
|
||||||
|
&*core::ptr::addr_of!(DMA_SRC_BUF[0..32]),
|
||||||
|
dest_buf_ref,
|
||||||
|
)
|
||||||
|
.expect("error preparing transfer");
|
||||||
|
}
|
||||||
|
// Enable all interrupts.
|
||||||
|
// Safety: Not using mask based critical sections.
|
||||||
|
unsafe {
|
||||||
|
dma0.enable_done_interrupt();
|
||||||
|
dma0.enable_active_interrupt();
|
||||||
|
};
|
||||||
|
// Enable the individual channel.
|
||||||
|
dma0.enable();
|
||||||
|
// We still need to manually trigger the DMA request.
|
||||||
|
dma0.trigger_with_sw_request();
|
||||||
|
// Use polling for completion status.
|
||||||
|
loop {
|
||||||
|
let mut dma_done = false;
|
||||||
|
cortex_m::interrupt::free(|cs| {
|
||||||
|
if DMA_ACTIVE_FLAG.borrow(cs).get() {
|
||||||
|
rprintln!("DMA0 is active with 16-bit transfer");
|
||||||
|
DMA_ACTIVE_FLAG.borrow(cs).set(false);
|
||||||
|
}
|
||||||
|
if DMA_DONE_FLAG.borrow(cs).get() {
|
||||||
|
dma_done = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if dma_done {
|
||||||
|
rprintln!("16-bit transfer done");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
delay_ms.delay_ms(1);
|
||||||
|
}
|
||||||
|
(0..32).for_each(|i| {
|
||||||
|
assert_eq!(
|
||||||
|
dest_buf_ref[i],
|
||||||
|
(i as u32 * u16::MAX as u32 / (dest_buf_ref.len() - 1) as u32) as u16
|
||||||
|
);
|
||||||
|
});
|
||||||
|
// Sentinel value, should be 0.
|
||||||
|
assert_eq!(dest_buf_ref[32], 0);
|
||||||
|
dest_buf_ref.fill(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transfer_example_32_bit(
|
||||||
|
src_buf: &mut [u32; 17],
|
||||||
|
dest_buf: &mut [u32; 17],
|
||||||
|
dma0: &mut DmaChannel,
|
||||||
|
delay_ms: &mut CountdownTimer<pac::Tim0>,
|
||||||
|
) {
|
||||||
|
// Set values scaled from 0 to 65535 to verify this is really a 16-bit transfer.
|
||||||
|
(0..16).for_each(|i| {
|
||||||
|
src_buf[i] = (i as u64 * u32::MAX as u64 / (src_buf.len() - 1) as u64) as u32;
|
||||||
|
});
|
||||||
|
cortex_m::interrupt::free(|cs| {
|
||||||
|
DMA_DONE_FLAG.borrow(cs).set(false);
|
||||||
|
});
|
||||||
|
cortex_m::interrupt::free(|cs| {
|
||||||
|
DMA_ACTIVE_FLAG.borrow(cs).set(false);
|
||||||
|
});
|
||||||
|
// Safety: The source and destination buffer are valid for the duration of the DMA transfer.
|
||||||
|
unsafe {
|
||||||
|
dma0.prepare_mem_to_mem_transfer_32_bit(src_buf, dest_buf)
|
||||||
|
.expect("error preparing transfer");
|
||||||
|
}
|
||||||
|
// Enable all interrupts.
|
||||||
|
// Safety: Not using mask based critical sections.
|
||||||
|
unsafe {
|
||||||
|
dma0.enable_done_interrupt();
|
||||||
|
dma0.enable_active_interrupt();
|
||||||
|
};
|
||||||
|
// Enable the individual channel.
|
||||||
|
dma0.enable();
|
||||||
|
// We still need to manually trigger the DMA request.
|
||||||
|
dma0.trigger_with_sw_request();
|
||||||
|
// Use polling for completion status.
|
||||||
|
loop {
|
||||||
|
let mut dma_done = false;
|
||||||
|
cortex_m::interrupt::free(|cs| {
|
||||||
|
if DMA_ACTIVE_FLAG.borrow(cs).get() {
|
||||||
|
rprintln!("DMA0 is active with 32-bit transfer");
|
||||||
|
DMA_ACTIVE_FLAG.borrow(cs).set(false);
|
||||||
|
}
|
||||||
|
if DMA_DONE_FLAG.borrow(cs).get() {
|
||||||
|
dma_done = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if dma_done {
|
||||||
|
rprintln!("32-bit transfer done");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
delay_ms.delay_ms(1);
|
||||||
|
}
|
||||||
|
(0..16).for_each(|i| {
|
||||||
|
assert_eq!(
|
||||||
|
dest_buf[i],
|
||||||
|
(i as u64 * u32::MAX as u64 / (src_buf.len() - 1) as u64) as u32
|
||||||
|
);
|
||||||
|
});
|
||||||
|
// Sentinel value, should be 0.
|
||||||
|
assert_eq!(dest_buf[16], 0);
|
||||||
|
dest_buf.fill(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[interrupt]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
fn DMA_DONE0() {
|
||||||
|
// Notify the main loop that the DMA transfer is finished.
|
||||||
|
cortex_m::interrupt::free(|cs| {
|
||||||
|
DMA_DONE_FLAG.borrow(cs).set(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[interrupt]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
fn DMA_ACTIVE0() {
|
||||||
|
// Notify the main loop that the DMA 0 is active now.
|
||||||
|
cortex_m::interrupt::free(|cs| {
|
||||||
|
DMA_ACTIVE_FLAG.borrow(cs).set(true);
|
||||||
|
});
|
||||||
|
}
|
@ -8,10 +8,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
|
|
||||||
## [unreleased]
|
## [unreleased]
|
||||||
|
|
||||||
## [v0.1.1] 2024-07-01
|
## [v0.2.0]
|
||||||
|
|
||||||
- Documentation improvements
|
- Documentation improvements
|
||||||
- Small fixes and improvements for ADC drivers
|
- Small fixes and improvements for ADC drivers
|
||||||
|
- Added basic DMA driver
|
||||||
|
|
||||||
## [v0.1.0] 2024-07-01
|
## [v0.1.0] 2024-07-01
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ embedded-io = "0.6"
|
|||||||
num_enum = { version = "0.7", default-features = false }
|
num_enum = { version = "0.7", default-features = false }
|
||||||
typenum = "1"
|
typenum = "1"
|
||||||
bitflags = "2"
|
bitflags = "2"
|
||||||
|
bitfield = "0.15"
|
||||||
defmt = { version = "0.3", optional = true }
|
defmt = { version = "0.3", optional = true }
|
||||||
fugit = "0.3"
|
fugit = "0.3"
|
||||||
delegate = "0.12"
|
delegate = "0.12"
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
MEMORY
|
|
||||||
{
|
|
||||||
FLASH : ORIGIN = 0x00000000, LENGTH = 256K
|
|
||||||
/* RAM is a mandatory region. This RAM refers to the SRAM_0 */
|
|
||||||
RAM : ORIGIN = 0x1FFF8000, LENGTH = 32K
|
|
||||||
SRAM_1 : ORIGIN = 0x20000000, LENGTH = 32K
|
|
||||||
}
|
|
||||||
|
|
||||||
/* This is where the call stack will be allocated. */
|
|
||||||
/* The stack is of the full descending type. */
|
|
||||||
/* NOTE Do NOT modify `_stack_start` unless you know what you are doing */
|
|
||||||
/* SRAM_0 can be used for all busses: Instruction, Data and System */
|
|
||||||
/* SRAM_1 only supports the system bus */
|
|
||||||
_stack_start = ORIGIN(RAM) + LENGTH(RAM) - 4;
|
|
571
va416xx-hal/src/dma.rs
Normal file
571
va416xx-hal/src/dma.rs
Normal file
@ -0,0 +1,571 @@
|
|||||||
|
//! API for the DMA peripheral
|
||||||
|
//!
|
||||||
|
//! ## Examples
|
||||||
|
//!
|
||||||
|
//! - [Simple DMA example](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/dma.rs)
|
||||||
|
use crate::{
|
||||||
|
clock::{PeripheralClock, PeripheralSelect},
|
||||||
|
enable_interrupt, pac,
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
|
|
||||||
|
const MAX_DMA_TRANSFERS_PER_CYCLE: usize = 1024;
|
||||||
|
const BASE_PTR_ADDR_MASK: u32 = 0b1111111;
|
||||||
|
|
||||||
|
/// DMA cycle control values.
|
||||||
|
///
|
||||||
|
/// Refer to chapter 6.3.1 and 6.6.3 of the datasheet for more details.
|
||||||
|
#[repr(u8)]
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum CycleControl {
|
||||||
|
/// Indicates that the data structure is invalid.
|
||||||
|
Stop = 0b000,
|
||||||
|
/// The controller must receive a new request prior to entering the arbitration
|
||||||
|
/// process, to enable the DMA cycle to complete. This means that the DMA will only
|
||||||
|
/// continue to do transfers as long as a trigger signal is still active. Therefore,
|
||||||
|
/// this should not be used for momentary triggers like a timer.
|
||||||
|
Basic = 0b001,
|
||||||
|
/// The controller automatically inserts a request for the appropriate channel during the
|
||||||
|
/// arbitration process. This means that the initial request is sufficient to enable the
|
||||||
|
/// DMA cycle to complete.
|
||||||
|
Auto = 0b010,
|
||||||
|
/// This is used to support continuous data flow. Both primary and alternate data structure
|
||||||
|
/// are used. The primary data structure is used first. When the first transfer is complete, an
|
||||||
|
/// interrupt can be generated, and the DMA switches to the alternate data structure. When the
|
||||||
|
/// second transfer is complete, the primary data structure is used. This pattern continues
|
||||||
|
/// until software disables the channel.
|
||||||
|
PingPong = 0b011,
|
||||||
|
MemScatterGatherPrimary = 0b100,
|
||||||
|
MemScatterGatherAlternate = 0b101,
|
||||||
|
PeriphScatterGatherPrimary = 0b110,
|
||||||
|
PeriphScatterGatherAlternate = 0b111,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum AddrIncrement {
|
||||||
|
Byte = 0b00,
|
||||||
|
Halfword = 0b01,
|
||||||
|
Word = 0b10,
|
||||||
|
None = 0b11,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum DataSize {
|
||||||
|
Byte = 0b00,
|
||||||
|
Halfword = 0b01,
|
||||||
|
Word = 0b10,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This configuration controls how many DMA transfers can occur before the controller arbitrates.
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum RPower {
|
||||||
|
EachTransfer = 0b0000,
|
||||||
|
Every2 = 0b0001,
|
||||||
|
Every4 = 0b0010,
|
||||||
|
Every8 = 0b0011,
|
||||||
|
Every16 = 0b0100,
|
||||||
|
Every32 = 0b0101,
|
||||||
|
Every64 = 0b0110,
|
||||||
|
Every128 = 0b0111,
|
||||||
|
Every256 = 0b1000,
|
||||||
|
Every512 = 0b1001,
|
||||||
|
Every1024Min = 0b1010,
|
||||||
|
Every1024 = 0b1111,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub struct InvalidCtrlBlockAddr;
|
||||||
|
|
||||||
|
bitfield::bitfield! {
|
||||||
|
#[repr(transparent)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct ChannelConfig(u32);
|
||||||
|
impl Debug;
|
||||||
|
u32;
|
||||||
|
pub raw, set_raw: 31,0;
|
||||||
|
u8;
|
||||||
|
pub dst_inc, set_dst_inc: 31, 30;
|
||||||
|
u8;
|
||||||
|
pub dst_size, set_dst_size: 29, 28;
|
||||||
|
u8;
|
||||||
|
pub src_inc, set_src_inc: 27, 26;
|
||||||
|
u8;
|
||||||
|
pub src_size, set_src_size: 25, 24;
|
||||||
|
u8;
|
||||||
|
pub dest_prot_ctrl, set_dest_prot_ctrl: 23, 21;
|
||||||
|
u8;
|
||||||
|
pub src_prot_ctrl, set_src_prot_ctrl: 20, 18;
|
||||||
|
u8;
|
||||||
|
pub r_power, set_r_power: 17, 14;
|
||||||
|
u16;
|
||||||
|
pub n_minus_1, set_n_minus_1: 13, 4;
|
||||||
|
bool;
|
||||||
|
pub next_useburst, set_next_useburst: 3;
|
||||||
|
u8;
|
||||||
|
pub cycle_ctrl, set_cycle_ctr: 2, 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub struct DmaChannelControl {
|
||||||
|
pub src_end_ptr: u32,
|
||||||
|
pub dest_end_ptr: u32,
|
||||||
|
pub cfg: ChannelConfig,
|
||||||
|
padding: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DmaChannelControl {
|
||||||
|
const fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
src_end_ptr: 0,
|
||||||
|
dest_end_ptr: 0,
|
||||||
|
cfg: ChannelConfig(0),
|
||||||
|
padding: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Default for DmaChannelControl {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[repr(C)]
|
||||||
|
#[repr(align(128))]
|
||||||
|
pub struct DmaCtrlBlock {
|
||||||
|
pub pri: [DmaChannelControl; 4],
|
||||||
|
pub alt: [DmaChannelControl; 4],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DmaCtrlBlock {
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
pri: [DmaChannelControl::new(); 4],
|
||||||
|
alt: [DmaChannelControl::new(); 4],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Default for DmaCtrlBlock {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DmaCtrlBlock {
|
||||||
|
/// This function creates a DMA control block at the specified memory address.
|
||||||
|
///
|
||||||
|
/// The passed address must be 128-byte aligned. The user must also take care of specifying
|
||||||
|
/// a valid memory address for the DMA control block which is accessible by the system as well.
|
||||||
|
/// For example, the control block can be placed in the SRAM1.
|
||||||
|
pub fn new_at_addr(addr: u32) -> Result<*mut DmaCtrlBlock, InvalidCtrlBlockAddr> {
|
||||||
|
if addr & BASE_PTR_ADDR_MASK > 0 {
|
||||||
|
return Err(InvalidCtrlBlockAddr);
|
||||||
|
}
|
||||||
|
let ctrl_block_ptr = addr as *mut DmaCtrlBlock;
|
||||||
|
unsafe { core::ptr::write(ctrl_block_ptr, DmaCtrlBlock::default()) }
|
||||||
|
Ok(ctrl_block_ptr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Dma {
|
||||||
|
dma: pac::Dma,
|
||||||
|
ctrl_block: *mut DmaCtrlBlock,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum DmaTransferInitError {
|
||||||
|
SourceDestLenMissmatch {
|
||||||
|
src_len: usize,
|
||||||
|
dest_len: usize,
|
||||||
|
},
|
||||||
|
/// Overflow when calculating the source or destination end address.
|
||||||
|
AddrOverflow,
|
||||||
|
/// Transfer size larger than 1024 units.
|
||||||
|
TransferSizeTooLarge(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
|
pub struct DmaCfg {
|
||||||
|
pub bufferable: bool,
|
||||||
|
pub cacheable: bool,
|
||||||
|
pub privileged: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DmaChannel {
|
||||||
|
channel: u8,
|
||||||
|
done_interrupt: pac::Interrupt,
|
||||||
|
active_interrupt: pac::Interrupt,
|
||||||
|
pub dma: pac::Dma,
|
||||||
|
pub ch_ctrl_pri: &'static mut DmaChannelControl,
|
||||||
|
pub ch_ctrl_alt: &'static mut DmaChannelControl,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DmaChannel {
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn channel(&self) -> u8 {
|
||||||
|
self.channel
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn enable(&mut self) {
|
||||||
|
self.dma
|
||||||
|
.chnl_enable_set()
|
||||||
|
.write(|w| unsafe { w.bits(1 << self.channel) });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn is_enabled(&mut self) -> bool {
|
||||||
|
((self.dma.chnl_enable_set().read().bits() >> self.channel) & 0b1) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn disable(&mut self) {
|
||||||
|
self.dma
|
||||||
|
.chnl_enable_clr()
|
||||||
|
.write(|w| unsafe { w.bits(1 << self.channel) });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn trigger_with_sw_request(&mut self) {
|
||||||
|
self.dma
|
||||||
|
.chnl_sw_request()
|
||||||
|
.write(|w| unsafe { w.bits(1 << self.channel) });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn state_raw(&self) -> u8 {
|
||||||
|
self.dma.status().read().state().bits()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn select_primary_structure(&self) {
|
||||||
|
self.dma
|
||||||
|
.chnl_pri_alt_clr()
|
||||||
|
.write(|w| unsafe { w.bits(1 << self.channel) });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn select_alternate_structure(&self) {
|
||||||
|
self.dma
|
||||||
|
.chnl_pri_alt_set()
|
||||||
|
.write(|w| unsafe { w.bits(1 << self.channel) });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enables the DMA_DONE interrupt for the DMA channel.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// This function is `unsafe` because it can break mask-based critical sections.
|
||||||
|
pub unsafe fn enable_done_interrupt(&mut self) {
|
||||||
|
enable_interrupt(self.done_interrupt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enables the DMA_ACTIVE interrupt for the DMA channel.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// This function is `unsafe` because it can break mask-based critical sections.
|
||||||
|
pub unsafe fn enable_active_interrupt(&mut self) {
|
||||||
|
enable_interrupt(self.active_interrupt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prepares a 8-bit DMA transfer from memory to memory.
|
||||||
|
///
|
||||||
|
/// This function does not enable the DMA channel and interrupts and only prepares
|
||||||
|
/// the DMA control block parameters for the transfer. It configures the primary channel control
|
||||||
|
/// structure to perform the transfer.
|
||||||
|
///
|
||||||
|
/// You can use [Self::enable], [Self::enable_done_interrupt], [Self::enable_active_interrupt]
|
||||||
|
/// to finish the transfer preparation and then use [Self::trigger_with_sw_request] to
|
||||||
|
/// start the DMA transfer.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// You must ensure that the destination buffer is safe for DMA writes and the source buffer
|
||||||
|
/// is safe for DMA reads. The specific requirements can be read here:
|
||||||
|
///
|
||||||
|
/// - [DMA source buffer](https://docs.rs/embedded-dma/latest/embedded_dma/trait.ReadBuffer.html)
|
||||||
|
/// - [DMA destination buffer](https://docs.rs/embedded-dma/latest/embedded_dma/trait.WriteBuffer.html)
|
||||||
|
///
|
||||||
|
/// More specifically, you must ensure that the passed slice remains valid while the DMA is
|
||||||
|
/// active or until the DMA is stopped.
|
||||||
|
pub unsafe fn prepare_mem_to_mem_transfer_8_bit(
|
||||||
|
&mut self,
|
||||||
|
source: &[u8],
|
||||||
|
dest: &mut [u8],
|
||||||
|
) -> Result<(), DmaTransferInitError> {
|
||||||
|
let len = Self::common_mem_transfer_checks(source.len(), dest.len())?;
|
||||||
|
self.generic_mem_to_mem_transfer_init(
|
||||||
|
len,
|
||||||
|
(source.as_ptr() as u32)
|
||||||
|
.checked_add(len as u32)
|
||||||
|
.ok_or(DmaTransferInitError::AddrOverflow)?,
|
||||||
|
(dest.as_ptr() as u32)
|
||||||
|
.checked_add(len as u32)
|
||||||
|
.ok_or(DmaTransferInitError::AddrOverflow)?,
|
||||||
|
DataSize::Byte,
|
||||||
|
AddrIncrement::Byte,
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prepares a 16-bit DMA transfer from memory to memory.
|
||||||
|
///
|
||||||
|
/// This function does not enable the DMA channel and interrupts and only prepares
|
||||||
|
/// the DMA control block parameters for the transfer. It configures the primary channel control
|
||||||
|
/// structure to perform the transfer.
|
||||||
|
///
|
||||||
|
/// You can use [Self::enable], [Self::enable_done_interrupt], [Self::enable_active_interrupt]
|
||||||
|
/// to finish the transfer preparation and then use [Self::trigger_with_sw_request] to
|
||||||
|
/// start the DMA transfer.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// You must ensure that the destination buffer is safe for DMA writes and the source buffer
|
||||||
|
/// is safe for DMA reads. The specific requirements can be read here:
|
||||||
|
///
|
||||||
|
/// - [DMA source buffer](https://docs.rs/embedded-dma/latest/embedded_dma/trait.ReadBuffer.html)
|
||||||
|
/// - [DMA destination buffer](https://docs.rs/embedded-dma/latest/embedded_dma/trait.WriteBuffer.html)
|
||||||
|
///
|
||||||
|
/// More specifically, you must ensure that the passed slice remains valid while the DMA is
|
||||||
|
/// active or until the DMA is stopped.
|
||||||
|
pub unsafe fn prepare_mem_to_mem_transfer_16_bit(
|
||||||
|
&mut self,
|
||||||
|
source: &[u16],
|
||||||
|
dest: &mut [u16],
|
||||||
|
) -> Result<(), DmaTransferInitError> {
|
||||||
|
let len = Self::common_mem_transfer_checks(source.len(), dest.len())?;
|
||||||
|
self.generic_mem_to_mem_transfer_init(
|
||||||
|
len,
|
||||||
|
(source.as_ptr() as u32)
|
||||||
|
.checked_add(len as u32 * core::mem::size_of::<u16>() as u32)
|
||||||
|
.ok_or(DmaTransferInitError::AddrOverflow)?,
|
||||||
|
(dest.as_ptr() as u32)
|
||||||
|
.checked_add(len as u32 * core::mem::size_of::<u16>() as u32)
|
||||||
|
.ok_or(DmaTransferInitError::AddrOverflow)?,
|
||||||
|
DataSize::Halfword,
|
||||||
|
AddrIncrement::Halfword,
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prepares a 32-bit DMA transfer from memory to memory.
|
||||||
|
///
|
||||||
|
/// This function does not enable the DMA channel and interrupts and only prepares
|
||||||
|
/// the DMA control block parameters for the transfer. It configures the primary channel control
|
||||||
|
/// structure to perform the transfer.
|
||||||
|
///
|
||||||
|
/// You can use [Self::enable], [Self::enable_done_interrupt], [Self::enable_active_interrupt]
|
||||||
|
/// to finish the transfer preparation and then use [Self::trigger_with_sw_request] to
|
||||||
|
/// start the DMA transfer.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// You must ensure that the destination buffer is safe for DMA writes and the source buffer
|
||||||
|
/// is safe for DMA reads. The specific requirements can be read here:
|
||||||
|
///
|
||||||
|
/// - [DMA source buffer](https://docs.rs/embedded-dma/latest/embedded_dma/trait.ReadBuffer.html)
|
||||||
|
/// - [DMA destination buffer](https://docs.rs/embedded-dma/latest/embedded_dma/trait.WriteBuffer.html)
|
||||||
|
///
|
||||||
|
/// More specifically, you must ensure that the passed slice remains valid while the DMA is
|
||||||
|
/// active or until the DMA is stopped.
|
||||||
|
pub unsafe fn prepare_mem_to_mem_transfer_32_bit(
|
||||||
|
&mut self,
|
||||||
|
source: &[u32],
|
||||||
|
dest: &mut [u32],
|
||||||
|
) -> Result<(), DmaTransferInitError> {
|
||||||
|
let len = Self::common_mem_transfer_checks(source.len(), dest.len())?;
|
||||||
|
self.generic_mem_to_mem_transfer_init(
|
||||||
|
len,
|
||||||
|
(source.as_ptr() as u32)
|
||||||
|
.checked_add(len as u32 * core::mem::size_of::<u32>() as u32)
|
||||||
|
.ok_or(DmaTransferInitError::AddrOverflow)?,
|
||||||
|
(dest.as_ptr() as u32)
|
||||||
|
.checked_add(len as u32 * core::mem::size_of::<u32>() as u32)
|
||||||
|
.ok_or(DmaTransferInitError::AddrOverflow)?,
|
||||||
|
DataSize::Word,
|
||||||
|
AddrIncrement::Word,
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prepares a 8-bit DMA transfer from memory to a peripheral.
|
||||||
|
///
|
||||||
|
/// It is assumed that a peripheral with a 16-byte FIFO is used here and that the
|
||||||
|
/// transfer is activated by an IRQ trigger when the half-full interrupt of the peripheral
|
||||||
|
/// is fired. Therefore, this function configured the DMA in [CycleControl::Basic] mode with
|
||||||
|
/// rearbitration happening every 8 DMA cycles. It also configures the primary channel control
|
||||||
|
/// structure to perform the transfer.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// You must ensure that the source buffer is safe for DMA reads. The specific requirements
|
||||||
|
/// can be read here:
|
||||||
|
///
|
||||||
|
/// - [DMA source buffer](https://docs.rs/embedded-dma/latest/embedded_dma/trait.ReadBuffer.html)
|
||||||
|
///
|
||||||
|
/// More specifically, you must ensure that the passed slice remains valid while the DMA is
|
||||||
|
/// active or until the DMA is stopped.
|
||||||
|
///
|
||||||
|
/// The destination address must be the pointer address of a peripheral FIFO register address.
|
||||||
|
/// You must also ensure that the regular synchronous transfer API of the peripheral is NOT
|
||||||
|
/// used to perform transfers.
|
||||||
|
pub unsafe fn prepare_mem_to_periph_transfer_8_bit(
|
||||||
|
&mut self,
|
||||||
|
source: &[u8],
|
||||||
|
dest: *mut u32,
|
||||||
|
) -> Result<(), DmaTransferInitError> {
|
||||||
|
if source.len() > MAX_DMA_TRANSFERS_PER_CYCLE {
|
||||||
|
return Err(DmaTransferInitError::TransferSizeTooLarge(source.len()));
|
||||||
|
}
|
||||||
|
let len = source.len() - 1;
|
||||||
|
self.ch_ctrl_pri.cfg.set_raw(0);
|
||||||
|
self.ch_ctrl_pri.src_end_ptr = (source.as_ptr() as u32)
|
||||||
|
.checked_add(len as u32)
|
||||||
|
.ok_or(DmaTransferInitError::AddrOverflow)?;
|
||||||
|
self.ch_ctrl_pri.dest_end_ptr = dest as u32;
|
||||||
|
self.ch_ctrl_pri
|
||||||
|
.cfg
|
||||||
|
.set_cycle_ctr(CycleControl::Basic as u8);
|
||||||
|
self.ch_ctrl_pri.cfg.set_src_size(DataSize::Byte as u8);
|
||||||
|
self.ch_ctrl_pri.cfg.set_src_inc(AddrIncrement::Byte as u8);
|
||||||
|
self.ch_ctrl_pri.cfg.set_dst_size(DataSize::Byte as u8);
|
||||||
|
self.ch_ctrl_pri.cfg.set_dst_inc(AddrIncrement::None as u8);
|
||||||
|
self.ch_ctrl_pri.cfg.set_n_minus_1(len as u16);
|
||||||
|
self.ch_ctrl_pri.cfg.set_r_power(RPower::Every8 as u8);
|
||||||
|
self.select_primary_structure();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function performs common checks and returns the source length minus one which is
|
||||||
|
// relevant for further configuration of the DMA. This is because the DMA API expects N minus
|
||||||
|
// 1 and the source and end pointer need to point to the last transfer address.
|
||||||
|
fn common_mem_transfer_checks(
|
||||||
|
src_len: usize,
|
||||||
|
dest_len: usize,
|
||||||
|
) -> Result<usize, DmaTransferInitError> {
|
||||||
|
if src_len != dest_len {
|
||||||
|
return Err(DmaTransferInitError::SourceDestLenMissmatch { src_len, dest_len });
|
||||||
|
}
|
||||||
|
if src_len > MAX_DMA_TRANSFERS_PER_CYCLE {
|
||||||
|
return Err(DmaTransferInitError::TransferSizeTooLarge(src_len));
|
||||||
|
}
|
||||||
|
Ok(src_len - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generic_mem_to_mem_transfer_init(
|
||||||
|
&mut self,
|
||||||
|
n_minus_one: usize,
|
||||||
|
src_end_ptr: u32,
|
||||||
|
dest_end_ptr: u32,
|
||||||
|
data_size: DataSize,
|
||||||
|
addr_incr: AddrIncrement,
|
||||||
|
) {
|
||||||
|
self.ch_ctrl_pri.cfg.set_raw(0);
|
||||||
|
self.ch_ctrl_pri.src_end_ptr = src_end_ptr;
|
||||||
|
self.ch_ctrl_pri.dest_end_ptr = dest_end_ptr;
|
||||||
|
self.ch_ctrl_pri.cfg.set_cycle_ctr(CycleControl::Auto as u8);
|
||||||
|
self.ch_ctrl_pri.cfg.set_src_size(data_size as u8);
|
||||||
|
self.ch_ctrl_pri.cfg.set_src_inc(addr_incr as u8);
|
||||||
|
self.ch_ctrl_pri.cfg.set_dst_size(data_size as u8);
|
||||||
|
self.ch_ctrl_pri.cfg.set_dst_inc(addr_incr as u8);
|
||||||
|
self.ch_ctrl_pri.cfg.set_n_minus_1(n_minus_one as u16);
|
||||||
|
self.ch_ctrl_pri.cfg.set_r_power(RPower::Every4 as u8);
|
||||||
|
self.select_primary_structure();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dma {
|
||||||
|
/// Create a new DMA instance.
|
||||||
|
///
|
||||||
|
/// You can also place the [DmaCtrlBlock] statically using a global static mutable
|
||||||
|
/// instance and the [DmaCtrlBlock::new] const constructor This also allows to place the control
|
||||||
|
/// block in a memory section using the [link_section](https://doc.rust-lang.org/reference/abi.html#the-link_section-attribute)
|
||||||
|
/// attribute and then creating a mutable pointer to it using [core::ptr::addr_of_mut].
|
||||||
|
///
|
||||||
|
/// Alternatively, the [DmaCtrlBlock::new_at_addr] function can be used to create the DMA
|
||||||
|
/// control block at a specific address.
|
||||||
|
pub fn new(
|
||||||
|
syscfg: &mut pac::Sysconfig,
|
||||||
|
dma: pac::Dma,
|
||||||
|
cfg: DmaCfg,
|
||||||
|
ctrl_block: *mut DmaCtrlBlock,
|
||||||
|
) -> Result<Self, InvalidCtrlBlockAddr> {
|
||||||
|
// The conversion to u32 is safe here because we are on a 32-bit system.
|
||||||
|
let raw_addr = ctrl_block as u32;
|
||||||
|
if raw_addr & BASE_PTR_ADDR_MASK > 0 {
|
||||||
|
return Err(InvalidCtrlBlockAddr);
|
||||||
|
}
|
||||||
|
syscfg.enable_peripheral_clock(PeripheralClock::Dma);
|
||||||
|
syscfg.assert_periph_reset_for_two_cycles(PeripheralSelect::Dma);
|
||||||
|
let dma = Dma { dma, ctrl_block };
|
||||||
|
dma.dma
|
||||||
|
.ctrl_base_ptr()
|
||||||
|
.write(|w| unsafe { w.bits(raw_addr) });
|
||||||
|
dma.set_protection_bits(&cfg);
|
||||||
|
dma.enable();
|
||||||
|
Ok(dma)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn enable(&self) {
|
||||||
|
self.dma.cfg().write(|w| w.master_enable().set_bit());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn disable(&self) {
|
||||||
|
self.dma.cfg().write(|w| w.master_enable().clear_bit());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn set_protection_bits(&self, cfg: &DmaCfg) {
|
||||||
|
self.dma.cfg().write(|w| unsafe {
|
||||||
|
w.chnl_prot_ctrl().bits(
|
||||||
|
cfg.privileged as u8 | ((cfg.bufferable as u8) << 1) | ((cfg.cacheable as u8) << 2),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Split the DMA instance into four DMA channels which can be used individually. This allows
|
||||||
|
/// using the inidividual DMA channels in separate tasks.
|
||||||
|
pub fn split(self) -> (DmaChannel, DmaChannel, DmaChannel, DmaChannel) {
|
||||||
|
// Safety: The DMA channel API only operates on its respective channels.
|
||||||
|
(
|
||||||
|
DmaChannel {
|
||||||
|
channel: 0,
|
||||||
|
done_interrupt: pac::Interrupt::DMA_DONE0,
|
||||||
|
active_interrupt: pac::Interrupt::DMA_ACTIVE0,
|
||||||
|
dma: unsafe { pac::Dma::steal() },
|
||||||
|
ch_ctrl_pri: unsafe { &mut (*self.ctrl_block).pri[0] },
|
||||||
|
ch_ctrl_alt: unsafe { &mut (*self.ctrl_block).alt[0] },
|
||||||
|
},
|
||||||
|
DmaChannel {
|
||||||
|
channel: 1,
|
||||||
|
done_interrupt: pac::Interrupt::DMA_DONE1,
|
||||||
|
active_interrupt: pac::Interrupt::DMA_ACTIVE1,
|
||||||
|
dma: unsafe { pac::Dma::steal() },
|
||||||
|
ch_ctrl_pri: unsafe { &mut (*self.ctrl_block).pri[1] },
|
||||||
|
ch_ctrl_alt: unsafe { &mut (*self.ctrl_block).alt[1] },
|
||||||
|
},
|
||||||
|
DmaChannel {
|
||||||
|
channel: 2,
|
||||||
|
done_interrupt: pac::Interrupt::DMA_DONE2,
|
||||||
|
active_interrupt: pac::Interrupt::DMA_ACTIVE2,
|
||||||
|
dma: unsafe { pac::Dma::steal() },
|
||||||
|
ch_ctrl_pri: unsafe { &mut (*self.ctrl_block).pri[2] },
|
||||||
|
ch_ctrl_alt: unsafe { &mut (*self.ctrl_block).alt[2] },
|
||||||
|
},
|
||||||
|
DmaChannel {
|
||||||
|
channel: 3,
|
||||||
|
done_interrupt: pac::Interrupt::DMA_DONE3,
|
||||||
|
active_interrupt: pac::Interrupt::DMA_ACTIVE3,
|
||||||
|
dma: unsafe { pac::Dma::steal() },
|
||||||
|
ch_ctrl_pri: unsafe { &mut (*self.ctrl_block).pri[3] },
|
||||||
|
ch_ctrl_alt: unsafe { &mut (*self.ctrl_block).alt[3] },
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -11,6 +11,7 @@ pub mod prelude;
|
|||||||
pub mod adc;
|
pub mod adc;
|
||||||
pub mod clock;
|
pub mod clock;
|
||||||
pub mod dac;
|
pub mod dac;
|
||||||
|
pub mod dma;
|
||||||
pub mod gpio;
|
pub mod gpio;
|
||||||
pub mod i2c;
|
pub mod i2c;
|
||||||
pub mod pwm;
|
pub mod pwm;
|
||||||
|
Loading…
Reference in New Issue
Block a user