implement PWM waveform generating with DMA
This commit is contained in:
parent
eebfee189a
commit
8c2a6df03b
7 changed files with 159 additions and 82 deletions
|
@ -1008,6 +1008,7 @@ fn main() {
|
||||||
(("quadspi", "QUADSPI"), quote!(crate::qspi::QuadDma)),
|
(("quadspi", "QUADSPI"), quote!(crate::qspi::QuadDma)),
|
||||||
(("dac", "CH1"), quote!(crate::dac::DacDma1)),
|
(("dac", "CH1"), quote!(crate::dac::DacDma1)),
|
||||||
(("dac", "CH2"), quote!(crate::dac::DacDma2)),
|
(("dac", "CH2"), quote!(crate::dac::DacDma2)),
|
||||||
|
(("timer", "UP"), quote!(crate::timer::UpDma)),
|
||||||
]
|
]
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
|
@ -1023,6 +1024,16 @@ fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(tr) = signals.get(&(regs.kind, ch.signal)) {
|
if let Some(tr) = signals.get(&(regs.kind, ch.signal)) {
|
||||||
|
// TIM6 of stm32f334 is special, DMA channel for TIM6 depending on SYSCFG state
|
||||||
|
if chip_name.starts_with("stm32f334") && p.name == "TIM6" {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TIM6 of stm32f378 is special, DMA channel for TIM6 depending on SYSCFG state
|
||||||
|
if chip_name.starts_with("stm32f378") && p.name == "TIM6" {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let peri = format_ident!("{}", p.name);
|
let peri = format_ident!("{}", p.name);
|
||||||
|
|
||||||
let channel = if let Some(channel) = &ch.channel {
|
let channel = if let Some(channel) = &ch.channel {
|
||||||
|
|
|
@ -91,7 +91,12 @@ pub(crate) mod sealed {
|
||||||
|
|
||||||
/// Enable/disable the update interrupt.
|
/// Enable/disable the update interrupt.
|
||||||
fn enable_update_interrupt(&mut self, enable: bool) {
|
fn enable_update_interrupt(&mut self, enable: bool) {
|
||||||
Self::regs().dier().write(|r| r.set_uie(enable));
|
Self::regs().dier().modify(|r| r.set_uie(enable));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enable/disable the update dma.
|
||||||
|
fn enable_update_dma(&mut self, enable: bool) {
|
||||||
|
Self::regs().dier().modify(|r| r.set_ude(enable));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enable/disable autoreload preload.
|
/// Enable/disable autoreload preload.
|
||||||
|
@ -288,6 +293,14 @@ pub(crate) mod sealed {
|
||||||
fn get_compare_value(&self, channel: Channel) -> u16 {
|
fn get_compare_value(&self, channel: Channel) -> u16 {
|
||||||
Self::regs_gp16().ccr(channel.index()).read().ccr()
|
Self::regs_gp16().ccr(channel.index()).read().ccr()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set output compare preload.
|
||||||
|
fn set_output_compare_preload(&mut self, channel: Channel, preload: bool) {
|
||||||
|
let channel_index = channel.index();
|
||||||
|
Self::regs_gp16()
|
||||||
|
.ccmr_output(channel_index / 2)
|
||||||
|
.modify(|w| w.set_ocpe(channel_index % 2, preload));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Capture/Compare 16-bit timer instance with complementary pin support.
|
/// Capture/Compare 16-bit timer instance with complementary pin support.
|
||||||
|
@ -676,3 +689,6 @@ foreach_interrupt! {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update Event trigger DMA for every timer
|
||||||
|
dma_trait!(UpDma, Basic16bitInstance);
|
||||||
|
|
|
@ -55,11 +55,12 @@ channel_impl!(new_ch3, Ch3, Channel3Pin);
|
||||||
channel_impl!(new_ch4, Ch4, Channel4Pin);
|
channel_impl!(new_ch4, Ch4, Channel4Pin);
|
||||||
|
|
||||||
/// Simple PWM driver.
|
/// Simple PWM driver.
|
||||||
pub struct SimplePwm<'d, T> {
|
pub struct SimplePwm<'d, T, Dma> {
|
||||||
inner: PeripheralRef<'d, T>,
|
inner: PeripheralRef<'d, T>,
|
||||||
|
dma: PeripheralRef<'d, Dma>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'d, T: CaptureCompare16bitInstance> SimplePwm<'d, T> {
|
impl<'d, T: CaptureCompare16bitInstance, Dma> SimplePwm<'d, T, Dma> {
|
||||||
/// Create a new simple PWM driver.
|
/// Create a new simple PWM driver.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
tim: impl Peripheral<P = T> + 'd,
|
tim: impl Peripheral<P = T> + 'd,
|
||||||
|
@ -69,16 +70,22 @@ impl<'d, T: CaptureCompare16bitInstance> SimplePwm<'d, T> {
|
||||||
_ch4: Option<PwmPin<'d, T, Ch4>>,
|
_ch4: Option<PwmPin<'d, T, Ch4>>,
|
||||||
freq: Hertz,
|
freq: Hertz,
|
||||||
counting_mode: CountingMode,
|
counting_mode: CountingMode,
|
||||||
|
dma: impl Peripheral<P = Dma> + 'd,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self::new_inner(tim, freq, counting_mode)
|
Self::new_inner(tim, freq, counting_mode, dma)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_inner(tim: impl Peripheral<P = T> + 'd, freq: Hertz, counting_mode: CountingMode) -> Self {
|
fn new_inner(
|
||||||
into_ref!(tim);
|
tim: impl Peripheral<P = T> + 'd,
|
||||||
|
freq: Hertz,
|
||||||
|
counting_mode: CountingMode,
|
||||||
|
dma: impl Peripheral<P = Dma> + 'd,
|
||||||
|
) -> Self {
|
||||||
|
into_ref!(tim, dma);
|
||||||
|
|
||||||
T::enable_and_reset();
|
T::enable_and_reset();
|
||||||
|
|
||||||
let mut this = Self { inner: tim };
|
let mut this = Self { inner: tim, dma };
|
||||||
|
|
||||||
this.inner.set_counting_mode(counting_mode);
|
this.inner.set_counting_mode(counting_mode);
|
||||||
this.set_frequency(freq);
|
this.set_frequency(freq);
|
||||||
|
@ -86,14 +93,13 @@ impl<'d, T: CaptureCompare16bitInstance> SimplePwm<'d, T> {
|
||||||
|
|
||||||
this.inner.enable_outputs();
|
this.inner.enable_outputs();
|
||||||
|
|
||||||
this.inner
|
[Channel::Ch1, Channel::Ch2, Channel::Ch3, Channel::Ch4]
|
||||||
.set_output_compare_mode(Channel::Ch1, OutputCompareMode::PwmMode1);
|
.iter()
|
||||||
this.inner
|
.for_each(|&channel| {
|
||||||
.set_output_compare_mode(Channel::Ch2, OutputCompareMode::PwmMode1);
|
this.inner.set_output_compare_mode(channel, OutputCompareMode::PwmMode1);
|
||||||
this.inner
|
this.inner.set_output_compare_preload(channel, true)
|
||||||
.set_output_compare_mode(Channel::Ch3, OutputCompareMode::PwmMode1);
|
});
|
||||||
this.inner
|
|
||||||
.set_output_compare_mode(Channel::Ch4, OutputCompareMode::PwmMode1);
|
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,7 +147,71 @@ impl<'d, T: CaptureCompare16bitInstance> SimplePwm<'d, T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'d, T: CaptureCompare16bitInstance> embedded_hal_02::Pwm for SimplePwm<'d, T> {
|
impl<'d, T: CaptureCompare16bitInstance + Basic16bitInstance, Dma> SimplePwm<'d, T, Dma>
|
||||||
|
where
|
||||||
|
Dma: super::UpDma<T>,
|
||||||
|
{
|
||||||
|
/// Generate a sequence of PWM waveform
|
||||||
|
pub async fn gen_waveform(&mut self, channel: Channel, duty: &[u16]) {
|
||||||
|
duty.iter().all(|v| v.le(&self.get_max_duty()));
|
||||||
|
|
||||||
|
self.inner.enable_update_dma(true);
|
||||||
|
|
||||||
|
#[cfg_attr(any(stm32f334, stm32f378), allow(clippy::let_unit_value))]
|
||||||
|
let req = self.dma.request();
|
||||||
|
|
||||||
|
self.enable(channel);
|
||||||
|
|
||||||
|
#[cfg(not(any(bdma, gpdma)))]
|
||||||
|
let dma_regs = self.dma.regs();
|
||||||
|
#[cfg(not(any(bdma, gpdma)))]
|
||||||
|
let isr_num = self.dma.num() / 4;
|
||||||
|
#[cfg(not(any(bdma, gpdma)))]
|
||||||
|
let isr_bit = self.dma.num() % 4;
|
||||||
|
|
||||||
|
#[cfg(not(any(bdma, gpdma)))]
|
||||||
|
// clean DMA FIFO error before a transfer
|
||||||
|
if dma_regs.isr(isr_num).read().feif(isr_bit) {
|
||||||
|
dma_regs.ifcr(isr_num).write(|v| v.set_feif(isr_bit, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
#[cfg(not(any(bdma, gpdma)))]
|
||||||
|
use crate::dma::{Burst, FifoThreshold};
|
||||||
|
use crate::dma::{Transfer, TransferOptions};
|
||||||
|
|
||||||
|
let dma_transfer_option = TransferOptions {
|
||||||
|
#[cfg(not(any(bdma, gpdma)))]
|
||||||
|
fifo_threshold: Some(FifoThreshold::Full),
|
||||||
|
#[cfg(not(any(bdma, gpdma)))]
|
||||||
|
mburst: Burst::Incr8,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
Transfer::new_write(
|
||||||
|
&mut self.dma,
|
||||||
|
req,
|
||||||
|
duty,
|
||||||
|
T::regs_gp16().ccr(channel.index()).as_ptr() as *mut _,
|
||||||
|
dma_transfer_option,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
};
|
||||||
|
|
||||||
|
self.disable(channel);
|
||||||
|
|
||||||
|
self.inner.enable_update_dma(false);
|
||||||
|
|
||||||
|
#[cfg(not(any(bdma, gpdma)))]
|
||||||
|
// Since DMA is closed before timer update event trigger DMA is turn off, it will almost always trigger a DMA FIFO error.
|
||||||
|
// Thus, we will always clean DMA FEIF after each transfer
|
||||||
|
if dma_regs.isr(isr_num).read().feif(isr_bit) {
|
||||||
|
dma_regs.ifcr(isr_num).write(|v| v.set_feif(isr_bit, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'d, T: CaptureCompare16bitInstance, Dma> embedded_hal_02::Pwm for SimplePwm<'d, T, Dma> {
|
||||||
type Channel = Channel;
|
type Channel = Channel;
|
||||||
type Time = Hertz;
|
type Time = Hertz;
|
||||||
type Duty = u16;
|
type Duty = u16;
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
use defmt::*;
|
use defmt::*;
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
|
use embassy_stm32::dma;
|
||||||
use embassy_stm32::gpio::OutputType;
|
use embassy_stm32::gpio::OutputType;
|
||||||
use embassy_stm32::time::khz;
|
use embassy_stm32::time::khz;
|
||||||
use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm};
|
use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm};
|
||||||
|
@ -16,7 +17,16 @@ async fn main(_spawner: Spawner) {
|
||||||
info!("Hello World!");
|
info!("Hello World!");
|
||||||
|
|
||||||
let ch1 = PwmPin::new_ch1(p.PE9, OutputType::PushPull);
|
let ch1 = PwmPin::new_ch1(p.PE9, OutputType::PushPull);
|
||||||
let mut pwm = SimplePwm::new(p.TIM1, Some(ch1), None, None, None, khz(10), Default::default());
|
let mut pwm = SimplePwm::new(
|
||||||
|
p.TIM1,
|
||||||
|
Some(ch1),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
khz(10),
|
||||||
|
Default::default(),
|
||||||
|
dma::NoDma,
|
||||||
|
);
|
||||||
let max = pwm.get_max_duty();
|
let max = pwm.get_max_duty();
|
||||||
pwm.enable(Channel::Ch1);
|
pwm.enable(Channel::Ch1);
|
||||||
|
|
||||||
|
|
|
@ -2,15 +2,9 @@
|
||||||
// We assume the DIN pin of ws2812 connect to GPIO PB4, and ws2812 is properly powered.
|
// We assume the DIN pin of ws2812 connect to GPIO PB4, and ws2812 is properly powered.
|
||||||
//
|
//
|
||||||
// The idea is that the data rate of ws2812 is 800 kHz, and it use different duty ratio to represent bit 0 and bit 1.
|
// The idea is that the data rate of ws2812 is 800 kHz, and it use different duty ratio to represent bit 0 and bit 1.
|
||||||
// Thus we can set TIM overflow at 800 kHz, and let TIM Update Event trigger a DMA transfer, then let DMA change CCR value,
|
// Thus we can set TIM overflow at 800 kHz, and change duty ratio of TIM to meet the bit representation of ws2812.
|
||||||
// such that pwm duty ratio meet the bit representation of ws2812.
|
|
||||||
//
|
//
|
||||||
// You may want to modify TIM CCR with Cortex core directly,
|
// you may also want to take a look at `ws2812_spi.rs` file, which make use of SPI instead.
|
||||||
// but according to my test, Cortex core will need to run far more than 100 MHz to catch up with TIM.
|
|
||||||
// Thus we need to use a DMA.
|
|
||||||
//
|
|
||||||
// This demo is a combination of HAL, PAC, and manually invoke `dma::Transfer`.
|
|
||||||
// If you need a simpler way to control ws2812, you may want to take a look at `ws2812_spi.rs` file, which make use of SPI.
|
|
||||||
//
|
//
|
||||||
// Warning:
|
// Warning:
|
||||||
// DO NOT stare at ws2812 directy (especially after each MCU Reset), its (max) brightness could easily make your eyes feel burn.
|
// DO NOT stare at ws2812 directy (especially after each MCU Reset), its (max) brightness could easily make your eyes feel burn.
|
||||||
|
@ -20,7 +14,6 @@
|
||||||
|
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
use embassy_stm32::gpio::OutputType;
|
use embassy_stm32::gpio::OutputType;
|
||||||
use embassy_stm32::pac;
|
|
||||||
use embassy_stm32::time::khz;
|
use embassy_stm32::time::khz;
|
||||||
use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm};
|
use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm};
|
||||||
use embassy_stm32::timer::{Channel, CountingMode};
|
use embassy_stm32::timer::{Channel, CountingMode};
|
||||||
|
@ -52,7 +45,7 @@ async fn main(_spawner: Spawner) {
|
||||||
device_config.rcc.sys = Sysclk::PLL1_P;
|
device_config.rcc.sys = Sysclk::PLL1_P;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut dp = embassy_stm32::init(device_config);
|
let dp = embassy_stm32::init(device_config);
|
||||||
|
|
||||||
let mut ws2812_pwm = SimplePwm::new(
|
let mut ws2812_pwm = SimplePwm::new(
|
||||||
dp.TIM3,
|
dp.TIM3,
|
||||||
|
@ -62,6 +55,7 @@ async fn main(_spawner: Spawner) {
|
||||||
None,
|
None,
|
||||||
khz(800), // data rate of ws2812
|
khz(800), // data rate of ws2812
|
||||||
CountingMode::EdgeAlignedUp,
|
CountingMode::EdgeAlignedUp,
|
||||||
|
dp.DMA1_CH2,
|
||||||
);
|
);
|
||||||
|
|
||||||
// construct ws2812 non-return-to-zero (NRZ) code bit by bit
|
// construct ws2812 non-return-to-zero (NRZ) code bit by bit
|
||||||
|
@ -89,62 +83,19 @@ async fn main(_spawner: Spawner) {
|
||||||
|
|
||||||
let pwm_channel = Channel::Ch1;
|
let pwm_channel = Channel::Ch1;
|
||||||
|
|
||||||
// PAC level hacking, enable output compare preload
|
|
||||||
// keep output waveform integrity
|
|
||||||
pac::TIM3
|
|
||||||
.ccmr_output(pwm_channel.index())
|
|
||||||
.modify(|v| v.set_ocpe(0, true));
|
|
||||||
|
|
||||||
// make sure PWM output keep low on first start
|
// make sure PWM output keep low on first start
|
||||||
ws2812_pwm.set_duty(pwm_channel, 0);
|
ws2812_pwm.set_duty(pwm_channel, 0);
|
||||||
|
|
||||||
{
|
|
||||||
use embassy_stm32::dma::{Burst, FifoThreshold, Transfer, TransferOptions};
|
|
||||||
|
|
||||||
// configure FIFO and MBURST of DMA, to minimize DMA occupation on AHB/APB
|
|
||||||
let mut dma_transfer_option = TransferOptions::default();
|
|
||||||
dma_transfer_option.fifo_threshold = Some(FifoThreshold::Full);
|
|
||||||
dma_transfer_option.mburst = Burst::Incr8;
|
|
||||||
|
|
||||||
// flip color at 2 Hz
|
// flip color at 2 Hz
|
||||||
let mut ticker = Ticker::every(Duration::from_millis(500));
|
let mut ticker = Ticker::every(Duration::from_millis(500));
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
for &color in color_list {
|
for &color in color_list {
|
||||||
// start PWM output
|
ws2812_pwm.gen_waveform(Channel::Ch1, color).await;
|
||||||
ws2812_pwm.enable(pwm_channel);
|
|
||||||
|
|
||||||
// PAC level hacking, enable timer-update-event trigger DMA
|
|
||||||
pac::TIM3.dier().modify(|v| v.set_ude(true));
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
Transfer::new_write(
|
|
||||||
// with &mut, we can easily reuse same DMA channel multiple times
|
|
||||||
&mut dp.DMA1_CH2,
|
|
||||||
5,
|
|
||||||
color,
|
|
||||||
pac::TIM3.ccr(pwm_channel.index()).as_ptr() as *mut _,
|
|
||||||
dma_transfer_option,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
// Turn off timer-update-event trigger DMA as soon as possible.
|
|
||||||
// Then clean the FIFO Error Flag if set.
|
|
||||||
pac::TIM3.dier().modify(|v| v.set_ude(false));
|
|
||||||
if pac::DMA1.isr(0).read().feif(2) {
|
|
||||||
pac::DMA1.ifcr(0).write(|v| v.set_feif(2, true));
|
|
||||||
}
|
|
||||||
|
|
||||||
// ws2812 need at least 50 us low level input to confirm the input data and change it's state
|
// ws2812 need at least 50 us low level input to confirm the input data and change it's state
|
||||||
Timer::after_micros(50).await;
|
Timer::after_micros(50).await;
|
||||||
}
|
|
||||||
|
|
||||||
// stop PWM output for saving some energy
|
|
||||||
ws2812_pwm.disable(pwm_channel);
|
|
||||||
|
|
||||||
// wait until ticker tick
|
// wait until ticker tick
|
||||||
ticker.next().await;
|
ticker.next().await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
use defmt::*;
|
use defmt::*;
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
|
use embassy_stm32::dma;
|
||||||
use embassy_stm32::gpio::OutputType;
|
use embassy_stm32::gpio::OutputType;
|
||||||
use embassy_stm32::time::khz;
|
use embassy_stm32::time::khz;
|
||||||
use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm};
|
use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm};
|
||||||
|
@ -16,7 +17,16 @@ async fn main(_spawner: Spawner) {
|
||||||
info!("Hello World!");
|
info!("Hello World!");
|
||||||
|
|
||||||
let ch1 = PwmPin::new_ch1(p.PC0, OutputType::PushPull);
|
let ch1 = PwmPin::new_ch1(p.PC0, OutputType::PushPull);
|
||||||
let mut pwm = SimplePwm::new(p.TIM1, Some(ch1), None, None, None, khz(10), Default::default());
|
let mut pwm = SimplePwm::new(
|
||||||
|
p.TIM1,
|
||||||
|
Some(ch1),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
khz(10),
|
||||||
|
Default::default(),
|
||||||
|
dma::NoDma,
|
||||||
|
);
|
||||||
let max = pwm.get_max_duty();
|
let max = pwm.get_max_duty();
|
||||||
pwm.enable(Channel::Ch1);
|
pwm.enable(Channel::Ch1);
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ use embassy_stm32::gpio::OutputType;
|
||||||
use embassy_stm32::time::khz;
|
use embassy_stm32::time::khz;
|
||||||
use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm};
|
use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm};
|
||||||
use embassy_stm32::timer::Channel;
|
use embassy_stm32::timer::Channel;
|
||||||
use embassy_stm32::Config;
|
use embassy_stm32::{dma, Config};
|
||||||
use embassy_time::Timer;
|
use embassy_time::Timer;
|
||||||
use {defmt_rtt as _, panic_probe as _};
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
|
|
||||||
|
@ -38,7 +38,16 @@ async fn main(_spawner: Spawner) {
|
||||||
info!("Hello World!");
|
info!("Hello World!");
|
||||||
|
|
||||||
let ch1 = PwmPin::new_ch1(p.PA6, OutputType::PushPull);
|
let ch1 = PwmPin::new_ch1(p.PA6, OutputType::PushPull);
|
||||||
let mut pwm = SimplePwm::new(p.TIM3, Some(ch1), None, None, None, khz(10), Default::default());
|
let mut pwm = SimplePwm::new(
|
||||||
|
p.TIM3,
|
||||||
|
Some(ch1),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
khz(10),
|
||||||
|
Default::default(),
|
||||||
|
dma::NoDma,
|
||||||
|
);
|
||||||
let max = pwm.get_max_duty();
|
let max = pwm.get_max_duty();
|
||||||
pwm.enable(Channel::Ch1);
|
pwm.enable(Channel::Ch1);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue