(三)stm32之串口通信DMA传输完成中断

发布者:快乐微笑最新更新时间:2017-02-04 来源: eefocus关键字:stm32  串口通信  DMA传输  中断 手机看文章 扫描二维码
随时随地手机看文章

一、DMA功能简介

  首先唠叨一下DMA的基本概念,DMA的出现大大减轻了CPU的工作量。在硬件系统中,主要由CPU(内核)、外设、内存(SRAM)、总线等结构组成,数据经常要在内存和外设之间,外设和外设之间转移。例如:CPU需要处理从外设采集回来的数据,CPU需要先将数据从ADC外设的寄存器读取到内存中(变量)去,然后进行运算处理,这是一般的解决方法。CPU的资源是非常宝贵的,我们可以设法把转移的工作交给其他部件来完成,CPU把更多的资源用于数据运算和中断响应上,如此DMA便登场了。DMA正是为CPU分担数据转移工作,因为DMA的存在,CPU才被解放出来,它可以在数据转移的同时进行数据运算,相应中断,大大提高了效率。

二、DMA的主要特性

  

三、DMA中断特性

四、DMA之串口通信

  我们实现一个简单的功能,在DMA中处理串口通信,把数据转移的工作交给DMA,DMA把数据从内存(数组)到外设(串口)的转移,在main函数中不断进行闪灯操作,这样我们可以看到DMA在工作的时候CPU也在工作。非常有必要复习一下DMA的对应关系,我们知道stm32总共有2个DMA控制器(DMA1有7个通道,DMA2有5个通道),每个通道专门用来管理来自一个或多个外设对存储器访问的请求,还有一个仲裁器来协调DMA请求的优先级(优先级分:很高、高、中等、低),这可不是随便对应的。

  

1、LED初始化程序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void LED_GPIO_Config(void)
{      
        /*定义一个GPIO_InitTypeDef类型的结构体*/
        GPIO_InitTypeDef GPIO_InitStructure;
        /*开启LED的外设时钟*/
        RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE);
        /*选择要控制的GPIOB引脚*/                                                             
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14; 
        /*设置引脚模式为通用推挽输出*/
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;  
        /*设置引脚速率为50MHz */  
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        /*调用库函数,初始化GPIOB0*/
        GPIO_Init(GPIOB, &GPIO_InitStructure);               
        /* 关闭所有led灯 */
        GPIO_SetBits(GPIOB, GPIO_Pin_14);
}

  这个地方地方没什么要注意的,唯一要注意的就是输入输出模式,我们按需求这样配就好了。

2、串口初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void USART3_Config(void)
{
        GPIO_InitTypeDef GPIO_InitStructure;
        USART_InitTypeDef USART_InitStructure;     
        /* config USART3 clock */
        RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB , ENABLE);
        RCC_APB1PeriphClockCmd( RCC_APB1Periph_USART3, ENABLE);
        /* USART1 GPIO config */
        /* Configure USART1 Tx (PA.09) as alternate function push-pull */
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOB, &GPIO_InitStructure);   
        /* Configure USART1 Rx (PA.10) as input floating */
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
        GPIO_Init(GPIOB, &GPIO_InitStructure);         
        /* USART1 mode config */
        USART_InitStructure.USART_BaudRate = 38400;
        USART_InitStructure.USART_WordLength = USART_WordLength_8b;
        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(USART3, &USART_InitStructure);
        USART_Cmd(USART3, ENABLE);
}

3、DMA初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
void USART3_DMA_Config(void)
{
    DMA_InitTypeDef DMA_InitStructure; 
    /*开启DMA时钟*/
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); 
    //NVIC_Config();                //配置DMA中断
    //NVIC_Configuration();
    /*设置DMA源:串口数据寄存器地址*/
    DMA_InitStructure.DMA_PeripheralBaseAddr = USART3_DR_Base;    
    /*内存地址(要传输的变量的指针)*/
    DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuff;
    /*方向:从内存到外设*/      
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; 
    /*传输大小DMA_BufferSize=SENDBUFF_SIZE*/   
    DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE;
    /*外设地址不增*/     
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    /*内存地址自增*/
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    /*外设数据单位*/ 
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    /*内存数据单位 8bit*/
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; 
    /*DMA模式:不断循环*/
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ;
    //DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;   
    /*优先级:中*/  
    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; 
    /*禁止内存到内存的传输    */
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    /*配置DMA1的2通道*/        
    DMA_Init(DMA1_Channel2, &DMA_InitStructure);      
        //DMA_ITConfig(DMA1_Channel2,DMA_IT_TC,ENABLE);  //配置DMA发送完成后产生中断
    /*使能DMA*/
    DMA_Cmd (DMA1_Channel2,ENABLE);                    
}

在这里我们要注意以下几点:

(1)DMA_InitStructure.DMA_PeripheralBaseAddr = USART3_DR_Base;这里对应USART数据寄存器地址,这个地址我们是这样定义的:#define USART3_DR_Base  0x40004804,这个值是怎么算出来的呢?我们可以查看stm32存储器映射表:

USART3的起始地址是0x40004800,我们查看stm32串口数据寄存器偏移地址为0x04

因此我们可以计算到USART3数据寄存器地址为0x40004804

(2)我们数据传输方向内存(变量)到外设(串口),所以DMA方向为内存到外设

(3)DMA传输模式有两种:DMA_Mode_Normal(普通模式),DMA只传输一次;DMA_Mode_Circular(循环模式),DMA循环传输,比如在AD采集时要配置成循环模式。

4、主函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
int main(void)
{
        /* USART1 config 115200 8-N-1 */
        USART3_Config();   
        USART3_DMA_Config();
        LED_GPIO_Config(); 
        printf("\r\n usart3 DMA TX 测试 \r\n");  
        {
            uint16_t i;        
            /*填充将要发送的数据*/
            for(i=0;i
            {
                SendBuff[i]  = 'A';
            }
        }      
        /* USART1 向 DMA发出TX请求 */
        USART_DMACmd(USART3, USART_DMAReq_Tx, ENABLE);
        /* 此时CPU是空闲的,可以干其他的事情 */   
        //例如同时控制LED
        for(;;)
        {
            LED1(ON);
            Delay(0xFFFFF);
            LED1(OFF);
            Delay(0xFFFFF);
        }
}

这个函数很简单,我们很容易就可以实现,达到效果,这里就不贴图片了。

五、串口通信DMA传输完成中断

  我们知道DMA可以在传输过半,传输完成,传输错误时产生中断。我们实现的功能是,DMA工作在普通模式下即只传输一次,LED灯初始化是关闭的,DMA传输完成后产生一个中断,在中断中我们做点灯操作。这个程序调了一天才调了出来,并不是因为它很难,而是有一些要注意的地方没有注意到,从而到时耽误了好长时间才调出来。不过有错误就会有进步嘛。

  我先贴出正确的代码,然后在讨论我犯的错误,由于和上一个程序好多都是一样的,这里我们只贴出不同的地方。

(1)DMA初始化程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
void USART3_DMA_Config(void)
{
    DMA_InitTypeDef DMA_InitStructure; 
    /*开启DMA时钟*/
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); 
    //NVIC_Config();                //配置DMA中断
    NVIC_Configuration();
    /*设置DMA源:串口数据寄存器地址*/
    DMA_InitStructure.DMA_PeripheralBaseAddr = USART3_DR_Base;    
    /*内存地址(要传输的变量的指针)*/
    DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuff;
    /*方向:从内存到外设*/      
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; 
    /*传输大小DMA_BufferSize=SENDBUFF_SIZE*/   
    DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE;
    /*外设地址不增*/     
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    /*内存地址自增*/
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    /*外设数据单位*/ 
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    /*内存数据单位 8bit*/
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; 
    /*DMA模式:不断循环*/
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ;
    //DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;   
    /*优先级:中*/  
    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; 
    /*禁止内存到内存的传输    */
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    /*配置DMA1的2通道*/        
    DMA_Init(DMA1_Channel2, &DMA_InitStructure);      
        DMA_ITConfig(DMA1_Channel2,DMA_IT_TC,ENABLE);  //配置DMA发送完成后产生中断
    /*使能DMA*/
    DMA_Cmd (DMA1_Channel2,ENABLE);            
         
}

注意我们在这里打开了DMA传输完成中断。

(2)NVIC初始化

1
2
3
4
5
6
7
8
9
10
static void NVIC_Configuration(void)
{
  NVIC_InitTypeDef NVIC_InitStructure;     /* Configure one bit for preemption priority */
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);     
  NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel2_IRQn;     
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;          
  NVIC_Init(&NVIC_InitStructure);
}

(3)中断处理程序

我们在stm32f10x_it.c中编写我们的中断处理程序:

1
2
3
4
5
6
7
8
void DMA1_Channel2_IRQHandler(void)
{
    if(DMA_GetITStatus(DMA1_IT_TC2))
    {
        LED1(ON);
        DMA_ClearITPendingBit(DMA1_IT_GL2); //清除全部中断标志
    }
}

我们也可以这样写中断处理程序:

1
2
3
4
5
6
7
8
void DMA1_Channel2_IRQHandler(void)
{
    if(DMA_GetFlagStatus(DMA1_FLAG_TC2))
    {
        LED1(ON);
        DMA_ClearFLAG(DMA1_FLAG_TC2); //清除全部中断标志
    }
}

这两种写法都行,我们在库开发文档可以查看。都代表DMA的通道2传输完成中断。  

(4)主函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int main(void)
{
        /* USART1 config 115200 8-N-1 */
        USART3_Config();   
        USART3_DMA_Config();   
        LED_GPIO_Config(); 
        printf("\r\n usart3 DMA TX 测试 \r\n");  
        {
            uint16_t i;        
            /*填充将要发送的数据*/
            for(i=0;i
            {
                SendBuff[i]  = 'A';
            }
        }      
        /* USART1 向 DMA发出TX请求 */
        USART_DMACmd(USART3, USART_DMAReq_Tx, ENABLE);
        /* 此时CPU是空闲的,可以干其他的事情 */       
        //例如同时控制LED
        for(;;)
        {
        }
}

这样我们实验便可以看到,LED灯初始化是关闭的,当串口发送完40000字节的‘A’后,LED等亮。

(5)补充

原意是测试DMA发送完成中断指的是每次指定字节发送完成后便产生一个中断还是最终都传输完成触发一次中断,刚开始中断处理函数写的程序如下:

1
2
3
4
5
6
7
8
9
10
11
void DMA1_Channel2_IRQHandler(void)
{   
    uint16_t n = 0;
    if(DMA_GetFlagStatus(DMA1_FLAG_TC2))   
    {  
        n = ~n;
        if(n) LED1(ON);
        else  LED1(OFF);
        DMA_ClearFlag(DMA1_FLAG_TC2); //清除全部中断标志
    }
}

通过测试,我发现LED灯并没有像试想的那样每次发送完成后便触发一次中断,然后灯会间隔闪烁,而实际是第一次传输完成后灯点亮,之后就一直保持亮的状态。刚开始我还以为DMA只会触发第一次中断,后来仔细分析后才发现了问题。正确的代码应该如下。


void DMA1_Channel2_IRQHandler(void)
{     
    static uint16_t n = 0; 
        if(DMA_GetFlagStatus(DMA1_FLAG_TC2))      
       {    
            n = ~n;            if(n) LED1(ON);            else  LED1(OFF);
            DMA_ClearFlag(DMA1_FLAG_TC2); //清除全部中断标志        }
}


 

在这里n是一个局部变量,如果不定义成静态变量,每次出中断时后n所占的内存(栈)便会释放,这样再次进入后n还是会初始化为0.与我们要达到的效果不符。因此,在这里我们把它指定为静态变量,那么内存就不会释放,它会保持上一次的的值,修改之后达到了效果,每次传输完成3000个字节后灯的状态就会改变一次。  

在这里我们整理一下变量:

全局动态变量:作用范围为整个工程,不释放内存,会保持上一次的值。

全局静态变量:作用范围为当前文件,不释放内存,会保持上一次的值。

局部动态变量:作用范围为当前函数,每次函数执行结束释放内存,不会保持上一次的值。

局部静态变量:作用范围为当前函数,不释放内存,会保持上一次的值。


关键字:stm32  串口通信  DMA传输  中断 引用地址:(三)stm32之串口通信DMA传输完成中断

上一篇:(二)stm32之中断配置
下一篇:(四)单片机系统动态内存分配,任务调度思想

推荐阅读最新更新时间:2024-03-16 15:32

STM32 SBSFU的实现与应用
STM32安全固件更新 STM32安全固件更新离不开STM32安全启动。 参考上图23,理解STM32安全固件更新的流程。 下载固件头 验证固件头 下载(加密)固件 重启 检查/设置安全环境 检查是否需要固件更新 验证固件头 解密/验证固件并烧入固件 认证固件 执行新固件 从STM32 SBSFU的流程中可以看出,固件的完整性,以及固件header的完整性都很重要,都必须要进行检查。STM32 SBSFU中固件header中包含固件的哈希值或者认证码。为什么这里可以包括一个哈希值而不是一个签名值?因为固件header是被签名或者受ADSGCM认证码TAG保护的。 STM32 SBSFU典型的header
[单片机]
我的单片机方法论2之STM32单片机特别篇
我的单片机方法论之二 STM32单片机特别篇 Write by zzw YanJun.tech 由于这段时间确实挺忙,当然也由于自己的拖延症,我的这篇STM32单片机学习心得迟迟没有动笔。年初三下午,有点无聊开始写起吧由于一直没网,就没发。2016.12.30书,2017.1.7发 第一篇文章是一篇概论,写的比较概括笼统的。文章总结到学单片机的八重心法,六大秘籍。建议大家先看一下,详情见链接: http://blog.csdn.net/zzw5945/article/details/53868643 今天我们就STM32单片机几大秘籍说起,本文结构如下: 1、STM32单片机的简介(各种系列简介) 2、STM32单片机的几大秘
[单片机]
STC89C52系列单片机内部资源——串口通信
计算机通信是将计算机技术和通信技术的相结合,完成计算机与外部设备或计算机与计算机之间的信息交换 。可以分为两大类:并行通信与串行通信。 并行通信通常是将数据字节的各位用多条数据线同时进行传送 。 并行通信控制简单、传输速度快;由于传输线较多,长距离传送时成本高且接收方的各位同时接收存在困难。 串行通信是将数据字节分成一位一位的形式在一条传输线上逐个地传送。 串行通信的特点:传输线少,长距离传送时成本低,且可以利用电话网等现成的设备,但数据的传送控制比并行通信复杂。 串行通信的基本概念 一、异步通信与同步通信 1、异步通信 异步通信是指通信的发送与接收设备使用各自的时钟控制数据的发送和接收过程。为使双方的收发协调
[单片机]
STC89C52系列单片机内部资源——<font color='red'>串口通信</font>
基于STM32指纹识别模块(TFS-M64)的学习
之前的同学想用指纹模块做个东西,便让我帮他看看。收到之后了解了一下,最终是通过串口做通信的。这款产品是深圳十指科技的指纹模块,不过两年前就已经停产了。现在的新版的模块通信方式是完全一样的(串口通信),通信协议的变动也很少,不管是新模块还是老模块操作方式还是兼容的。 开始的时候用STM32的板子和它通信,忽略了响应时间的问题导致串口一直没有收到响应消息,后面找到这个原因之后便可完整的通过串口通信操作指纹模块了,为了方便后续使用封装了一份操作函数库: 头文件: #ifndef __TFS_M64_H #define __TFS_M64_H #include stm32f10x.h #define TRUE 1 #d
[单片机]
基于<font color='red'>STM32</font>指纹识别模块(TFS-M64)的学习
Windows98下硬件中断驱动程序的开发
摘 要: 介绍了Windows98的内核管理机制和应用程序权限级别,简述了在Windows98下进行虚拟驱动程序开发的几种工具和编程方法,并给出了借助VToolsD用C++语言编写的处理硬件中断的程序实例。 关键词: 虚拟设备驱动程序 VToolsD 中断服务例程 美国微软公司出品的Windows98以其友好的图形用户界面,在我国赢得了广泛的市场。在给广大办公环境工作人员带来方便的同时,也给不少工程技术人员带来了一些麻烦。一些原本在DOS下很容易编出的控制硬件的程序,现在在Windows98下就不那么容易实现了。作为一个完善的操作系统也必须能控制硬件,象DOS那样直接与硬件打交道是Windows98
[嵌入式]
这是一个单片机C51串口接收中断和发送例程
//这是一个单片机C51串口接收(中断)和发送例程,可以用来测试51单片机的中断接收 //和查询发送,另外我觉得发送没有必要用中断,因为程序的开销是一样的 #include reg51.h #include string.h #define INBUF_LEN 4 //数据长度 unsigned char inbuf1 ; unsigned char checksum,count3; bit read_flag=0; void init_serialcomm(void) { SCON = 0x50; //SCON: serail mode 1, 8-bit UART, enable ucvr
[单片机]
STM32-串口超时判断方式接收未知长度数据
usart.c串口中断处理函数: void USART1_IRQHandler(void) { u8 res; if(USART1- SR&(1 5))//接收到数据 { res=USART1- DR; if(USART1_Recv_LenCV_LEN) //还可以接收数据 { TIM3- CNT=0; //计数器清空 if(Rec_Over_Flag==0)TIM3_Set(1); //使能定时器4的中断 USART1_RX_BUF =res; //记录接收到的值
[单片机]
史上最全STM32调试步骤!
STM32调试步骤 调试前,首先跳线J9的2-3脚短接,跳线J4的右边两个脚短接,跳线J5短接,J8短接,J2的1-2短接,3-4短接。 将Jlink与学习板,USB延长线与学习板,串口延长线与学习板(或者USB转串口线与学习板)连接起来,这时LED灯LED5,LED6都应该点亮,如果不亮,说明板子有问题。 打开串口助手,按照如下设置: 1、从桌面打开J-Flash ARM V4.02如图所示。 也可以按照如下顺序打开J-Flash ARM 。 “开始à程序àSEGGERàJ-Link ARM V4.02àJ-Flash ARM”,如下图所示。 2、打开J-Flash ARM后,先进行芯片选项设置,打开Optionsà
[单片机]
史上最全<font color='red'>STM32</font>调试步骤!
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
  • 学习ARM开发(16)
    ARM有很多东西要学习,那么中断,就肯定是需要学习的东西。自从CPU引入中断以来,才真正地进入多任务系统工作,并且大大提高了工作效率。采 ...
  • 学习ARM开发(17)
    因为嵌入式系统里全部要使用中断的,那么我的S3C44B0怎么样中断流程呢?那我就需要了解整个流程了。要深入了解,最好的方法,就是去写程序 ...
  • 学习ARM开发(18)
    上一次已经了解ARM的中断处理过程,并且可以设置中断函数,那么它这样就可以工作了吗?答案是否定的。因为S3C44B0还有好几个寄存器是控制中 ...
  • 嵌入式系统调试仿真工具
    嵌入式硬件系统设计出来后就要进行调试,不管是硬件调试还是软件调试或者程序固化,都需要用到调试仿真工具。 随着处理器新品种、新 ...
  • 最近困扰在心中的一个小疑问终于解惑了~~
    最近在驱动方面一直在概念上不能很好的理解 有时候结合别人写的一点usb的例子能有点感觉,但是因为arm体系里面没有像单片机那样直接讲解引脚 ...
  • 学习ARM开发(1)
  • 学习ARM开发(2)
  • 学习ARM开发(4)
  • 学习ARM开发(6)
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved