STM32 HAL – Hard n Fast – Cheat Sheet

STM32 Hard N Fast Cheat Sheet – ElectronInjection
electroninjection.com // STM32 Hard N Fast Series
HAL Quick Reference Cheat Sheet
GPIO · I2C · SPI · UART · EXTI · PWM/Timers — blocking variants shown. IT/DMA follow same pattern + callback.
General Purpose I/O
HAL_GPIO_WritePin
(GPIOx, GPIO_Pin,
PinState)
Set a pin HIGH (GPIO_PIN_SET) or LOW (GPIO_PIN_RESET).
HAL_GPIO_ReadPin
(GPIOx, GPIO_Pin)
Returns GPIO_PIN_SET or GPIO_PIN_RESET.
HAL_GPIO_TogglePin
(GPIOx, GPIO_Pin)
Flip the pin state. Handy for blink without tracking state manually.
HAL_GPIO_LockPin
(GPIOx, GPIO_Pin)
Locks pin config until next reset. Rarely needed but it exists.
Gotchas
  • CubeMX generates LED_GPIO_Port and LED_Pin defines from your pin label — use those, not raw GPIOA / GPIO_PIN_5, so the code survives a pin reassignment.
  • Output pins default to push-pull. If you need open-drain (e.g. shared bus, level shifting), set it in CubeMX — don’t assume.
  • Forgot to enable the GPIO clock? __HAL_RCC_GPIOx_CLK_ENABLE() must run before any GPIO config. CubeMX handles this in MX_GPIO_Init() — don’t call GPIO functions before that runs.
  • Floating input reading random garbage? Enable the internal pull-up or pull-down in CubeMX rather than leaving it floating.
Inter-Integrated Circuit
HAL_I2C_Master_Transmit
(&hi2c, addr<<1,
buf, len, timeout)
Send raw bytes to a device.
HAL_I2C_Master_Receive
(&hi2c, addr<<1,
buf, len, timeout)
Read raw bytes from a device.
HAL_I2C_Mem_Write
(&hi2c, addr<<1, reg,
regSize, buf, len, timeout)
Write to a register. Use for most sensors. regSize = I2C_MEMADD_SIZE_8BIT.
HAL_I2C_Mem_Read
(&hi2c, addr<<1, reg,
regSize, buf, len, timeout)
Read from a register. Your go-to for sensor data.
Gotchas
  • HAL expects the 7-bit address shifted left by 1. Datasheet says 0x48? Pass 0x90.
  • Never pass 0 for timeout while prototyping — use HAL_MAX_DELAY.
  • Bus stuck (SDA held low): power-cycled mid-transaction? May need to toggle SCL manually or reset.
  • HAL can’t save you from missing or wrong-value pull-up resistors.
Serial Peripheral Interface
HAL_SPI_Transmit
(&hspi, buf,
len, timeout)
Send bytes. Manage CS manually before/after.
HAL_SPI_Receive
(&hspi, buf,
len, timeout)
Receive bytes. Clock is still driven by master.
HAL_SPI_TransmitReceive
(&hspi, txBuf, rxBuf,
len, timeout)
Full-duplex. Use this even for reads — clock dummy bytes out while data comes in.
Gotchas
  • CS is entirely your job. GPIO_PIN_RESET before, GPIO_PIN_SET after. HAL doesn’t touch it.
  • CPOL/CPHA must match your device (Mode 0–3). Set in CubeMX. Wrong mode = garbage data.
  • Corrupt data at speed? Lower the baud prescaler first before debugging anything else.
  • Check your datasheet for MSB vs LSB first — HAL doesn’t auto-swap byte order.
Serial Read / Write
HAL_UART_Transmit
(&huart, buf,
len, timeout)
Blocking send. Good for debug output.
HAL_UART_Receive
(&huart, buf,
len, timeout)
Blocking receive. CPU waits. Only for fixed-length responses.
HAL_UART_Receive_IT
(&huart, buf, len)
Non-blocking. Fires RxCpltCallback when N bytes arrive. Re-arm in callback or it stops.
HAL_UART_RxCpltCallback
Override this weak function. Process data, then re-arm Receive_IT at the end.
Gotchas
  • Re-arm the receiver inside RxCpltCallback — it will never fire again until you do.
  • ORE (Overrun Error): data arrives while you’re not listening, HAL locks the receiver. Enable UART_ADVFEATURE_RXOVERRUNDISABLE or handle the error callback.
  • Garbage characters almost always mean baud rate or clock config mismatch — check HSI/HSE in CubeMX first.
External Interrupts
HAL_GPIO_EXTI_Callback
(uint16_t GPIO_Pin)
Override this weak function. Check GPIO_Pin to identify which pin fired. Set a flag and exit.
Gotchas
  • One pin per line number across all ports. PA3 and PB3 cannot both be EXTI — only one will fire, silently, and you will lose your mind.
  • Set a flag in the callback. Do the work in main or a task. No blocking code, no HAL calls, no printf in ISR context.
  • HAL does not debounce. Noisy buttons will fire many times. Handle in software or hardware.
  • Enable the NVIC interrupt in CubeMX — easy to forget, causes nothing to happen.
PWM / Timers
HAL_TIM_PWM_Start
(&htim, TIM_CHANNEL_x)
Starts PWM output. CubeMX does NOT auto-start — you must call this.
__HAL_TIM_SET_COMPARE
(&htim, TIM_CHANNEL_x,
value)
Update duty cycle live. Write directly to CCR — don’t call PWM_Start again.
HAL_TIM_Base_Start_IT
(&htim)
Start timer with period interrupt. Fires PeriodElapsedCallback on each rollover.
HAL_TIM_PeriodElapsedCallback
Override this weak function. Check htim->Instance if using multiple timers.
Frequency Formula
F = TimerClock / (PSC+1) / (ARR+1) | Duty = CCR / ARR
Gotchas
  • PWM output is silent until you call HAL_TIM_PWM_Start() — CubeMX init does not start it.
  • Off-by-one is real: the formula uses (PSC+1) and (ARR+1).
  • Stopped the timer and reconfigured the pin as GPIO? HAL may not return the alternate function. Avoid stopping if you can, or re-init.
  • Multiple timers sharing a callback: always check htim->Instance == TIMx before acting.