STM32L476 systick clock seems to be 4MHz, not 8MHz

Hey,

Being new to Rust (not new to embedded) I started to go through the Rust Embedded Book

I'm using a STM32L476G Discovery Kit because I just happened to have one.

Hello world works OK, semihosting output indeed shows the "Hello, world!".

Next I build the "exception" example. The essential part is this:

#[entry]
fn main() -> ! {
    let p = Peripherals::take().unwrap();
    let mut syst = p.SYST;

    // configures the system timer to trigger a SysTick exception every second
    syst.set_clock_source(SystClkSource::Core);
    syst.set_reload(8_000_000); // period = 1s
    syst.enable_counter();
    syst.enable_interrupt();

    loop {}
}

#[exception]
fn SysTick() {
    hprint!(".").unwrap();
}

What I'm trying to understand is that the dots come every two seconds. Not every second. As if the clock is 4MHz. All documentation that I could find describe that the clock is 8MHz. This puzzles me, and I wonder if I have something wrong in my configuration. How can I verify this?

I don't know your chip at all, so I can't really help, but you might have better luck with an answer if you ask on the Rust Embedded Matrix channel. People usually get good results there.

According to the STM32L47xxx Reference Manual, page 207, SysTick can either work with HCLK divided by 8, or HCLK directly. I think your clock source configuration (SystClkSource::Core) means that you're using HCLK directly. So assuming there's nothing weird going on, that would indicate HCLK is set to 16 MHz.

Since your example doesn't include anything related to HCLK configuration, I can't say much more than that. Are you using stm32l4xx-hal? The stm32l4 PAC? Neither?

Looking through stm32l4xx-hal's rcc module, it seems that HCLK is set to 16 MHz by default. If you're using stm32l4xx-hal and don't override the SYSCLK or HCLK configuration, then the 2 dots per second you're seeing make perfect sense.

Thanks Hanno, for the hints.

Let me first correct you, I'm seeing one dot per two seconds. In other words, as if the HCLK is 4MHz.

I'm using stm32l4xx-hal. But the very first time, I think I was using stm32l4. The observation was the same: one dot per two seconds.

When you say: "it seems that HCLK is set to 16 MHz by default", you are talking about these pieces in rcc.rs

const HSI: u32 = 16_000_000; // Hz

and

            // 3. HSI as fallback
            else {
                (HSI, PllSource::HSI16)
            }

If that is correct I must now figure out what the value of AHB PRESC is (see diagram on page 208 of the reference manual). AHB PRESC, that is RCC_CFGR.HPRE[3:0] I think. If that value is 1001, meaning the divider is 4 then it will explain my HCLK at 4MHz.

Searching ..., Reading ...

Sorry, I misread your post.

Regarding rcc.rs, I was looking at different code. I first searched for HCLK and found this (line 802):

Clocks {
    hclk: Hertz(hclk),
    // ...
}

Then I looked where hclk is defined and found line 658:

let hclk = sysclk / hpre_div;

hpre_div is defined right above that, starting in line 641:

let (hpre_bits, hpre_div) = self
    .hclk
    .map(|hclk| match sysclk / hclk {
        // From p 194 in RM0394
        0 => unreachable!(),
        1 => (0b0000, 1),
        2 => (0b1000, 2),
        3..=5 => (0b1001, 4),
        6..=11 => (0b1010, 8),
        12..=39 => (0b1011, 16),
        40..=95 => (0b1100, 64),
        96..=191 => (0b1101, 128),
        192..=383 => (0b1110, 256),
        _ => (0b1111, 512),
    })
    .unwrap_or((0b0000, 1));

Assuming self.hclk is None, which is the default, that leaves us with a hpre_div of 1, meaning hclk == sysclk.

sysclk is defined in line 637:

let sysclk = self.sysclk.unwrap_or(HSI);

Assuming self.sysclk is None (again, the default), sysclk is set to the value of HSI which, as you pointed out, is 16 MHz.

So, assuming both this code and my understanding of it are correct (both of which I can't guarantee :smile:), HCLK should be 16 MHz and you should be getting two dots per second. But it makes sense to double-check the argument I presented here against the Reference Manual and the actual register values.

But are you actually running that code? If all of what I said is correct, then 16 MHz is the default configuration that results from stm32l4xx-hal's RCC configuration. But it looks like you're only using features that are not specific to the STM32L4 (SysTick and Semihosting), so maybe you're getting away without running any of that code? Hard to say without seeing your full example.

Oh, and an off-topic note: Semihosting is extremely slow and not generally recommended (even though not all of the documentation reflects that). It should be fine for figuring out what's going on here, but for the future, I'd look into using RTT through cargo embed on the host side and rtt-target on the device.

Using the debugger I saw that the code in rcc.rs is not executed. So something else must be doing that.

I tried another example, the blinky example from stm32l4xx-hal. Slightly modified. Again I see a clock that is half as slow as it should be, at .5Hz. (BTW, I build the blinky example with mbed and that nicely blinks at 1Hz)

Here is the complete example.

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

// use panic_halt as _;
use panic_semihosting as _;

use cortex_m::peripheral::syst::SystClkSource;
use cortex_m_rt::{entry};
//use cortex_m_semihosting::{hprint, hprintln};

use stm32l4xx_hal::delay::Delay;
use stm32l4xx_hal::prelude::*;
use stm32l4xx_hal::stm32::{Peripherals};

#[entry]
fn main() -> ! {
    let cp = cortex_m::Peripherals::take().unwrap();
    let dp = Peripherals::take().unwrap();
    let syst = cp.SYST;

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

    // Try a different clock configuration
    let clocks = rcc.cfgr.hclk(8.mhz()).freeze(&mut flash.acr, &mut pwr);

    let mut gpioe = dp.GPIOE.split(&mut rcc.ahb2);
    let mut led_green = gpioe
        .pe8
        .into_push_pull_output(&mut gpioe.moder, &mut gpioe.otyper);

    let mut timer = Delay::new(syst, clocks);

    loop {
        timer.delay_ms(500_u32);
        led_green.set_high().ok();
        timer.delay_ms(500_u32);
        led_green.set_low().ok();
    }
}

This time I can set breakpoints in freeze in rcc.rs. Stepping through the code I see the value of hclk being 8000000 and sysclk being 16000000.

Breakpoint 6, stm32l4xx_hal::rcc::CFGR::freeze (self=0x20017f34, acr=0x20017ee0, pwr=0x20017f18) at /home/kees/.cargo/registry/src/github.com-1ecc6299db9ec823/stm32l4xx-hal-0.6.0/src/rcc.rs:630
630	        let hclk = sysclk / hpre_div;
(gdb) n
632	        assert!(hclk <= sysclk);
(gdb) p hclk
$1 = 8000000
(gdb) p sysclk
$2 = 16000000
(gdb) p hpre_bits
$3 = 8
(gdb) p hpre_div
$4 = 2

That is what it should be. Code seems OK, but the behavior doesn't match.

I think I know the cause of your problem:

let mut total_rvr = us * (self.clocks.sysclk().0 / 1_000_000);

The delay code in stm32l4xx-hal uses the SYSCLK frequency to compute the delay, not HCLK. So if HCLK is different from SYSCLK, the delay is going to be wrong. In fact, there's already an open issue.

Maybe you could take this opportunity to get more involved in the Embedded Rust community by fixing the issue and opening a pull request. The HCLK frequency is available in the Clocks struct that this code has access to, so it should be a trivial fix.

Thanks Hanno, that indeed fixes my blinky frequency. Your help is much appreciated. I'll try to send a PR for this.

That said, the same problem remains in the exception.rs example. So, the search continues.

You're welcome!

Can you please post the full code if the example that's still causing you problems? You said that rcc.rs wasn't being run, but I'd like to get a more complete picture of what the example is actually doing.

I started with this cargo generate --git https://github.com/rust-embedded/cortex-m-quickstart as described in the rust-embedded book.

Because it was complaining during the build about missing vector table I added this line (without really understanding why).

use stm32l4::stm32l4x6::NVIC;
//! Overriding an exception handler
//!
//! You can override an exception handler using the [`#[exception]`][1] attribute.
//!
//! [1]: https://rust-embedded.github.io/cortex-m-rt/0.6.1/cortex_m_rt_macros/fn.exception.html
//!
//! ---

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

// use panic_halt as _;
use panic_semihosting as _;

use cortex_m::peripheral::syst::SystClkSource;
use cortex_m::Peripherals;
use cortex_m_rt::{entry, exception};
use cortex_m_semihosting::{hprint};

use stm32l4::stm32l4x6::NVIC;

#[entry]
fn main() -> ! {
    let p = Peripherals::take().unwrap();
    let mut syst = p.SYST;

    // configures the system timer to trigger a SysTick exception every second
    syst.set_clock_source(SystClkSource::Core);
    syst.set_reload(8_000_000); // period = 1s
    syst.enable_counter();
    syst.enable_interrupt();

    loop {}
}

#[exception]
fn SysTick() {
    hprint!(".").unwrap();
}

I understand the semihosting is slow, but I don't think it should cause the slow down. As prove of this, if I change the constant to 4_000_000 then the pace of dots is roughly 1Hz.

I'm half-guessing here, but I'd say that because you didn't reference stm32l4 in the code, it wasn't included at all in the binary. Hence the missing vector table. If that's correct, than a use stm32l4 as _; should have the same effect.

I completely agree. I wasn't trying to imply that semihosting was involved in this problem, just pointing to something that might be a better solution going forward.


Okay, so looking at your code, there's simply nothing that touches the RCC, so all that's relevant to figuring this out are the default values in the RCC registers. To the Reference Manual!

Figure 15 on page 208 has an overview over the whole clock situation. Here we see that HCLK is defined by SYSCLK and AHB PRESC.

The AHB prescaler is defined in the RCC_CFGR register. The overview over the bits on page 227 shows that the reset value for all bits of that register is 0. On the next page, we see that HPRE defines the AHB prescaler. A value of 0 means that SYSCLK is not divided.

So by default, HCLK = SYSCLK. Page 205 states that after reset, MSI is used to feed SYSCLK, at 4 MHz.

Problem solved, no? After reset, SYSCLK/HCLK is running at 4 MHz, so at a SYSTICK reload value of 8 million, you should get a frequency of 0.5 Hz. (Unless I'm missing something, which, as always, is totally possible.)

Great Hanno. This really sounds plausible. I'll check it once more later this weekend.
Again, you were of great help. Thanks for that.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.