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

Worked example: 1 Hz timer

Given: 72 MHz timer clock

Find: PSC and ARR.

Answer: PSC 7199, ARR 9999

Worked example: USART 115200

Given: PCLK 72 MHz

Find: BRR.

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

Applications

Key takeaways

Going further