STM32
直播中

王利祥

7年用户 1421经验值
私信 关注
[问答]

FIFO是如何结合STM32的DMA去实现串口数据的收发呢

FIFO是如何结合STM32的DMA去实现串口数据的收发呢?

回帖(1)

张嘉

2021-12-7 13:37:52
1.概述

本文实现如下功能:



  • 通用串口驱动
  • FIFO结合STM32的DMA实现串口数据的收发。

2.串口驱动相关结构体定义

串口参数结构体
typedef struct{        USART_TypeDef*                        Uartx;                        //指向物理串口的结构体        UartIOCfg                                CfgIO;                        //串口收发管脚配置结构体        USART_InitTypeDef                CfgUart;                //串口参数结构体        NVIC_InitTypeDef                 CfgNvic;                //串口中断结构体        UartCfgRx                                CfgRx;                        //串口接收DMA配置结构体        UartCfgTx                                CfgTx;                        //串口发送DMA配置结构体}SerialTypeDef; 串口IO配置结构体
typedef struct{        GPIO_TypeDef*                        TxPort;                                //发送端口        GPIO_TypeDef*                        RxPort;                                //接收端口        INT16U                                        TxPin;                                //发送管脚        INT16U                                        RxPin;                                //接收管脚}UartIOCfg; 接收DMA配置结构体
typedef struct{        DMA_Stream_TypeDef*                 DmaStream;                        //DMA流        INT32U                                        DmaChannel;                        //通道        INT16U                                        Used;                                //DMA使用量        FifoTypeDef*                        Fifo;                                //流对应使用的FIFO。        INT16U                                        FifoSize;                        //接收fifo的大小。        NVIC_InitTypeDef                 CfgNvic;                        //DMA中断配置结构体}UartCfgRx; 发送DMA配置结构体
typedef struct{        DMA_Stream_TypeDef*                 DmaStream;                        //DMA流        INT32U                                        DmaChannel;                        //通道        FifoTypeDef*                        Fifo;                                //流对应使用的FIFO。        INT16U                                        FifoSize;                        //发送fifo的大小。        INT8U*                                        Buff;                                //发送的buff        INT16U                                        BuffSize;                        //发送buff的大小        NVIC_InitTypeDef                 CfgNvic;}UartCfgTx;
3.串口驱动实现

3.1 static局部函数

3.1.1串口IO初始化

/****************************************************** @brief   bsp_uart_IOCfg* @note    初始化IO为复用,USART或UART* @param   serial           指向串口参数* @retval  NONE* @data    2021.07.01* @auth    WXL* @his     1.0.0.0     2021.07.01     WXL*                                 create*****************************************************/static void bsp_uart_IOCfg(SerialTypeDef* serial){        GPIO_InitTypeDef         GPIO_InitStructure;        //IO初始化        RCC_AHB1PeriphClockCmd(1<<( (((INT32U)serial->CfgIO.TxPort)-AHB1PERIPH_BASE) / 0x0400 ),ENABLE);        GPIO_InitStructure.GPIO_Pin         = (1 << serial->CfgIO.TxPin);        GPIO_InitStructure.GPIO_Mode         = GPIO_Mode_AF;        GPIO_InitStructure.GPIO_Speed         = GPIO_Speed_50MHz;        GPIO_InitStructure.GPIO_OType         = GPIO_OType_PP;        GPIO_InitStructure.GPIO_PuPd         = GPIO_PuPd_UP;        GPIO_Init(serial->CfgIO.TxPort , &GPIO_InitStructure);        RCC_AHB1PeriphClockCmd(1<<( ( ((INT32U)serial->CfgIO.RxPort)-AHB1PERIPH_BASE) / 0x0400 ),ENABLE);        GPIO_InitStructure.GPIO_Pin         = (1 << serial->CfgIO.RxPin);        GPIO_Init(serial->CfgIO.RxPort , &GPIO_InitStructure);        //IO复用        if(serial->Uartx == USART1 || serial->Uartx == USART2 || serial->Uartx == USART3)        {                GPIO_PinAFConfig(serial->CfgIO.TxPort , serial->CfgIO.TxPin , 7);                GPIO_PinAFConfig(serial->CfgIO.RxPort , serial->CfgIO.RxPin , 7);        }        if(serial->Uartx == UART4 || serial->Uartx == UART5 || serial->Uartx == USART6)        {                GPIO_PinAFConfig(serial->CfgIO.TxPort , serial->CfgIO.TxPin , 8);                GPIO_PinAFConfig(serial->CfgIO.RxPort , serial->CfgIO.RxPin , 8);        }}
根据串口参数结构体的内容进行相关收发IO的初始化。注意一下几点:



  • 时钟使能

                STM32的标准库中RCC_AHB1PeriphClockCmd参数1可设置为RCC_AHB1Periph_GPIOA到RCC_AHB1Periph_GPIOK。对应内容如下
RCC_AHB1Periph_GPIOA = 1<<( (((INT32U)serial->CfgIO.TxPort)-AHB1PERIPH_BASE) / 0x0400;

RCC_AHB1Periph_GPIOxGPIOx
RCC_AHB1Periph_GPIOA0x00000001GPIOA(AHB1PERIPH_BASE + 0x0000)
RCC_AHB1Periph_GPIOB0x00000002GPIOB(AHB1PERIPH_BASE + 0x0400)
RCC_AHB1Periph_GPIOC0x00000004GPIOC(AHB1PERIPH_BASE + 0x0800)
RCC_AHB1Periph_GPIOD0x00000008GPIOD(AHB1PERIPH_BASE + 0x0C00)
RCC_AHB1Periph_GPIOE0x00000010GPIOE(AHB1PERIPH_BASE + 0x1000)
RCC_AHB1Periph_GPIOF0x00000020GPIOF(AHB1PERIPH_BASE + 0x1400)
RCC_AHB1Periph_GPIOG0x00000040GPIOG(AHB1PERIPH_BASE + 0x1800)
RCC_AHB1Periph_GPIOH0x00000080GPIOH(AHB1PERIPH_BASE + 0x1C00)
RCC_AHB1Periph_GPIOI0x00000100GPIOI(AHB1PERIPH_BASE + 0x2000)
RCC_AHB1Periph_GPIOJ0x00000200GPIOJ(AHB1PERIPH_BASE + 0x2400)
RCC_AHB1Periph_GPIOK0x00000400GPIOK(AHB1PERIPH_BASE + 0x2800)



  • 管脚复用

                USART1,USART2,USART3的收发管脚均复用为功能7。UART4,UART5,USART6管脚复用功能为8需要注意。

3.1.2串口参数初始化

/****************************************************** @brief   bsp_usart_UartInit* @note    串口参数初始化* @param   serial           指向串口参数* @retval  NONE* @data    2021.07.01* @auth    WXL* @his     1.0.0.0     2021.07.01     WXL*                                 create*****************************************************/static void bsp_usart_UartInit(SerialTypeDef* serial){        //使能时钟。        if(serial->Uartx == USART1)                RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);        else if(serial->Uartx == USART2)                RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);        else if(serial->Uartx == USART3)                RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3,ENABLE);        else if(serial->Uartx == UART4)                RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART4,ENABLE);        else if(serial->Uartx == UART5)                RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART5,ENABLE);        else if(serial->Uartx == USART6)                RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART6,ENABLE);        USART_Init(serial->Uartx, &serial->CfgUart);        USART_Cmd(serial->Uartx, ENABLE);}
根据串口参数结构体中的物理串口进行相应的时钟使能,参数配置。
3.1.3中断初始化

/****************************************************** @brief   bsp_usart_NVICCfg* @note    串口中断初始化* @param   serial           指向串口参数* @retval  NONE* @data    2021.07.01* @auth    WXL* @his     1.0.0.0     2021.07.01     WXL*                                 create*****************************************************/static void bsp_usart_NVICCfg(SerialTypeDef* serial){        USART_ITConfig(serial->Uartx, USART_IT_IDLE, ENABLE);        NVIC_Init(&serial->CfgNvic);        USART_DMACmd(serial->Uartx,USART_DMAReq_Rx,ENABLE);        USART_DMACmd(serial->Uartx,USART_DMAReq_Tx,ENABLE);}
3.1.4中断初始化

/****************************************************** @brief   bsp_usart_DMACfg* @note    串口DMA配置* @param   serial           指向串口参数* @retval  0         成功*          1         申请内存失败* @data    2021.07.01* @auth    WXL* @his     1.0.0.0     2021.07.01     WXL*                                 create*****************************************************/static INT8U bsp_usart_DMACfg(SerialTypeDef* serial){        DMA_InitTypeDef                  DMA_InitStructure;        //创建接收FIFO        serial->CfgRx.Fifo = func_fifo_Create(serial->CfgRx.FifoSize);        if(serial->CfgRx.Fifo == NULL)                return 1;        serial->CfgRx.Used = 0;        //创建发送FIFO        serial->CfgTx.Fifo = func_fifo_Create(serial->CfgTx.FifoSize);        if(serial->CfgTx.Fifo == NULL)        {                func_fifo_Free(serial->CfgRx.Fifo);                return 1;        }        //创建发送BUFF        serial->CfgTx.Buff = malloc(serial->CfgTx.BuffSize);        if(serial->CfgTx.Buff == NULL)        {                func_fifo_Free(serial->CfgRx.Fifo);                func_fifo_Free(serial->CfgTx.Fifo);                return 1;        }        //接收   DMA配置        DMA_Stream_TypeDef* stream  = serial->CfgRx.DmaStream;        INT32U                                channle = serial->CfgRx.DmaChannel;        if(stream < DMA2_Stream0)                RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE);        else                RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);        DMA_DeInit(stream);        while(DMA_GetCmdStatus(stream) != DISABLE)        {}        DMA_InitStructure.DMA_Channel                                 = channle;        DMA_InitStructure.DMA_PeripheralBaseAddr         = (u32)&(serial->Uartx->DR);        DMA_InitStructure.DMA_Memory0BaseAddr                 = (u32)serial->CfgRx.Fifo->Buf;        DMA_InitStructure.DMA_DIR                                         = DMA_DIR_PeripheralToMemory;        DMA_InitStructure.DMA_BufferSize                         = serial->CfgRx.FifoSize;        DMA_InitStructure.DMA_PeripheralInc                 = DMA_PeripheralInc_Disable;        DMA_InitStructure.DMA_MemoryInc                         = DMA_MemoryInc_Enable;        DMA_InitStructure.DMA_PeripheralDataSize         = DMA_PeripheralDataSize_Byte;        DMA_InitStructure.DMA_MemoryDataSize                 = DMA_MemoryDataSize_Byte;        DMA_InitStructure.DMA_Mode                                         = DMA_Mode_Circular;        DMA_InitStructure.DMA_Priority                                 = DMA_Priority_Medium;        DMA_InitStructure.DMA_FIFOMode                                 = DMA_FIFOMode_Disable;        DMA_InitStructure.DMA_FIFOThreshold                 = DMA_FIFOThreshold_Full;        DMA_InitStructure.DMA_MemoryBurst                         = DMA_MemoryBurst_Single;        DMA_InitStructure.DMA_PeripheralBurst                = DMA_PeripheralBurst_Single;        DMA_Init(stream, &DMA_InitStructure);        DMA_Cmd(stream, ENABLE);        NVIC_Init(&serial->CfgRx.CfgNvic);        DMA_Cmd(stream, ENABLE);        DMA_ITConfig(stream, DMA_IT_TC, ENABLE);        //发送        stream  = serial->CfgTx.DmaStream;        channle = serial->CfgTx.DmaChannel;        if(stream < DMA2_Stream0)                RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE);        else                RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);        DMA_DeInit(stream);        while(DMA_GetCmdStatus(stream) != DISABLE)        {}        DMA_InitStructure.DMA_Channel                                 = channle;        DMA_InitStructure.DMA_PeripheralBaseAddr         = (u32)&(serial->Uartx->DR);        DMA_InitStructure.DMA_Memory0BaseAddr                 = (u32)serial->CfgTx.Buff;        DMA_InitStructure.DMA_DIR                                         = DMA_DIR_MemoryToPeripheral;        DMA_InitStructure.DMA_BufferSize                         = 0;        DMA_InitStructure.DMA_PeripheralInc                 = DMA_PeripheralInc_Disable;        DMA_InitStructure.DMA_MemoryInc                         = DMA_MemoryInc_Enable;        DMA_InitStructure.DMA_PeripheralDataSize         = DMA_PeripheralDataSize_Byte;        DMA_InitStructure.DMA_MemoryDataSize                 = DMA_MemoryDataSize_Byte;        DMA_InitStructure.DMA_Mode                                         = DMA_Mode_Normal;        DMA_InitStructure.DMA_Priority                                 = DMA_Priority_Medium;        DMA_InitStructure.DMA_FIFOMode                                 = DMA_FIFOMode_Disable;        DMA_InitStructure.DMA_FIFOThreshold                 = DMA_FIFOThreshold_Full;        DMA_InitStructure.DMA_MemoryBurst                         = DMA_MemoryBurst_Single;        DMA_InitStructure.DMA_PeripheralBurst                = DMA_PeripheralBurst_Single;        DMA_Init(stream, &DMA_InitStructure);        DMA_Cmd(stream, ENABLE);        NVIC_Init(&serial->CfgTx.CfgNvic);        DMA_Cmd(stream, ENABLE);        DMA_ITConfig(stream, DMA_IT_TC, ENABLE);        return 0;}
串口DMA初始化需要完成如下内容:


  • FIFO申请:申请用于DMA接收的FIFO,申请用于DMA发送的数据缓存FIFO
  • BUFF申请:申请用于DMA发送时使用的BUFF
  • 接收DMA配置为循环模式,地址为FIFO中的BUFF地址,使DMA完成自动循环写入FIFO中。在DMA中断中进行FIFO无数据写入,实现数据的FIFO接收。
  • 发送DMA配置,配置发送地址为上面申请的BUFF地址,发送时将发送FIFO中的内容读取到BUFF中进行DMA发送。
  • DMA中断配置,使能DMA完成中断。

3.1.5接收数据回调函数

/*****************************************************  * @brief   bsp_uart_rx_callback  * @note    IDLE中断和DMA完成中断回调函数。  * @param   com             端口。  *                  par                1   DMA完成中断  *                                          0   IDLE中断。  * @retval  NONE  * @data    2021.01.20  * @auth    WXL  * @his     1.0.0.1     2021.01.20     WXL  *                   新建文件*****************************************************/static void bsp_uart_rx_callback(SerialTypeDef* serial,INT8U par){    INT16U count = 0;    INT16U used = 0;    //DMA完成中断调用。    if(par == 1)    {            count = serial->CfgRx.FifoSize - serial->CfgRx.Used;//计算本次接收元素个数。            serial->CfgRx.Used = 0;                                                                //DMA为Circular模式,中断中需要清零该变量。    }    //IDLE中断调用。    else    {            //计算本次接收元素个数。            used = serial->CfgRx.FifoSize - DMA_GetCurrDataCounter(serial->CfgRx.DmaStream);        count = used - serial->CfgRx.Used;        serial->CfgRx.Used = used;    }        //更新接收FIFO的指针。    func_fifo_PushNoData(serial->CfgRx.Fifo, count);}         
在接收DMA完成中断和串口IDLE中断中调用。计算写入量,将FIFO进行相应操作。以上函数均为局部函数,不对外开放。
3.2全局函数

3.2.1设置串口配置参数为默认

/*****************************************************  * @brief   bsp_uart_ParaSetDefault  * @note    设置串口参数未默认IDLE中断和DMA完成中断回调函数。  * @param   serial             指向设置的串口  * @retval  NONE  * @data    2021.01.20  * @auth    WXL  * @his     1.0.0.1     2021.01.20     WXL  *                   新建文件*****************************************************/void bsp_uart_ParaSetDefault(SerialTypeDef* serial){        serial->Uartx = USART1;        serial->CfgIO.RxPin                = 10;        serial->CfgIO.RxPort        = GPIOA;        serial->CfgIO.TxPin         = 9;        serial->CfgIO.TxPort        = GPIOA;        serial->CfgUart.USART_BaudRate                                 = 115200;        serial->CfgUart.USART_HardwareFlowControl         = USART_HardwareFlowControl_None;        serial->CfgUart.USART_Mode                                         = USART_Mode_Tx | USART_Mode_Rx;        serial->CfgUart.USART_Parity                                 = USART_Parity_No;        serial->CfgUart.USART_StopBits                                = USART_StopBits_1;        serial->CfgUart.USART_WordLength                                = USART_WordLength_8b;        serial->CfgNvic.NVIC_IRQChannel                                         = USART1_IRQn;        serial->CfgNvic.NVIC_IRQChannelPreemptionPriority        = 1;        serial->CfgNvic.NVIC_IRQChannelSubPriority                        = 1;        serial->CfgNvic.NVIC_IRQChannelCmd                                        = ENABLE;        serial->CfgRx.DmaChannel  = DMA_Channel_4;        serial->CfgRx.DmaStream         = DMA2_Stream5;        serial->CfgRx.FifoSize         = 200;        serial->CfgRx.CfgNvic.NVIC_IRQChannel                                         = DMA2_Stream5_IRQn;        serial->CfgRx.CfgNvic.NVIC_IRQChannelPreemptionPriority        = 1;        serial->CfgRx.CfgNvic.NVIC_IRQChannelSubPriority                = 1;        serial->CfgRx.CfgNvic.NVIC_IRQChannelCmd                                = ENABLE;        serial->CfgTx.DmaChannel         = DMA_Channel_4;        serial->CfgTx.DmaStream          = DMA2_Stream7;        serial->CfgTx.FifoSize           = 200;        serial->CfgTx.BuffSize           = 100;        serial->CfgTx.CfgNvic.NVIC_IRQChannel                                         = DMA2_Stream7_IRQn;        serial->CfgTx.CfgNvic.NVIC_IRQChannelPreemptionPriority        = 1;        serial->CfgTx.CfgNvic.NVIC_IRQChannelSubPriority                = 1;        serial->CfgTx.CfgNvic.NVIC_IRQChannelCmd                                = ENABLE;}
3.2.2串口初始化

/****************************************************** @brief   bsp_uart_init* @note    Serial初始化,完成端口IO,中断及DMA的初始化工作* @param   serial           指向串口参数* @retval  0         成功*          1         申请内存失败* @data    2021.07.01* @auth    WXL* @his     1.0.0.0     2021.07.01     WXL*                                 create*****************************************************/INT8U bsp_uart_init(SerialTypeDef* serial){        if(bsp_usart_DMACfg(serial) == 1)                return 1;        //IO初始化        bsp_uart_IOCfg(serial);        //串口参数初始化        bsp_usart_UartInit(serial);        //中断配置初始化        bsp_usart_NVICCfg(serial);        return 0;}
3.2.3串口发送函数

/****************************************************** @brief   bsp_uart_Transmittx* @note    串口发送数据* @param   serial           指向串口参数* @retval  0                 成功*          1                 DMA繁忙,未发送。* @data    2021.07.01* @auth    WXL* @his     1.0.0.0     2021.07.01     WXL*                                 create*****************************************************/INT8U bsp_uart_Transmit(SerialTypeDef* Uartx){    INT8U count = 0;    //DMA发送繁忙,不进行操作。        if(DMA_GetCurrDataCounter(Uartx->CfgTx.DmaStream)!= 0)                return 1;        if(Uartx->CfgTx.Fifo->CntUsed == 0)                return 0;        if(Uartx->CfgTx.Fifo->CntUsed > Uartx->CfgTx.BuffSize)        {                count = Uartx->CfgTx.BuffSize;        }        else                count = Uartx->CfgTx.Fifo->CntUsed;        //将数据从FIFO中拷贝到DMA的发送缓冲区,重启DMA。        func_fifo_Pull(Uartx->CfgTx.Fifo, count, Uartx->CfgTx.Buff);        DMA_Cmd(Uartx->CfgTx.DmaStream,DISABLE);        while (DMA_GetCmdStatus(Uartx->CfgTx.DmaStream) != DISABLE)        {}        DMA_SetCurrDataCounter(Uartx->CfgTx.DmaStream,count);        DMA_Cmd(Uartx->CfgTx.DmaStream, ENABLE);    return 0;}
3.3中断函数

3.3.1DMA发送中断

/*****************************************************  * @brief   DMA2_Stream7_IRQHandler  * @note    DMA2_Stream7的中断函数。            USART1_TX  * @param   NONE  * @retval  NONE  * @data    2021.01.20  * @auth    WXL  * @his     1.0.0.1     2021.01.20     WXL  *                   新建文件*****************************************************/void DMA2_Stream7_IRQHandler(){        if(DMA_GetITStatus(DMA2_Stream7, DMA_IT_TCIF7))        {                DMA_ClearFlag(DMA2_Stream5,DMA_FLAG_FEIF7 | DMA_FLAG_DMEIF7 | DMA_FLAG_TEIF7 | DMA_FLAG_HTIF7 | DMA_FLAG_TCIF7);                bsp_uart_Transmit(&Serial1);        }}
3.3.2DMA接收中断

/*****************************************************  * @brief   DMA2_Stream5_IRQHandler  * @note    DMA2_Stream5的中断函数。          USART1_RX  * @param   NONE  * @retval  NONE  * @data    2021.01.20  * @auth    WXL  * @his     1.0.0.1     2021.01.20     WXL  *                   新建文件*****************************************************/void DMA2_Stream5_IRQHandler(void){        if(DMA_GetITStatus(DMA2_Stream5,DMA_IT_TCIF5))        {                DMA_ClearFlag(DMA2_Stream5,DMA_FLAG_FEIF5 | DMA_FLAG_DMEIF5 | DMA_FLAG_TEIF5 | DMA_FLAG_HTIF5 | DMA_FLAG_TCIF5);                bsp_uart_rx_callback(&Serial1,1);        }}
3.3.3串口中断

/*****************************************************  * @brief   USART1_IRQHandler  * @note    串口1中断函数,用于处理接收完成  * @param   NONE  * @retval  NONE  * @data    2021.01.20  * @auth    WXL  * @his     1.0.0.1     2021.01.20     WXL  *                   新建文件*****************************************************/void USART1_IRQHandler(void){    INT16U data = 0;        if(USART_GetITStatus(USART1,USART_IT_IDLE) != RESET)    {        //清除IDLE标志位        data = USART1->SR;        data = USART1->DR;        bsp_uart_rx_callback(&Serial1,0);    }}
举报

更多回帖

发帖
×
20
完善资料,
赚取积分