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

推荐阅读更多精彩内容

  • 姓名:周崇杰 学号:16140120059 专业:机械设计制造及其自动化 转载自:http://blog.csd...
    CJbaby阅读 3,476评论 0 3
  • 大学的时候,帮朋友写的操作系统调研的作业,最近整理过去的文档时候偶然发现,遂作为博客发出来。 从串口驱动到Linu...
    free_will阅读 7,366评论 7 59
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,599评论 18 139
  • #清凉法语#人生次第 【人生次弟】先学做人,再学做事;先学生存,再学生活;先学由简入繁,再学化繁为简;先学随众,再...
    xcy无名阅读 430评论 0 0
  • 悟空,在一些人心里,他是无所不能,无所不用其极,可爱,基本没什么烦恼的一个人、猴子、斗战圣佛,南京人戴荃一曲《悟空...
    歪理邪说之爽曰阅读 249评论 0 4