-
2013-07-18 17:13:37
无论是外设与MEM的哪种组合,DMA的操作本质是地址到地址的操作。
STM的RM中没有具体说明外设到外设的SETTING,通过猜想与验证,得到以下结论。
DMA传输方式的外设到外设,只需将其中一个外设的寄存器地址当成MEM的地址即可,其他设置与外设到MEM一致即可实现外设之间的DMA传输。
注:特殊的在于DMA的MEM2MEM需要使能CCR中MEM2MEM,作为软件触发源,但此时不能与circle mode共用,意味着只能传输一次,若需要重复传输,只能Disable Channel
,然后重新设定DMA_CNDTR,再放好数据后,使能channel,触发单次传输。
DMA_CNDTR:If this register is zero, no transaction can be served whether the channel is enabled or not.
更多相关内容 -
DMA外设详解
2022-04-29 11:52:33存储器到存储器,存储器到外设,外设到存储器以及串口空闲中断深入理解目录
一.DMA简介
直接存储器存取(DMA)(Direct Memory Access)也是一个挂载在AHB总线上的外设,用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。
无须CPU干预,数据可以通过DMA快速地移动,这就节省了CPU的资源来做其他操作
。两个DMA控制器有12个通道(DMA1有7个通道,DMA2(只存在于大容量和互联网产品中)有5个通道),每个通道专门用来管理来自于一个或多个外设对存储器访问的请求
。还有一个仲裁器来协调各个DMA请求的优先权注:
大容量产品:是指闪存存储器容量在256K至512K字节之间的STM32F101xx和STM32F103xx微控制器。
互联型产品:是指STM32F105xx和STM32F107xx微控制器。二.DMA 功能框图(重点)
- DMA 请求
每个通道都直接连接专用的硬件DMA请求,每个通道都同样支持软件触发。这些功能通过软件来配置
DMA 具有 12 个独立可编程的通道,其中 DMA1 有 7 个通道,DMA2 有 5 个通道,每个通道对应不同的外设的 DMA 请求。虽然每个通道可以接收多个外设的请求,但是同一时间只能有一个有效
,不能同时接收多个。如果外设要想通过 DMA 来传输数据,
必须先给 DMA 控制器发送 DMA 请求
,DMA收到请求信号之后,控制器会给外设一个应答信号,当外设应答后且 DMA 控制器收到应答信号之后,就会启动 DMA 的传输,直到传输完毕。不同的DMA控制器通道对应这不同外设的DMA请求:
我这里找了部分外设发送DMA请求的函数,想开启哪个外设的DMA请求直接去相应的外设头文件里面找就行了
- 仲裁器
仲裁器根据通道请求的优先级来启动外设/存储器的访问。
优先权管理分2个阶段:
● 软件:每个通道的优先权可以在DMA_CCRx寄存器中设置,有4个等级:
─ 最高优先级
─ 高优先级
─ 中等优先级
─ 低优先级
● 硬件:如果2个请求有相同的软件优先级,则较低编号的通道
比较高编号的通道有较高的优先权。举个例子,通道2优先于通道4。
在大容量产品和互联产品中,DMA1控制器拥有高于DMA2控制器的优先级
先比较软件优先级,再比较通道编号DMA控制器和Cortex™-M3核心共享系统数据总线,执行直接存储器数据传输。
当CPU和DMA同时访问相同的目标(RAM或外设)时
,DMA请求会暂停CPU访问系统总线达若干个周期,总线仲裁器执行循环调度,以保证CPU至少可以得到一半的系统总线(存储器或外设)带宽。根据上面的描述我们可以得出一个结论,DMA可以独立CPU之外进行数据的传输 (DMA可以通过数据总线访问存储器和外设的数据寄存器)
次过程不需要CPU的参与,CPU可以转而做其他事情,说白了DAM就是帮CPU打工的.
DMA 传输数据的方向有三个:从外设到存储器,从存储器到外设,从存储器到存储器。具体的方向 DMA_CCR 位 4 DIR 配置:0 表示从外设到存储器,1 表示从存储器到外设。这里面涉及到的外设地址由 DMA_CPAR 配置,存储器地址由 DMA_CMAR配置。
有人就会疑惑了,存储器有哪些呢,什么数据存放在存储器上,闪存FLASH、SRAM、就是所谓的存储器。
内部FLASH
简单介绍在flash存储内容:我们写好的程序编译之后都是一条条指令(二进制代码),存放在 FLASH 中,我们常量或常变量C 语言中的 const 关键字修饰也存放在FLASH.
内部SRAM
就是我们常说的电脑内存条,程序函数内部的局部变量和全局变量,堆(malloc分配)栈(局部变量)等的开销都是基于内部的SRAM
。内核通过 DCode 总线来访问它当然一个数组也是一个变量,当然是存放在SRAM这个存储器上咯,也就是说DMA访问这个数组也就是访问存储池器。
外设的话,一般外设都有一个数据寄存器,来暂存数据。
- 外设到存储器
当我们使用从外设到存储器传输时,以 ADC 采集为例。DMA 外设寄存器的地址对应的就是
ADC 数据寄存器的地址
,DMA 存储器的地址就是我们自定义的变量(用来接收存储 AD 采集的数据)的地址
。方向我们设置外设为源地址。- 存储器到外设
当我们使用从存储器到外设传输时,以串口向电脑端发送数据为例。DMA 外设寄存器的地址对应的就是
串口数据寄存器的地址
,DMA 存储器的地址就是我们自定义的变量(一般是一个数组,相当于一个缓冲区,用来存储通过串口发送到电脑的数据)的地址
。方向我们设置外设为目标地址。- 存储器到存储器
当我们使用从存储器到存储器传输时,以内部 FLASH 向内部 SRAM 复制数据为例。DMA 外设寄存器的地址对应的就是
内部 FLASH(我们这里把内部 FALSH 当作一个外设来看)的地址
,DMA 存储器的地址就是我们自定义的变量
(相当于一个缓冲区,用来存储来自内部 FLASH 的数据)的地址。方向我们设置外设(即内部 FLASH)为源地址。跟上面两个不一样的是,这里需要把 DMA_CCR 位 14:MEM2MEM:存储器到存储器模式配置为 1,启动 M2M 模式。其实上面是我复制粘贴的,其实光看文字肯定理解不够深刻,上面提到的我后面有对应的三个实验,如果想深入理解一定要看到最后
通道配置过程
下面是配置DMA通道x的过程(x代表通道号):-
在DMA_CPARx寄存器中设置
外设寄存器的地址
。发生外设数据传输请求时,这个地址将是数据传输的源或目标。 -
在DMA_CMARx寄存器中设置
数据存储器的地址
。发生外设数据传输请求时,传输的数据将从这个地址读出或写入这个地址。 -
在DMA_CNDTRx寄存器中设置要传输的数据量。在每个数据传输后,这个数值递减。
-
在DMA_CCRx寄存器的PL[1:0]位中设置通道的优先级。
-
在DMA_CCRx寄存器中设置数据传输的方向、循环模式、外设和存储器的增量模式、外设和存储器的数据宽度、传输一半产生中断或传输完成产生中断。
传输方向:
循环模式:
外设和存储器的增量模式:
如果还不理解的话得先补习一下指针的知识—>《指针从入门到熟练掌握》
外设和存储器的数据宽度:
要想数据传输正确,源和目标地址存储的数据宽度还必须一致,串口数据寄存器是 8位的,所以我们定义的要发送的数据也必须是 8 位
如果两边的设置的数据宽度不一样会怎么样呢。
-
设置DMA_CCRx寄存器的ENABLE位,启动该通道一旦启动了DMA通道,它即可响应连到该通道上的外设的DMA请求。
当传输一半的数据后,半传输标志(HTIF)被置1,当设置了允许半传输中断位(HTIE)时,将产生一个中断请求。在数据传输结束后,传输完成标志(TCIF)被置1,当设置了允许传输完成中断位(TCIE)时,将产生一个中断请求
当通道配置为非循环模式时,传输结束后(即传输计数变为0)将不再产生DMA操作。要开始新的DMA传输,需要在关闭DMA通道的情况下,在DMA_CNDTRx寄存器中重新写入传输数目。
要开始新的DMA传输必须先关闭DMA通道,在DMA_CNDTRx寄存器中重新写入传输数目,再打开DAM通道,这点非常重要。
中断
三.DMA初始化结构体
上面每个结构体成员变量要配置哪个寄存器,前面已经详述。-
DMA_PeripheralBaseAddr:外设地址,一般设置为
外设的数据寄存器地址
,如果是存储器到存储器模式则设置为其中一个存储器地址。 -
DMA_Memory0BaseAddr:存储器(FLASH、SRAM)地址,一般设置为我们自定义存储区(一般为一个数组)的首地址(数组名)。
-
DMA_DIR:传输方向选择,可选外设到存储器、存储器到外设。这里并没有存储器到存储器的方向选择,当使用存储器到存储器时,只需要把其中一个存储器当作外设使用即可。
-
DMA_BufferSize:设定待传输数据数量,数量范围为0~65535
-
DMA_PeripheralInc:如果配置为 DMA_PeripheralInc_Enable,使能外设地址自动递增功能,一般外设都是只有一个数据寄存器,所以一般不会使能该位。
-
DMA_MemoryInc:如果配置为 DMA_MemoryInc_Enable,使能存储器地址自动递增功能,我们自定义的存储区一般都是存放多个数据的,所以要使能存储器地址自动递增功能。
-
DMA_PeripheralDataSize:外设数据宽度,可选字节(8 位)、半字(16 位)和字(32位),
-
DMA_MemoryDataSize:存储器数据宽度,可选字节(8 位)、半字(16 位)和字(32位),当外设和存储器之间传数据时,两边的数据宽度应该设置为一致大小。
-
DMA_Mode:DMA 传输模式选择,可选一次传输或者循环传输,我们的 ADC 采集是持续循环进行的,所以使用循环传输模式。
-
DMA_Priority:软件设置道的优先级,有 4 个可选优先级分别为非常高、高、中和低,DMA 通道优先级只有在
多个 DMA 通道
同时使用时才有意义,如果是单个通道,优先级可以随便设置。 -
DMA_M2M :存储器到存储器模式 .
接下来就进入实战重点,深入理解原理,一共对应三个实验,存储器到外设、外设到存储器、存储器到存储器。
四.存储器到存储器
实验目的
我们先定义(const)一个静态的源数据,存放在内部 FLASH,然后使用 DMA 传输把源数据拷贝到目标地址上(内部 SRAM),最后对比源数据和目标地址的数据,看看是否传输准确,程序开始先亮一会红灯,等待数据传输完成然后对比数据,若数据传输正确,则亮绿灯,否则又亮红灯。
内部FLASH
简单介绍在flash存储内容:我们写好的程序编译之后都是一条条指令(二进制代码),存放在 FLASH 中,我们常量或常变量C 语言中的 const 关键字修饰也存放在FLASH.
内部SRAM
就是我们常说的电脑内存条,程序函数内部的局部变量和全局变量,堆(malloc分配)栈(局部变量)等的开销都是基于内部的SRAM
。内核通过 DCode 总线来访问它实验原理
上代码:
dma_mtm.h
#ifndef DMA_MTM_H #define DMA_MTM_H #include "stm32f10x.h" // 当使用存储器到存储器模式时候,通道可以随便选,没有硬性的规定 #define DMA_CHANNEL DMA1_Channel6 #define DMA_CLOCK RCC_AHBPeriph_DMA1 // 传输完成标志 #define DMA_FLAG_TC DMA1_FLAG_TC6 // 要发送的数据大小 #define BUFFER_SIZE 32 extern const uint32_t aSRC_Const_Buffer[BUFFER_SIZE]; extern uint32_t aDST_Buffer[BUFFER_SIZE]; void MtM_DMA_Config(void); uint8_t Buffer_cmp(const uint32_t * pBuffer,const uint32_t *pBuffer1,uint32_t Bufferlength); #endif /* DMA_MTM_H */
dma_mtm.c
#include "dma_mtm.h" /* 定义aSRC_Const_Buffer数组作为DMA传输数据源 * const关键字将aSRC_Const_Buffer数组变量定义为常量类型 * 表示数据存储在内部的FLASH中 */ const uint32_t aSRC_Const_Buffer[BUFFER_SIZE]= { 0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10, 0x11121314,0x15161718,0x191A1B1C,0x1D1E1F20, 0x21222324,0x25262728,0x292A2B2C,0x2D2E2F30, 0x31323334,0x35363738,0x393A3B3C,0x3D3E3F40, 0x41424344,0x45464748,0x494A4B4C,0x4D4E4F50, 0x51525354,0x55565758,0x595A5B5C,0x5D5E5F60, 0x61626364,0x65666768,0x696A6B6C,0x6D6E6F70, 0x71727374,0x75767778,0x797A7B7C,0x7D7E7F80}; /* 定义DMA传输目标存储器 * 存储在内部的SRAM中 */ uint32_t aDST_Buffer[BUFFER_SIZE]; void MtM_DMA_Config(void) { DMA_InitTypeDef DMA_InitStructure; //开启DMA时钟(注意:DMA挂载在AHB总线上) RCC_AHBPeriphClockCmd(DMA_CLOCK, ENABLE); // 源数据地址 DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)aSRC_Const_Buffer; // 目标地址 DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)aDST_Buffer; // 方向:外设到存储器(这里的外设是内部的FLASH) DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; // 传输大小 DMA_InitStructure.DMA_BufferSize = BUFFER_SIZE; // 外设(内部的FLASH)地址递增 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable; // 内存地址递增 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 外设数据单位 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; // 内存数据单位 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word; // DMA模式,一次或者循环模式 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ; //DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // 优先级:高 DMA_InitStructure.DMA_Priority = DMA_Priority_High; // 使能内存到内存的传输 DMA_InitStructure.DMA_M2M = DMA_M2M_Enable; // 配置DMA通道 DMA_Init(DMA_CHANNEL, &DMA_InitStructure); //清除DMA数据流传输完成标志位,为了后面检测数据是否完成 DMA_ClearFlag(DMA_FLAG_TC); // 使能DMA DMA_Cmd(DMA_CHANNEL,ENABLE); } //数组比较函数 uint8_t Buffer_cmp(const uint32_t * pBuffer,const uint32_t *pBuffer1,uint32_t Bufferlength) { while(Bufferlength--) { if(*pBuffer !=*pBuffer1) return 0; else pBuffer++; pBuffer1++; } return 1; }
main.c
#include "stm32f10x.h" #include "led.h" #include "dma_mtm.h" #define SOFT_DELAY Delay(0x0FFFFF); void Delay(__IO u32 nCount); int main(void) { /* LED 端口初始化 */ LED_GPIO_Config(); //先亮一会红灯 LED_G(OFF); LED_R(NO); SOFT_DELAY; //DAM初始化 MtM_DMA_Config(); //等待DAM数据全部传输完成 while ( DMA_GetFlagStatus( DMA_FLAG_TC)==RESET); /* 比较源数据与传输后数据 */ if(Buffer_cmp( aSRC_Const_Buffer,aDST_Buffer,BUFFER_SIZE)) { LED_G(NO); LED_R(OFF); } else { LED_G(OFF); LED_R(NO); } } void Delay(__IO uint32_t nCount) //简单的延时函数 { for(; nCount != 0; nCount--); }
这里要注意的一个点DMA是挂载在AHB总线,开启DAM时钟。
RCC_AHBPeriphClockCmd(DMA_CLOCK, ENABLE);
实验效果
五.存储器到外设
在看该实验时若串口还不是很熟悉,请看STM32串口通信详解
实验目的
我们先定义一个数组变量,存于 SRAM 中,然后通过 DMA 的方式传输到串口的数据寄存器,然后通过串口把这些数据发送到电脑的上位机显示出来,传输的同时CPU表示很闲所以边传输的时候边让CPU点灯。
实验原理
利用DMA发送
使用DMA进行发送,可以通过设置USART_CR3寄存器上的DMAT位激活。当TXE位被置为’1’时,DMA就从指定的SRAM区传送数据到USART_DR寄存器
- 在DMA控制寄存器上将USART_DR寄存器地址配置成DMA传输的目的地址。在每个TXE事件后,数据将被传送到这个地址。
- 在DMA控制寄存器上将存储器地址配置成DMA传输的源地址。在每个TXE事件后,将从此存储器区读出数据并传送到USART_DR寄存器。
总结:DAM传输数据时到USATR时可以实现连续通信,原本如果我们要实现连续通信,传输一个一帧数据时我们需要等待TXE位置1才能,写入下一个数据,不然数据寄存器的数据会被覆盖,但DMA传输数据到串口数据寄存器时每传输一帧数据,DMA会自己等待TXE置1(数据寄存器为空,数据已经被转移到数据移位寄存器中),再传输下一帧数据,DMA与串口配合的天衣无缝,实现连续通信。
要想理解原理,下面两张图一定一定要看懂
这里解释一下,既然是存储器到外设,为啥串口发送的是串口发送(TX)的请求呢,DMA只是一个帮忙传输数据到串口与CPU作用一样,其实我们真正的目的是利用串口向另外一个设备(电脑)发送数据,所以串口向DMA发送串口发送请求。
上代码:
dma_mtp.h#ifndef __DMA_MTP_H #define __DMA_MTP_H #include "stm32f10x.h" // 要发送的数据大小 #define SENDBUFFER_SIZE 32 extern uint8_t aSRC_Buffer[SENDBUFFER_SIZE]; #define DEBUG_USARTx USART1 #define DEBUG_USART_CLK RCC_APB2Periph_USART1 #define DEBUG_USART_APBxClkCmd RCC_APB2PeriphClockCmd #define DEBUG_USART_BAUDRATE 115200 // USART GPIO 引脚宏定义 #define DEBUG_USART_GPIO_CLK (RCC_APB2Periph_GPIOA) #define DEBUG_USART_GPIO_APBxClkCmd RCC_APB2PeriphClockCmd #define DEBUG_USART_TX_GPIO_PORT GPIOA #define DEBUG_USART_TX_GPIO_PIN GPIO_Pin_9 #define DEBUG_USART_RX_GPIO_PORT GPIOA #define DEBUG_USART_RX_GPIO_PIN GPIO_Pin_10 #define DMA1_MtP_FLAG_TC DMA1_FLAG_TC4 #define DMA1_MtP_CLK RCC_AHBPeriph_DMA1 #define DMA1_MtP_Channel DMA1_Channel4 #define USART_DR_BASE (USART1_BASE+0X04) void DMA_MtoP_Config(void); uint16_t Buffer_cmp(const uint32_t * aSRC_Buffer,uint32_t * aDST_Buffer,uint16_t length); void Usart_GPIO_Config(void); #endif /*__DMA_MTP_H */
dma_mtp.c
#include "dma_Mtp.h" uint8_t aSRC_Buffer[SENDBUFFER_SIZE]; void Usart_GPIO_Config(void) { GPIO_InitTypeDef GPIO_InitStuctrue; USART_InitTypeDef USART_InitStuctrue; //开启GPIO外设时钟 DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK,ENABLE); //开启USART外设时钟 DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK,ENABLE); GPIO_InitStuctrue.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN; GPIO_InitStuctrue.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStuctrue.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(DEBUG_USART_TX_GPIO_PORT,&GPIO_InitStuctrue); GPIO_InitStuctrue.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN; GPIO_InitStuctrue.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(DEBUG_USART_RX_GPIO_PORT,&GPIO_InitStuctrue); USART_InitStuctrue.USART_BaudRate = DEBUG_USART_BAUDRATE; USART_InitStuctrue.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_InitStuctrue.USART_Parity = USART_Parity_No; USART_InitStuctrue.USART_StopBits = USART_StopBits_1; USART_InitStuctrue.USART_WordLength = USART_WordLength_8b; USART_InitStuctrue.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_Init(DEBUG_USARTx,&USART_InitStuctrue); USART_Cmd(DEBUG_USARTx,ENABLE); } void DMA_MtoP_Config(void) { DMA_InitTypeDef DMA_InitStructure; RCC_AHBPeriphClockCmd(DMA1_MtP_CLK, ENABLE); DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)USART_DR_BASE; DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)aSRC_Buffer; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; DMA_InitStructure.DMA_BufferSize = SENDBUFFER_SIZE; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //DMA的循环传输 DMA_InitStructure.DMA_Mode =DMA_Mode_Circular; DMA_InitStructure.DMA_Priority =DMA_Priority_High; DMA_InitStructure.DMA_M2M =DMA_M2M_Disable; DMA_Init(DMA1_MtP_Channel,&DMA_InitStructure); DMA_ClearFlag(DMA1_MtP_FLAG_TC); DMA_Cmd(DMA1_MtP_Channel,ENABLE); }
main.c
#include "stm32f10x.h" #include "led.h" #include "delay.h" #include "dma_Mtp.h" #define SOFT_DELAY Delay(0x0FFFFF); void Delay(__IO u32 nCount); int main(void) { uint32_t i =0; delay_init(); /* LED 端口初始化 */ LED_GPIO_Config(); //串口初始化 Usart_GPIO_Config(); //初始化数组 for(i=0;i<SENDBUFFER_SIZE;i++) { aSRC_Buffer[i]='k'; } //DMA配置 DMA_MtoP_Config(); //向DMA发送串口TX请求 USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE); //CPU同时点灯 while(1) { LED_G(NO); LED_R(OFF); delay_ms(300); LED_R(NO); LED_G(OFF); delay_ms(300); } } void Delay(__IO uint32_t nCount) //简单的延时函数 { for(; nCount != 0; nCount--); }
原谅我不想写注释,不过这里跟上面存储器到存储器差不多,不写注释也看的懂的。
注意:这里串口DMA发送请求一定要是DAM1通道4
实验效果
六.外设到存储器(重点理解)
实验目的
我们先定义一个数组变量,存于 SRAM 中,用来存放串口接收的数据,实验流程就是,电脑向串口接收数据寄存器发送数据(多帧),然后通过DMA从串口接收数据寄存器读取数据传输到存储在SRAM的数组中,然后通过串口又将数组接收到的数据发送回电脑端。
实验原理
利用DMA接收
可以通过设置USART_CR3寄存器的DMAR位激活使用DMA进行接收,每次接收到一个字节,DMA控制器就就把数据从USART_DR寄存器传送到指定的SRAM区(参考DMA相关说明)。- 通过DMA控制寄存器把USART_DR寄存器地址配置成传输的源地址。在每个RXNE事件后,将从此地址读出数据并传输到存储器。
- 通过DMA控制寄存器把存储器地址配置成传输的目的地址。在每个RXNE事件后,数据将从USART_DR传输到此存储器区
总结:DAM接收数据时RXNE位置1(接收数据寄存器不为空)时,来向读取接收数据寄存器读取数据传输到存储器SRAM(数组中)。
我们还有一个很大的问题没有解决,我们肯定要实现这样一个效果,电脑端发送几个数据帧(8位),串口就返回电脑端几个数据帧(8位),但有一个问题,DMA要配置数据帧传输数量,如果我们采用DMA的传输完成的中断,则我们电脑端发送的数据一定得等于DMA配置的数据帧传输数量,这样一来就达不到我们发送几个数据帧就返回几个数据帧的目的了,因为必须接收到DMA配置数据帧传输数量数据帧,才会触发中断。
所以我们换一个思路:
利用串口的空闲中断总结:
清除串口空闲中断的方法为:
USART1->DR USART1->SR
分别读两个寄存器
触发串口空闲中断的方式:
在接收数据之后(RXNE位置1)
,若出现一个字节的高电平(空闲)状态,在串口不发送也不接受的情况下,就是处于空闲状态,但是要注意的是如果你发送了数据然后不发送了,然后就会触发空闲中断,但是记住后面如果你没有发数据了不会再触发空闲中断了,只有接收到下一组数据之后,才会检测空闲中断,若此时总线空闲则触发下一次空闲中断。注意:
其实发送的两个数据帧(一个字节)之间间隔非常短,所以在两个数据帧之间不叫空闲。空闲中断是检测到有数据被接收后
,总线上在一个字节的时间内没有再接收到数据的时候发生的,也就是数据(多个数据)全部发送完毕,才会检测到空闲中断。直接上代码讲解:
dma_usart_rx.h
#ifndef __DMA_USART_RX_H #define __DMA_USART_RX_H #include "stm32f10x.h" #include <stdio.h> // 串口工作参数宏定义 #define DEBUG_USARTx USART1 #define DEBUG_USART_CLK RCC_APB2Periph_USART1 #define DEBUG_USART_APBxClkCmd RCC_APB2PeriphClockCmd #define DEBUG_USART_BAUDRATE 115200 // USART GPIO 引脚宏定义 #define DEBUG_USART_GPIO_CLK (RCC_APB2Periph_GPIOA) #define DEBUG_USART_GPIO_APBxClkCmd RCC_APB2PeriphClockCmd #define DEBUG_USART_TX_GPIO_PORT GPIOA #define DEBUG_USART_TX_GPIO_PIN GPIO_Pin_9 #define DEBUG_USART_RX_GPIO_PORT GPIOA #define DEBUG_USART_RX_GPIO_PIN GPIO_Pin_10 #define DEBUG_USART_IRQ USART1_IRQn #define DEBUG_USART_IRQHandler USART1_IRQHandler // 串口对应的DMA请求通道 #define USART_TX_DMA_CHANNEL DMA1_Channel5 // 外设寄存器地址 #define USART_DR_ADDRESS (USART1_BASE+0x04) // 一次发送的数据量 #define RECEIVEBUFF_SIZE 5000 void USART_Config(void); void DMA_USART_RX_Config(void); void Usart_SendArray( USART_TypeDef * pUSARTx, uint8_t *array, uint16_t num); #endif /* __DMA_USART_RX_H */
dma_usart_rx.c
#include "dma_usart_rx.h" //用来接收数据的数组 uint8_t ReceiveBuff[RECEIVEBUFF_SIZE]; static void NVIC_DMA_USART(void) { NVIC_InitTypeDef NVIC_InitStructure; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //串口1中断 NVIC_InitStructure.NVIC_IRQChannel=USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1; NVIC_InitStructure.NVIC_IRQChannelSubPriority=1; NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE; NVIC_Init(&NVIC_InitStructure); } void USART_Config() { USART_InitTypeDef USART_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; //打开串口GPIO的时钟 DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK,ENABLE); // 打开串口外设的时钟 DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK,ENABLE); // 将USART Tx的GPIO配置为推挽复用模式 GPIO_InitStructure.GPIO_Mode =GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Pin =DEBUG_USART_TX_GPIO_PIN; GPIO_InitStructure.GPIO_Speed =GPIO_Speed_50MHz; GPIO_Init(DEBUG_USART_TX_GPIO_PORT,&GPIO_InitStructure); // 将USART Rx的GPIO配置为浮空输入模式 GPIO_InitStructure.GPIO_Mode =GPIO_Mode_IN_FLOATING; GPIO_InitStructure.GPIO_Pin =DEBUG_USART_RX_GPIO_PIN; GPIO_Init(DEBUG_USART_RX_GPIO_PORT,&GPIO_InitStructure); //配置中断 NVIC_DMA_USART(); // 配置串口的工作参数 // 配置波特率 USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE; // 配置 针数据字长 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(DEBUG_USARTx, &USART_InitStructure); //使能空闲中断 USART_ITConfig(DEBUG_USARTx,USART_IT_IDLE,ENABLE); // 使能串口 USART_Cmd(DEBUG_USARTx, ENABLE); } void DMA_USART_RX_Config() { DMA_InitTypeDef DMA_InitStructure; //开启DMA时钟(注意:DMA挂载在AHB总线上) RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE); // 设置DMA源地址:串口数据寄存器地址*/ DMA_InitStructure.DMA_PeripheralBaseAddr = USART_DR_ADDRESS; // 内存地址(要传输的变量的指针) DMA_InitStructure.DMA_MemoryBaseAddr =(uint32_t)ReceiveBuff; // 方向:从外设到内存 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; // 传输大小 DMA_InitStructure.DMA_BufferSize = RECEIVEBUFF_SIZE; // 外设地址不增 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 内存地址自增 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 外设数据单位 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // 内存数据单位 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; // 配置DMA通道 DMA_Init(USART_TX_DMA_CHANNEL, &DMA_InitStructure); // 使能DMA DMA_Cmd (USART_TX_DMA_CHANNEL,ENABLE); } /***************** 发送一个字节 **********************/ void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch) { /* 发送一个字节数据到USART */ USART_SendData(pUSARTx,ch); /* 等待发送数据寄存器为空 */ while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET); } /****************** 发送8位的数组 ************************/ void Usart_SendArray( USART_TypeDef * pUSARTx, uint8_t *array, uint16_t num) { uint8_t i; for(i=0; i<num; i++) { /* 发送一个字节数据到USART */ Usart_SendByte(pUSARTx,array[i]); } /* 等待发送完成 */ while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET); } /***************** 发送字符串 **********************/ void Usart_SendString( USART_TypeDef * pUSARTx, char *str) { unsigned int k=0; do { Usart_SendByte( pUSARTx, *(str + k) ); k++; } while(*(str + k)!='\0'); /* 等待发送完成 */ while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET) {} } /***************** 发送一个16位数 **********************/ void Usart_SendHalfWord( USART_TypeDef * pUSARTx, uint16_t ch) { uint8_t temp_h, temp_l; /* 取出高八位 */ temp_h = (ch&0XFF00)>>8; /* 取出低八位 */ temp_l = ch&0XFF; /* 发送高八位 */ USART_SendData(pUSARTx,temp_h); while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET); /* 发送低八位 */ USART_SendData(pUSARTx,temp_l); while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET); } ///重定向c库函数printf到串口,重定向后可使用printf函数 int fputc(int ch, FILE *f) { /* 发送一个字节数据到串口 */ USART_SendData(DEBUG_USARTx, (uint8_t) ch); /* 等待发送完毕 */ while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET); return (ch); } ///重定向c库函数scanf到串口,重写向后可使用scanf、getchar等函数 int fgetc(FILE *f) { /* 等待串口输入数据 */ while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_RXNE) == RESET); return (int)USART_ReceiveData(DEBUG_USARTx); } void USART1_IRQHandler(void) { uint16_t tmp; //检测串口空闲中断是否发生 if( USART_GetITStatus(DEBUG_USARTx,USART_IT_IDLE)==SET ) { //关闭DAM传输 DMA_Cmd(USART_TX_DMA_CHANNEL,DISABLE); //获取DMA未接收数据数量 tmp=DMA_GetCurrDataCounter(USART_TX_DMA_CHANNEL); //向电脑返回数据(接收数据数量 = SENDBUFF_SIZE - 剩余未传输的数据数量) Usart_SendArray(DEBUG_USARTx,ReceiveBuff,RECEIVEBUFF_SIZE-tmp); printf("\r\n"); //重新设置传输的数据数量 DMA_SetCurrDataCounter(USART_TX_DMA_CHANNEL,RECEIVEBUFF_SIZE); //开启DMA传输 DMA_Cmd(USART_TX_DMA_CHANNEL,ENABLE); //清除空闲中断标志位 USART1->SR; USART1->DR; //USART_ClearFlag(DEBUG_USARTx,USART_FLAG_IDLE); } }
main.c
#include "stm32f10x.h" #include "led.h" #include "dma_usart_rx.h" #define SOFT_DELAY Delay(0x0FFFFF); void Delay(__IO u32 nCount); int main(void) { // LED 端口初始化 LED_GPIO_Config(); //初始化串口 USART_Config(); //配置DMA模式 DMA_USART_RX_Config(); //串口向DAM发送RX请求 USART_DMACmd(DEBUG_USARTx,USART_DMAReq_Rx, ENABLE); LED_R(OFF); LED_G(OFF); printf("\r\nDMA外设到存储器模式,用电脑向开发板串口发送数据,数据会返回到电脑。\r\n"); while(1) { LED_G(NO); SOFT_DELAY LED_G(OFF); SOFT_DELAY } } void Delay(__IO uint32_t nCount) //简单的延时函数 { for(; nCount != 0; nCount--); }
中断服务函数一定要搞懂,主要是空闲中断的作用。
实验效果
总结
就算是库函数开发,但寄存器的每个位的作用一定一定要搞明白,不然虽然你实现了功能,但原理还是模模糊糊的,不利于打好基础,如果对DMA操作还有疑问的欢迎在评论区讨论!!!
-
RISC-V MCU应用教程之DMA(存储器到外设)
2022-04-24 16:43:08简介 ...关于DMA,具有三种数据传输方式:存储器到存储器、存储器到外设、外设到存储器。前面已讲解过关于存储器到存储器数据传输方式,本章将讲解存储器到外设的传输方式以及在下一章将会讲解外设简介
CH32V103系列是以青稞V3A处理器为核心的32位通用MCU,该处理器是基于RISC-V开源指令集设计。片上集成了时钟安全机制、多级电源管理、通用DMA控制器。此系列具有1路USB2.0主机/设备接口、多通道12位ADC转换模块、多通道TouchKey、多组定时器、多路IIC/USART/SPI接口等丰富的外设资源。
关于DMA,具有三种数据传输方式:存储器到存储器、存储器到外设、外设到存储器。前面已讲解过关于存储器到存储器数据传输方式,本章将讲解存储器到外设的传输方式以及在下一章将会讲解外设到存储器的传输方式。关于DMA存储器到外设传输方式,程序中,首先定义一个静态的源数据,存放在内部 FLASH中,然后通过DMA的方式传输到串口的数据寄存器,然后通过串口把这些数据发送到电脑的上位机显示出来。
1、DMA简介及相关函数介绍
直接存储器访问控制器(DMA)提供在外设和存储器之间或者存储器和存储器之间的高速数据传输方式,无须CPU干预,数据可以通过DMA快速地移动,以节省CPU的资源来做其他操作。
DMA控制器有7个通道,每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。还有一个仲裁器来协调各通道之间的优先级。
DMA作为一种高效、高速的数据传输方式,具有以下几个特性:
具有7个独立可配置通道,且每个通道都连接专用的硬件DMA请求,并支持软件触发
支持循环的缓冲器管理
多个通道之间的请求优先权可以通过软件编程设置(最高、高、中和低),优先权设置相等时由通道号决定(通道号低优先级高)
支持三种传输方式:存储器到存储器、存储器到外设、外设到存储器
闪存、SRAM、外设的SRAM、APB1、APB2和AHB外设均可作为访问的源和目标
独立数据源独立数据源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。源和目标地址必须按数据传输宽度对齐。
每个通道都有3个事件标志(DMA传输过半、DMA传输完成和DMA传输错误),这3个事件标志逻辑或成为一个单独的中断请求
关于DMA具体信息,可参考CH32V103应用手册。DMA标准库函数第十三章已做过介绍,在此不再赘述。
2、硬件设计
本章教程所用资源均为CH32V103开发板内部资源,无需进行其他硬件连接,只需进行程序配置即可。
3、软件设计
本章主要进行存储器到外设的DMA数据传输方式,其相较于存储器到存储器的传输方式,程序中对DMA配置进行一些修改即可,具体如下:
dma.h文件
dma.h文件主要进行函数声明;
dma.c文件
4、下载验证
将编译好的程序下载到开发板并复位,可见存储器存储数据被发送到串口:
-
嵌入式外设之DMA
2020-02-11 11:24:12关于DMA是需要硬件支持的 总结 DMA简介 DMA 不是独立的某个外设,而是一个硬件模块 支持DMA的功能 一般对应的,也是按个数来的,对应的叫做多少个通道channel。 【整理】以快递为例来说明DMA的功能 DMA...目录
DMA简介
DMA
不是独立的某个外设,而是一个硬件模块
支持DMA的功能
一般对应的,也是按个数来的,对应的叫做多少个通道channel。
【整理】以快递为例来说明DMA的功能
DMA本意解析
DMA==Direct Memory Access==直接存储器访问
Direct:直接,对应的就有间接:之前都是,CPU参与,一点点把数据,从一个地方拷贝,即像搬家一样搬到,另一个地方
很明显,此时,相对时间比较宝贵(比较值钱)的CPU,把时间,就用在(浪费在)拷贝数据了。
Memory:存储器
一般多数都指的是内存
当然,DMA也会涉及到,外设的一些Buffer,数据寄存器等等操作
Access:访问
即操作上面所提到的,存储器
即数据的读写,所以要访问,操作对应的存储器
为何会出现DMA?
所以,尤其很明显可以看出:
之前就是觉得,对于数据拷贝这样,相对低级的,简单的任务,
结果却要,时间比较值钱的CPU,去干这样的“杂货”
就有点浪费CPU的时间了
所以,才出现这个DMA
专门去干,拷贝数据这个活
由此,释放了CPU,CPU就可以去干其他的,相对更加有价值(值钱的)事情了
DMA使用示例
比如,拿uboot中的
S3c2410_nand.c (drivers\mtd\nand) 4513 2013/10/17 中的:
nand_read_buf
为例来说明:
之前就只是CPU去一点点的,慢慢的读数据:
1
2
3
4
5
6
7
8
static
void
nand_read_buf(
struct
mtd_info *mtd, u_char *buf,
int
len)
{
int
i;
struct
nand_chip *
this
= mtd->priv;
for
(i = 0; i < len; i++)
buf[i] = readb(
this
->IO_ADDR_R);
}
而如果是改为DMA
则只需要:
CPU去配置好DMA
然后DMA自动会去读数据
就不用CPU再操心了
就不用CPU再费时间去读数据了。
CPU就有空去执行其他更重要的事情了。
另外再举个例子:
之前已经实现的,linux的kernel中的nand flash驱动中的dma的例子:
在对应的hwecc的read函数:
as353x_nand_read_page_hwecc
中,当开启了DMA的READ和普通read的相关代码为:
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
static
int
as353x_nand_read_page_hwecc(
struct
mtd_info *mtd,
struct
nand_chip *chip, uint8_t *buf)
{
...
#ifdef NAF_USE_DMA_READ
info->len = mtd->writesize;
/* map CPU buffer(virtual address) to DMA buffer(DMA address) */
info->txaddr = dma_map_single( info->device,
(
void
*)buf,
info->len,
/* here len is in bytes, not in words !*/
DMA_FROM_DEVICE);
if
(unlikely(dma_mapping_error(info->device, info->txaddr))) {
ret = -ENOMEM;
dev_err(info->device,
"DMA read map failed\n"
);
goto
dma_map_err;
}
/* apply DMA slave and client */
as353x_nand_dma_init_tx(info);
/* set info for use in call back */
info->callback_param.client_info = (
void
*)info;
/* init values */
info->txsg.length = info->len;
info->txsg.page_link = 0;
info->txsg.offset = 0;
info->txsg.dma_address = info->txaddr;
desc = info->txchan->device->device_prep_slave_sg(
info->txchan,
&info->txsg,
1,
DMA_FROM_DEVICE,
DMA_PREP_INTERRUPT);
if
( unlikely(!desc) ) {
dev_err(info->device,
"Unable to get descriptor for DMA read\n"
);
ret = -EBUSY;
goto
prep_sg_err;
}
desc->callback = as353x_nand_dma_complete_callback;
desc->callback_param = &info->callback_param;
desc->tx_submit(desc);
/* inform dma controller to start */
info->txchan->device->device_issue_pending(info->txchan);
/* inform nand controller to start */
set_bit32(NAF_CFG_DMA_ON, info->regs + NAF_CONFIG);
if
(unlikely(!wait_for_completion_timeout(&info->done,
msecs_to_jiffies(1 * 1000)))) {
dev_err(info->device,
"DMA read timeout\n"
);
clear_bit32(NAF_CFG_DMA_ON, info->regs + NAF_CONFIG);
ret = -ENXIO;
info->txchan->device->device_free_chan_resources(info->txchan);
info->txchan = NULL;
AS353XNAND_DBG(
"len=%d, read=%x\n"
, info->len, info->txcount);
as353x_nand_show_reg(mtd);
goto
dma_timeout_err;
}
#else
/* none-DMA trasfer */
/* read page data */
as353x_nand_read_buf_hwbch4(mtd, buf, mtd->writesize);
#endif
...
}
/* for read, from nand to buffer, using DMA_FROM_DEVICE */
static
void
as353x_nand_dma_init_tx(
struct
as353x_nand_info *info)
{
struct
dma_client *txclient;
struct
dma_slave *txslave;
txslave = &info->txslave;
txclient = &info->txclient;
txslave->tx_reg = 0;
txslave->rx_reg = info->dmabase + NAF_FIFODATA;
txslave->reg_width = DMA_SLAVE_WIDTH_32BIT;
txslave->dev = info->device;
txclient->event_callback = as353x_nand_dma_req_tx_chan_callback;
dma_cap_set(DMA_SLAVE, txclient->cap_mask);
txclient->slave = txslave;
dma_async_client_register(txclient);
dma_async_client_chan_request(txclient);
}
static
void
as353x_nand_read_buf_hwbch4(
struct
mtd_info *mtd,
u_char *buf,
int
len)
{
int
i, j;
u32 *buf_u32 = (u32 *)buf;
struct
as353x_nand_info *info = as353x_nand_mtd_toinfo(mtd);
/* to words */
len = BYTE2WORD(len);
for
( j = ( len / NAF_FIFO_FILLSIZE_IN_WORDS ); j > 0; --j ) {
/* wait for fifo to get filled (again) - with high speed flashes this */
as353x_nand_wait_until_almost_full(mtd);
for
( i = 0; i < NAF_FIFO_FILLSIZE_IN_WORDS; i++ )
{
*buf_u32 = readl(info->regs + NAF_FIFODATA);
++buf_u32;
}
}
/* if any words left in FIFO in case of
non n-times words of NAF_FIFO_FILLSIZE_IN_WORDS */
for
( i = 0; i < ( len % NAF_FIFO_FILLSIZE_IN_WORDS ); i++) {
while
(as353x_fifo_isempty(mtd));
/* wait for FIFO is filled */
*buf_u32 = readl(info->regs + NAF_FIFODATA);
++buf_u32;
}
}
可见,当不用DMA时,对应就是去:
对应的读取寄存器中的数据
1
readl(info->regs + NAF_FIFODATA)
而开启了DMA的话,则是去利用对应的DMA驱动中,申请对应的DMA通道和资源,
然后再去用DMA去传输数据的。
DMA中,对应的指定源地址是:
1
txslave->rx_reg = info->dmabase + NAF_FIFODATA;
目的地址则是对应的,一点点增加的,每次增加的是32bit=4字节:
1
txslave->reg_width = DMA_SLAVE_WIDTH_32BIT;
DMA vs 快递
由此可看出,其实DMA,和快递,很类似:
快递:
你的需求是:
想要送东西,从某地到某地
想要通过快递去实现此需求
而之所以选择快递而不自己去送,
有的是自己没时间;
有的是自己有时间,但是成本更高,不值得花在送东西这上面:
比如,寄点东西,本身就只值100元,打算从南京送到北京,快递的话,可能也就10元,20元就够了,而要自己去送,单独火车票,甚至飞机票,都要几百,甚至几千。所以,还是通过快递公司送东西,更划算。
有的是,自己有时间,但是自己的时间,更值钱,不值得花在送东西这上面:
假如你是身家不菲,比如比尔盖茨,即使不嫌弃自己送东西的成本更高,但是也是自己的时间浪费不起,自己的时间,如果花费在送东西上,加起来会值更多的钱,所以不值得自己把宝贵的时间,用在送东西的小事情上面,所以还是选择快递更合适。
等等情况。
对于快递来说:
其优点是:
对于多数用户来说,选择快递寄东西,成本更低,更经济,更划算;
而快递对于用户来说,其所关心的是:
告诉其目的地:对应的,起始的出发地点,在你送东西时,就已经知道了,所以不用再问你
告诉其价格:用户只要支持对应的价格,快递就可以寄送了。
由此类似的DMA:
CPU,就像DMA的用户
CPU的时间很值钱,在有DMA的前提下,
还是把数据拷贝这个事情,交个DMA去做,更经济,更划算。
然后CPU就有空去做其他更值钱,更有意义的事情了。
而对于DMA来说:
其只需要CPU配置好DMA,DMA就可以去干活了,就可以去搬运数据了。
而CPU配置DMA,实际上就是告诉DMA:
搬运数据的起始地址和目标地址:就类似于送快递时的,出发点和目的地
而关于快递时用户要支付的价格,对于CPU来说,表面上是没有去额外给DMA什么补偿的。
只不过,对于整个系统来说,如果你的CPU可以借用DMA去传数据,那么:
系统中是要存在DMA这个硬件(模块,功能)的:这对于设计系统的硬件时,是否增加DMA功能,本身就是成本和效率方面的衡量后的考虑;
不过,CPU使用DMA传输数据,和用户使用快递寄东西,有些方面不太一样:
- 速度
CPU使用DMA传输数据,往往是为了提高CPU利用率,而结果,更重要的是:
DMA传输数据的话,速度更快,效率更高;
对应的用户选择快递寄东西,有时候,未必是比自己亲自去送,的速度更快,花的时间更短。
但是总的来说,往往是最经济的。
- DMA通道个数是有限的
现实中的快递公司,除了大的节假日之外,对于普通用户来说,那处理能力,基本都是无限的。
不会由于你多寄了个东西,快递公司,就忙不过来了。
而现实中的DMA,其资源是有限的:
DMA的个数,是按照通道channel来算的;
同一时刻,一个channel的DMA,只能做一件数据搬家的工作;
而且,往往是:
一个嵌入式系统中,往往很多内部功能模块,都希望有机会用到DMA,但是实际上DMA通道个数有限,
使得很难都满足其需求。
所以,在DMA的使用上,是需要你程序设计者去决定哪个模块使用DMA,然后在对应的驱动中,将数据拷贝的功能,用DMA来实现,以此提升性能的。
不过,另外,一般情况下,也是有对应的DMA驱动,去管理DMA资源,使得只要错开同时使用,也是以可以使得多个模块,都能用到DMA的。但是,往往系统中,某个模块用DMA的话,都是相对比较频繁的,因为是很多时候都在处理数据拷贝,所以往往是某个模块,要是用DMA的话,都是独占单个的DMA通道的。
比如:
系统中,假如只有一个通道的DMA的话,
而Nand Flash中,SD卡驱动中,都希望用到,那么:
你只能根据自己的需求去决定:
假如我的嵌入式系统,物理上的主要的存储设备是Nand Flash
为了提升系统性能,决定把DMA给Nand Flash使用
在保证系统整体的性能相对较好的前提下,而对于SD卡,只是用于存储用户数据,速度稍微慢一点,其也是能接受的。
关于DMA是需要硬件支持的
比如AS3536中,就支持:
AHB1中的8个DMA的channel,16个request:
AHB2中有8个DMAchannel,32个request:
总结
DMA,资源有限,需要合理利用。
且需硬件支持。
转载请注明:在路上 » 【整理】嵌入式外设之DMA
-
STM32 DMA详解
2020-08-04 22:57:32本文根据STM32F207用户手册DMA章节翻译总结的DMA知识点 -
DMA的外设地址简单说明
2015-03-11 09:37:28说成外设寄存器地址不就完了么,和内核的总线地址又容易混起来,总之dma的源和目的地址,涉及到外设的,就是外设寄存器,iis数据要从总线跑到外设寄存器上,dma才能亮剑 关于在STM32的DMA中使用外设地址 在... -
STM32 F429 DMA传输1-串口(外设)DMA传输
2021-08-13 16:46:46STM32 F429 DMA传输1-串口(外设)DMA传输主要内容如何配置各参数含义及配置细节 主要内容 本文主要是我自己记录DMA配置过程函数以及参数细节,同时与需要的人分享学习,对于DMA的作用、定义之类的在这不进行阐述,... -
stm32DMA之存储器向外设传输数据
2018-06-24 16:06:00DMA简介: 直接内存访问,是一种不经过CPU而直接从内存存取数据的数据交换模式。...数据传输支持从外设到存储器或者存储器到存储器,这里的存储器可以是SRAM 或者是FLASH 。DMA 控制器包含了DMA1 和DMA2,其... -
STM32F4外设配置速查【DMA部分】
2020-12-07 10:45:09STM32F4 DMA(直接内存存取) DMA原理 DMA即Direct Memory Access 直接存储器访问:将数据从一个地址复制到另一个...编程接口仅支持32位访问的AHB使用DMA 最多2个DMA控制器,总共2*8=16个数据流,每个DMA控制器用于管 -
STM32外设使用(一) DMA
2019-12-06 22:21:57一、DMA简介 1、DMA简介 2、DMA的工作原理 二、STM32的DMA结构 1、DMA的主要特性 2、两个DMA控制器结构 ① DMA1 controller ② DMA2 controller 3、DMA寄存器列表 ① 中断类 ② 控制传输类 4、STM32的... -
HAL库学习笔记- 9 DMA
2021-04-09 10:40:19端口存储器端口、外设端口编程端口二、DMA数据配置1.源、目标和传输模式2.指针递增3.流控制器4.循环模式5.传输类型6.直接模式7.双缓冲模式8.DMA中断三、HAL库中的DMA1.DMA_InitTypeDef初始化结构体2.DMA_... -
STM32H7 LTDC与DMA2D外设(二)
2021-03-24 11:16:58接着(一)继续 有幸把我第一条LTDC的例子在开发板上显示出来了...后来发现LTDC本身就是一个支持2层混合的外设。与DMA2D功能上一样的,至于为什么很多例子都一起用,后续再试试。目前感觉是速度问题。 单层显示的LTD -
【STM32】DMA原理,配置步骤超详细,一文搞懂DMA
2022-01-05 13:41:48数据传输支持从外设到存储器或者存储器到存储器,这里的存储器可以是SRAM或者是FLASH。DMA控制器包含了DMA1和DMA2,其中DMA1有7个通道,DMA2有5个通道,这里的通道可以理解为传输数据的一种管道。要注意的是DMA2只... -
ADC、DMA、EXTI、定时器、NVIC等片上外设篇
2020-10-11 14:16:562.2为什么我们要引入NVIC外设 3 2.3 NVIC主要做了那些工作呢 3 2.3.1 NVIC管理时用到的寄存器 3 2.4 NVIC编程 4 3. 说完NVIC,我们就来聊聊外部中断EXTI(IO上的中断) 4 3.1EXTI 功能框图 4 3.2中断/事件线 5 ... -
STM32F4 DMA双缓冲的正确打开方式
2019-04-28 17:23:10目前STM32家族中有些系列支持DMA的双缓冲模式,比如STM32F2/STM32F4/STM32F7等系列。尤其随着人们对STM32F4/F7系列应用不断拓宽和加深,在设计中运用到DMA双缓冲的场合也越来越多。STM32芯片中的DMA又可分为两大类,... -
【STM32】DMA详解
2020-10-10 10:26:15DMA寄存器9.1 DMA 低中断状态寄存器 (DMA_LISR)9.2 DMA 高中断状态寄存器 (DMA_HISR)9.3 DMA 低中断标志清零寄存器 (DMA_LIFCR)9.4 DMA 高中断标志清零寄存器 (DMA_HIFCR)9.5 DMA 数据流 x 配置寄存器 (DMA_SxCR) ... -
DMA基本原理+实验
2021-09-19 21:30:17DMA简介: DMA 全称Direct Memory Access,即直接存储器访问。 DMA传输将数据从一个地址空间复制到另一个地址空间。当CPU初始化这个传输动作,传输动作本身是由DMA控制器来实现和完成的。 DMA传输方式无需CPU直接... -
STM32中采用DMA实现方波的产生和捕获
2021-01-19 18:50:02它在性能、价格、功耗和实时性方面树立了一个新的标杆,集成了Cortex-M3内核,以及双ADC、多用途的通用时钟TIMx、RTC、I2C、SPI、UART、CAN、DMA、USB等丰富的外设。其功耗在全速72MHz所有模块都打开时也仅仅为36 mA... -
DMA—直接存储区访问
2017-06-28 15:00:00本章参考资料:《 STM32F4xx 中文参考手册》 DMA 控制器章节。学习本章时,配合《 STM32F4xx 中文参考...DMA 简介DMA(Direct Memory Access,直接存储区访问)为实现数据高速在外设寄存器与存储器之间或者存储器与存储... -
STM32 DMA使用详解
2020-12-24 23:45:46一、DMA简介1、DMA简介DMA(Direct Memory Access:...比如希望外设A的数据拷贝到外设B,只要给两种外设提供一条数据通路,再加上一些控制转移的部件就可以完成数据的拷贝。DMA就是基于以上设想设计的,它的作用就是... -
STM32-DMA数据传输(USART-ADC-数组)
2022-01-04 11:49:03DMA结构体初始化 DMA功能框图 DMA数据配置 DMA传输数据的思路 存储器到外设传输数据 存储器到存储器传输数据 -
STM32-DMA直接存储器访问
2019-07-18 20:44:00DMA(Direct Memory Access,直接存储区访问)为实现数据高速在外设寄存器与存储器之间或者存储器与存储器之间传输提供了高效的方法。之所以称之为高效,是因为 DMA 传输实现高速数据移动过程无需任何 CPU 操作控制。从... -
【STM32】 DMA原理,步骤超细详解,一文看懂DMA
2020-03-19 21:50:24DMA传输将数据从一个地址空间复制到另一个地址空间,提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。 我们知道CPU有转移数据、计算、控制程序转移等很多功能,系统运作的核心就是CPU, CPU无时不刻的... -
ZYNQ DMA
2021-06-08 21:10:34ZYNQ之DMA LOOPBACK实验1 原理介绍1.1 AXI4(AXI-full)总线学习1.2 zynq ps端几种接口1.3 AXI DMA简介1.4 AXI DMA编程顺序1.5 实验目的2 实验步骤2.1 建立BD文件2.2 SDK文件3 结果分析3.1 实验条件3.2 实验结果3.3 ... -
DMA工作原理-STM32 DMA和ARM9 DMA,彻底理解DMA
2020-04-20 01:16:12前序 网上文章一大堆都有介绍DMA的作用,是直接内存获取控制器,但由于用途的局限或者用在了复杂的外设上面,导致没有很好的把DMA的作用说的很系统,本人也是根据网上的资料,进行一些DMA的总结,个人觉得比较系统,... -
单片机与DSP中的STM32中采用DMA实现方波的产生和捕获
2020-10-21 14:58:48它在性能、价格、功耗和实时性方面树立了一个新的标杆,集成了Cortex-M3内核,以及双ADC、多用途的通用时钟TIMx、RTC、I2C、SPI、UART、CAN、DMA、USB等丰富的外设。其功耗在全速72MHz所有模块都打开时也仅仅为36 mA... -
串口+DMA 数据收发编程实践
2022-02-20 10:41:43了解DMA 的工作原理,通过配置 STM32F407 芯片的DMA,实现串口+ DMA数据收发。 -
DMA学习笔记
2022-04-03 18:32:48DMA(Derict Memory Access,直接存储器存储),回顾STM32的运行架构,数据的转移时常发生,需要内核操作,比较浪费计算资源,而DMA代CPU完成数据的转移可以减轻CPU的工作量,使之专注于计算工作,大大提升效率,这大概... -
DMA 实验
2021-09-05 16:45:43DMA基本原理 DMA(Direct Memory Access) Direct Memory Access 直接存储器访问 将数据从一个地址空间复制到另一个地址空间 当CPU初始化这个传输动作,传输动作本身是由DMA控制器来实现和完成的 DMA传输方式无需CPU... -
GD32F130之DMA
2022-03-24 22:34:29DMA,即Direct Memory Access,是一种在无需CPU参与的情况下,将数据块在内存(SRAM)和外设(一般是I/O设备)之间高效传输的硬件机制。实现这种功能的集成电路单元叫做DMA Controller,即DMA 控制器。 使用DMA的...