David-OConnor / stm32-hal

This library provides access to STM32 peripherals in Rust.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

TIM1/TIM8 PWM mode not working on STM32L476

badrbouslikhin opened this issue · comments

Hi,

I've been trying to use TIM1 and TIM8 advanced timers on STM32L476 but there seems to be an issue.

The following example doesn't output anything on PC6:

#![no_main]
#![no_std]

use cortex_m::{self, delay::Delay};
use cortex_m_rt::entry;

// These lines are part of our setup for debug printing.
use defmt_rtt as _;
use panic_probe as _;

// Import parts of this library we use. You could use this style, or perhaps import
// less here.
use stm32_hal2::{
    self,
    clocks::Clocks,
    gpio::{Pin, PinMode, Port},
    pac,
    timer::{OutputCompare, TimChannel, Timer, TimerConfig},
};

#[entry]
fn main() -> ! {
    // Set up ARM Cortex-M peripherals. These are common to many MCUs, including all STM32 ones.
    let cp = cortex_m::Peripherals::take().unwrap();
    // Set up microcontroller peripherals
    let mut dp = pac::Peripherals::take().unwrap();

    stm32_hal2::debug_workaround();

    // Create an initial clock configuration that uses the MCU's internal oscillator (HSI),
    // sets the MCU to its maximum system clock speed.
    let clock_cfg = Clocks::default();

    clock_cfg.setup().unwrap();

    // Set up PWM pin
    defmt::println!("Setting up PWM pins");
    let _pwm_pin = Pin::new(Port::C, 6, PinMode::Alt(3));

    defmt::println!("Configuring PWM timer");
    let mut pwm_timer = Timer::new_tim8(dp.TIM8, 2_400., Default::default(), &clock_cfg);
    pwm_timer.enable_pwm_output(TimChannel::C1, OutputCompare::Pwm1, 0.5);
    pwm_timer.enable();

    // Setup a delay, based on the Cortex-m systick.
    let mut delay = Delay::new(cp.SYST, clock_cfg.systick());

    // Port::A, 5
    let mut led = Pin::new(Port::A, 5, PinMode::Output);

    loop {
        defmt::println!("Looping!");
        led.set_high();
        //delay.delay_ms(100);
        //led.set_low();
        //delay.delay_ms(100);
    }
}

// same panicking *behavior* as `panic-probe` but doesn't print a panic message
// this prevents the panic message being printed *twice* when `defmt::panic` is invoked
#[defmt::panic_handler]
fn panic() -> ! {
    cortex_m::asm::udf()
}

/// Terminates the application and makes `probe-run` exit with exit-code = 0
pub fn exit() -> ! {
    loop {
        cortex_m::asm::bkpt();
    }
}

But changing the PWM related lines to use TIM3 on the same PC6 pin works:

    defmt::println!("Setting up PWM pins"); 
    let _pwm_pin = Pin::new(Port::C, 6, PinMode::Alt(2));

    defmt::println!("Configuring PWM timer");
    // Configure PWM timer
    let mut pwm_timer = Timer::new_tim3(dp.TIM3, 2_400., Default::default(), &clock_cfg);
    pwm_timer.enable_pwm_output(TimChannel::C1, OutputCompare::Pwm1, 0.5);
    pwm_timer.enable();

Trying to use TIM1 with stm32-rs/stm32l4xx-hal works fine (I couldn't use TIM8, for some reason it doesn't seem implemented):

//! Testing PWM output

#![deny(unsafe_code)]
#![deny(warnings)]
#![no_main]
#![no_std]

extern crate panic_halt;

use std::println;

// use cortex_m::asm;
use cortex_m_rt::entry;
use stm32l4xx_hal::{delay, prelude::*, stm32};

#[entry]
fn main() -> ! {
    let c = cortex_m::Peripherals::take().unwrap();
    let p = stm32::Peripherals::take().unwrap();

    let mut flash = p.FLASH.constrain();
    let mut rcc = p.RCC.constrain();
    let mut pwr = p.PWR.constrain(&mut rcc.apb1r1);

    let clocks = rcc.cfgr.freeze(&mut flash.acr, &mut pwr);

    let mut gpioa = p.GPIOA.split(&mut rcc.ahb2);

    // TIM1
    let c1 = gpioa
        .pa8
        .into_alternate(&mut gpioa.moder, &mut gpioa.otyper, &mut gpioa.afrh);

    let mut pwm = p.TIM1.pwm(c1, 1.kHz(), clocks, &mut rcc.apb2);

    let max = pwm.get_max_duty();

    pwm.enable();

    let mut timer = delay::Delay::new(c.SYST, clocks);
    let second: u32 = 100;

    // NB: if the pins are LEDs, brightness is not
    //     linear in duty value.
    loop {
        pwm.set_duty(max);
        timer.delay_ms(second);
        // asm::bkpt();
    }
}

I don't have an answer yet, but am suspicious this is related to them being "advanced control timers". I suspect I'm omitting a step. (Have not used them myself)

Are you able to use TIM1 or TIM8 for anything, like triggering an interrupt, or reading its value?

I was thinking the same, I tried to compare how your hal and stm32-rs/stm32l4xx-hal init. this peripheral but it wasn't easy for me to do so with all the macros.

I'll go through the reference manual and check what's missing.

I did read the counter value in the loop at some point, I could see it incrementing and resetting:

TIM8 counter 1
TIM8 counter 9
TIM8 counter 17
TIM8 counter 25
TIM8 counter 33
TIM8 counter 40
TIM8 counter 48
TIM8 counter 56
TIM8 counter 64
TIM8 counter 71
TIM8 counter 79
TIM8 counter 87
TIM8 counter 94
TIM8 counter 102
TIM8 counter 110
TIM8 counter 118
TIM8 counter 125
TIM8 counter 133
TIM8 counter 141
TIM8 counter 149
TIM8 counter 156
TIM8 counter 164
TIM8 counter 172
TIM8 counter 180
TIM8 counter 4
TIM8 counter 12
TIM8 counter 20
TIM8 counter 27
TIM8 counter 35
TIM8 counter 43
TIM8 counter 51
TIM8 counter 58
TIM8 counter 66
TIM8 counter 74
TIM8 counter 82
TIM8 counter 89
TIM8 counter 97
TIM8 counter 105
TIM8 counter 113
TIM8 counter 120
TIM8 counter 128
TIM8 counter 136
TIM8 counter 143
TIM8 counter 151
TIM8 counter 159
TIM8 counter 167

Interesting. I suspect then it has to do with how it's being configured for PWM. I also skimmed the L4xx code - of note, they don't impl TIM1 or 8 for normal timers, and for PWM, TIM1 doesn't appear to be diff from the other ones.

OK, found something relevant in L4xx hal. Try adding this after timer setup, and see if it works. If it does, I'll update this lib. I haven't checked what these do, but noticed L4xx has this for advanced control timers:

let TIM8 = unsafe { &(*PAC::TIM8::ptr()) };

TIM8.bdtr.write(|w| w.moe().set_bit());
TIM8.egr.write(|w| w.ug().set_bit());

That did the trick!
Actually only TIM8.bdtr.write(|w| w.moe().set_bit()); does the trick.

Bit 15 MOE: Main output enable

This bit is cleared asynchronously by hardware as soon as one of the break inputs is active (BRK or BRK2). It is set by software or automatically depending on the AOE bit. It is acting only on the channels which are configured in output.

0: In response to a break 2 event. OC and OCN outputs are disabled

In response to a break event or if MOE is written to 0: OC and OCN outputs are disabled

or forced to idle state depending on the OSSI bit.

1: OC and OCN outputs are enabled if their respective enable bits are set (CCxE, CCxNE in

TIMx_CCER register).

That makes sense.

Thanks a lot!

Nice. Fixed on GH. Could you please let me know if that works now without adding that workaround to your code?

Nvm, issues with this fix for now; need to figure out the best way to do it

Nvm, issues with this fix for now; need to figure out the best way to do it

Alright, let me know when it's ready to test!

Added to errata for now; fix would be more macro mess.