赞
踩
DMA 控制器独立于内核,属于一个单独的外设,结构比较简单,从编程的角度来看,我们只需掌握结构框图中的三部分内容即可。
如图:(大家也可以查看《STM32F10x中文参考手册》-10 DMA控制器(DMA)章节
内容)
如果外设要想通过 DMA 来传输数据,必须先给 DMA 控制器发送 DMA
请求, DMA收到请求信号之后,控制器会给外设一个应答信号,当外设
应答后且 DMA 控制器收到应答信号之后,就会启动 DMA 的传输,直到
传输完毕。
根据前面介绍我们知道,DMA含有DMA1和DMA2两个控制器,其中DMA1
含有7个通道,DMA2含有5个通道,不同的 DMA 控制器的通道对应着不同
的外设请求,如图
DMA 具有 12 个独立可编程的通道,其中 DMA1 有 7 个通道, DMA2
有 5 个通道,每个通道对应不同的外设的 DMA 请求。虽然每个通道可
以接收多个外设的请求,但是同一时间只能接收一个,不能同时接收多
个。
当发生多个 DMA 通道请求时,就意味着有先后响应处理的顺序问题
,这个就由仲裁器也管理。仲裁器管理 DMA 通道请求分为两个阶段。第
一阶段属于软件阶段,可以在DMA_CCRx 寄存器中设置,有 4 个等级:
非常高、高、中和低四个优先级。第二阶段属于硬件阶段,如果两个或
以上的 DMA 通道请求设置的优先级一样,则他们优先级取决于通道编号
,编号越低优先权越高,比如通道 0 高于通道 1。在大容量产品和互联
型产品中,DMA1 控制器拥有高于 DMA2 控制器的优先级。
当我们使用从外设到存储器传输时,以 ADC 采集为例。 DMA 外设寄
存器的地址对应的就是 ADC 数据寄存器的地址, DMA 存储器的地址就
是我们自定义的变量(用来接收存储 AD 采集的数据)的地址。方向我
们设置外设为源地址。
当我们使用从存储器到外设传输时,以串口向电脑端发送数据为例。
DMA 外设寄存器的地址对应的就是串口数据寄存器的地址, DMA 存储器
的地址就是我们自定义的变量(相当于一个缓冲区,用来存储通过串口
发送到电脑的数据)的地址。方向我们设置外设为目标地址。
接下来我们介绍下如何使用库函数对DMA进行配置。这个也是在编写
程序中必须要了解的。具体步骤如下:(DMA相关库函数在
stm32f10x_dma.c和stm32f10x_dma.h文件中)
1)使能DMA控制器(DMA1或DMA2)时钟
void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph,
FunctionalState NewState);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
(2)初始化DMA通道,包括配置通道、外设和内存地址、传输数据量等
void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx,DMA_InitTypeDef* DMA_InitStruct);
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_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请求映射图对应的外设)
USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //使能串口1的DMA
发送
(4)开启DMA的通道传输
void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState);
DMA_Cmd(DMA1_Channel4,ENABLE);
(5)查询DMA传输状态
FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);
例如我们要查询DMA1通道4传输是否完成,方法是:
DMA_GetFlagStatus(DMA1_FLAG_TC4);
uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);
void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber);
本实验使用到硬件资源如下:
(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.h
#ifndef _dma_H
#define _dma_H
#include "system.h"
void DMAx_Init(DMA_Channel_TypeDef* DMAy_Channelx,u32 par,u32 mar,u16 ndtr);//配置DMAx_CHx
void DMAx_Enable(DMA_Channel_TypeDef *DMAy_Channelx,u16 ndtr); //使能一次DMA传输
#endif
dma.c
#include "dma.h" /******************************************************************************* * 函 数 名 : 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_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传输 }
main.c
#include "system.h" #include "SysTick.h" #include "led.h" #include "usart.h" #include "key.h" #include "dma.h" #define send_buf_len 5000 u8 send_buf[send_buf_len]; /******************************************************************************* * 函 数 名 : Send_Data * 函数功能 : 要发送的数据 * 输 入 : p:指针变量 * 输 出 : 无 *******************************************************************************/ void Send_Data(u8 *p) { u16 i; for(i=0;i<send_buf_len;i++) { *p='5'; p++; } } /******************************************************************************* * 函 数 名 : 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); } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。