Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PWM DMA burst #107

Closed
dkotTech opened this issue Jul 20, 2024 · 8 comments
Closed

PWM DMA burst #107

dkotTech opened this issue Jul 20, 2024 · 8 comments

Comments

@dkotTech
Copy link

Hi, I am working on an example that demonstrates the usage of a DRV8844 motor driver with PWM and DMA.

However, something is not working as expected.
The duty cycle does not change after a DMA write request.

full src link

code:

#![no_main]
#![no_std]

use defmt_rtt as _;
use panic_probe as _;

use hal::{
    self,
    clocks::Clocks,
    dma,
    dma::{ChannelCfg, Dma, DmaChannel, DmaInput, DmaPeriph},
    gpio::{Pin, PinMode, Port},
    pac,
    pac::{TIM2, TIM3},
    timer::{
        OutputCompare, TimChannel, Timer, TimerConfig,
        TimerInterrupt,
    },
};
pub static BUF: [u16; 4] = [1, 2, 3, 4];

#[rtic::app(device = pac, peripherals = true)]
mod app {
    use super::*;

    #[shared]
    struct Shared {}

    #[local]
    struct Local {
        timer_pwd: Timer<TIM2>,
        timer: Timer<TIM3>,
    }

    #[init]
    fn init(ctx: init::Context) -> (Shared, Local) {
        let dp = ctx.device;

        let clock_cfg = Clocks::default();
        clock_cfg.setup().unwrap();

        Pin::new(Port::A, 0, PinMode::Alt(1));

        let mut dma = Dma::new(dp.DMA1);
        dma::mux(DmaPeriph::Dma1, DmaChannel::C1, DmaInput::Tim2Up);

        let mut timer_pwd = Timer::new_tim2(
            dp.TIM2,
            20_000.,
            TimerConfig::default(),
            &clock_cfg,
        );

        timer_pwd.enable_pwm_output(TimChannel::C1, OutputCompare::Pwm1, 0.1);

        timer_pwd.enable_interrupt(TimerInterrupt::UpdateDma);

        unsafe {
            timer_pwd.write_dma_burst(
                &BUF,
                13,
                4,
                DmaChannel::C1,
                ChannelCfg ::default(),
                true,
                DmaPeriph::Dma1,
            );
        }

        let mut timer = Timer::new_tim3(dp.TIM3, 0.2, Default::default(), &clock_cfg);
        timer.enable_interrupt(TimerInterrupt::Update);
        timer.enable();

        (Shared {}, Local { timer_pwd, timer })
    }

    #[task(binds = TIM3, local=[timer_pwd, timer, a: i32 = 0], priority = 1)]
    fn on_dma1_complete(cx: on_dma1_complete::Context) {
        cx.local.timer.clear_interrupt(TimerInterrupt::Update);

        defmt::println!("psc: {:?}", cx.local.timer_pwd.regs.psc.read().bits());
        defmt::println!("arr: {:?}", cx.local.timer_pwd.regs.arr.read().bits());
        defmt::println!("rcr: {:?}", cx.local.timer_pwd.regs.rcr.read().bits());

        defmt::println!(
            "cx.local.timer_pwd1: {:?}",
            cx.local.timer_pwd.get_duty(TimChannel::C1)
        );
        defmt::println!(
            "cx.local.timer_pwd2: {:?}",
            cx.local.timer_pwd.get_duty(TimChannel::C2)
        );
        defmt::println!(
            "cx.local.timer_pwd3: {:?}",
            cx.local.timer_pwd.get_duty(TimChannel::C3)
        );
        defmt::println!(
            "cx.local.timer_pwd4: {:?}",
            cx.local.timer_pwd.get_duty(TimChannel::C4)
        );
    }
}

#[defmt::panic_handler]
fn panic() -> ! {
    cortex_m::asm::udf()
}

cargo:

[package]
name = "pwm-dma-example"
version = "0.1.0"
edition = "2021"

[dependencies]
defmt = "0.3.4"
defmt-rtt = "0.4.0"
panic-probe = { version = "0.3.1", features = ["print-defmt"] }

cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
hal = { package = "stm32-hal2", version = "^1.8.3", features = ["g431", "g4rt"]}
rtic = { version = "2.1.1", features = ["cortex-m", "thumbv7-backend", "rtic-monotonics"] }

output:

<lvl> psc: 0
└─ dma_pwm::app::on_dma1_complete @ dma_pwm/src/main.rs:82  
<lvl> arr: 8499
└─ dma_pwm::app::on_dma1_complete @ dma_pwm/src/main.rs:83  
<lvl> rcr: 0
└─ dma_pwm::app::on_dma1_complete @ dma_pwm/src/main.rs:84  
<lvl> cx.local.timer_pwd1: 849
└─ dma_pwm::app::on_dma1_complete @ dma_pwm/src/main.rs:86  
<lvl> cx.local.timer_pwd2: 0
└─ dma_pwm::app::on_dma1_complete @ dma_pwm/src/main.rs:90  
<lvl> cx.local.timer_pwd3: 0
└─ dma_pwm::app::on_dma1_complete @ dma_pwm/src/main.rs:94  
<lvl> cx.local.timer_pwd4: 0
└─ dma_pwm::app::on_dma1_complete @ dma_pwm/src/main.rs:98 
@akhilles
Copy link
Contributor

Is it possible that the issue is with measuring the duty cycle rather than generating the signal? In case it's useful, I have a working example of PWM DMA burst tested on a STM32H747I-DISCO. I wasn't able to get examples/timer.rs to compile.

image

#![no_main]
#![no_std]

use cortex_m::delay::Delay;
use hal::{
    clocks::Clocks,
    dma::{self, Dma, DmaChannel, DmaInput, DmaPeriph},
    gpio::{Pin, PinMode, Port},
    pac,
    timer::{OutputCompare, TimChannel, Timer, TimerConfig, TimerInterrupt},
};

use defmt_rtt as _;
use panic_probe as _;

// Calculate the DMA offset by taking the Adddress Offset for
// the associated CCR channel in the RM register table, and dividing by 4.
const TIMX_CCR1_OFFSET: u8 = 0x34;

// 10 duty cycles from 10% to 80% assuming max duty cycle is 5332
const DUTY_CYCLES: [u16; 8] = [533, 1066, 1600, 2133, 2666, 3199, 3732, 4266];

#[cortex_m_rt::entry]
fn main() -> ! {
    let cp = cortex_m::Peripherals::take().unwrap();
    let dp = pac::Peripherals::take().unwrap();
    let clock_cfg = Clocks::default();
    clock_cfg.setup().unwrap();

    let mut delay = Delay::new(cp.SYST, clock_cfg.systick());

    // Generate a PWM signal with 50% duty cycle and 2400 Hz frequency on PC6 (TIM3_CH1)
    // TIM3_CH1 is connected to PC6 as alternate function 2
    let _pwm_pin = Pin::new(Port::C, 6, PinMode::Alt(2));
    let mut pwm_timer = Timer::new_tim3(
        dp.TIM3,
        37_500.,
        TimerConfig::default(),
        &clock_cfg,
    );
    pwm_timer.enable_pwm_output(TimChannel::C1, OutputCompare::Pwm1, 0.5);

    let _dma = Dma::new(dp.DMA1);
    dma::mux(DmaPeriph::Dma1, DmaChannel::C1, DmaInput::Tim3Up);
    pwm_timer.enable_interrupt(TimerInterrupt::UpdateDma);

    loop {
        delay.delay_ms(2000);
        pwm_timer.enable();
        defmt::info!("Generating 50% duty cycle PWM signal");
        delay.delay_ms(5);

        // Set duty cycle using DMA
        unsafe {
            pwm_timer.write_dma_burst(
                &DUTY_CYCLES,
                TIMX_CCR1_OFFSET / 4,
                1,
                DmaChannel::C1,
                dma::ChannelCfg {
                    circular: dma::Circular::Enabled,
                    ..Default::default()
                },
                false,
                DmaPeriph::Dma1,
            );
        }
        defmt::info!("Generating 10% to 80% duty cycle PWM signal");
    }
}

@David-OConnor
Copy link
Owner

Hey! I don't know what's going on. That's possible. Timer shenanigans on STM32 are tricky! LMK if you come up with anything; would love to get this fixed.

@dkotTech
Copy link
Author

@akhilles Hello, I tried your example, but it's not working for me either. Some parts of the code differ between the G4 and H7 chips. Anyway, I tried to set up the project in CubeIDE.

The HAL_TIM_DMABurst_WriteStart function works strangely—some values are transferred from DMA to the timer registers, but some are not. After several attempts, I still can't find the correct way to use this function.

The HAL_TIM_PWM_Start_DMA function works as expected. I can set a buffer for DMA, and DMA sends this buffer to CCRx in a circular manner.

@David-OConnor
Copy link
Owner

David-OConnor commented Sep 22, 2024

LMK if you come up with anything. I've used the burst DMA on G4 only; haven't tried on other MCUs. (And only for one specific use case that may not generalize) I suspect the fix is something simple, but I'm not sure what it is.

What I would do: See if you can get it working using the Reference manual and PAC directly, then post the results here, and/or compare to what the HAL functions are doing.

@dkotTech
Copy link
Author

Hi! After a few hours of searching, I finally managed to achieve what I wanted, and even more. Now, I know two ways to solve the problem.

I’ll clean up the code and post it later.

So I tried to figure out what was wrong with the HAL library code but couldn't find anything suspicious. Everything looks correct, even the parts I had doubts about turned out fine.

In the end, just one line placed in the right spot fixed everything:

dma::enable_mux1();

I’m not sure why the mux doesn’t need to be enabled for the H7 chip; it seems to work differently. Also, note that the mux must be enabled before specifying the routing.

@akhilles
Copy link
Contributor

Nice find! It might be a good idea to enable the DMA mux RCC clock for G4 targets automatically when calling dma::mux? It's just 1 register write, and setting up dma muxing if fairly infrequent.

@David-OConnor
Copy link
Owner

David-OConnor commented Sep 24, 2024

Nice find! I agree that it would be nice to enable it on G4 in dma::mux. Done; in the latest commit.

I also wish the docs could be easily hosted/built for all targets: The enable_mux fn is not in the published docs because it's only on G4.

@dkotTech
Copy link
Author

Hi, thanks for the help.

If anyone has encountered a problem working with DMA, you can check out example with a deep dive into registers.
https://github.com/dkotTech/stm32g431-exp/blob/main/dma_pwm_pac/src/main.rs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants