Skip to main content
Associate II
July 28, 2026
Solved

Bare-metal TMC2209 UART driver for STM32 (CMSIS) — Feedback on API design & next steps

  • July 28, 2026
  • 6 replies
  • 149 views

Hi everyone!

I'm writing my first bare-metal driver for STM32 in C (CMSIS, no HAL). The driver controls a TMC2209 stepper motor driver over half-duplex UART.

The basic functionality is currently working: initialization, frame assembly, CRC calculation, and movement control via VACTUAL. To keep it hardware-agnostic, the UART send function is decoupled using a function pointer (handler->send). Registers are defined using union and bitfields (struct).

Since this is my first driver, I'd love to get feedback from experienced embedded developers on the following questions:

  1. API Design & User Access: What level of API access should I expose to the user, and how should it be structured? Is it better to hide register manipulation behind high-level functions (e.g., set_speed, set_current), or keep direct register access accessible?

  2. Bitfields vs. Shifts/Masks: Is using union + struct (bitfields) acceptable for production-ready embedded code, or is it better to switch to explicit bit shifts and masks (1 << N) for strict C standard compliance and cross-compiler portability?

  3. Where to go next: In what direction should I evolve this driver overall? Which features or architectural improvements should I focus on next (e.g., implementing UART read, error handling, register caching, StallGuard, etc.)?

I'd really appreciate any critique on my code, architecture, or general advice on best practices!

Best answer by Andrew Neil

Also, to change parameters on the fly, does that mean reading the register, modifying the bits via shifts and masks, and writing it back? 

What “register” are you talking about here?

  • In the TMC2209 ?
  • In the host processor (STM32, AVR, whatever) ?
  • Both ?
  • Other ?

As ​@Pavel A. suggests, you will have to study the datasheets for the devices to determine what is possible.

In some cases (STM32 and AVR certainly have some), there are specific bit set/reset registers - which allow things to be changed without doing a read-modify-write.

But, if your device has only read and write, then you’ll have to do a read, then modify, then write.

6 replies

Andrew Neil
Super User
July 28, 2026

To keep it hardware-agnostic …

 

…  Bitfields vs. Shifts/Masks: Is using union + struct (bitfields) acceptable

 

Bitfields are very much not portable; as K&R says, “Almost everything about fields is implementation-dependent”.

So, if being “hardware-agnostic” is a requirement, definitely prefer shifts & masks.

A complex system that works is invariably found to have evolved from a simple system that worked.A complex system designed from scratch never works and cannot be patched up to make it work.
RafoAuthor
Associate II
July 28, 2026

Thanks for the reply! I want this driver to be universal and usable across STM32, ESP32, and even AVR. So, as suggested, I will get rid of union and bitfields. I'll implement an initial setup function like tmc2209_init(), and then expose API functions to change specific key parameters. The tmc2209_write_register() function will be called internally through those API functions and hidden from the user.

Also, to change parameters on the fly, does that mean reading the register, modifying the bits via shifts and masks, and writing it back? Is this the right approach? What would you suggest?

Andrew Neil
Andrew NeilBest answer
Super User
July 29, 2026

Also, to change parameters on the fly, does that mean reading the register, modifying the bits via shifts and masks, and writing it back? 

What “register” are you talking about here?

  • In the TMC2209 ?
  • In the host processor (STM32, AVR, whatever) ?
  • Both ?
  • Other ?

As ​@Pavel A. suggests, you will have to study the datasheets for the devices to determine what is possible.

In some cases (STM32 and AVR certainly have some), there are specific bit set/reset registers - which allow things to be changed without doing a read-modify-write.

But, if your device has only read and write, then you’ll have to do a read, then modify, then write.

A complex system that works is invariably found to have evolved from a simple system that worked.A complex system designed from scratch never works and cannot be patched up to make it work.
Pavel A.
July 28, 2026

Hi,

Usually drivers are developed to satisfy some requirements and specifications (for example, be compatible with some existing or new software). Without knowing your requirements we cannot give feedback on “architecture” etc.

Being portable among STM32, other ARMS, ESP32 and more is good, but by itself it is not architecture, and not clear whether it is a feature.

> Also, to change parameters on the fly, does that mean reading the register, modifying the bits via shifts and masks, and writing it back? Is this the right approach?

ARM cortex has only “load” and “store” operations on device registers, so yes - to modify some bits in a UART register you have to load it into a CPU register, modify the bits and store it back. ST in their “HAL” library has added an interesting twist: use of ldrex & strex instructions. The intent is enable lock-less “atomic” operations on the same UART register in several program flows, such as receive and transmit. Using ldrex & strex on device registers is AFAIK a vendor-specific extension of Cortex-M, so be warned. If your driver needs to synchronize modification of one or more registers in parallel flows, provide locking.

Good luck.

RafoAuthor
Associate II
July 29, 2026

Thanks for the detailed answer and the info I'll go study the theory behind what you mentioned