DMA 配置步骤 接下来我们介绍下如何使用库函数对 DMA 进行配置。这个也是在编写程序中必须要了解的。具体步骤如下:(DMA 相关库函数在 stm32f10x_dma.c 和stm32f10x_dma.h 文件中) (1)使能 DMA 控制器(DMA1 或 DMA2)时钟 要使能 DMA 时钟,需通过AHB1ENR 寄存器来控制,使能 DMA时钟库函数为: - void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState
- NewState);
复制代码
例如使能 DMA1 时钟,函数如下:
- RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
复制代码
(2)初始化 DMA 通道,包括配置通道、外设和内存地址、传输数据量等要使用 DMA,必须对其相关参数进行设置,包括通道选择、外设和内存地址、 通道优先级、传输数据量的配置等。该部分设置通过 DMA 初始化函数 DMA_Init完成的: - void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx,DMA_InitTypeDef*
- DMA_InitStruct);
复制代码
函数中第一个参数是用来确定 DMA 通道,参数范围为: - DMA1_Channel_0~DMA1_Channel_7(DMA2 是DMA2_Channel_0-DMA2_Channel_5)
复制代码
第二个参数是一个结构体指针变量,结构体类型是 DMA_InitTypeDef,其内包含了 DMA 相关参数的设置。下面我们简单介绍下它的成员: - typedef struct
- {
- uint32_t DMA_PeripheralBaseAddr; // 外设地址
- uint32_t DMA_MemoryBaseAddr; // 存储器地址
- uint32_t DMA_DIR; // 传输方向
- uint32_t DMA_BufferSize; // 传输数目
- uint32_t DMA_PeripheralInc; // 外设地址增量模式
- uint32_t DMA_MemoryInc; // 存储器地址增量模式
- uint32_t DMA_PeripheralDataSize; // 外设数据宽度
- uint32_t DMA_MemoryDataSize; // 存储器数据宽度
- uint32_t DMA_Mode; // 模式选择
- uint32_t DMA_Priority; // 通道优先级
- uint32_t DMA_M2M; // 存储器到存储器模式
- } DMA_InitTypeDef;
复制代码
DMA_PeripheralBaseAddr:外设地址,通过 DMA_CPAR 寄存器设置,一般设置为外设的数据寄存器地址,比如要进行串口 DMA 传输,那么外设基地址为串口接受发送数据存储器 USART1->DR 的地址,表示方法为&USART1->DR。如果是存储器到存储器模式则设置为其中一个存储区地址。
DMA_Memory0BaseAddr:存储器地址,通过 DMA_CMAR 寄存器设置,一般设置为我们自定义存储区的首地址,即我们存放 DMA 传输数据的内存地址。比如我们定义一个 u32 类型数组,将数组首地址(直接使用数组名即可)赋值给DMA_Memory0BaseAddr,在DMA传输的时候就可以把数组内的数据发送或接收。
DMA_DIR:数据传输方向选择,可选择外设到存储器、存储器到外设以及存储器到存储器。通过设定 DMA_CCR 寄存器的 DIR[1:0]位的值决定。比如本章实验是从内存读取数据发送到串口,所以数据传输方向为存储器到外设,配置DMA_DIR_MemoryToPeripheral。
DMA_BufferSize:用来设置一次传输数据的大小,通过 DMA_CNDTR 寄存器设置。
DMA_PeripheralInc:用来设置外设地址是递增还是不变,通过 DMA_CCR寄存器的 PINC 位设置,如果设置为递增,那么下一次传输的时候地址加 1。通常外 设 只 有 一 个 数 据 寄 存 器 , 所 以 一 般 不 会 使 能 该 位 , 即 配 置 为DMA_PeripheralInc_Disable。 DMA_MemoryInc:用来设置内存地址是否递增,通过 DMA_CCR 寄存器的MINC位设置。我们自定义的存储区一般都是存放多个数据的,所以需要使能存储器地址自动递增功能,即配置为 DMA_MemoryInc_Enable。
DMA_PeripheralDataSize:外设数据宽度选择,可以为字节(8 位)、半字(16位)、字(32 位),通过 DMA_CCR 寄存器的 PSIZE[1:0]位设置。例如本实验数据是按照 8 位字节传输,所以配置为DMA_PeripheralDataSize_Byte。
DMA_MemoryDataSize:存储器数据宽度选择,可以为字节(8 位)、半字(16位)、字(32 位),通过 DMA_CCR 寄存器的 MSIZE[1:0]位设置。本章实验同样设置为 8 位字节传输,这个要和我们定义的数组对应,所以配置为DMA_MemoryDataSize_Byte。
DMA_Mode:DMA 传输模式选择, 可选择一次传输或者循环传输, 通过DMA_CCR寄存器的 CIRC 位来设定。比如我们要从内存 (存储器) 中传输 64 个字节到串口,如果设置为循环传输,那么它会在 64 个字节传输完成之后继续从内存的第一个地址传输,如此循环。这里我们设置为一次传输完成之后不循环。所以设置值为DMA_Mode_Normal。
DMA_Priority:用来设置 DMA 通道的优先级,有低,中,高,超高四种级别,可通过 DMA_CCR 寄存器的PL[1:0]位来设定。DMA 优先级只有在多个 DMA 通道同时使用时才有意义,本章实验我们只使用了一个 DMA 通道,所以可以任意设置DMA 优先级,这里我们就设置为中等优先级,配置参数为DMA_Priority_Medium。
DMA_Priority:用来设置 DMA 通道的优先级,有低,中,高,超高四种级别,可通过 DMA_CCR 寄存器的PL[1:0]位来设定。DMA 优先级只有在多个 DMA 通道同时使用时才有意义,本章实验我们只使用了一个 DMA 通道,所以可以任意设置DMA 优先级,这里我们就设置为中等优先级,配置参数为DMA_Priority_Medium。
DMA_M2M:用来设置存储器到存储器模式,使用存储器到存储器时用到,设定 DMA_CCR 的位 14 MEN2MEN 即可启动存储器到存储器模式。
了解结构体成员功能后,就可以进行配置,本实验配置代码如下: - DMA_InitTypeDef DMA_InitStructure;
- DMA_InitStructure.DMA_PeripheralBaseAddr = par;//DMA外设地址
- DMA_InitStructure.DMA_MemoryBaseAddr = mar;//DMA 存储器0地址
- DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;//存储器到外
- 设模式
- DMA_InitStructure.DMA_BufferSize = ndtr;//数据传输量
- DMA_InitStructure.DMA_PeripheralInc =
- DMA_PeripheralInc_Disable;//外设非增量模式
- DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//存储器
- 增量模式
- DMA_InitStructure.DMA_PeripheralDataSize =
- DMA_PeripheralDataSize_Byte;//外设数据长度:8 位
- DMA_InitStructure.DMA_MemoryDataSize =
- DMA_MemoryDataSize_Byte;//存储器数据长度:8 位
- DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;// 使用普通模式
- DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//中等优先
- 级
- DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA 通道 x 没有
复制代码
设置为内存到内存传输
- DMA_Init(DMAy_Channelx, &DMA_InitStructure);//初始化DMA
复制代码
(3)使能外设 DMA功能(DMA 请求映射图对应的外设)
配置好 DMA 后,我们就需要使能外设 DMA 功能,例如我们要使能串口的DMA发送功能,调用的库函数为: - USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //使能串口 1 的 DMA发送
复制代码
如果是要使能串口DMA接受, 那么第二个参数修改为USART_DMAReq_Rx即可。
如果是其他的外设需开启DMA功能, 只需要在对应的标准外设库函数中查找到对应的外设 DMA 使能函数。 (4)开启 DMA 的通道传输 初始化 DMA 后,要使用DMA还必须开启它,开启 DMA 通道传输的库函数为: - void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalStateNewState);
复制代码
第一个参数为外设所对应的 DMA 通道,例如本章使用的是 USART1_TXDMA请求,因此它对应的是 DMA1_Channel4,可通过前面DMA 请求映射图选择。第二个参数相信不说也知道,就是使能或失能。 本实验使能 DMA1_Channel4函数为: - DMA_Cmd(DMA1_Channel4,ENABLE);
复制代码
(5)查询 DMA 传输状态
通过以上 4 步设置,我们就可以启动一次 DMA 传输了。但是在 DMA 传输过程中,我们还需要查询 DMA传输通道的状态,使用的库函数是: - FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);
复制代码
例如我们要查询 DMA1通道4 传输是否完成,方法是: - DMA_GetFlagStatus(DMA1_FLAG_TC4);
复制代码
标准库中,还提供了获取当前剩余数据量大小的函数: - uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef*DMAy_Channelx);
复制代码
例如我们要获取 DMA1通道4 还有多少个数据没有传输,方法是: - DMA_GetCurrDataCounter(DMA1_Channel4);
复制代码
同样,标准库中还提供了设置传输数据量大小的函数: - void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx,uint16_t DataNumber);
复制代码
将以上几步全部配置好后,我们就可以使用 DMA 来传输对应外设的数据了。
硬件设计 本实验使用到硬件资源如下: (1)D1 和 D2 指示灯 (2)K_UP 按键 (3)串口 1 (4)DMA D1和 D2 指示灯、K_UP 按键、串口 1 电路在前面章节都介绍过,这里就不多说,至于 DMA 它属于 STM32F1 芯片内部的资源,只要通过软件配置好 DMA即可使用。D1指示灯用来提示系统运行状态,K_UP 按键用来控制 DMA发送,每按一次K_UP键,DMA 就将内存(自定义的一个数组)内数据发送 USART1,并通过串口 1将发送的内容打印出来,在 DMA 数据传输的过程中让 D2 指示灯不断闪烁,直到数据传输完成。D2 指示灯闪烁表示 CPU在执行其他的任务,说明 DMA 传输是不需要占用 CPU 的。 所要实现的功能是:通过 K_UP 按键控制 DMA 串口 1 数据的传送,在传送过程中让 D2 指示灯不断闪烁,直到数据传送完成。D1 指示灯闪烁提示系统正常运行。程序框架如下: (1)初始化 USART1_TX对应的 DMA 通道相关参数 (2)编写主函数 前面介绍 DMA 配置步骤时, 就已经讲解如何初始化 DMA。下面我们打开 “DMA实验” 工程, 在 APP 工程组中可以看到添加了dma.c文件(里面包含了 DMA 驱动程序),在 StdPeriph_Driver 工程组中添加了stm32f10x_dma.c 库文件。DMA 操作的库函数都放在 stm32f10x_dma.c 和stm32f10x_dma.h 文件中,所以使用到 DMA 就必须加入 stm32f10x_dma.c文件,同时还要包含对应的头文件路径。 这里我们分析几个重要函数,其他部分程序大家可以打开工程查看。
DMA 初始化函数 要使用 DMA,我们必须先对它进行配置。初始化代码如下: - /****************************************************************
- * 函 数 名 : DMAx_Init
- * 函数功能 : DMA 初始化函数
- * 输 入 :
- DMAy_Channelx:DMA 通 道 选 择 ,@ref DMA_channel
- DMA_Channel_0~DMA_Channel_7
- par:外设地址
- mar:存储器地址
- ndtr:数据传输量
- * 输 出 : 无
- *****************************************************************/
- void DMAx_Init(DMA_Channel_TypeDef* DMAy_Channelx,u32 par,u32
- mar,u16 ndtr)
- {
- DMA_InitTypeDef DMA_InitStructure;
- RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//DMA1时钟使能
- //DMA_DeInit(DMAy_Channelx);
- /* 配置 DMA */
- DMA_InitStructure.DMA_PeripheralBaseAddr = par;//DMA外设地址
- DMA_InitStructure.DMA_MemoryBaseAddr = mar;//DMA 存储器0地址
- DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;//存储器到外设模式
- DMA_InitStructure.DMA_BufferSize = ndtr;//数据传输量
- DMA_InitStructure.DMA_PeripheralInc =
- DMA_PeripheralInc_Disable;//外设非增量模式
- DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//存储器增量模式
- DMA_InitStructure.DMA_PeripheralDataSize =
- DMA_PeripheralDataSize_Byte;//外设数据长度:8 位
- DMA_InitStructure.DMA_MemoryDataSize =DMA_MemoryDataSize_Byte;//存储器数据长度:8 位
- DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;// 使用普通模式
- DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//中等优先级
- DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA 通道 x 没有设置为内存到内存传输
- DMA_Init(DMAy_Channelx, &DMA_InitStructure);//初始化DMA
- }
复制代码
在 DMAx_Init()函数中,首先使能 DMA1 时钟,然后初始化 DMA 各参数,即配置 DMA_InitStructure结构体,初始化 DMA 通道,这一过程在前面步骤介绍中已经提了。 通道的选择有函数参数DMAy_Channelx传递进来, 函数中还有另外3个形参,par 传递的是外设地址,mar传递的是存储器(内存)地址,ndtr传递的是DMA数据传输量。这样做的好处是方便大家修改。
开启 DMA 传输函数 配置好 DMA 后,我们需要开启它,代码如下: - /****************************************************************
- * 函 数 名 : DMAx_Enable
- * 函数功能 : 开启一次 DMA 传输
- * 输 入 : DMAy_Channelx:DMA 通道选择,@ref DMA_channel
- DMA_Channel_0~DMA_Channel_7
- ndtr:数据传输量
- * 输 出 : 无
- *****************************************************************/
- void DMAx_Enable(DMA_Channel_TypeDef *DMAy_Channelx,u16 ndtr)
- {
- DMA_Cmd(DMAy_Channelx, DISABLE); //关闭
- DMA 传输
- DMA_SetCurrDataCounter(DMAy_Channelx,ndtr); //数据传输量
- DMA_Cmd(DMAy_Channelx, ENABLE); //开启DMA传输
- }
复制代码
此函数功能很简单,首先失能 DMA 传输通道,然后设置传输的数据量大小,最后使能 DMA 传输通道。函数带有形参 DMAy_Channelx 和 ndtr,用于方便选择对应外设的通道和数据量。
主函数 编写好 DMA 的初始化和使能函数后, 接下来就可以编写主函数了, 代码如下: - /****************************************************************
- * 函 数 名 : main
- * 函数功能 : 主函数
- * 输 入 : 无
- * 输 出 : 无
- *****************************************************************/
- int main()
- {
- u8 i=0;
- u8 key;
- SysTick_Init(72);
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断优先级分组 分2 组
- LED_Init();
- USART1_Init(9600);
- KEY_Init();
- DMAx_Init(DMA1_Channel4,(u32)&USART1->DR,(u32)send_buf,send_buf_len);
- Send_Data(send_buf);
- while(1)
- {
- key=KEY_Scan(0);
- if(key==KEY_UP)
- {
- USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //使能串口 1的DMA 发送
- DMAx_Enable(DMA1_Channel4,send_buf_len); //开始一次DMA 传输!
- //等待 DMA传输完成,此时我们来做另外一些事
- //实际应用中,传输数据期间,可以执行另外的任务
- while(1)
- {
- if(DMA_GetFlagStatus(DMA1_FLAG_TC4)!=0)//判断通道 4传输完成
- {
- DMA_ClearFlag(DMA1_FLAG_TC4);
- break;
- }
- led2=!led2;
- delay_ms(300);
- }
- }
- i++;
- if(i%20==0)
- {
- led1=!led1;
- }
- delay_ms(10);
- }
- }
复制代码
主函数实现的功能很简单,首先调用之前编写好的硬件初始化函数,包括SysTick系统时钟, 中断分组, LED初始化等。然后调用我们前面编写的DMAx_Init函数,由于 USART1_TX 是在 DMA1 的通道 4 中,所以通道选择为 DMA1_Channel4。 外设地址传递的是&USART1->DR,因为 USART1 用来接收数据的寄存器是USART1->DR,加上一个&就转换成地址了。存储器地址为send_buf(数组名就是数组的地址) , 这个是我们自定义的一个u8类型数组, 数组大小为send_buf_len,这个也是我们定义的一个宏,值为 5000。然后调用Send_Data 函数,此函数功能是将数组 send_buf 内所有成员全部赋值为字符 5,代码如下: - #define send_buf_len 5000
- u8 send_buf[send_buf_len];
- void Send_Data(u8 *p)
- {
- u16 i;
- for(i=0;i<send_buf_len;i++)
- {
- *p='5';
- p++;
- }
- }
复制代码
最后进入 while 循环,调用KEY_Scan 函数,不断检测 K_UP按键是否按下,如果 K_UP 按键按下,启动一次 USART_TX 的 DMA 传输,在传输的过程中让 CPU控制 D2指示灯闪烁,直到 DMA 数据传输完成。D1 指示灯间隔200ms 闪烁,提示系统正常运行。 将工程程序编译后下载到开发板内,可以看到 D1 指示灯不断闪烁,表示程序正常运行。当 K_UP 按键按下,DMA 开始将内存数组内的数据传输到串口1 上,同时传输过程中,D2 指示灯闪烁,直到传输完成。如果想在串口调试助手上看到传输信息,可以打开“串口调试助手”,首先勾选下标号 1 DTR 框,然后再取消勾选。这是因为此串口助手启动时会把系统复位住,通过 DTR 状态切换下即可。然后设置好波特率等参数后,串口助手上即会收到串口发送过来的信息。(串口助手上先勾选下标号1 DTR 框,然后再取消勾选)如图所示:
|