本文是基于STM32G431的LL库做的,针对裸机的串口接收和发送库。其中UART接收采用DMA+idle中断+多级缓冲模式。
1. 结构体
先创建几个必要的结构体
typedef struct uart_rx {
uint8_t mode;
uint8_t *buf; /* poniter to receive buf */
uint16_t size;
uint32_t wr_index;
uint32_t rd_index;
FlagStatus cplt_flag;
#ifdef UART_RX_MODE_DMA
DMA_TypeDef *dma;
uint32_t dma_channel;
#endif
Queue_TypeDef queue;
} *uart_rx_t;
typedef struct uart_tx {
uint8_t mode;
uint8_t *buf;
uint32_t size;
uint32_t count;
FlagStatus cplt_flag;
#ifdef UART_TX_MODE_DMA
DMA_TypeDef *dma;
uint32_t dma_channel;
#endif
} *uart_tx_t;
typedef struct uart_handle {
USART_TypeDef *periph;
struct uart_tx tx;
struct uart_rx rx;
} *uart_handle_t;
2.初始化
直接上代码
#ifdef UART2_ENABLE
struct uart_handle huart2;
static uint8_t uart2_recv_buf[UART2_RECV_BUF_SIZE];
#endif
static void dma_set_config(DMA_TypeDef *hdma, uint32_t channel, uint32_t src_address, uint32_t dst_address, uint32_t data_length)
{
if (LL_DMA_GetDataTransferDirection(hdma, channel) == LL_DMA_DIRECTION_PERIPH_TO_MEMORY) {
// LL_DMA_SetM2MDstAddress(hdma, channel, dst_address);
LL_DMA_SetMemoryAddress(hdma, channel, dst_address);
LL_DMA_SetDataLength(hdma, channel, data_length);
LL_DMA_SetPeriphAddress(hdma, channel, src_address);
} else if (LL_DMA_GetDataTransferDirection(hdma, channel) == LL_DMA_DIRECTION_MEMORY_TO_PERIPH) {
// LL_DMA_SetM2MSrcAddress(hdma, channel, src_address);
LL_DMA_SetMemoryAddress(hdma, channel, src_address);
LL_DMA_SetDataLength(hdma, channel, data_length);
LL_DMA_SetPeriphAddress(hdma, channel, dst_address);
} else if (LL_DMA_GetDataTransferDirection(hdma, channel) == LL_DMA_DIRECTION_MEMORY_TO_MEMORY) {
/* TODO: */
} else {
/* TODO: */
}
}
void bsp_uart2_init(uint32_t baudrate)
{
/* USER CODE BEGIN USART2_Init 0 */
/* USER CODE END USART2_Init 0 */
LL_USART_InitTypeDef USART_InitStruct = {0};
LL_GPIO_InitTypeDef GPIO_InitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
/** Initializes the peripherals clocks
*/
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART2;
PeriphClkInit.Usart2ClockSelection = RCC_USART2CLKSOURCE_PCLK1;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK) {
Error_Handler();
}
/* Peripheral clock enable */
LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_USART2);
LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOA);
/**USART2 GPIO Configuration
PA2 ------> USART2_TX
PA3 ------> USART2_RX
*/
GPIO_InitStruct.Pin = LL_GPIO_PIN_2;
GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
GPIO_InitStruct.Alternate = LL_GPIO_AF_7;
LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = LL_GPIO_PIN_3;
GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
GPIO_InitStruct.Alternate = LL_GPIO_AF_7;
LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* USART2 DMA Init */
/* DMA2_Channel1_IRQn interrupt configuration */
// NVIC_SetPriority(DMA2_Channel1_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 7, 0));
// NVIC_EnableIRQ(DMA2_Channel1_IRQn);
/* DMA2_Channel2_IRQn interrupt configuration */
NVIC_SetPriority(DMA2_Channel2_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 7, 0));
NVIC_EnableIRQ(DMA2_Channel2_IRQn);
/* USART2_RX Init */
LL_DMA_SetPeriphRequest(DMA2, LL_DMA_CHANNEL_1, LL_DMAMUX_REQ_USART2_RX);
LL_DMA_SetDataTransferDirection(DMA2, LL_DMA_CHANNEL_1, LL_DMA_DIRECTION_PERIPH_TO_MEMORY);
LL_DMA_SetChannelPriorityLevel(DMA2, LL_DMA_CHANNEL_1, LL_DMA_PRIORITY_LOW);
LL_DMA_SetMode(DMA2, LL_DMA_CHANNEL_1, LL_DMA_MODE_CIRCULAR);
LL_DMA_SetPeriphIncMode(DMA2, LL_DMA_CHANNEL_1, LL_DMA_PERIPH_NOINCREMENT);
LL_DMA_SetMemoryIncMode(DMA2, LL_DMA_CHANNEL_1, LL_DMA_MEMORY_INCREMENT);
LL_DMA_SetPeriphSize(DMA2, LL_DMA_CHANNEL_1, LL_DMA_PDATAALIGN_BYTE);
LL_DMA_SetMemorySize(DMA2, LL_DMA_CHANNEL_1, LL_DMA_MDATAALIGN_BYTE);
/* USART2_TX Init */
LL_DMA_SetPeriphRequest(DMA2, LL_DMA_CHANNEL_2, LL_DMAMUX_REQ_USART2_TX);
LL_DMA_SetDataTransferDirection(DMA2, LL_DMA_CHANNEL_2, LL_DMA_DIRECTION_MEMORY_TO_PERIPH);
LL_DMA_SetChannelPriorityLevel(DMA2, LL_DMA_CHANNEL_2, LL_DMA_PRIORITY_LOW);
LL_DMA_SetMode(DMA2, LL_DMA_CHANNEL_2, LL_DMA_MODE_NORMAL);
LL_DMA_SetPeriphIncMode(DMA2, LL_DMA_CHANNEL_2, LL_DMA_PERIPH_NOINCREMENT);
LL_DMA_SetMemoryIncMode(DMA2, LL_DMA_CHANNEL_2, LL_DMA_MEMORY_INCREMENT);
LL_DMA_SetPeriphSize(DMA2, LL_DMA_CHANNEL_2, LL_DMA_PDATAALIGN_BYTE);
LL_DMA_SetMemorySize(DMA2, LL_DMA_CHANNEL_2, LL_DMA_MDATAALIGN_BYTE);
/* USART2 interrupt Init */
NVIC_SetPriority(USART2_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 7, 0));
NVIC_EnableIRQ(USART2_IRQn);
/* USER CODE BEGIN USART2_Init 1 */
/* USER CODE END USART2_Init 1 */
USART_InitStruct.PrescalerValue = LL_USART_PRESCALER_DIV1;
USART_InitStruct.BaudRate = baudrate;
USART_InitStruct.DataWidth = LL_USART_DATAWIDTH_8B;
USART_InitStruct.StopBits = LL_USART_STOPBITS_1;
USART_InitStruct.Parity = LL_USART_PARITY_NONE;
USART_InitStruct.TransferDirection = LL_USART_DIRECTION_TX_RX;
USART_InitStruct.HardwareFlowControl = LL_USART_HWCONTROL_NONE;
USART_InitStruct.OverSampling = LL_USART_OVERSAMPLING_16;
LL_USART_Init(USART2, &USART_InitStruct);
LL_USART_SetTXFIFOThreshold(USART2, LL_USART_FIFOTHRESHOLD_1_8);
LL_USART_SetRXFIFOThreshold(USART2, LL_USART_FIFOTHRESHOLD_1_8);
LL_USART_DisableFIFO(USART2);
LL_USART_ConfigAsyncMode(USART2);
/* USER CODE BEGIN WKUPType USART2 */
/* USER CODE END WKUPType USART2 */
/* USER CODE BEGIN USART2_Init 2 */
huart2.periph = USART2;
huart2.rx.mode = UART_RX_MODE_DMA;
huart2.rx.buf = uart2_recv_buf;
huart2.rx.rd_index = 0;
huart2.rx.wr_index = 0;
huart2.rx.size = UART2_RECV_BUF_SIZE;
huart2.rx.cplt_flag = RESET;
huart2.rx.dma = DMA2;
huart2.rx.dma_channel = LL_DMA_CHANNEL_1;
dma_set_config(DMA2, LL_DMA_CHANNEL_1, (uint32_t) & (USART2->RDR), (uint32_t)uart2_recv_buf, UART2_RECV_BUF_SIZE);
/* DMA可以搬运数据了 */
huart2.tx.mode = UART_TX_MODE_DMA;
huart2.tx.buf = NULL;
huart2.tx.count = 0;
huart2.tx.size = 0;
huart2.tx.cplt_flag = SET; /* 默认发送结束 */
huart2.tx.dma = DMA2;
huart2.tx.dma_channel = LL_DMA_CHANNEL_2;
QueueCreat(&huart2.rx.queue);
// LL_DMA_EnableIT_TC(DMA2, LL_DMA_CHANNEL_1); /* 使能发送给完成中断 */
LL_DMA_EnableChannel(DMA2, LL_DMA_CHANNEL_1); /* 使能DMA指定通道 */
/* 使能串口IDLE中断 */
LL_USART_EnableDMAReq_RX(USART2);
LL_USART_EnableDMAReq_TX(USART2);
LL_USART_EnableIT_IDLE(USART2);
LL_USART_Enable(USART2);
/* Polling USART2 initialisation */
while ((!(LL_USART_IsActiveFlag_TEACK(USART2))) || (!(LL_USART_IsActiveFlag_REACK(USART2)))) {}
/* USER CODE END USART2_Init 2 */
}
我比较喜欢用cubemx生产初始化代码,然后添加需要增加的必要初始化。有几个重要的点需要注意一下:
1. UART DMA接收设置了循环模式,这样做可以少开一个DMA接收完成中断。UART DMA发送采用正常模式。
2. 使能UART IDLE中断,这样做的好处是,UART每次收到一包数据之后,会产生一个IDLE中断,通过读DMA相关寄存器,可以获取包数据的长度。
3. 中断接收、
直接贴代码
void usart_irq_handler(uart_handle_t huart)
{
struct uart_message message;
uint32_t recv_length = 0;
uint32_t cur_recv_length = 0;
if ((LL_USART_IsActiveFlag_IDLE(huart->periph) == 1) && (LL_USART_IsEnabledIT_IDLE(huart->periph) == 1)) {
LL_USART_ClearFlag_IDLE(huart->periph);
if (huart->rx.mode == UART_RX_MODE_DMA) {
/* 记录本次起始地址 */
message.buf = (uint8_t *)&(huart->rx.buf[huart->rx.wr_index]);
/* 计算本次接收数据的长度 */
recv_length = huart->rx.size - LL_DMA_GetDataLength(huart->rx.dma, huart->rx.dma_channel); /* 已经接收到的数据长度 */
if (recv_length >= huart->rx.wr_index) {
cur_recv_length = recv_length - huart->rx.wr_index;
} else {
cur_recv_length = huart->rx.size - huart->rx.wr_index + recv_length;
}
message.length = cur_recv_length;
/* 记录当前数据的长度 */
huart->rx.wr_index = recv_length;
/* 入队列 */
if (message.length != 0) {
QueueSend(&(huart->rx.queue), message);
}
}
}
}
void USART2_IRQHandler(void)
{
usart_irq_handler(&huart2);
}
在中断中,每次IDLE中断,通过LL_DMA_GetDataLength(huart->rx.dma, huart->rx.dma_channel)
获取DMA缓冲区还有接收多少字节数,计算出本次IDLE中断接收到了多少数据。并保存本次接收数据的起始地址。将起始地址和长度整体插入队列中。
因此,UART接收需要队列的支持。后面会通过队列的代码。
因为初始化的时候,已经将DMA设置有循环模式,所以不需要考虑DMA接收完成的问题。
4. UART接收函数
直接贴代码
int bsp_uart_receive(uart_handle_t huart, uint8_t *pbuf, uint16_t size, uint32_t timeout)
{
int i, retval = -1;
struct uart_message message;
if (huart->rx.mode == UART_RX_MODE_DMA) {
if (QueueReceive(&huart->rx.queue, &message) == 0) {
for (i = 0; i < size && i < message.length; i++) {
pbuf[i] = *(message.buf++);
if (message.buf > (uint8_t *)&huart->rx.buf[huart->rx.size - 1]) {
message.buf = huart->rx.buf;
}
}
retval = i;
}
}
return retval;
}
裸机代码中,在main循环中,轮询队列是否有数据,如果有数据,根据结构体message的包含的起始地址和长度,将数据复制到pbuf中。
5. UART DMA发送
int bsp_uart_transmit(uart_handle_t huart, const uint8_t *pbuf, uint16_t size, uint32_t timeout)
{
int retval = -1;
uint32_t tickstart = HAL_GetTick();
if (huart->tx.mode == UART_TX_MODE_DMA) {
/* 等待上一次数据发送完成,或者超时 */
while ((huart->tx.cplt_flag == RESET) || (HAL_GetTick() - tickstart >= timeout)) {};
if (huart->tx.cplt_flag == SET) {
huart->tx.cplt_flag = RESET;
/* DMA:源地址,长度 */
dma_set_config(huart->tx.dma, huart->tx.dma_channel, (uint32_t)pbuf, (uint32_t) & (huart->periph->TDR), size);
LL_DMA_EnableIT_TC(huart->tx.dma, huart->tx.dma_channel); /* 使能发送给完成中断 */
LL_DMA_EnableChannel(huart->tx.dma, huart->tx.dma_channel); /* 使能DMA指定通道 */
/* 这个时候就开始发送了 */
retval = 0;
} else {
/* TODO:超时了,怎么办? */
huart->tx.cplt_flag = RESET;
retval = -1;
}
}
return retval;
}
void DMA2_Channel2_IRQHandler(void)
{
uart_handle_t huart;
if ((LL_DMA_IsActiveFlag_TC2(DMA2) == 1) && (LL_DMA_IsEnabledIT_TC(DMA2, LL_DMA_CHANNEL_2) == 1)) {
huart = get_uart_handle_by_tx_dma(DMA2, LL_DMA_CHANNEL_2);
huart->tx.cplt_flag = SET;
/* 清除中断标识位 */
LL_DMA_ClearFlag_TC2(DMA2);
LL_DMA_DisableChannel(DMA2, LL_DMA_CHANNEL_2);
LL_DMA_DisableIT_TC(DMA2, LL_DMA_CHANNEL_2);
}
}
DMA发送模式,不需要死等是否发送完成,可以在下次发送时,判断上次是否发送完成,没完成就等待发送完成。
6. 补充
需要在main.h中增加部分文件。
#include "stm32g4xx_ll_usart.h"
#include "stm32g4xx_ll_rcc.h"
#include "stm32g4xx_ll_bus.h"
#include "stm32g4xx_ll_cortex.h"
#include "stm32g4xx_ll_system.h"
#include "stm32g4xx_ll_utils.h"
#include "stm32g4xx_ll_pwr.h"
#include "stm32g4xx_ll_gpio.h"
#include "stm32g4xx_ll_dma.h"
#include "stm32g4xx_ll_exti.h"
并在main.c中增加DMA初始化
__HAL_RCC_DMAMUX1_CLK_ENABLE();
__HAL_RCC_DMA2_CLK_ENABLE();
有更好的关于串口接收的模式,欢迎留言。