I've not used DMA with the STM32 U(S)ARTs yet, it's on my todo list though.
My typical use is to have an ISR that reads data from the USART into a ring buffer if a char is available. I ignore errored characters (e.g. drop them in the ISR, they never make it into the ring buffer). If the transmitter empty interrupt fired, I check to see if there are outgoing characters in the tx ring buffer and send them out. If there are no more chars in the TX ring buffer, I disable the transmitter empty interrupt. My uart_write() routine writes chars into the transmitter ring buffer and enables the transmitter empty interrupt. This way both directions of the hardware are handled efficiently. I think that DMA would be slightly more efficient, which is why I mention it's on my todo list, although realistically I've been using the ISR method reliably even at 2Mbps without issue.
Working example lifted from my own code below. I just noticed I don't loop read/write until there are no more available characters (rx) or no more space (tx). That's an area for improvement, as is cleaning up the generic ISR itself, as it's more or less lifted directly from HAL code and isn't up to my usual standards.
struct uart {
UART_HandleTypeDef *huart; /* pointer to the HAL_UART struct */
struct fifo *tx, *rx; /* transmit (out of STM32) and receive (in to STM32) FIFOs */
};
static struct uart uart3;
static struct uart uart6;
static void generic_usart_handler(struct uart *u)
{
UART_HandleTypeDef *huart;
uint32_t tmp1, tmp2;
bool err;
err = false;
huart = u->huart;
/* UART parity error interrupt occurred ------------------------------------*/
tmp1 = __HAL_UART_GET_FLAG(huart, UART_FLAG_PE);
tmp2 = __HAL_UART_GET_IT_SOURCE(huart, UART_IT_PE);
if ((tmp1 != RESET) && (tmp2 != RESET)) {
__HAL_UART_CLEAR_PEFLAG(huart);
err = true;
}
/* UART frame error interrupt occurred -------------------------------------*/
tmp1 = __HAL_UART_GET_FLAG(huart, UART_FLAG_FE);
tmp2 = __HAL_UART_GET_IT_SOURCE(huart, UART_IT_ERR);
if ((tmp1 != RESET) && (tmp2 != RESET)) {
__HAL_UART_CLEAR_FEFLAG(huart);
err = true;
}
/* UART noise error interrupt occurred -------------------------------------*/
tmp1 = __HAL_UART_GET_FLAG(huart, UART_FLAG_NE);
tmp2 = __HAL_UART_GET_IT_SOURCE(huart, UART_IT_ERR);
if ((tmp1 != RESET) && (tmp2 != RESET)) {
__HAL_UART_CLEAR_NEFLAG(huart);
err = true;
}
/* UART Over-Run interrupt occurred ----------------------------------------*/
tmp1 = __HAL_UART_GET_FLAG(huart, UART_FLAG_ORE);
tmp2 = __HAL_UART_GET_IT_SOURCE(huart, UART_IT_ERR);
if ((tmp1 != RESET) && (tmp2 != RESET)) {
__HAL_UART_CLEAR_OREFLAG(huart);
err = true;
}
/* UART in mode Receiver ---------------------------------------------------*/
tmp1 = __HAL_UART_GET_FLAG(huart, UART_FLAG_RXNE);
tmp2 = __HAL_UART_GET_IT_SOURCE(huart, UART_IT_RXNE);
if((tmp1 != RESET) && (tmp2 != RESET)) {
uint16_t val;
val = (uint16_t)(huart->Instance->DR);
/* don't put errored data into the FIFO */
if (!err) {
_fifo_put(u->rx, val);
}
}
/* UART in mode Transmitter ------------------------------------------------*/
tmp1 = __HAL_UART_GET_FLAG(huart, UART_FLAG_TXE);
tmp2 = __HAL_UART_GET_IT_SOURCE(huart, UART_IT_TXE);
if((tmp1 != RESET) && (tmp2 != RESET)) {
char val;
/*
* if there's data to send, send it.
* otherwise disable the transmit empty interrupt.
*/
if (_fifo_get(u->tx, &val) == 0) {
huart->Instance->DR = val;
} else {
__HAL_UART_DISABLE_IT(huart, UART_IT_TXE);
}
}
}
/* HW specific IRQ handlers (routes to the generic handler */
void USART3_IRQHandler(void)
{
generic_usart_handler(&uart3);
}
void USART6_IRQHandler(void)
{
generic_usart_handler(&uart6);
}
/*
* HW-specific UART init, returns augmented uart struct or NULL
* NOTE: should probably do the low level stuff in the MSP functions instead
*/
struct uart *uart_hw_init(UART_HandleTypeDef *huart)
{
GPIO_InitTypeDef io;
struct uart *u;
switch ((int)huart->Instance) {
case (int)USART3:
/* enable GPIO and UART clocks. */
__HAL_RCC_GPIOD_CLK_ENABLE(); /* TX/RX on PORTD bit 8/9 */
__HAL_RCC_USART3_CLK_ENABLE();
/* UART TX GPIO pin configuration */
io.Pin = GPIO_PIN_8;
io.Mode = GPIO_MODE_AF_PP;
io.Pull = GPIO_NOPULL;
io.Speed = GPIO_SPEED_FAST;
io.Alternate = GPIO_AF7_USART3;
HAL_GPIO_Init(GPIOD, &io);
/* UART RX GPIO pin configuration */
io.Pin = GPIO_PIN_9;
io.Pull = GPIO_PULLUP;
io.Alternate = GPIO_AF7_USART3;
HAL_GPIO_Init(GPIOD, &io);
huart->Init.BaudRate = 115200;
huart->Init.WordLength = UART_WORDLENGTH_8B;
huart->Init.StopBits = UART_STOPBITS_1;
huart->Init.Parity = UART_PARITY_NONE;
huart->Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart->Init.Mode = UART_MODE_TX_RX;
huart->Init.OverSampling = UART_OVERSAMPLING_16;
HAL_UART_Init(huart);
/* NVIC setup */
HAL_NVIC_SetPriority(USART3_IRQn, configMAX_SYSCALL_INTERRUPT_PRIORITY, 0);
HAL_NVIC_EnableIRQ(USART3_IRQn);
u = &uart3;
break;
case (int)USART6:
/* enable GPIO and UART clocks. */
__HAL_RCC_GPIOC_CLK_ENABLE(); /* TX/RX on PORTC bit 6/7 */
__HAL_RCC_USART6_CLK_ENABLE();
/* UART TX GPIO pin configuration */
io.Pin = GPIO_PIN_6;
io.Mode = GPIO_MODE_AF_PP;
io.Pull = GPIO_NOPULL;
io.Speed = GPIO_SPEED_FAST;
io.Alternate = GPIO_AF8_USART6;
HAL_GPIO_Init(GPIOC, &io);
/* UART RX GPIO pin configuration */
io.Pin = GPIO_PIN_7;
io.Pull = GPIO_PULLUP;
io.Alternate = GPIO_AF8_USART6;
HAL_GPIO_Init(GPIOC, &io);
huart->Init.BaudRate = 115200;
huart->Init.WordLength = UART_WORDLENGTH_8B;
huart->Init.StopBits = UART_STOPBITS_1;
huart->Init.Parity = UART_PARITY_NONE;
huart->Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart->Init.Mode = UART_MODE_TX_RX;
huart->Init.OverSampling = UART_OVERSAMPLING_16;
HAL_UART_Init(huart);
/* NVIC setup */
HAL_NVIC_SetPriority(USART6_IRQn, configMAX_SYSCALL_INTERRUPT_PRIORITY, 0);
HAL_NVIC_EnableIRQ(USART6_IRQn);
u = &uart6;
break;
default:
/* do nothing */
u = NULL;
break;
};
/* initialize the uart's FIFO subsystem */
if (u) {
u->huart = huart;
u->tx = fifo_init(128);
u->rx = fifo_init(128);
/* enable UART RX interrupt. Disable the TX interrupt since the FIFO's empty right now */
//__HAL_UART_ENABLE_IT(huart, UART_IT_RXNE); /* receiver not empty */
__HAL_UART_DISABLE_IT(huart, UART_IT_TXE); /* transmit empty */
}
return u;
}
/* writes to the transmitter FIFO. Returns the number of bytes written */
int uart_write(struct uart *u, const char *buf, int len)
{
int nwritten;
nwritten = 0;
while (len) {
if (fifo_put(u->tx, *buf) == 0) {
++buf;
++nwritten;
--len;
} else {
break;
}
/* enable the transmitter empty interrupt to start things flowing */
__HAL_UART_ENABLE_IT(u->huart, UART_IT_TXE);
};
return nwritten;
}
/* reads from the receiver FIFO. Returns the number of bytes read */
int uart_read(struct uart *u, char *buf, int maxlen)
{
int nread, left;
nread = 0;
left = maxlen;
while (left) {
if (fifo_get(u->rx, buf)) {
++buf;
++nread;
--left;
/* no character available, drop out immediately */
} else {
break;
}
};
return nread;
}