HAL vs Bare Metal
July 10, 2026 · by Aayush Rai
When we start learning STM32, or any microcontroller, seriously, one confusion always shows up: should I start with HAL, or jump straight into bare-metal programming? This isn't just a beginner question — even people who've worked with microcontrollers for a while get stuck here, because both approaches have their own charm. HAL feels easy, clean, and fast. Bare metal feels powerful, raw, and close to the actual hardware.
But if you're just starting, the better question isn't "which one is better?" It's which one will help me understand the hardware properly without killing my confidence in the beginning? And for that, we need to understand both of them properly.
What is HAL?
HAL stands for Hardware Abstraction Layer — a layer of code provided by STMicroelectronics that lets us use STM32 peripherals without directly touching the registers every time. Blinking an LED with HAL doesn't require manually configuring every bit inside RCC, the GPIO mode register, the output register, or the clock enable register:
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET); HAL_Delay(1000); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET); HAL_Delay(1000);
This makes life easy in the beginning — you can focus on making things work first. And that matters, because if everything feels too difficult too early, you may lose interest before actually entering the real game.
What is Bare Metal?
Bare-metal programming means writing code very close to the hardware. You deal with registers directly — no asking HAL to configure GPIO for you. You go to the reference manual, find the register, find the required bit position, and configure it yourself:
// Enable GPIOA clock RCC->AHB1ENR |= (1 << 0); // Set PA5 as output GPIOA->MODER |= (1 << 10); // Toggle PA5 GPIOA->ODR ^= (1 << 5);
It looks scary at first. But once it starts making sense, it becomes beautiful — because now you're not just using the microcontroller. You're actually talking to it.
Which One is Suitable for Beginners?
For most beginners, HAL is the better starting point — not because bare metal is bad, but because HAL gives a smoother entry into the STM32 ecosystem. When you're new, there's already a lot to absorb: board selection, pin configuration, clock setup, IDE setup, the debugger, the datasheet, the reference manual, the build process, the flashing process, peripheral working. Stack register-level programming on top of all that immediately, and it can become overwhelming.
Small wins matter: HAL gives you a working base — generate code with CubeMX, open it in STM32CubeIDE, flash it, debug it, and see the output. That first LED blink, that first UART message, that first successful build all build the confidence you need when learning something this deep.
Why Should You Start Low?
This may sound opposite to the previous point, but stay with it: start with HAL, but don't stay completely dependent on it forever. Microcontrollers aren't magic boxes — they're hardware blocks controlled by registers. To get good at embedded systems, robotics, PCB bring-up, debugging, driver writing, or firmware development, you need to understand what's happening below the abstraction layer. Starting low means understanding:
- What happens when a GPIO clock is enabled
- Why the GPIO mode register needs to be configured
- How alternate functions work, and how UART baud rate is calculated
- How timers count and how interrupts are triggered
- Why some peripherals silently fail unless clocks are configured properly
- Why one wrong bit can break an entire peripheral
HAL helps you run. Bare metal teaches you how the legs are moving.
Getting Familiar With Hardware Through HAL
HAL is a genuinely good learning tool if you use it properly. The wrong way is: generate code, call HAL functions, copy-paste from the internet, never read what's happening. The right way is: generate code, make it work, observe the generated files, and understand what HAL is doing internally.
CubeMX-generated code lands inside files like gpio.c, gpio.h, and main.c. Open them and you'll start seeing
structures like:
GPIO_InitTypeDef GPIO_InitStruct = {0};Clock enable macros, pin mode selection, pull-up/pull-down configuration, speed configuration — it's all there. HAL isn't just helping you write code here; it's showing you the proper initialization flow. It's training wheels on a cycle. You shouldn't keep them forever, but in the beginning, they save you from falling too hard.
How to Switch from HAL to Bare Metal
The best way to switch is not by throwing HAL away in one day — do it slowly and smartly. Start with a
simple GPIO output project generated by CubeMX and open the generated files: main.c, gpio.c, gpio.h,
stm32xxxx_hal_msp.c. Understand which function runs first, where the clock is
enabled, where the pin mode is configured, and where the output pin is written.
Then make small changes. Create your own source file — say my_gpio.c and
my_gpio.h — and write your own function:
void LED_On(void)
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);
}Include it in main.c with #include "my_gpio.h"
— now you're learning file structure, modular coding, and project organization. Next, replace one HAL
function with direct register access:
// Instead of: HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); // Try: GPIOA->ODR ^= (1 << 5);
Then slowly replace initialization too, one peripheral at a time:
This way, you don't jump straight into deep water. You learn swimming in the shallow side first, then go deep.
Advantages of Bare-Metal Programming
Bare metal gives you real control, and in embedded systems, control is everything. You know exactly which register is touched, which bit is set, why the peripheral is working — and when it doesn't, you know where to look.
Better understanding of hardware
Bare metal forces you to read the reference manual — not YouTube, not random GitHub code, not even HAL. The actual behavior of the chip lives in the datasheet, and reading it builds real confidence.
Better debugging skills
When HAL code breaks, beginners often get stuck. Knowing the register-level working lets you check whether the clock is enabled, the pin mode is correct, the alternate function is selected, the interrupt is enabled, the flag is set, or the data register is receiving anything — the real debugging mindset.
Less dependency on libraries
Libraries help, but blind dependence on them is risky. Bare metal makes you independent: the register names change across STM32, AVR, ESP32, RP2040, NXP, or TI, but the thinking stays the same.
Optimized code
Bare-metal code can be smaller and faster, since it avoids unnecessary abstraction — something that matters a lot in resource-limited systems where RAM, flash, and timing are tight.
A stronger career foundation
It separates the person who only knows how to call functions from the person who understands what those functions are actually doing — a strong foundation for embedded systems, robotics, firmware, or low-level driver work.
Beginner Bare-Metal Project Ideas
Don't start bare metal by writing USB drivers or Ethernet stacks — that's pain. Start small and build confidence project by project.
LED blink using registers
The "hello world" of embedded systems — teaches clock enabling, GPIO mode configuration, the output data register, and delay loops.
Button input and LED output
Teaches input mode, pull-up/pull-down, reading the input data register, and basic decision making.
LED pattern using GPIO
Builds comfort with bitwise operations — AND, OR, XOR, shifting, and masking.
UART transmit
Send "Hello STM32" on the serial monitor using direct register access — UART shows up everywhere in embedded systems.
UART receive and echo
Teaches status flags, the data register, polling, and basic communication flow.
Timer-based LED blink
Stop using software delay and use hardware timers instead — teaches the importance of proper timing.
External interrupt from a button
Teaches interrupt configuration, the NVIC, and writing an ISR.
ADC read and print value
Read analog voltage from a potentiometer and send it over UART — teaches ADC setup, analog input, conversion start, and reading ADC data.
These projects look simple, but done properly at register level, they build a genuinely strong foundation.
HAL vs Bare Metal: The Real Answer
Start with HAL. Understand the ecosystem — get familiar with STM32CubeIDE, CubeMX, the debugger, flashing, clock configuration, and project structure. Then slowly start reading the generated code. Then start modifying it. Then create your own files. Then replace HAL functions one by one with register-level code, and slowly move toward pure bare metal.
This path beats jumping straight into bare metal and getting frustrated, because the goal isn't to prove you're hardcore on day one — it's to actually understand the microcontroller. HAL is a good entry gate. Bare metal is the real workshop. Use HAL to enter. Use bare metal to become strong.
HAL and bare metal aren't enemies — they're two stages of the same journey. HAL helps you start. Bare metal helps you grow. In the beginning, HAL gives you speed and confidence. Later, bare metal gives you control and understanding. The best embedded engineer isn't the one who hates HAL — it's the one who understands what HAL is doing under the hood and can write the same thing manually when needed.
So start simple. Blink the LED. Read the code. Open the reference manual. Break things. Fix them. And slowly, the microcontroller will stop looking like a black box. It will start looking like a machine that's waiting for your command.
"Start with the tools that make you comfortable, but never stop before understanding the machine that makes you powerful."
Keep building. Keep reading the registers. Keep becoming the magician of your own machine.