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.
GPIO
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_PortandLED_Pindefines from your pin label — use those, not rawGPIOA/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 inMX_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.
I2C
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? Pass0x90. - Never pass
0for timeout while prototyping — useHAL_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.
SPI
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_RESETbefore,GPIO_PIN_SETafter. 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.
UART
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_RXOVERRUNDISABLEor handle the error callback. - Garbage characters almost always mean baud rate or clock config mismatch — check HSI/HSE in CubeMX first.
EXTI
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
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 == TIMxbefore acting.