STM32 DMA直接存储器存取(STM32笔记)

发布时间:2026/7/6 15:07:37

STM32 DMA直接存储器存取(STM32笔记) 目录DMA简介存储器映像DMA框图DMA基本结构DMA请求数据宽度与对齐补充DMA库函数DMA数据转换代码MyDMA.h模块MyDMA.c模块main.c模块DMA数据转换AD多通道代码AD.h模块AD.c模块main.c模块DMA简介DMADirect Memory Access直接存储器存取DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输无须CPU干预节省了CPU的资源12个独立可配置的通道 DMA17个通道 DMA25个通道每个通道都支持软件触发和特定的硬件触发STM32F103C8T6 DMA资源DMA17个通道存储器和存储器之间用软件触发软件可以用很快的速度一股脑的转运完成。外设和存储器之间用硬件触发因为外设是有一定的时机的要等到ADC外设数模转运完成才可以转运并且每个DMA的通道对应的硬件触发源不一样。使用某个外设的硬件触发源就要使用它连接的那个通道而不能任意选择通道。存储器映像stm32有以上这些存储器ROM:只读存储器是一种非易失性掉电不丢失的存储器。RAM就是随机存储器是一种易失性、掉电丢失的存储器人话RAM是手机运行内存ROM是手机硬盘内存例如我的手机是IQOO Neo 9 16256g的手机那么RAM 16gROM 256gFlash储存程序代码和const定义的常量。我们定义的变量储存在SRAM里。DMA框图其中除了标红的区域包含了CPU和内核外设等等其余的都可看做是存储器寄存器是一种特殊的存储器一方面CPU可以对寄存器进行读写操作就像读写运行内存一样。另一方面寄存器的每一位背后都连接了一根导线这些导线可以用于控制外设电路的状态。所以寄存器是软件和硬件的桥梁CPU读写寄存器就是在控制硬件电路的执行。为了高效有条理的访问存储器中间设计了一个总线矩阵。左边主动单元拥有存储器的访问权。右边被动单元它们的存储器只能被左边的主动单元读写。左边DCode总线专门访问Flash的系统总线是访问其他的DMA1和DMA2分别有一条线路连接到总线上。其中DMA1有7个通道DMA2有5个通道。各个通道可以设置他们转运数据的源地址和目标地址。在DMA1和DMA2框图中都显示一个仲裁器。因为虽然多个通道可以独立转运数据但是最终DMA总线只有一条。所以所有的通道都只能分时复用这一条DMA总线。如果产生冲突就会由仲裁器根据优先级决定谁先用谁后用。在总线矩阵中也会有一个仲裁器就是当CPU和DMA同时访问同一个目标那么DMA就会停止CPU的访问防止冲突。AHB从设备也就是DMA自身的寄存器。CPU可以通过线路对DMA进行配置。两个红框中的各个外设引出两条DMA请求线路。这个DMA请求就是DMA的硬件触发源。比如ADC转换完成串口接收到数据。需要触发DMA转运数据的时候发出请求。注图中的Flash是只读存储器通过总线访问都是只读的只能读取不能写入。但是可以通过配置Flash接口控制器来对Flash进行写入只不过过程就会有点麻烦了DMA基本结构参数1方向外设----存储器 / 存储器----存储器参数2外设和存储器的起始地址参数3数据宽度。指定一次转运要按多大的数据宽度来转运参数4地址是否自增。当一次转运完成后地址是否。相当于指针指向数组第一个数转运后指针指向第二个数参数5传输计数器指定可以传输几次。当它的值为5时每传输一次它的值就-1直到减为零就不再进行数据传输。此时的地址就会变为新的起始地址以方便新的一轮转换。参数6自动重装值当传输计数器值减为0时是否要让他自动恢复到最初的值。参数7M2M决定触发源是由硬件触发还是有软件触发软件触发以最快的速度连续不断的触发DMA争取早日把传输计数器清零。完成这一轮的转换开关控制DMA_Cmd();注意写传输计数器时必须要先关闭DMA_Cmd()DMA请求每个通道都有硬件触发和软件触发。每个通道的硬件触发源都是不同的。数据宽度与对齐当两个站点的数据宽度不一致时小的数据转大的数据高位补零大的数据转小的数据舍高位补充我们上一篇说过ADC扫描模式下每个通道的数据转运完后不会置标志位也不会请求中断。只有在所有数据都转运完后才会置标志位和申请终端。但是在每个通道的数据转运完后会申请一个DMA请求来保存此次转运的数据。因为在扫描模式下规则组只能输出最后一个转运的数据前面的数据都会被覆盖掉DMA库函数/* 复位DMA配置 */ void DMA_DeInit(DMA_Channel_TypeDef* DMAy_Channelx); /* 初始化函数 */ void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct); /* 初始化结构体 */ void DMA_StructInit(DMA_InitTypeDef* DMA_InitStruct); /* 开启使能 */ void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState); /* 开启DMA中断 */ void DMA_ITConfig(DMA_Channel_TypeDef* DMAy_Channelx, uint32_t DMA_IT, FunctionalState NewState); /* 给传输计数器写数据的 */ void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber); /* 返回当前传输计数器的值 */ uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx); /* 获取标志位状态 */ FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG); /* 清除标志位状态 */ void DMA_ClearFlag(uint32_t DMAy_FLAG); /* 获取中断状态 */ ITStatus DMA_GetITStatus(uint32_t DMAy_IT); /* 清除中断标志位 */ void DMA_ClearITPendingBit(uint32_t DMAy_IT);DMA数据转换代码MyDMA.h模块#ifndef __MYDMA__H #define __MYDMA__H void MyDMA_Init(uint32_t ADDrA, uint32_t ADDrB, uint16_t size); void MyDMA_Transfer(void); #endifMyDMA.c模块#include stm32f10x.h // Device header /* * 1.RCC开启DMA的时钟。 * 2.调用DMA_Init()初始化各个参数。 * 3.DMA_Cmd()。 * (如果调用硬件触发要在对应的外设调用相应的XXX_DMACMd开启一下触发信号的输出) * (如果需要DMA的中断那就调用DMA_ITConfig(),开启中断输出再在NVIC里配置相应的终端通道写相应的中断函数) * 结束后再想给传输计数器赋值的话就DMA_Cmd()失能赋值DMA_Cmd()使能。 */ /* * * DMA启动的三个条件 * 1.传输计数器 0; * 2.触发源有触发信号 * 3.DMA使能 * */ uint16_t MyDMA_size; void MyDMA_Init(uint32_t ADDrA, uint32_t ADDrB, uint16_t size) { MyDMA_size size; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); DMA_InitTypeDef DMA1_Initstructure; /* 外设 */ DMA1_Initstructure.DMA_PeripheralBaseAddr ADDrA; //起始地址 DMA1_Initstructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_Byte; //数据宽度 DMA1_Initstructure.DMA_PeripheralInc DMA_PeripheralInc_Enable; //地址是否自增 /* 存储器 */ DMA1_Initstructure.DMA_MemoryBaseAddr ADDrB; //起始地址 DMA1_Initstructure.DMA_MemoryDataSize DMA_MemoryDataSize_Byte; //数据宽度 DMA1_Initstructure.DMA_MemoryInc DMA_MemoryInc_Enable; //地址是否自增 DMA1_Initstructure.DMA_DIR DMA_DIR_PeripheralSRC; //传输方向 DMA1_Initstructure.DMA_BufferSize size; //传输计数器 DMA1_Initstructure.DMA_Mode DMA_Mode_Normal; //传输模式其实就是是否使用自动重装 DMA1_Initstructure.DMA_M2M DMA_M2M_Enable; //选择是硬件触发还是软件触发 DMA1_Initstructure.DMA_Priority DMA_Priority_Medium; //配置优先级 DMA_Init(DMA1_Channel1, DMA1_Initstructure); //软件触发任意1~7通道都可以 DMA_Cmd(DMA1_Channel1, DISABLE); } //再启动一次DMA void MyDMA_Transfer(void) { DMA_Cmd(DMA1_Channel1, DISABLE); DMA_SetCurrDataCounter(DMA1_Channel1, MyDMA_size); DMA_Cmd(DMA1_Channel1, ENABLE); while(DMA_GetFlagStatus(DMA1_FLAG_TC1) RESET); //没完成RESET完成SET DMA_ClearFlag(DMA1_FLAG_TC1); }main.c模块#include stm32f10x.h // Device header #include Delay.h #include Oled.h #include MYDMA.h uint8_t DataA[] {0x01, 0x02, 0x03, 0x04}; uint8_t DataB[] {0, 0, 0, 0}; int main(void) { OLED_Init(); //开启转运 MyDMA_Init((uint32_t)DataA, (uint32_t)DataB, 4); OLED_ShowString(1, 1, DataA); OLED_ShowString(3, 1, DataB); OLED_ShowHexNum(1, 8, (uint32_t)DataA, 8); OLED_ShowHexNum(3, 8, (uint32_t)DataB, 8); while(1) { DataA[0] ; DataA[1] ; DataA[2] ; DataA[3] ; //转运前的DataA[] OLED_ShowHexNum(2, 1, DataA[0], 2); OLED_ShowHexNum(2, 4, DataA[1], 2); OLED_ShowHexNum(2, 7, DataA[2], 2); OLED_ShowHexNum(2, 10, DataA[3], 2); //转运前的DataB[] OLED_ShowNum(4, 1, DataB[0], 2); OLED_ShowNum(4, 4, DataB[1], 2); OLED_ShowNum(4, 7, DataB[2], 2); OLED_ShowNum(4, 10, DataB[3], 2); Delay_ms(1000); MyDMA_Transfer(); OLED_ShowHexNum(2, 1, DataA[0], 2); OLED_ShowHexNum(2, 4, DataA[1], 2); OLED_ShowHexNum(2, 7, DataA[2], 2); OLED_ShowHexNum(2, 10, DataA[3], 2); OLED_ShowNum(4, 1, DataB[0], 2); OLED_ShowNum(4, 4, DataB[1], 2); OLED_ShowNum(4, 7, DataB[2], 2); OLED_ShowNum(4, 10, DataB[3], 2); Delay_ms(1000); } }DMA数据转换AD多通道代码AD.h模块#ifndef __AD__H #define __AD__H extern uint16_t AD_Value[4]; void AD_Init(void); void ADC_GetValue(void); #endifAD.c模块#include stm32f10x.h // Device header uint16_t AD_Value[4]; void AD_Init(void) { //1.开启RCC时钟包括ADC和GPIO的时钟。ADCCLK的分频器也要配置 RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); RCC_ADCCLKConfig(RCC_PCLK2_Div6); //ADCCLK时钟最大14MHz 6分频后是12MHz //2.配置GPIO为模拟输入模式 GPIO_InitTypeDef GPIOA_InitStructure; /*这里的模拟输入模式是ADC的专属模式让GPIO的输入输出失效信号直接进入板子里面的ADC外设*/ GPIOA_InitStructure.GPIO_Mode GPIO_Mode_AIN; GPIOA_InitStructure.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3; GPIOA_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIOA_InitStructure); //3.配置多路开关把左边的通道接入右边的规则组里面点菜 ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); /*ADC1的菜单里菜品通道0排在第一位采样时间是55.5个ADC周期*/ //还想再点几个菜的话就多调用计次这个函数 ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5); //4.配置ADC转换器 ADC_InitTypeDef ADC_InitStructure; ADC_InitStructure.ADC_Mode ADC_Mode_Independent; //配置ADC的工作模式独立模式/双ADC模式 ADC_InitStructure.ADC_DataAlign ADC_DataAlign_Right; //设置右对齐还是左对齐 ADC_InitStructure.ADC_ExternalTrigConv ADC_ExternalTrigConv_None; //外部触发转换选择 ADC_InitStructure.ADC_ContinuousConvMode DISABLE; //选择 连续/单次 转换模式 ENABLE(连续)/DISABLE(单次) ADC_InitStructure.ADC_ScanConvMode ENABLE; //选择扫描模式(ENABLE)还是非扫描模式(DISABLE) ADC_InitStructure.ADC_NbrOfChannel 4; //通道数目在扫描模式下总共会用到几个通道 ADC_Init(ADC1, ADC_InitStructure); DMA_InitTypeDef DMA1_Initstructure; /* 外设 */ DMA1_Initstructure.DMA_PeripheralBaseAddr (uint32_t)ADC1-DR; //起始地址(ADC转换完成后放在ADC-DR中) DMA1_Initstructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_HalfWord; //数据宽度 DMA1_Initstructure.DMA_PeripheralInc DMA_PeripheralInc_Disable; //地址是否自增 /* 这里选择不自增是因为ADC的规则组里面的寄存器只有一个。多个菜品只在这个寄存器里面的多位自增 */ /* 存储器 */ DMA1_Initstructure.DMA_MemoryBaseAddr (uint32_t)AD_Value; //起始地址 DMA1_Initstructure.DMA_MemoryDataSize DMA_MemoryDataSize_HalfWord; //数据宽度 DMA1_Initstructure.DMA_MemoryInc DMA_MemoryInc_Enable; //地址是否自增 DMA1_Initstructure.DMA_DIR DMA_DIR_PeripheralSRC; //传输方向 DMA1_Initstructure.DMA_BufferSize 4; //传输计数器 DMA1_Initstructure.DMA_Mode DMA_Mode_Normal; //传输模式其实就是是否使用自动重装 DMA1_Initstructure.DMA_M2M DMA_M2M_Disable; //选择是硬件触发还是软件触发,这里是硬件出发 DMA1_Initstructure.DMA_Priority DMA_Priority_Medium; //配置优先级 DMA_Init(DMA1_Channel1, DMA1_Initstructure); //软件触发ADC外设接在了通道1上所以这里只能使用通道1 DMA_Cmd(DMA1_Channel1, ENABLE); ADC_DMACmd(ADC1, ENABLE);//开启ADC到DMA的输出 ADC_Cmd(ADC1, ENABLE); //5.对ADC进行校准 ADC_ResetCalibration(ADC1); //复位校准 /*软件置1开启复位校准复位校准完成后硬件自动清零*/ while(ADC_GetResetCalibrationStatus(ADC1) SET); //等待复位校准完成(未完成则一直开始等待) ADC_StartCalibration(ADC1); //开始校准 /*软件置1开启校准校准完成后硬件自动清零*/ while(ADC_GetCalibrationStatus(ADC1) SET); //等待校准完成 } //多通道简易实现将函数的参数改为ADC规则通道配置的参数 void ADC_GetValue(void) { //写入传输计数器 DMA_Cmd(DMA1_Channel1, DISABLE); DMA_SetCurrDataCounter(DMA1_Channel1, 4); DMA_Cmd(DMA1_Channel1, ENABLE); ADC_SoftwareStartConvCmd(ADC1, ENABLE); //软件开启转换命令 //等待DMA完成 while(DMA_GetFlagStatus(DMA1_FLAG_TC1) RESET); //没完成RESET完成SET DMA_ClearFlag(DMA1_FLAG_TC1); }main.c模块#include stm32f10x.h // Device header #include Delay.h #include Oled.h #include AD.h int main(void) { OLED_Init(); AD_Init(); OLED_ShowString(1,1,AD0:); OLED_ShowString(2,1,AD1:); OLED_ShowString(3,1,AD2:); OLED_ShowString(4,1,AD3:); while(1) { ADC_GetValue(); OLED_ShowNum(1,5,AD_Value[0],5); OLED_ShowNum(2,5,AD_Value[1],5); OLED_ShowNum(3,5,AD_Value[2],5); OLED_ShowNum(4,5,AD_Value[3],5); Delay_ms(500); } }DMA数据转换AD多通道扫描模式可以对多个进行转换但是转换后的数据存在ADC_DR寄存器中不及时取出数据就会导致数被下一个通道转换的数据覆盖。

相关新闻