The STM32L0 series from STMicroelectronics is a family of ultra-low-power 32-bit microcontrollers (MCUs) built on the ARM Cortex-M0+ core, running up to 32 MHz. Optimized for battery-powered and energy-sensitive applications, the STM32L0 is widely used in wearables, medical devices, IoT nodes, and wireless sensors, where long operating life and efficiency matter most.
this blog cover the key features, explore low-power modes, show HAL-based peripheral code examples, and compare STM32L0 with its sibling, the STM32C0 series.
Why STM32L0?
The STM32L0 series stands out as one of STMicroelectronics’ most power-efficient microcontroller families, combining the ARM Cortex-M0+ core with advanced low-power technologies. Designed specifically for battery-powered, portable, and long-life applications, it enables engineers to push the boundaries of energy harvesting, IoT sensing, and wearable devices without sacrificing essential performance.
At the heart of STM32L0 is a relentless focus on power efficiency without compromising capability. It balances processing performance, memory scalability, and peripheral integration with industry-leading power figures.
- Dynamic run mode consumption:
- 49 µA/MHz (with external DC/DC)
- 76 µA/MHz (with internal LDO)
- Ultra-low-power standby modes:
- 230 nA (with backup registers)
- 340 nA (with full RAM retention + low-power timer)
- Memory options: Up to 192 KB Flash (with ECC), 20 KB RAM, and 6 KB EEPROM (with ECC for data reliability in harsh conditions)
- Fast wake-up: Just 3.5 µs from Stop mode
But the STM32L0 is more than just a low-power MCU. It provides:
- Integrated analog features like high-resolution ADC with oversampling, DAC, and comparators, reducing external component count.
- Hardware security through AES encryption and Flash sector protection, vital for medical and IoT security requirements.
- Advanced peripherals such as LCD drivers, touch-sensing controllers, and USB device support in select variants.
- Flexible packaging from tiny 14-pin WLCSP options for ultra-compact designs to 100-pin LQFP for feature-rich systems.
Thanks to its ultra-low-power modes, backup registers, and EEPROM, the STM32L0 can retain critical data, resume operation instantly, and extend battery life to years—making it an ideal fit for energy harvesting and coin-cell powered designs in wearables, remote sensors, and medical implants.
Peripheral Highlights
STM32L0 packs a rich set of peripherals tailored for low-power use cases:
- Analog interfaces: 12-bit ADC (with oversampling for 16-bit effective resolution), DAC, comparators
- Security: Hardware AES encryption, Flash sector protection
- Display/Interaction: LCD drivers, touch sensing
- Connectivity: I²C, SPI, UART, USB device (in select models)
- Low-power timers & comms: Operable even in sleep/stop modes
The wide package range (14 to 100 pins) makes it suitable for both tiny wearable sensors and larger industrial designs.
STM32L0 – HAL Code Examples
Using STM32CubeMX and STM32CubeIDE, you can configure peripherals graphically and generate HAL-based code quickly. Below are practical examples.
GPIO — Blinking an LED (detailed STM32CubeMX configuration steps)
Below is a step-by-step walkthrough of how to configure STM32CubeMX to blink an LED on PA5 (typical for many Nucleo/Discovery boards). I’ll show exact menu choices, recommended settings, what CubeMX generates, and where to put the toggle code in STM32CubeIDE.
1) Start a new project
- Open STM32CubeMX.
- Click New Project → Board Selector (if you have a dev board) orMCU Selector (if you have only the MCU part).
- If you know your board (e.g., Nucleo-Fxxx), search for it and select it.
- If you know the MCU (e.g.,
STM32L0xx
variant), choose the exact part number.
- Click Start Project.
2) Pinout: set PA5 as GPIO_Output
- In the Pinout & Configuration view you’ll see a package diagram. Find PA5 and click it.
- A pop-up shows pin functions. Choose GPIO_Output.
- CubeMX will mark PA5 as
GPIO_Output
in green.
- CubeMX will mark PA5 as
Why this matters: setting the pin to GPIO_Output makes CubeMX generate MX_GPIO_Init()
code that configures the pin’s mode, pull resistors, and speed.
3) Configure the GPIO parameters (GPIO settings)
- In the left pane under System Core, expand GPIO and click GPIO (or click the PA5 entry in the left list).
- In the GPIO configuration table find the row for PA5 and set:
- Mode:
Output Push Pull
(default; lower leakage than open-drain when not used with pull-ups) - Pull-up/Pull-down:
No pull
(orPull-down
/Pull-up
if your hardware needs a default) - Speed:
Low
(minimize switching speed reduces EMI and often lowers dynamic current) - GPIO Output Level:
Low
(initially keep LED off; you can choose High if you prefer LED on at reset)
- Mode:
Notes:
- If you plan to use the LED as active-low, you might set Output Level
High
so the LED is off after reset. - For ultra-low-power designs, pick the slowest speed that still meets timing requirements.
4) Clock Configuration (basic)
- Click the Clock Configuration tab at the top.
- For a simple LED blink you can use the default HSI (internal 16 MHz) oscillator. Ensure:
- HCLK is set to a reasonable value (16 MHz is fine for PA5 toggling).
- System Clock Mux =
HSI
orPLL
configured if you need a different speed.
Why: HAL_Delay() (SysTick) timing depends on the system clock configuration. Using HSI avoids needing external crystals.
5) Middleware / NVIC (not required for simple blink)
- You don’t need to enable any middleware.
- No NVIC interrupt setup is required for a simple polling-based blink. If you plan to use a timer interrupt for toggling, enable the timer (TIMx) and set NVIC priority in Configuration → NVIC.
6) Project Manager: project settings and toolchain
- Click Project Manager.
- Fill in Project Name (e.g.,
LED_blink_PA5
). - Toolchain / IDE: select STM32CubeIDE (or Keil/TrueSTUDIO if you use those).
- Optionally add Project location and Project type (C/C++).
- Click Generate Code (or Generate and Open Project).
Tip: If you plan to open later in CubeIDE, choosing STM32CubeIDE makes the import seamless.
7) Generated code: where to look
After generation, CubeMX writes the HAL initialization and user hooks:
main.c
— application entry; containsHAL_Init()
,SystemClock_Config()
, and a call toMX_GPIO_Init()
.gpio.c
— containsMX_GPIO_Init()
implementation which configures PA5.gpio.h
— declarations for GPIO init and pin defines (e.g.,#define LED_Pin GPIO_PIN_5
,#define LED_GPIO_Port GPIOA
).
Open gpio.c
and you’ll see something like:
void MX_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; /* GPIO Ports Clock Enable */ __HAL_RCC_GPIOA_CLK_ENABLE(); /*Configure GPIO pin Output Level */ HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET); /*Configure GPIO pin : PA5 */ GPIO_InitStruct.Pin = GPIO_PIN_5; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); }
8) Add blink logic in main.c
- Open
main.c
. Find thewhile (1)
loop. - Insert the toggle + delay calls (CubeMX may have a template comment like
/* USER CODE BEGIN WHILE */
— put your code between theUSER CODE
blocks so regenerated code won’t overwrite it):
/* USER CODE BEGIN WHILE */ while (1) { HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); // Toggle PA5 HAL_Delay(500); // 500 ms } /* USER CODE END WHILE */
Why use USER CODE blocks: They protect your custom code when you re-open CubeMX and regenerate the project.
9) Build and flash in STM32CubeIDE
- Launch STM32CubeIDE (or open the generated project if CubeMX opened it).
- Build the project: Project → Build All (or click the hammer icon).
- Connect your ST-LINK (on-board or external).
- Debug/Run: click the green Debug/Run icon to flash, or Run → Debug then Resume. The LED should start blinking.
10) Optional: use a Timer instead of HAL_Delay (recommended for low power/precision)
HAL_Delay()
uses SysTick and busy waits; for better timing and lower CPU usage:
- In CubeMX, enable a timer (e.g., TIM2) under Timers.
- Configure it in Mode =
Time Base
, set Prescaler and Period so that it triggers at your desired interval. Enable Update Interrupt if you want to toggle in ISR. - Implement the toggle in the timer callback
HAL_TIM_PeriodElapsedCallback()
or useHAL_TIM_Base_Start_IT()
inmain()
. - Using interrupts allows the MCU to enter sleep modes between toggles.
11) Low-power considerations (quick checklist)
- Use
GPIO_SPEED_LOW
. - Use
GPIO_NOPULL
unless hardware needs a pull. - If LED drive current matters, consider turning the LED on only briefly or use duty-cycling.
- For maximum battery life, use timers + sleep/stop modes; wake on timer/EXTI to toggle or sample.
- Debounce any mechanical switches with either hardware RC or software debounce if you add inputs.
12) Troubleshooting tips
- If PA5 doesn’t toggle: verify
__HAL_RCC_GPIOA_CLK_ENABLE()
is present inMX_GPIO_Init()
(CubeMX should add it automatically). - Check that the correct MCU/board is selected in Project Manager; wrong part = wrong pin mapping.
- Use a debugger to set breakpoints or inspect
HAL_GPIO_ReadPin()
for pin state. - If LED is inverted (active-low), invert logic or change initial output level in CubeMX.
I²C — Reading a TMP102 Temperature Sensor
Below is a step-by-step walkthrough of how to configure STM32CubeMX to use I²C1 with a TMP102 temperature sensor. We’ll set up the pins, configure I²C1, and show how CubeMX generates the code. Then, we’ll add the HAL code to perform the read.
1) Start a new project
(Same as GPIO)
- Open STM32CubeMX → New Project.
- Select your board (e.g., NUCLEO-L053R8) or MCU (e.g., STM32L052R8).
- Click Start Project.
2) Pinout: enable I²C1 and assign pins
- In the Pinout & Configuration view, click on I2C1 in the left column (under Connectivity).
- Choose I2C1 → I2C. CubeMX will automatically map pins:
- PB6 = I2C1_SCL
- PB7 = I2C1_SDA
(if your device supports alternate pin mappings, CubeMX will show options).
- In the package diagram, PB6 and PB7 will turn green, labeled as SCL and SDA.
Why this matters: enabling I²C1 configures the pin alternate functions and ensures CubeMX generates MX_I2C1_Init()
for initialization.
3) Configure I²C1 parameters
- In the left pane, click I2C1 under Connectivity.
- In the Mode tab:
- Mode:
I²C
- Addressing Mode:
7-bit
(TMP102 uses a 7-bit address, default 0x48). - Clock Speed:
100 kHz
(standard mode, supported by TMP102). You can choose 400 kHz (Fast Mode) if desired. - Duty Cycle: Standard.
- Dual Address / General Call / No Stretch: Leave disabled (not needed).
- Mode:
Why: The TMP102 supports 7-bit addressing and standard/fast modes, so these settings ensure compatibility.
4) Configure GPIO for SDA and SCL
- In the Pinout & Configuration → System Core → GPIO, look at PB6 and PB7 entries.
- Ensure they are set to:
- Mode: Alternate Function Open-Drain (CubeMX does this automatically for I²C).
- Pull-up/Pull-down: Pull-Up (I²C requires pull-ups on both lines).
- Speed: Very High (I²C requires fast edges).
Tip: External 4.7kΩ pull-up resistors on SDA and SCL are recommended. If not available, CubeMX can configure internal pull-ups, but external ones are more reliable.
5) NVIC (optional for interrupts)
- If you want interrupt-driven I²C, go to Configuration → NVIC Settings.
- Enable I2C1 event interrupt and I2C1 error interrupt.
- For simple blocking communication (HAL functions), interrupts are not required.
6) DMA (optional)
- In Configuration → DMA Settings, you can add channels for:
I2C1_RX
(receive)I2C1_TX
(transmit)
- Set Direction, Priority, and enable Circular Mode if you want continuous streaming.
- For a simple TMP102 read, DMA is not required.
7) Clock Configuration
- Go to Clock Configuration.
- Ensure APB1 clock (used by I²C1) is running at a frequency that supports 100 kHz or 400 kHz bus speeds.
- CubeMX will auto-calculate the
Timing
register value (e.g.,0x00C0216C
).
8) Project Manager
- Name your project (e.g.,
I2C_TMP102_Test
). - Select toolchain = STM32CubeIDE.
- Generate code.
9) Generated code: where to look
CubeMX creates these files:
i2c.c
— containsMX_I2C1_Init()
andI2C_HandleTypeDef hi2c1
.i2c.h
— declareshi2c1
.main.c
— callsMX_I2C1_Init()
insidemain()
.
Example from i2c.c
:
hi2c1.Instance = I2C1; hi2c1.Init.Timing = 0x00C0216C; // auto-generated by CubeMX hi2c1.Init.OwnAddress1 = 0; hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; HAL_I2C_Init(&hi2c1);
10) Add TMP102 read logic in main.c
In main.c
, inside the while (1)
loop, add:
#define TMP102_ADDR (0x48 << 1) // 7-bit addr shifted left uint8_t reg = 0x00; uint8_t buf[2]; while (1) { // Tell TMP102 we want to read from register 0x00 HAL_I2C_Master_Transmit(&hi2c1, TMP102_ADDR, ®, 1, HAL_MAX_DELAY); // Read 2 bytes from the temperature register HAL_I2C_Master_Receive(&hi2c1, TMP102_ADDR, buf, 2, HAL_MAX_DELAY); // Convert 12-bit data to Celsius int16_t raw = (buf[0] << 4) | (buf[1] >> 4); float tempC = raw * 0.0625f; HAL_Delay(1000); // Delay 1 second }
11) Low-power considerations
- Use Stop mode between reads instead of
HAL_Delay()
. - Configure a timer to wake the MCU every 1 second, then perform the I²C read.
- For sensors with alert pins, configure an EXTI interrupt on the ALERT pin instead of polling.
12) Troubleshooting tips
- Bus stuck low → check for missing pull-ups.
- Always 0xFF read → check CS/address: TMP102 default is
0x48
. UseHAL_I2C_IsDeviceReady()
to verify. - Timeouts → verify correct AF mapping (PB6/PB7 = AF4 for I²C1 on L0 series).
- Noise/glitches → use external pull-ups (internal pull-ups are weak).
SPI — Communicating with an EEPROM
Objective: Configure SPI1 as master to send a Read ID (0x9F) command to an EEPROM and read back 3 ID bytes.
1) Start a new project
(Same as GPIO/I²C — select board/MCU, start project.)
2) Pinout: enable SPI1 and CS pin
- In Pinout & Configuration, click SPI1 → select Full-Duplex Master.
- CubeMX maps by default:
- PA5 = SCK
- PA6 = MISO
- PA7 = MOSI
- CubeMX maps by default:
- For Chip Select (CS), pick a GPIO pin (e.g., PA4) → set as GPIO_Output, initial state High.
Why this matters: CubeMX configures SPI alternate functions for pins and ensures MX_SPI1_Init()
is generated. CS must be handled manually.
3) Configure SPI1 parameters
- In Configuration → SPI1:
- Mode: Master
- Direction: 2-Lines (Full Duplex)
- Data Size: 8-bit
- Clock Polarity (CPOL): Low
- Clock Phase (CPHA): 1 Edge (typical for many EEPROMs; check datasheet)
- NSS: Software (we’ll control CS in code)
- Baud Rate Prescaler: e.g., /16 → 1 MHz if system clock is 16 MHz
- First Bit: MSB
- Leave CRC calculation disabled.
4) GPIO for CS pin
- In GPIO Configuration, set PA4:
- Mode: Output Push-Pull
- Pull: No Pull
- Speed: Low (sufficient for CS toggling)
- Output Level: High (inactive by default)
5) NVIC / DMA (optional)
- Enable SPI1 global interrupt if using interrupt-based transfers.
- Configure DMA channels (TX and RX) if planning large data transfers with low CPU load.
6) Clock Configuration
- Ensure APB2 clock is set correctly so CubeMX can achieve desired SPI clock speed.
7) Project Manager
- Name project (e.g.,
SPI_EEPROM_Test
). - Select toolchain = STM32CubeIDE.
- Generate code.
8) Generated code: where to look
CubeMX generates:
spi.c
/spi.h
withMX_SPI1_Init()
andSPI_HandleTypeDef hspi1
.- Example init in
spi.c
:
hspi1.Instance = SPI1; hspi1.Init.Mode = SPI_MODE_MASTER; hspi1.Init.Direction = SPI_DIRECTION_2LINES; hspi1.Init.DataSize = SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; hspi1.Init.NSS = SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16; hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; HAL_SPI_Init(&hspi1);
9) Add EEPROM logic in main.c
Inside the while (1)
loop:
uint8_t cmd = 0x9F; // Read ID command uint8_t id[3]; HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); // CS low HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY); HAL_SPI_Receive(&hspi1, id, 3, HAL_MAX_DELAY); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); // CS high HAL_Delay(1000);
10) Low-power considerations
- Use DMA + Stop mode for long reads/writes.
- Ensure CS is released (High) before entering sleep.
- Reduce SPI clock speed if not needed; slower edges = less EMI/power.
11) Troubleshooting tips
- Read garbage (0xFF/0x00): check CPOL/CPHA and CS timing.
- Nothing on bus: verify SPI pins are in AF mode and not GPIO.
- Multiple devices: ensure each has its own CS and no conflicts.
UART — “Hello, World!” Over Serial
Objective: Configure USART2 to transmit "Hello, World!\r\n"
to a PC at 115200 baud.
1) Start a new project
(Same as before — select board/MCU, start project.)
2) Pinout: enable USART2 TX/RX
- In Pinout & Configuration, click USART2 → select Asynchronous.
- CubeMX assigns default pins:
- PA2 = TX
- PA3 = RX
Why this matters: CubeMX sets pins to AF mode and generates MX_USART2_UART_Init()
.
3) Configure USART2 parameters
- In Configuration → USART2:
- Baud Rate: 115200
- Word Length: 8 bits
- Stop Bits: 1
- Parity: None
- Mode: TX/RX (enable both, even if only TX used)
- Hardware Flow Control: None
- Oversampling: 16
- CubeMX auto-calculates BRR based on clock source.
4) NVIC / DMA (optional)
- Enable USART2 global interrupt if using interrupts for RX/TX.
- Add DMA channels for TX/RX if streaming large data.
5) Clock Configuration
- Ensure APB1 clock can provide accurate baud rate. CubeMX shows actual error percentage (try to keep <1%).
6) Project Manager
- Name project (e.g.,
UART_HelloWorld
). - Toolchain = STM32CubeIDE.
- Generate code.
7) Generated code: where to look
CubeMX generates:
usart.c
/usart.h
withMX_USART2_UART_Init()
andUART_HandleTypeDef huart2
.- Example init:
huart2.Instance = USART2; huart2.Init.BaudRate = 115200; huart2.Init.WordLength = UART_WORDLENGTH_8B; huart2.Init.StopBits = UART_STOPBITS_1; huart2.Init.Parity = UART_PARITY_NONE; huart2.Init.Mode = UART_MODE_TX_RX; huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart2.Init.OverSampling = UART_OVERSAMPLING_16; HAL_UART_Init(&huart2);
8) Add Hello World logic in main.c
Inside the while (1)
loop:
#include <string.h> char msg[] = "Hello, World!\r\n"; while (1) { HAL_UART_Transmit(&huart2, (uint8_t*)msg, strlen(msg), HAL_MAX_DELAY); HAL_Delay(1000); }
9) Low-power considerations
- Use interrupts or DMA for TX/RX instead of blocking.
- Enable wake-on-RX to wake the MCU from Stop mode on UART activity.
- Minimize baud rate if high throughput isn’t needed (lower frequency = lower power).
10) Troubleshooting tips
- No output: check PC terminal settings (115200, 8-N-1).
- Garbage characters: wrong baud/clock mismatch — adjust in Clock Configuration.
- No COM port: ensure ST-LINK VCP drivers are installed and connected.
STM32L0 vs STM32C0: Which One Should You Choose?
ST’s STM32C0 series targets cost-sensitive designs, while the STM32L0 emphasizes low-power. Here’s a quick comparison:
Feature | STM32L0 | STM32C0 |
---|---|---|
Core | Cortex-M0+ | Cortex-M0+ |
Max Freq | 32 MHz | 48 MHz |
Flash | Up to 192 KB (ECC) | Up to 256 KB |
RAM | Up to 20 KB | Up to 36 KB |
EEPROM | Yes (up to 6 KB, ECC) | No |
Run Mode Power | 49 µA/MHz (DC/DC), 76 µA/MHz (LDO) | ~80 µA/MHz |
Low-Power | Standby: 230 nA, Wake: 3.5 µs | Standby: 8 µA, Wake: 385 µs |
Extras | AES, LCD driver, USB device | USB FS (crystal-less), FDCAN (some) |
Package | 14–100 pins | 8–64 pins |
Target | Battery-powered, IoT, wearables | Entry-level, cost-sensitive |
Choose STM32L0 if:
- Battery life is critical (e.g., wearables, sensors, medical).
- You need advanced analog, EEPROM, or ultra-low-power standby.
Choose STM32C0 if:
- Budget is the priority.
- Your app needs more memory and clock speed but not ultra-low-power modes.
Conclusion
The STM32L0 series is a strong candidate when every microamp matters. Its ultra-low-power modes, integrated analog peripherals, and support for USB, EEPROM, and hardware encryption make it perfect for always-on, autonomous, battery-powered devices.
Meanwhile, the STM32C0 offers an affordable, performance-oriented alternative for industrial controllers, basic IoT, and appliances.
With STM32CubeMX + STM32CubeIDE, you can jumpstart development quickly using HAL-based APIs, ensuring portability across STM32 families.
Whether you’re optimizing for power efficiency or cost, ST’s ecosystem ensures a smooth development journey.