美文网首页我爱编程
STM32 串口 DMA收发 双缓冲发送 环形缓冲接收

STM32 串口 DMA收发 双缓冲发送 环形缓冲接收

作者: lippon | 来源:发表于2017-07-30 14:29 被阅读1367次

    环境

    • 硬件平台:STM32F103ZET6
    • 开发环境:KEIL5

    DMA

    DMA(Direct Memory Access ,直接存储器存取),是一种可以大大减轻CPU工作量的数据存取方式,因而被广泛使用,STM32中DMA是以类似外设的形式添加到内核之外。当有大量的数据需要在内存与外设之间搬运时,使用DMA模式可以提高传输效率和整体运行性能。
    F103具有2个DMA,DMA1有7个通道,DMA2有5和通道,每个通道对应不同的外设,在这次开发中,使用的USART1的接收和发送分别用到了DMA1的通道4和5。

    串口初始化

    /**
    * @ Function Name : usart_init
    * @ Author        : hlb
    * @ Brief         : 串口初始化
    * @ Date          : 2017.07.18
    * @ Modify        : ...
     **/
    void usart_init(void)
    {
    
        GPIO_InitTypeDef  GPIO_InitStructure;
        USART_InitTypeDef USART_InitStructure;
        NVIC_InitTypeDef  NVIC_InitStructure;
        
        //串口数据初始化
        usart_data_init();
         
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);     //使能USART1,GPIOA时钟
      
        //USART1_TX   GPIOA.9
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;               //PA.9
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;         //复用推挽输出
        GPIO_Init(GPIOA, &GPIO_InitStructure);                  //初始化GPIOA.9
       
        //USART1_RX   GPIOA.10初始化
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;              //PA10
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;   //浮空输入
        GPIO_Init(GPIOA, &GPIO_InitStructure);                  //初始化GPIOA.10  
    
        //USART 初始化设置
        USART_InitStructure.USART_BaudRate = BOUNDRATE;         //串口波特率
        USART_InitStructure.USART_WordLength = USART_WordLength_8b;                     //字长为8位数据格式
        USART_InitStructure.USART_StopBits = USART_StopBits_1;  //一个停止位
        USART_InitStructure.USART_Parity = USART_Parity_No;     //无奇偶校验
        USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //无硬件数据流控制
        USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;                 //收发模式
    
        USART_Init(USART1, &USART_InitStructure);               //初始化串口1
        USART_Cmd(USART1, ENABLE);                              //使能串口1 
        
        usart1_dma_init();                                      // 初始化dma
    
    }
    
    

    DMA初始化

    在同一般的串口初始化配置后,进行串口DMA的初始化。
    由于使用双缓冲发送,DMA发送通道的源地址暂时设置为空,在之后的缓冲区激活和锁定当中,变换源地址。
    DMA接收通道源地址为接收缓冲区的地址。

    /**
    * @ Function Name : usart1_dma_init
    * @ Author        : hlb
    * @ Brief         : 初始化串口1的dma。
    * @ Date          : 2017.07.18
    * @ Modify        : ...
     **/
    void usart1_dma_init(void)
    {
        DMA_InitTypeDef  dma_initstruct;  
        NVIC_InitTypeDef NVIC_InitStructure;
        
        /*------------------------------TX_DMA----------------------------------*/
        //DMA发送中断设置  
        NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel4_IRQn;  
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;  
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;  
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;  
        NVIC_Init(&NVIC_InitStructure);  
        
        // 重置TX_DMA通道
        DMA_DeInit(USART1_TX_DMA_CHANNEL);
        // 使能时钟
        RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
            
        dma_initstruct.DMA_DIR                = DMA_DIR_PeripheralDST;            // 设置dma的方向由内存到外设
        dma_initstruct.DMA_M2M                = DMA_M2M_Disable;                  // 禁止内存到内存的传输
        dma_initstruct.DMA_BufferSize         = USART_TX_LEN;                     // 设置DMA在传输时缓冲区的长度    
        dma_initstruct.DMA_MemoryBaseAddr     = null;                             // 设置源地址
        dma_initstruct.DMA_PeripheralBaseAddr = (u32)&(USART1->DR);               // 设置传输地址
        dma_initstruct.DMA_MemoryDataSize     = DMA_MemoryDataSize_Byte;          // 每次传输单位为字节
        dma_initstruct.DMA_MemoryInc          = DMA_MemoryInc_Enable;             // 允许内存自增地址
        dma_initstruct.DMA_PeripheralInc      = DMA_PeripheralInc_Disable;        // 禁止外设自增
        dma_initstruct.DMA_Mode               = DMA_Mode_Normal;                  // 普通模式
        dma_initstruct.DMA_Priority           = USART1_TX_DMA_PRIORITY;           // 设置DMA通道的优先级
        // 初始化DMA1的4通道(USART1, TX)
        DMA_Init(USART1_TX_DMA_CHANNEL, &dma_initstruct);
        
        /*------------------------------RX_DMA----------------------------------*/
        DMA_Cmd(USART1_RX_DMA_CHANNEL, DISABLE);                                  // 关DMA通道
        DMA_DeInit(USART1_RX_DMA_CHANNEL);
        dma_initstruct.DMA_MemoryBaseAddr     = (u32)UsartRxBuffer.Buff;          // 设置源地址
        dma_initstruct.DMA_PeripheralBaseAddr = (u32)&(USART1->DR);               // 设置传输地址
        dma_initstruct.DMA_DIR                = DMA_DIR_PeripheralSRC;            // 由外设到内存
        dma_initstruct.DMA_BufferSize         = USART_RX_LEN;                     // 设置DMA在传输时缓冲区的长度    
        dma_initstruct.DMA_PeripheralInc      = DMA_PeripheralInc_Disable;        // 禁止外设自增
        dma_initstruct.DMA_MemoryInc          = DMA_MemoryInc_Enable;             // 允许内存自增地址
        dma_initstruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;      // 外设数据字长
        dma_initstruct.DMA_MemoryDataSize     = DMA_MemoryDataSize_Byte;          // 内存数据字长
        dma_initstruct.DMA_Mode               = DMA_Mode_Circular;                // 循环接收
        dma_initstruct.DMA_Priority           = USART1_RX_DMA_PRIORITY;           // 设置DMA通道的优先级
        dma_initstruct.DMA_M2M                = DMA_M2M_Disable;                  // 禁止内存到内存的传输
        // 初始化DMA1的5通道(USART1, RX)
        DMA_Init(USART1_RX_DMA_CHANNEL, &dma_initstruct);
        
        //使能接收通道  
        DMA_Cmd(USART1_RX_DMA_CHANNEL,ENABLE);  
        
        // 允许串口DMA
        USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);
        USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE);
            
    }
    

    DMA传输设置

    /**
    * @ Function Name : dma_usart_tx_restart
    * @ Author        : hlb
    * @ Brief         : 重启串口的dma
    * @ Date          : 2017.07.18
    * @ Input         : u8 *res           发送的数据  
                        u32 size            传输数据的长度
    * @ Modify        : ...
    **/
    void dma_usart_tx_restart(u8 *res, u32 size)
    {
        // 禁用DMA
        DMA_Cmd(USART1_TX_DMA_CHANNEL, DISABLE);
        
        // 设置DMA的传输值
        USART1_TX_DMA_CHANNEL -> CNDTR = size;
        
        // 设置传输地址
        USART1_TX_DMA_CHANNEL -> CMAR  = (u32)res;
        
        // 启动DMA
        DMA_Cmd(USART1_TX_DMA_CHANNEL, ENABLE)
    
    }
    
    /**
    * @ Function Name : dma_usart_tx
    * @ Author        : hlb
    * @ Brief         : 串口DMA发送函数
    * @ Date          : 2017.07.19
    * @ Input         : u8* buff       待发送数组头指针
    *                   u32 size       待发送数组大小
    * @ Modify        : ...
    **/
    void dma_usart_tx(u8 *buff, u32 size)
    {
        // 清除TC标志
        USART_ClearFlag(USART1, USART_FLAG_TC);
        
        //重启DMA发送
        dma_usart_tx_restart(buff, size);
    }
    

    DMA状态查询

    
    /**
    * @ Function Name : usart1_get_tx_dma_tc_state
    * @ Author        : hlb
    * @ Brief         : 取串口1的发送dma传输标志位
    * @ Date          : 2017.07.18
    * @ OutPut        : 串口1发送DMA传送完成标志位
    * @ Modify        : ...
     **/
    bool usart1_get_tx_dma_tc_state(void)
    {
        if(DMA_GetFlagStatus(DMA1_FLAG_TC4) == SET)
        {
            DMA_ClearFlag(DMA1_FLAG_TC4);
            
            return true;
        }
        else
        {
            return false;
        }
    }
    
    
    /**
    * @ Function Name : dma_usart_chek_tx_state
    * @ Author        : hlb
    * @ Brief         : DMA发送状态查询
    * @ Date          : 2017.07.19
    * @ Modify        : ...
    **/
    void dma_usart_chek_tx_state(void)
    {
        //如果DMA为忙
        if(UsartTxBuffer.DmaBusy)
        {
            // 查询dma的完成标志
            if (usart1_get_tx_dma_tc_state())
            {
                UsartTxBuffer.DmaBusy = false;
            }       
        }
    }
    
    

    双缓冲

    当数据处理中的生产者速度大于消费者的时候,使用一般的循环缓冲队列会造成数据的丢失和混乱,这时候可以考虑使用双缓冲。
    所谓双缓冲,就是使用两个缓冲区装载数据,一个用于生产者,另一个用于消费者,当消费者将其所在的缓冲区使用完毕,生产者在其缓冲区又有新的数据产生后,二者交换两个缓冲区的使用权。从而可以达到数据的发送与生产互不干涉,存取流畅。
    在串口DMA发送中,DMA作为消费者,发送缓冲区数据,CPU作为数据生产者,向提供的发送接口填充数据。当DMA非忙的时候,消费者已经将缓冲耗尽,交换缓冲区。

    缓冲区结构体定义

    #pragma pack(push, 1) 
    //串口数据缓冲定义
    typedef struct
    {
        u8  Buff[TX_BUFFER_NUM_DEFAULT][USART_TX_LEN];               //缓冲区
        u16 Idx[TX_BUFFER_NUM_DEFAULT];                              //添加索引
        u8  PartAvailAction;                                         //激活缓冲区位置
        bool DmaBusy;                                                //发送DMA是否为忙
        
    }Usart_Tx_Buff_TypeDef;
    #pragma pack(pop)
    

    DMA发送处理函数

    /**
    * @ Function Name : dma_usart_tx_handle
    * @ Author        : hlb
    * @ Brief         : dma串口发送处理函数
    * @ Date          : 2017.07.19
    * @ Modify        : ...
    **/
    void dma_usart_tx_handle(void)
    {
        //如果DMA非忙
        if(!UsartTxBuffer.DmaBusy)
        {
            //激活的缓冲区非空
            if(UsartTxBuffer.Idx[UsartTxBuffer.PartAvailAction] != BUFFER_HEAD_DEFAULT)
            {
                //设置DMA传输对象
                dma_usart_tx(UsartTxBuffer.Buff[UsartTxBuffer.PartAvailAction], \
                UsartTxBuffer.Idx[UsartTxBuffer.PartAvailAction]);
                
                //恢复缓冲区索引
                UsartTxBuffer.Idx[UsartTxBuffer.PartAvailAction] = BUFFER_HEAD_DEFAULT;
                
                //激活另外一个缓冲,锁定当前缓冲
                UsartTxBuffer.PartAvailAction = 1 - UsartTxBuffer.PartAvailAction;
                
                //锁定DMA
                UsartTxBuffer.DmaBusy = true;
            }
        }
    }
    
    

    串口数据发送接口

    /**
    * @ Function Name : usart_tx
    * @ Author        : hlb
    * @ Brief         : 串口发送接口
    * @ Date          : 2017.07.19
    * @ Input         : u8 *buff   发送的缓冲地址
    *                   u32 size 发送的数据长度
    * @ Modify        : ...
    **/
    void usart_tx(u8 *buff, u32 size)
    {
        u32 freeSize = 0;
        freeSize = USART_TX_LEN - UsartTxBuffer.Idx[UsartTxBuffer.PartAvailAction];
        
        if(freeSize >= size)
        {
            memcpy(&UsartTxBuffer.Buff[UsartTxBuffer.PartAvailAction][UsartTxBuffer.Idx[UsartTxBuffer.PartAvailAction]], \
                buff, \
                size);
        }
        UsartTxBuffer.Idx[UsartTxBuffer.PartAvailAction] += size;
    }
    
    

    串口数据发送线程

    /**
    * @ Function Name : usart_send_handle
    * @ Author        : hlb
    * @ Brief         : 串口数据发送线程
    * @ Date          : 2017.07.19
    * @ Modify        : ...
    **/
    void usart_send_handle(void)
    {
        //检查DMA发送状态
        dma_usart_chek_tx_state();
    
        //发送处理线程
        dma_usart_tx_handle();
    }
    

    环形缓冲

    环形缓冲区是一个先进先出的缓冲区,是通信中常用的缓冲模式。
    通常环形缓冲拥有一个读指针和一个写指针,生产者控制写指针,消费者控制读指针。

    缓冲区结构体定义

    #pragma pack(push, 1) 
    //串口数据缓冲定义
    typedef struct
    {
         u8  Buff[USART_RX_LEN];                 //缓冲区
         u16 AddIdx;                             //添加索引
         u16 GetIdx;                             //取出索引
        
    }Usart_Rx_Buff_TypeDef;
    #pragma pack(pop)
    

    相关文章

      网友评论

        本文标题:STM32 串口 DMA收发 双缓冲发送 环形缓冲接收

        本文链接:https://www.haomeiwen.com/subject/thuhlxtx.html