STM32L4 UART driver for FreeRTOS without HAL

This is a driver for STM32L432 LPUART. It should also work with the “full” UART. The LPUART is a simple peripheral (compared to the clock tree or ADC). In this case it is easier to master the usage of a couple of registers, than use full-size HAL drivers, as they are very generic to cover every possible flavor of a peripheral across the whole STM32 line, which in turn makes them big in terms of code size and actually harder to follow than the register layout.

The driver can be safely used within FreeRTOS, It can even be used by multiple tasks, but it probably would make little sense anyway, unless there can be different devices connected at runtime to the same UART or the application has separate operating modes implemented in different tasks.

For simplicity everything is interrupt driven, rather than DMA driven. In my particular case there is not much traffic, so setting up the DMA every time for just a couple of bytes could be less efficient than interrupts. Data is passed between tasks and interrupts using two FreeRTOS queues. One for the received bytes, another for the bytes to be transmitted.

Interface

The interface has an init function that should be called only once. It initializes the UART for a particular speed and the data queues.

There is a group of functions for sending data – uart_putc, uart_puts, uart_write (the last one for raw binary). These functions block if the TX queue is full.

Data can be retrieved using the uart_getc (blocks until there is a byte available) and uart_try_getc (does not block).

uart_read_line is a helper function for implementing ASCII protocols that send data line by line. This function keeps loading data to the provided buffer until a newline character is encountered. It can be useful for receiving AT command responses from a modem. uart_unblock_read_line can be used (from another task) if it is necessary to unblock the read line function early.

Implementation

Thanks to FreeRTOS queues the implementation is very simple. All transmit functions load data to the transmit queue and set the UART to generate transmit buffer empty interrupts. All receive functions try to fetch data from the receive queue. When an interrupt fires it check if the UART has just received or sent a byte and acts accordingly by placing the received byte to the RX queue or by transmitting a byte from the TX queue.