STM32基础教程——DMA
目录
前言
编辑 技术实现
接线图
代码实现
技术要点
DMA时钟
DMA初始化
DMA数据传输设置
数据改变与显示
实验结果
问题记录
前言
DMA(Direct Memory Access)直接存储器存取,用来提供在外设和存储器 之间或者存储器和存储器之间的高速数据传输。无需CPU干预,数据可以通过DMA快速地移动,这样可以节省CPU的资源进行其他操作。两个DMA控制器有12个通道(DMA1有7个通道,DMA2有5个通道),每个通道专门用来管理来自与一个或多个外设对存储器访问的请求。还有一个仲裁器来协调各个DMA请求的优先权。
DMA的主要特性
- 12个独立的可配置的通道(请求):DMA1有7个 通道,DMA2有5个通道
- 每个通道都直接连接专用的硬件DMA请求,每个通道都同样支持软件触发。这些功能通过软件来配置。
- 在同一个DMA模块上多个请求见的优先权可以通过软件编程设置(共有四级:很高、高、中等和低),优先权设置相等时由硬件决定(请求0优先与请求1,以此类推)。
- 独立数据源和目标数据区的传输字节宽度(字节、半字、字),模拟打包和拆包的过程。源和目标地址必须按数据传输宽度对齐。
- 支持循环的缓冲器管理
- 每个通道都有3个事件标志(DMA半传输、DMA传输完成和DMA传输出错),这三个事件标志逻辑或成为一个单独的中断请求。
- 存储器和存储器之间的传输。
- 外设和存储器、存储器和外设之间的传输。
- Flash、SRAM、外设的SRAM、APB1、APB2和AHB外设均可作为访问的源和目标。
- 可编程的数据传输数目。
技术实现
接线图
代码实现
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h" //延时函数
#include "OLED.h"
#include "My_DMA.h"uint8_t Dat_A[] = {0x01,0x02,0x03,0x04};
uint8_t Dat_B[] = {0,0,0,0};int main(void)
{/*OLED初始化*/OLED_Init();/*DMA初始化*/MyDMA_Init((uint32_t)Dat_A,(uint32_t)Dat_B,4);OLED_ShowString(1,1,"Dat_A");OLED_ShowHexNum(1,8,(uint32_t)Dat_A,8); //显示Dat_A数组的地址(存放在SRAM)OLED_ShowString(3,1,"Dat_B");OLED_ShowHexNum(3,8,(uint32_t)Dat_B,8); //显示Dat_B数组的地址(存放在SRAM)/*显示初始的数据*/OLED_ShowHexNum(2,1,Dat_A[0],2);OLED_ShowHexNum(2,5,Dat_A[1],2);OLED_ShowHexNum(2,9,Dat_A[2],2);OLED_ShowHexNum(2,13,Dat_A[3],2);OLED_ShowHexNum(4,1,Dat_B[0],2);OLED_ShowHexNum(4,5,Dat_B[1],2);OLED_ShowHexNum(4,9,Dat_B[2],2);OLED_ShowHexNum(4,13,Dat_B[3],2);Delay_ms(1000);while(1){/*源数据自增*/Dat_A[0]++;Dat_A[1]++;Dat_A[2]++;Dat_A[3]++;/*显示转运前的数据*/OLED_ShowHexNum(2,1,Dat_A[0],2);OLED_ShowHexNum(2,5,Dat_A[1],2);OLED_ShowHexNum(2,9,Dat_A[2],2);OLED_ShowHexNum(2,13,Dat_A[3],2);OLED_ShowHexNum(4,1,Dat_B[0],2);OLED_ShowHexNum(4,5,Dat_B[1],2);OLED_ShowHexNum(4,9,Dat_B[2],2);OLED_ShowHexNum(4,13,Dat_B[3],2);Delay_ms(1000);/*启动DMA转运*/MyDMA_Transfer();/*显示转运后的数据*/OLED_ShowHexNum(2,1,Dat_A[0],2);OLED_ShowHexNum(2,5,Dat_A[1],2);OLED_ShowHexNum(2,9,Dat_A[2],2);OLED_ShowHexNum(2,13,Dat_A[3],2);OLED_ShowHexNum(4,1,Dat_B[0],2);OLED_ShowHexNum(4,5,Dat_B[1],2);OLED_ShowHexNum(4,9,Dat_B[2],2);OLED_ShowHexNum(4,13,Dat_B[3],2);Delay_ms(1000);}
}
My_DMA.h
#ifndef MY_DMA_H
#define MY_DMA_H#include "stm32f10x.h"void MyDMA_Init(uint32_t Addr_A, uint32_t Addr_B,uint16_t Size);
void MyDMA_Transfer(void);#endif
My_DMA.c
#include "My_DMA.h"uint16_t MyDMA_Size = 8;/** * @brief 初始化DMA* @param 用于指定源数据和目标存储器的地址以及缓冲区大小* @arg Addr_A 源数据地址* @arg Addr_B 目标存储器地址* @arg Size 缓冲区大小* @retval None
**/
void MyDMA_Init(uint32_t Addr_A, uint32_t Addr_B,uint16_t Size)
{MyDMA_Size = Size;//开启DMA时钟RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);//初始化DMA DMA_InitTypeDef DMA_InitStruct;DMA_InitStruct.DMA_BufferSize = Size; //缓冲区的大小,由于CNDTR寄存器的高16位保留,这里缓冲区大小的变量的大小使用//uint16_t即可DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC; //外设站点是数据源DMA_InitStruct.DMA_MemoryBaseAddr = Addr_B; //存储器站点的基地址DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //接收的数据以字节形式传输DMA_InitStruct.DMA_Mode = DMA_Mode_Normal; //DAM传输计数器不使用自动重装模式DMA_InitStruct.DMA_M2M = DMA_M2M_Enable; //DMA为存储器到存储器传输的方式DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable; //存储器站点自增DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Enable; //外设站点自增DMA_InitStruct.DMA_PeripheralBaseAddr = Addr_A; //要传输的数据的基地址DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //传输数组中的数据以字节形式传输DMA_InitStruct.DMA_Priority = DMA_Priority_VeryHigh; //只有一个通道,DMA优先级随便选DMA_Init(DMA1_Channel1,&DMA_InitStruct); //使用软件触发,通道可以任意选择DMA_Cmd(DMA1_Channel1,DISABLE);
}/** * @brief DMA传输函数* @param None* @retval None
**/
void MyDMA_Transfer(void)
{DMA_Cmd(DMA1_Channel1,DISABLE); //DMA失能DMA_SetCurrDataCounter(DMA1_Channel1,MyDMA_Size); //传输计数器赋值DMA_Cmd(DMA1_Channel1,ENABLE); while(DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET); //等待DMA转运完成DMA_ClearFlag(DMA1_FLAG_TC1);
}
OLED部分代码参照文章《STM32基础教程——OLED显示》http://【STM32基础教程 ——OLED显示 - CSDN App】https://blog.csdn.net/2301_80319641/article/details/145837521?sharetype=blog&shareId=145837521&sharerefer=APP&sharesource=2301_80319641&sharefrom=link
技术要点
DMA时钟
//开启DMA时钟RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
由图可知DMA隶属于AHB外设, 故应调用RCC_AHBPeriphClockCmd()函数开启AHB外设时钟。
DMA初始化
//初始化DMA DMA_InitTypeDef DMA_InitStruct;DMA_InitStruct.DMA_BufferSize = Size; //缓冲区的大小,由于CNDTR寄存器的高16位保留,这里缓冲区大小的变量的大小使用//uint16_t即可DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC; //这里是将一个数组中的数据转运到另一个数组,是存储器(片上SRAM)到存储器(片上SRAM),这里用不到外设,值可以设置为默认值DMA_InitStruct.DMA_MemoryBaseAddr = Addr_B; //存储器站点的基地址DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //接收的数据以字节形式传输DMA_InitStruct.DMA_Mode = DMA_Mode_Normal; //DAM传输计数器不使用自动重装模式DMA_InitStruct.DMA_M2M = DMA_M2M_Enable; //DMA为存储器到存储器传输的方式DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable; //存储器站点自增DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Enable; //外设站点自增DMA_InitStruct.DMA_PeripheralBaseAddr = Addr_A; //要传输的数据的基地址DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //传输数组中的数据以字节形式传输DMA_InitStruct.DMA_Priority = DMA_Priority_VeryHigh; //只有一个通道,DMA优先级随便选DMA_Init(DMA1_Channel1,&DMA_InitStruct); //使用软件触发,通道可以任意选择
DMA的每个通道都可以在有固定地址的外设寄存器和存储器地址之间执行DMA传输,DMA数据是可编程的,最大达到65536,由DMA_CNDTR寄存器控制,该寄存器只启用低16位,这个寄存器只能在不工作(DMA_CCRx的EN=0)时写入。通道开启后该寄存器变为只读,指示剩余的带传输字节数目,数据项数量寄存器,在每次传输后递减。数据传输结束后,寄存器的内容或者变为0,或者当该通道配置为自动重加载模式时,寄存器的内容被自动重新加载为之前配置时的数值。当寄存器的内容为0时,无论通道是否开启,都不会发生任何数据传输。
由于本实验是将一个数组中的数据转移到另外一个数组,初始化结构成员DMA_M2M设置为DMA_M2M_Enable,即DMA数据转运为存储器到存储器,后续DIR成员设置数据读取的方向,将数组Dat_A[]数组赋给外设基地址(此时外设只是一个标号,实际为存储器,因为将DMA_CCR寄存器的MEM2MEM位置1了,使能了存储器到存储器模式),将Dat_B[]数组地址赋给存储器基地址。在数据传输过程中外设和存储器的数据宽度均设为字节长度。DMA数据传输寄存器不使用自动重装模式,即完成一次DMA转换后即停止,实验中使用手动开启DMA转换。
数据缓冲区的大小设置为了4,要传输4组数据,两个数据站点均要自增,不然接收数据的存储器不自增则数据会遭到覆盖,最终数据为最后传输的数据。若数据源的存储器站点不自增,则发送的数据为重复的数据。
DMA_Cmd(DMA1_Channel1,DISABLE);
DMA失能,后续使用单独函数设置DMA传输计数器并使能DMA。
DMA数据传输设置
/** * @brief DMA传输函数* @param None* @retval None
**/
void MyDMA_Transfer(void)
{DMA_Cmd(DMA1_Channel1,DISABLE); //DMA失能DMA_SetCurrDataCounter(DMA1_Channel1,MyDMA_Size); //传输计数器赋值DMA_Cmd(DMA1_Channel1,ENABLE); while(DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET); //等待DMA转运完成DMA_ClearFlag(DMA1_FLAG_TC1);
}
失能DMA,设置数据传输计数器的大小然后使能DMA,启动DMA数据转换,DMA数据转换完成后,硬件会自动将DMA_ISR寄存器的TCIF位置1,while循环等待DMA数据转换的完成,数据转换完成后清除标志位。
数据改变与显示
/*DMA初始化*/MyDMA_Init((uint32_t)Dat_A,(uint32_t)Dat_B,4);OLED_ShowString(1,1,"Dat_A");OLED_ShowHexNum(1,8,(uint32_t)Dat_A,8); //显示Dat_A数组的地址(存放在SRAM)OLED_ShowString(3,1,"Dat_B");OLED_ShowHexNum(3,8,(uint32_t)Dat_B,8); //显示Dat_B数组的地址(存放在SRAM)/*显示初始的数据*/OLED_ShowHexNum(2,1,Dat_A[0],2);OLED_ShowHexNum(2,5,Dat_A[1],2);OLED_ShowHexNum(2,9,Dat_A[2],2);OLED_ShowHexNum(2,13,Dat_A[3],2);OLED_ShowHexNum(4,1,Dat_B[0],2);OLED_ShowHexNum(4,5,Dat_B[1],2);OLED_ShowHexNum(4,9,Dat_B[2],2);OLED_ShowHexNum(4,13,Dat_B[3],2);Delay_ms(1000);while(1){/*源数据自增*/Dat_A[0]++;Dat_A[1]++;Dat_A[2]++;Dat_A[3]++;/*显示转运前的数据*/OLED_ShowHexNum(2,1,Dat_A[0],2);OLED_ShowHexNum(2,5,Dat_A[1],2);OLED_ShowHexNum(2,9,Dat_A[2],2);OLED_ShowHexNum(2,13,Dat_A[3],2);OLED_ShowHexNum(4,1,Dat_B[0],2);OLED_ShowHexNum(4,5,Dat_B[1],2);OLED_ShowHexNum(4,9,Dat_B[2],2);OLED_ShowHexNum(4,13,Dat_B[3],2);Delay_ms(1000);/*启动DMA转运*/MyDMA_Transfer();/*显示转运后的数据*/OLED_ShowHexNum(2,1,Dat_A[0],2);OLED_ShowHexNum(2,5,Dat_A[1],2);OLED_ShowHexNum(2,9,Dat_A[2],2);OLED_ShowHexNum(2,13,Dat_A[3],2);OLED_ShowHexNum(4,1,Dat_B[0],2);OLED_ShowHexNum(4,5,Dat_B[1],2);OLED_ShowHexNum(4,9,Dat_B[2],2);OLED_ShowHexNum(4,13,Dat_B[3],2);Delay_ms(1000);}
先初始化DMA,在OLED第一行一列显示Dat_A,其后显示数组的地址。在第三行第三列显示Dat_B,其后紧跟该数组的地址。
然后在第二行和第四行分别显示数组A和数组B的初始数据,延时1000ms,防止变化过快人眼观察不到。进入死循环后,先递增数组原始数据,然后在转运前显示修改后的数据,然后启动DMA转换,再显示经DMA转换后的数据,以此流程循环显示。
uint16_t MyDMA_Size = 8;
设置公共变量MyDMA_Size用于指定DMA数据传输计数器的大小,默认值为8(在不影响程序运行的情况下随意设置,但最大值不应超过65536)。
void MyDMA_Init(uint32_t Addr_A, uint32_t Addr_B,uint16_t Size)
{MyDMA_Size = Size;
进入初始化函数之后将缓冲区的大小传递给它,两种并无直接关联,这里只是方便设置。
void MyDMA_Transfer(void)
{DMA_Cmd(DMA1_Channel1,DISABLE); //DMA失能DMA_SetCurrDataCounter(DMA1_Channel1,MyDMA_Size); //传输计数器赋值
在MyDMA_Transfer()函数中MyDMA_Size用于传递数据传输计数器的大小。
实验结果
DMA
问题记录
暂无