ARM Cortex-M & STM32
32-bit ARM Cortex-M microcontrollers dominate modern embedded design. Learn the Cortex-M family (M0/M3/M4/M7/M33), the STM32 ecosystem, clock tree and PLL, RCC peripheral clock enabling, GPIO modes, timers (PSC/ARR/CCR), USART baud (BRR), NVIC, CMSIS and HAL vs register (LL) programming.
STM32 offers hundreds of Cortex-M parts from tiny 16 MHz M0s to 550 MHz M7s with DSP, FPU, USB, Ethernet and CAN. Learning one STM32 teaches the patterns used across NXP, TI, Microchip SAM and Nordic MCUs.
An analogy
An STM32 is like a large office building: before using any room (peripheral), you must turn on its power (RCC clock enable), then set up the furniture (configuration registers).
Cortex-M family
M0/M0+ (ultra-low power, small), M3 (mainstream), M4 (DSP + optional FPU), M7 (high performance, caches), M23/M33 (ARMv8-M with TrustZone). All share the NVIC, SysTick and CMSIS software interface.
Clock tree
HSI (internal RC) or HSE (crystal) feed a PLL to reach the system clock; AHB/APB prescalers divide it for buses. Timer clocks may be ×2 when APB prescaler ≠ 1. CubeMX visualises and generates clock configuration.
Peripherals
GPIO (input, output push-pull/open-drain, alternate function, analog), advanced timers with dead-time for motor control, USART, SPI, I²C, CAN, USB, ADC with DMA, DAC, RTC, watchdogs.
Software layers
CMSIS core headers, vendor HAL/LL drivers, middleware (FreeRTOS, USB, TCP/IP), and tools: STM32CubeIDE, Keil, IAR, GCC + OpenOCD. Debug via SWD with breakpoints, SWO trace and fault analysis (HardFault registers).
Formulas
- Timer update frequency:
f = f_TIM / ((PSC + 1)(ARR + 1)) - PWM duty:
D = CCR / (ARR + 1) - USART (oversampling 16):
BRR = f_PCLK / baud - PLL output:
f_SYS = f_in × N / (M × P)
Worked example: 1 Hz timer
Given: 72 MHz timer clock
Find: PSC and ARR.
- PSC = 7199 → 10 kHz
- ARR = 9999 → 1 Hz
Answer: PSC 7199, ARR 9999
Worked example: USART 115200
Given: PCLK 72 MHz
Find: BRR.
- BRR = 72 000 000/115 200 = 625
Answer: BRR = 625 (exact)
Register-level GPIO + PWM on STM32F1 (CMSIS)
#include "stm32f1xx.h"
void pwm_init(void) { /* TIM2_CH1 on PA0, 20 kHz */
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; /* clock GPIOA */
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; /* clock TIM2 */
GPIOA->CRL &= ~(0xFu << 0); /* PA0: alternate-function push-pull 50 MHz */
GPIOA->CRL |= (0xBu << 0);
TIM2->PSC = 0; /* 72 MHz timer clock */
TIM2->ARR = 3599; /* 72e6 / 3600 = 20 kHz */
TIM2->CCR1 = 1800; /* 50 % duty */
TIM2->CCMR1 = (6u << 4) | TIM_CCMR1_OC1PE; /* PWM mode 1, preload */
TIM2->CCER |= TIM_CCER_CC1E; /* enable output */
TIM2->CR1 |= TIM_CR1_ARPE | TIM_CR1_CEN;
}
void set_duty(uint16_t permille) { TIM2->CCR1 = (3600u * permille) / 1000u; }Every peripheral needs its RCC clock enabled first. PSC and ARR set frequency, CCR sets duty. HAL (HAL_TIM_PWM_Start) wraps the same registers with portability at some cost in size and speed.
Common mistakes
- Wrong: Forgetting RCC clock enable. — Right: Enable the peripheral clock before configuring it.
- Wrong: Wrong timer clock assumption. — Right: APB prescaler ≠ 1 doubles the timer clock.
Applications
- Motor control: Advanced timers, ADC, FOC algorithms.
- IoT gateways: Ethernet, USB, CAN.
- Audio & DSP: Cortex-M4/M7 with FPU and I²S.
Key takeaways
- Cortex-M family: M0 → M7, M33.
- RCC enable, then configure.
- f = f_TIM/((PSC + 1)(ARR + 1)).
- BRR = f_PCLK/baud.
Going further
- DMA + ADC: Continuous sampling without CPU.
- Low-power modes: Stop/standby with RTC wake-up.
- Fault handling: Decoding HardFault stack frames.