STM32:串口多级缓冲接收

本文是基于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();

有更好的关于串口接收的模式,欢迎留言。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,701评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,649评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,037评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,994评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,018评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,796评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,481评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,370评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,868评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,014评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,153评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,832评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,494评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,039评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,156评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,437评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,131评论 2 356

推荐阅读更多精彩内容