“ Developers who are new to microcontroller (MCU) development often find themselves confused: should they manipulate registers directly or use library functions? This is akin to choosing between manual and automatic transmission — the former offers precision but is complex, while the latter is simple but lacks a sense of control. This article uses STM32 as an example to explain the characteristics, tools, and differences between the two approaches, helping you find the right choice for yourself.”
For developers who are just starting with microcontrollers (MCUs), a common dilemma arises: should they manipulate registers directly or use library functions? These two approaches are like manual and automatic cars — manual transmission allows for precise control but is complex, while automatic transmission is easy to handle but lacks direct control. This article uses STM32 as an example to explain the characteristics, commonly used tools, and practical differences between the two programming methods, helping you find the right choice for yourself.
01
—
Differences Between the Two Programming Approaches
In simple terms, register-level programming involves directly “communicating” with the hardware by configuring the internal registers of the chip (hardware storage units) to achieve functionality; library function programming, on the other hand, involves indirectly manipulating hardware through vendor-provided encapsulated functions, without directly dealing with register details. The core difference between the two can be summarized as a balance between “control granularity” and “development efficiency”:
|
Characteristics |
Register-Level Programming |
Library Function Programming |
|
Target Object |
Directly read and write registers (e.g., GPIOA->ODR) |
Call encapsulated functions (e.g., HAL_GPIO_WritePin) |
|
Learning Curve |
High. Requires memorization of register addresses and bit functions, relies on chip manuals |
Low. Function names are intuitive, no need to memorize hardware details |
|
Development Speed |
Slow. Each step requires manual configuration, debugging is time-consuming |
Fast. Calls existing functions, suitable for rapid implementation of features |
|
Code Flexibility |
Very high. Allows precise control over every detail of the hardware |
Moderate. Limited by the scope of library function encapsulation |
|
Maintenance |
Difficult. Code depends on hardware details, changing chips may require rewriting |
Easy. Function interfaces are unified, logic is clear |
For example: to set the PA0 pin to output a low level, register-level programming requires writing GPIOA->ODR &= ~(1<<0) (directly manipulating the register), while library function programming only requires HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET) (calling the function).
02
—
Common “Toolkits” for STM32
No matter which programming method is used, specific “toolkits” (library files) are needed to simplify operations. These toolkits act like “translators,” helping us convert C language code into instructions that the hardware can understand.
1. The “Dictionary” for Register-Level Programming: Chip-Specific Header Files
Register-level programming relies on header files provided by chip manufacturers, such as stm32f10x.h for the STM32F1 series. This file serves as a “hardware dictionary”:
It converts the physical addresses of registers (e.g., 0x40010800) into understandable names (e.g., GPIOA), allowing us to use GPIOA->ODR instead of lengthy addresses;
It defines the function of each bit in the register (e.g., GPIO_ODR_ODR0 represents bit 0), avoiding obscure code like 1<<0.
Additionally, the CMSIS standard library (e.g., core_cm3.h) is also used, but it mainly handles “core control” (e.g., interrupts, system clock), while peripheral operations (GPIO, UART, etc.) primarily rely on chip-specific header files.
2. The “Toolkit” for Library Function Programming: From Standard Libraries to HAL Libraries
The library functions for STM32 have developed over the years into three mainstream toolkits, each suitable for different scenarios:
Standard Peripheral Library (SPL):
An older “toolkit” that only supports specific series (e.g., F1, F4). The function encapsulation is lightweight (e.g., GPIO_Init), and within the same series (e.g., F103 and F105), it can be directly ported, but cross-series (e.g., from F1 to F4) often requires rewriting. It is mainly used for maintaining legacy projects.
HAL Library (Hardware Abstraction Layer):
A new “universal toolkit” that supports all STM32 series (F1/F4/F7, etc.). It masks hardware differences through a unified interface, for example, configuring GPIO for F1 and F7 uses the same HAL_GPIO_Init function. The advantage is high portability, suitable for rapid development; the downside is that the code is somewhat redundant.
LL Library (Low-Layer Library):
A “lightweight toolkit” that sits between register-level and HAL libraries. The functions are close to hardware operations (e.g., LL_GPIO_SetOutputPin), with efficiency close to register-level, yet simpler than directly writing registers, suitable for scenarios that require a balance between efficiency and development speed.
03
—
Practical Example: Lighting Up an LED
Using the classic example of “lighting up the LED connected to PA0,” let’s look at the code differences among the four methods (low level lights up the LED, PA0 needs to be configured as push-pull output).
1. Register-Level Programming
Directly manipulating registers, the code is concise but requires hardware knowledge:
#include "stm32f10x.h" // Chip-specific header fileint main(void) { // 1. Enable GPIOA clock (RCC register controls peripheral clock) RCC->APB2ENR |= (1 << 2); // Bit 2 corresponds to GPIOA clock
// 2. Configure PA0 as push-pull output (50MHz) GPIOA->CRL &= ~0x0F; // Clear PA0 configuration bits (lower 4 bits)
GPIOA->CRL |= 0x03; // Configure as push-pull output, speed 50MHz
// 3. Output low level, light up LED while(1) { GPIOA->ODR &= ~(1 << 0); // Clear bit 0, PA0=0 } return 0;}
2. LL Library Programming
Using lightweight functions to encapsulate register operations, balancing simplicity and efficiency:
#include "stm32f1xx_ll_gpio.h"#include "stm32f1xx_ll_rcc.h"int main(void) { // 1. Enable GPIOA clock LL_RCC_GPIOA_CLK_ENABLE();
// 2. Configure PA0 as push-pull output LL_GPIO_SetPinMode(GPIOA, LL_GPIO_PIN_0, LL_GPIO_MODE_OUTPUT); LL_GPIO_SetPinOutputType(GPIOA, LL_GPIO_PIN_0, LL_GPIO_OUTPUT_PUSHPULL);
// 3. Output low level, light up LED while(1) { LL_GPIO_ResetOutputPin(GPIOA, LL_GPIO_PIN_0); }}
3. Standard Library Programming
Using structures to unify configuration parameters, suitable for development within the same series:
#include "stm32f10x_gpio.h"#include "stm32f10x_rcc.h"int main(void) { GPIO_InitTypeDef GPIO_InitStruct; // Define configuration structure
// 1. Enable GPIOA clock RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 2. Configure PA0 parameters GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; // Push-pull output
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStruct); // Apply configuration
// 3. Output low level, light up LED while(1) { GPIO_ResetBits(GPIOA, GPIO_Pin_0); }}
4. HAL Library Programming
More standardized initialization process, supports cross-series portability:
#include "stm32f1xx_hal.h"
GPIO_InitTypeDef GPIO_InitStruct;int main(void) { // 1. Initialize HAL library (including basic kernel configuration) HAL_Init();
// 2. Configure system clock (required for all STM32, not unique to HAL) SystemClock_Config(); // Configure to 72MHz, specific implementation omitted
// 3. Enable GPIOA clock and configure __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitStruct.Pin = GPIO_PIN_0; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// 4. Output low level, light up LED while(1) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET); }}
Note: Configuring the system clock (e.g., SystemClock_Config) is a necessary step for all STM32 programs, regardless of whether the HAL library is used — just as a car must start the engine to drive, the chip also needs the correct clock to function.
04
—
Runtime Effects and Resource Consumption
All four methods can ultimately light up the LED, but the “cost” behind them varies. Below is a comparison of resource consumption and execution efficiency among the different methods (values are approximate ranges, influenced by compiler and chip model):
1. Resource Consumption: Who is More “Space Efficient”?
|
Programming Method |
Flash Usage (Code Storage) |
RAM Usage (Runtime Memory) |
Characteristics |
|
Register-Level |
Minimum |
Minimum |
Almost no redundant code |
|
LL Library |
Somewhat larger |
Close to register-level |
Minor encapsulation overhead |
|
Standard Library |
Medium |
Medium |
Structures and function calls increase consumption |
|
HAL Library |
Maximum |
Maximum |
Compatibility logic leads to more redundancy |
In simple terms: register-level is the most space-efficient, while HAL library consumes the most. For chips with only 8KB of Flash (e.g., STM32F030), using the HAL library may not fit, necessitating the choice of register-level or LL library.
2. Execution Efficiency: Who Responds Faster?
From the perspective of response speed for “setting pin levels”:
Register-level is the fastest: Directly manipulating hardware, with almost no delay;
LL Library is next: Function encapsulation is simple, with very little delay;
Standard Library is slightly slower: Function calls and parameter parsing add a bit of delay;
HAL Library is the slowest: Internal compatibility checks, status evaluations, and other logic introduce noticeable delays.
The overall trend is: register-level < LL Library < Standard Library < HAL Library.
05
—
How to Choose?
There is no absolute “good” or “bad”; it only depends on whether it fits the scenario:
If you want to delve into hardware, choose register-level
Starting with registers can help you understand how STM32 works (e.g., how clocks are allocated, how register bits control peripherals), laying a solid foundation for low-level development.
If you want to quickly develop products, choose HAL Library
Paired with the STM32CubeMX tool (visual configuration, automatic code generation), complex features can be implemented in just a few hours, suitable for rapidly iterating projects.
If resources are tight or you need to balance efficiency, choose LL Library
The code volume is close to register-level, yet simpler than directly writing registers, suitable for medium to small capacity chips.
If maintaining legacy projects, refer to the existing code
If the project uses the standard library, continue using it; for cross-series upgrades, it is recommended to switch to the HAL library.
In actual development, many people will “mix and match”: core functionalities (like high-frequency signal generation) use register-level for efficiency, while peripheral functionalities (like serial communication) use HAL library to save time.
06
—
Conclusion
Register-level programming is like manual transmission, allowing precise control but requiring more operations; library function programming is like automatic transmission, simple and easy to handle but slightly less flexible. When choosing, there is no need to be either/or; decide based on project requirements (development speed, resource constraints, portability) and your own status (learning stage, familiarity with hardware).
If you are a complete beginner and want to quickly work on projects: It is recommended to start with HAL library + STM32CubeMX.
If you are learning older models and want to balance the richness of resources: Start with the standard library.
If you want to balance efficiency and low-level understanding while learning new models: Start with the LL library.
If you have a solid foundation and aim for low-level development: Start with registers (not recommended for direct entry).
Previous Articles:
AI Smart Rings: Small Size, Big Technological Energy
Detailed Explanation of Motor Principles and Classifications
Introduction to Peripheral Component Selection and Layout for Switching Power Supplies (DCDC)
Bare Metal Programming vs. RTOS Programming
Development Board “Battle Royale”: Which of the 5 Popular Development Boards (FeiLing, YouShan, TianMai, etc.) is Your “Dish”?
Calculating FreeRTOS Task Stack Size
5 Major State Machine Design Patterns in Embedded Systems
Differences Between MCU and MPU
In-Depth Analysis of AMD Ryzen Embedded 8000 Series Processors
Usage of the “volatile” Keyword in Embedded C Language