cancel
Showing results for 
Search instead for 
Did you mean: 

Experimenting with STM32 Clock Configuration Outside of CubeMX

GuilhermeSschultz
Associate II

(firstly sorry for the repost, I accidentally made the original post in the wrong topic)

If you, like me, have explored using STM32 with other systems languages or even custom HALs, you’ve probably had this thought at some point:
“it would be really nice to somehow combine this with STM32CubeMX

At least that’s how it was for me :D

I’ve been working with STM32 in bare-metal for a while now, building small custom abstractions, and this idea kept coming back: not necessarily using CubeMX directly, but trying to bring some of its ideas — especially around validation and configuration — into these more independent setups.

For a few months this was just one of those ideas that comes and goes. Until around 5–6 months ago, when I finally decided to try doing something about it; Since then I’ve been experimenting with it little by little — still very much an ongoing work — but I think it has reached a point that’s interesting enough to share and get some feedback.

The main goal is to bring some of the features of STM32CubeMX into something easier to read and adapt, so that it can be reused in independent projects, regardless of language or framework.

One of the first things that stood out to me was one of the biggest inconveniences of working without it: RCC configuration.

Without direct access to CubeMX code generation, configuring and validating clocks becomes quite tedious — you often end up going back and forth with the tool just to make sure each change is still valid, checking VCO limits, prescalers, and so on. This is where my experiments with ClockHelper come in: it started in Zig, but Rust and other languages are also on my radar.

The idea behind the library is to embed a good portion of CubeMX’s clock configuration logic, but using zero/low-cost abstractions. By leveraging compile-time mechanisms available in many modern languages, it becomes possible not only to compute the full clock tree at compile time, but also to eliminate any runtime overhead that would normally be involved.

It’s still far from being complete or production-ready — more of an ongoing exploration — but it’s already usable in some real scenarios.

The examples below use STM32H723 as a base, but I’ve also tested targets like STM32U575 and STM32F777, as well as simpler families like C0, F1, and F3.

One of the first tests was validating clock calculation and verification across multiple domains.

Starting with the “happy path”, using a valid configuration — just checking that all computed values match expectations:
teste1.png

Nothing too surprising here: the calculations are correct, everything behaves as expected.

But things get more interesting once the configuration becomes invalid.

For example, taking a configuration targeting ~550 MHz on the STM32H723:
exemplo2.png

 

 


Everything looks fine — now let’s start breaking it on purpose:

Removing the AHB prescaler (HPRE) causes the frequency to exceed the allowed limit:
exemplo3.png

 

Enabling USB and selecting the PLL as its source, but not using HSE as the PLL input:

exemple4.png

In this case, the validation ensures that when the PLL is used as the USB clock source, its input must be derived from HSE — otherwise the configuration is considered invalid.

Another interesting case is the relationship between VCI (PLL input) and VCO:

By adjusting the input divider (M), it’s possible to push the VCI out of its valid range, which in turn invalidates the VCO — even if the final frequency might look correct at first glance:

example5.pngexemplo6.png

This kind of issue is particularly tricky to catch manually, since it involves multiple stages of the PLL at once.


Finally, even parameters that are not explicitly configured — such as VCI/VCO ranges, VOS, and flash latency — are automatically derived based on the current clock configuration.

The idea is that once the clock is defined, the rest of the system adjusts itself to remain within valid operating limits, without requiring manual tuning of every parameter.

In addition, by using data from the embassy / stm32-data project, most of the generated values are bit-accurate with the hardware, making the setup essentially plug-and-play when applied to registers.

Captura de tela_2026-04-13_15-24-02.png


Overall, this is still far from being a complete solution or something “production-ready” — it’s more of an ongoing exploration of how far this approach can go.

That said, it already covers a good portion of common (and some less common) cases, especially around clock configuration, which tends to be one of the most error-prone parts.

There’s still a lot that could be improved (a future automatic PLL solver would be especially interesting), so any feedback, edge cases, or suggestions are very welcome.




4 REPLIES 4
GuilhermeSschultz
Associate II
waclawek.jan
Super User

So, assertions.

There are many ways to skin a cat.

JW

Why not use c++ for this? constexpr and consteval are very useful for this

Kudo posts if you have the same problem and kudo replies if the solution works.
Click "Accept as Solution" if a reply solved your problem. If no solution was posted please answer with your own.

My choice of Zig is purely personal for this testing phase; if everything works, I intend to create versions for other languages ​​that also have comptime mechanisms.