wordpress点击分类目录空白,seo公司上海,wordpress如何更改上传文件大小,郑州便宜网站建设公司DMA——直接存储器访问
DMA#xff1a;Data Memory Access, 直接存储器访问。
DMA和我们之前学过的串口、GPIO都是类似的#xff0c;都是STM32中的一个外设。串口是用来发送通信数据的#xff0c;而DMA则是用来把数据从一个地方搬到另一个地方#xff0c;而且不占用CPU。…DMA——直接存储器访问
DMAData Memory Access, 直接存储器访问。
DMA和我们之前学过的串口、GPIO都是类似的都是STM32中的一个外设。串口是用来发送通信数据的而DMA则是用来把数据从一个地方搬到另一个地方而且不占用CPU。
举个例子
我们如果要把一串数据发送给串口 CPU先要把这一串数据先一个一个取回来暂存在CPU中的寄存器中然后再一个一个发送给串口。
这样就会导致CPU不能做其他事情CPU一直处于被占用的状态。
当DMA出现后CPU只需要给DMA发送一条命令如将数据发送给串口然后DMA就来完成这个上述需要CPU完成的工作了。这就节省了CPU的资源来完成其他操作。 上面我们解释完DMA是什么后我们接着来看DMA具体有几种方式。
1.P-M(外设到存储器)
后面的ADC数据采集使用的是这一类
2.M-P(存储器到外设)
之前的串口发送实验属于这一类
3.M-M(存储器到存储器)
存储器到存储器的实验一会儿在后面讲
我们先来看一下DMA的功能框图
DMA功能框图讲解 我们先把DMA的功能框图分为三大主要部分来讲解1.DMA请求。2.通道。3.仲裁器
DMA请求
通过上面的DMA框图我们可以看到DMA请求通常是由外设发起的例如串口、GPIO、ADC等等。
那么具体的外设是如何发送DMA请求的呢
这里就要先提一下通道这个概念不同的外设通过具体的通道向DMA控制器发送请求然后DMA控制器再根据通道的优先权来处理请求。
DMA有DMA1和DMA2两个控制器DMA1有7个通道DMA2有5个通道不同的DMA控制器的通道对应这不同的外设请求。 从上图可以看出芯片的参考手册已经帮我们把不同的外设分别对应的通道分配好了我们使用的时候只需要去查询这个表就行了。
仲裁器
虽然每个通道可以接收多个外设的请求但是同一时间只能接收一个不能接收多个。
当多个通道同时向DMA控制器发送DMA请求的时候谁可以先使用DMA控制器呢这时就需要仲裁器来判断通道的优先级了。
仲裁器管理DMA通道请求分为两个阶段。
第一阶段属于软件阶段可以在DMA_CCRx寄存器中设置有4个等级非常高、高、中和低四个优先级。见下图 第二阶段属于硬件阶段 如果两个或以上的DMA通道请求设置的优先级一样则他们优先级取决于通道编号编号越低优先权越高比如通道0高于通道1。
DMA数据配置
使用DMA最核心的就是配置要传输的数据包括数据从哪里来要到那里去传输数据的单位是什么要传多少数据是一次传输还是循环传输等等
从哪里来到那里去
我们知道DMA传输数据的方向有三个从外设到存储器从存储器到外设从存储器到存储器。 具体的传输方向DMA_CCR位4 DIR配置0表示从外设到存储器1表示从存储器到外设。 这里面涉及到的外设地址由DMA_CPAR配置存储器地址由DMA_CMAR配置。
我们在使用固件库编程的时候通常不会直接配置这三个寄存器都是通过初始化结构体来配置的这三个寄存器对应的结构体变量如下图 配置内容对应寄存器外设地址DMA_CPAR存储器地址DMA_CMAR传输方向DMA_CCR:DIR
外设到存储器
当我们使用从外设到存储器传输时以ADC采集为例。DMA外设寄存器的地址对应的就是ADC数据寄存器的地址 DMA存储器的地址就是我们自定义的变量用来接收存储AD采集的数据的地址。方向我们设置外设为源地址。
存储器到外设
当我们使用从存储器到外设传输时以串口向电脑端发送数据为例。DMA外设寄存器的地址对应的就是串口数据寄存器的地址 DMA存储器的地址就是我们自定义的变量相当于一个缓冲区用来存储通过串口发送到电脑的数据的地址。方向我们设置外设为目标地址。
存储器到存储器
当我们使用从存储器到存储器传输时以内部FLASH向内部SRAM复制数据为例。 DMA外设寄存器的地址对应的就是内部FLASH我们这里把内部FALSH当作一个外设来看的地址 DMA存储器的地址就是我们自定义的变量相当于一个缓冲区用来存储来自内部FLASH的数据的地址。 方向我们设置外设即内部FLASH为源地址。跟上面两个不一样的是这里需要把DMA_CCR位14MEM2MEM存储器到存储器模式配置为1启动M2M模式。
数据要传多少传的单位是什么 配置内容对应寄存器传输数目DMA_CNDTR外设地址是否递增DMA_CCRx:PINC存储器地址是否递增DMA_CCRx:MINC外设数据宽度DMA_CCRx:PSIZE存储器数据宽度DMA_CCRx:MSIZE
当我们配置好数据要从哪里来到哪里去之后我们还需要知道我们要传输的数据是多少数据的单位是什么。
以串口向电脑发送数据为例我们可以一次性给电脑发送很多数据具体多少由DMA_CNDTR配置 这是一个32位的寄存器一次最多只能传输65535个数据。
要想数据传输正确源和目标地址存储的数据宽度还必须一致
串口数据寄存器是8位的 所以我们定义的要发送的数据也必须是8位。
外设的数据宽度由**DMA_CCR的PSIZE[1:0]**配置 可以是8/16/32位
存储器的数据宽度由**DMA_CCR的MSIZE[1:0]**配置可以是8/16/32位。
那么当PSIZE和MSIZE不相同时也能传输但是可能传输结果与你的预期不同我们可以参照下表来查询 在DMA控制器的控制下数据要想有条不紊的从一个地方搬到另外一个地方还必须正确设置两边数据指针的增量模式。
外设的地址指针由DMA_CCRx的PINC配置存储器的地址指针由MINC配置。以串口向电脑发送数据为例要发送的数据很多 每发送完一个那么存储器的地址指针就应该加1而串口数据寄存器只有一个 那么外设的地址指针就固定不变。具体的数据指针的增量模式由实际情况决定。
什么时候传输完成 配置内容对应寄存器模式选择一次传输、循环传输DMA_CCRx:CIRC传输过半、传输完成、传输出错标志位DMA_ISR
数据什么时候传输完成我们可以通过查询标志位或者通过中断的方式来鉴别。
每个DMA通道在DMA传输过半、 传输完成和传输错误时都会有相应的标志位如果使能了该类型的中断后则会产生中断。
有关各个标志位的详细描述请参考DMA中断状态寄存器DMA_ISR的详细描述。
传输完成还分两种模式是一次传输还是循环传输
一次传输很好理解即是传输一次之后就停止
要想再传输的话 必须关断DMA使能后再重新配置后才能继续传输。
循环传输则是一次传输完成之后又恢复第一次传输时的配置循环传输 不断的重复。
具体的由DMA_CCR寄存器的CIRC 循环模式位控制。
以上就是所有关于DMA的理论部分下面我来进行程序编写。我先设计一个程序将存储器中的数据通过DMA的方式发送给串口。
我依然按照我上面讲DMA数据配置的顺序来写程序。
DMA存储器到外设实现代码
首先创建一个数组作为数据源一会儿发送给串口
//准备将存储器中的这个数组中的数据发送到串口
u32 SourceBuffer[500];然后开始配置DMA上面我们讲DMA数据配置的时候已经说过了我们配置DMA的时候是通过向DMA初始化结构体中填数据来配置的所以我们先定义一个DMA初始化结构体然后打开DMA外设的时钟信号。
//定义DMA初始化结构体
DMA_InitTypeDef DMA_initStruct;//首先查询DMA挂载在哪根总线上然后打开DMA时钟信号
RCC_AHBPeriphClockCmd(RCC_AHBENR_DMA1EN,ENABLE);
数据从哪里来到哪里去
配置外设基地址
存储器基地址
数据传输方向
//通过上一篇文章对串口(USART)介绍的文章可以找到串口数据发送的寄存器是放在USART_DR中的
DMA_initStruct.DMA_PeripheralBaseAddr USART1_BASE 0x04;//配置存储器的基地址也就是数组名
DMA_initStruct.DMA_MemoryBaseAddr (u32)SourceBuffer;//配置DMA数据传输方向从存储器到外设所以是外设Destination
DMA_initStruct.DMA_DIR DMA_DIR_PeripheralDST;数据要传多少传的单位是什么
我就不赘述了请看代码中的注释
//配置发送的数据大小为500因为数组大小为500
DMA_initStruct.DMA_BufferSize 500;//外设地址不用自增因为串口的发送寄存器只有一个字节这么大
DMA_initStruct.DMA_PeripheralInc DMA_PeripheralInc_Disable;//配置外设的数据宽度为字节
DMA_initStruct.DMA_PeripheralDataSize DMA_PeripheralDataSize_Byte;//由于数组是按顺序发送所以存储器的地址可以自增
DMA_initStruct.DMA_MemoryInc DMA_MemoryInc_Enable;//由于数组中的数据类型为u32所以存储器的数据宽度配置为字
DMA_initStruct.DMA_MemoryDataSize DMA_MemoryDataSize_Word;
什么时候传输完成
//发送模式配置为发送一次建议不要配置成循环发送因为我的查看串口发送信息的工具会卡死
DMA_initStruct.DMA_Mode DMA_Mode_Normal;//优先级随便配置没人跟你抢
DMA_initStruct.DMA_Priority DMA_Priority_High;//存储器到存储器的单独配置位这里我们是存储器到外设所以这个位为disable
DMA_initStruct.DMA_M2M DMA_M2M_Disable;上面配置好后才算完成DMA初始化结构体的配置接下来调用初始化函数。并给通道使能信号
//调用初始化函数
DMA_Init(DMA1_Channel4,DMA_initStruct);//DMA通道使能信号
DMA_Cmd(DMA1_Channel4,ENABLE);DMA配置好了下面来写main函数注意串口也是需要配置的。但是由于我在上一篇文章里已经详细讲述过串口的配置方法在这里我就直接调用串口的配置函数了。
int main(){u16 i;//先用一个for循环向数组中填数据一会儿这个数据会通过串口发送出去for(i0; i500; i){SourceBuffer[i]14;}//调用串口配置函数USART_GPIO_Config();USART_Config();//调用DMA配置函数DMA_Config();//串口1向DMA发出TX请求USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);}