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

环境

  • 硬件平台: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收发 双缓冲发送 环形缓冲接收)