DMA数据转运

📅 2026/7/14 4:25:59
DMA数据转运
DMA简介这里存储器是指运行内存SRAM和程序存储器Flash。如果需要进行存储器到存储器的转运就需用到软件触发例如想要将一批数据从Flash转运到SRAM里面就需要软件触发了使用软件触发后DMA就会一股到将数据转运到SRAM里面去而如果需要进行外设到存储器的转运就要用到硬件触发例如ADC转运就是等ADC将数据转换完成后硬件触发一次DMA之后DMA才会转运注意每个DMA通道它的硬件触发源是不同的你要使用某个外设的硬件触发源就得使用它连接的通道。存储器映像RMO存储器就是只读存储器是一种非易失、掉电不丢失的存储器。RAM存储器就是随机存储器是一种易失性掉电丢失的存储器。DMA基本结构左右两个绿色框的外设和存储器其实只是名字外设里面可以放存储器的地址存储器里可以放外设的地址只要选好方向就行了。**起始地址**有外设存储端的起始地址和存储器端的存储地址这两个参数决定了数据是从哪里来的到哪里去。**数据宽度**这个参数的作用是指定一次转运要按多大的数据宽度来进行可以选择字节Byte半字Halfword和字word32位。**地址是否自增**指定一次转运完成后下一次转运是不是要把地址移到下一个位置。**传输计数器**比如我要传输一个数组里面有4个元素那么就填4计数器就会自减到0为0时停止工作**自动重装器**这个的作用就是传输计数器减到0后是否帮它恢复初始值。注意自动重装不能和软件触发同时使用因为软件触发是想尽快吧传输计数器清零而自动重装器又会帮它恢复初始值一起使用就会无限循环了**M2M**选择软件触发还是硬件触发。初始化配置uint16_tMyDMA_Size;voidMyDMA_Init(uint32_tAddrA,uint32_tAddrB,uint16_tSize){MyDMA_SizeSize;RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);//DMA是AHB总线的设备开启AHB外设时钟DMA_InitTypeDef DMA_InitStructure;DMA_InitStructure.DMA_PeripheralBaseAddrAddrA;//外设站点的起始地址DMA_InitStructure.DMA_PeripheralDataSizeDMA_PeripheralDataSize_Byte;//数据长度DMA_InitStructure.DMA_PeripheralIncDMA_PeripheralInc_Enable;//是否自增DMA_InitStructure.DMA_MemoryBaseAddrAddrB;//存储器站点的起始地址DMA_InitStructure.DMA_MemoryDataSizeDMA_MemoryDataSize_Byte;//数据长度DMA_InitStructure.DMA_MemoryIncDMA_MemoryInc_Enable;//是否自增DMA_InitStructure.DMA_BufferSizeSize;//缓存区大小(传输计数器)DMA_InitStructure.DMA_DIRDMA_DIR_PeripheralSRC;//设置外设寄存器为目的还是起始这里是起源DMA_InitStructure.DMA_M2MDMA_M2M_Enable;//设置软件触发还是硬件触发这里是软件DMA_InitStructure.DMA_ModeDMA_Mode_Normal;//是否开启自动重装器DMA_InitStructure.DMA_PriorityDMA_Priority_Medium;//设置传输通道优先级。DMA_Init(DMA1_Channel1,DMA_InitStructure);DMA_Cmd(DMA1_Channel1,ENABLE);}voidMyDMA_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);//等待转运完成DMA_ClearFlag(DMA1_FLAG_TC1);//手动清除标志位}这个代码是两个数组之间的数据转运。这里目标数据宽度比原端数据宽度大时那么就会在数据前面多出来的空位自动补零例如源端数据宽度为8目标数据宽度为16时源端地址/数据0x00xB1,转运后目标地址/数据0x00x00B1如果目标数据宽度比原端数据宽度小时那么就会舍弃高位数据例如源端数据宽度为16目标数据宽度为8时源端地址/数据0x0/0xB1B2,转运后目标地址/数据0x0/0xB2所以在传输地址时要将数据类型强转为32位无符号整型数据以免出错可见配置DMA大部分是用一个DMA_Init函数配置结构体的各个参数就行了结构体参数DMA_PeripheralInc和DMA_MemoryInc决定地址是否自增我们要进行数组之间的传递见下图这里如果要实现两个数组转运数据就都要将DMA_PeripheralInc和DMA_MemoryInc配置为地址自增。DMA_DIR传输方向选择有两个参数 DMA_DIR_PeripheralSRC选择外设地址为源地址和DMA_DIR_PeripheralDST选择外设地址为目的地址这里的外设只是个名字只是为了区分要怎么配置可以按照实际需要选择我们要把数组A的元素传递给数组B又把数组A的首地址放在了外设站点所以选择外设地址为源地址。DMA_M2M选择硬件或软件触发ENABLE是软件触发DISABLE是硬件触发我们要实现数组之间的传递因此不需要用到硬件触发所以选择软件触发那么自动重装就不能选了。最后使能DMA这里通道数可以选择1~7任意一个。在同时满足触发源有触发信号(软件触发一直有触发信号)、传输计数器大于0DMA使能这三个条件DMA就会开始工作。这里还没给DMA使能和给传输计数器赋值。在while循环里不断调用自定义的第二个函数就能不断的将变化的数组A里的值传递到数组B里比如每秒都将数组A里的元素1可以用OLED观察数组的变化情况。注意写传输计数器时必须要先关闭DMA再进行,这也是第二个自定义函数先关闭DMA使能的原因。ADC扫描模式DMA转运这里因为ADC选择扫描模式会自动将每个序列扫描一遍所以无需配置地址自增但DMA存储地址要配置。ADC转换中我们选择的是非扫描模式是因为DR寄存器只有一个位置如果选择扫描模式就会不断将数据转运到DR寄存器那么就会把前面的数据覆盖掉所以这里DMA转运就派上用场了我们可以将DR寄存器里面的数据转运到SRAM数组里面去。这里如果ADC选择的是单次扫描那么可以不配置自动重装转换一轮就停止如果选择的是连续扫描那么就要配置自动重装在ADC启动下一轮转换的时候DMA也启动下一轮转运。可以看到这里选择的是ADC硬件触发因为这里ADC_DR的值要在ADC单个通道转换完成才有效所以DMA的转运时机要与ADC单个通道转换完成同步。初始化配置uint16_tAD_Value[4];voidAD_Init(void){RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);//ADC都是挂载在APB2总线上RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);//DMA是AHB总线的设备开启AHB外设时钟//配置ADC时钟RCC_ADCCLKConfig(RCC_PCLK2_Div6);//选择6分频 ADC时钟最大频率为14MHzGPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_ModeGPIO_Mode_AIN;//选择模拟输入模式 ADC专属GPIO_InitStructure.GPIO_PinGPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;GPIO_InitStructure.GPIO_SpeedGPIO_Speed_50MHz;GPIO_Init(GPIOA,GPIO_InitStructure);ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);//设置指定的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);//初始化ADCADC_InitTypeDef ADC_InitStructure;ADC_InitStructure.ADC_ModeADC_Mode_Independent;//选择独立模式还是双ADC模式ADC_InitStructure.ADC_DataAlignADC_DataAlign_Right;//选择数据对齐方式ADC_InitStructure.ADC_ExternalTrigConvADC_ExternalTrigConv_None;//外部触发转换选择(触发控制的触发源选择)ADC_InitStructure.ADC_ContinuousConvModeENABLE;//选择连续转换ADC_InitStructure.ADC_ScanConvModeENABLE;//选择扫描模式ADC_InitStructure.ADC_NbrOfChannel4;//选择通道数目(1~16)ADC_Init(ADC1,ADC_InitStructure);//初始化DMADMA_InitTypeDef DMA_InitStructure;DMA_InitStructure.DMA_PeripheralBaseAddr(uint32_t)ADC1-DR;//ADC转换结果放在DR寄存器里防止数据被覆盖从这里取数据DMA_InitStructure.DMA_PeripheralDataSizeDMA_PeripheralDataSize_HalfWord;//DR寄存器只有低16位有效所以选择半字长度DMA_InitStructure.DMA_PeripheralIncDMA_PeripheralInc_Disable;//不自增始终转运DR的数据DMA_InitStructure.DMA_MemoryBaseAddr(uint32_t)AD_Value;//存储器站点的起始地址DMA_InitStructure.DMA_MemoryDataSizeDMA_MemoryDataSize_HalfWord;//数据长度DMA_InitStructure.DMA_MemoryIncDMA_MemoryInc_Enable;//选择自增DMA_InitStructure.DMA_DIRDMA_DIR_PeripheralSRC;//传输方向DMA_InitStructure.DMA_BufferSize4;//缓存区大小(传输计数器)DMA_InitStructure.DMA_ModeDMA_Mode_Circular;//传输模式(是否自动重装)DMA_InitStructure.DMA_M2MDMA_M2M_Disable;//硬件触发|软件触发DMA_InitStructure.DMA_PriorityDMA_Priority_Medium;//优先级DMA_Init(DMA1_Channel1,DMA_InitStructure);//使能DMADMA_Cmd(DMA1_Channel1,ENABLE);//开启ADC到DMA的通道ADC_DMACmd(ADC1,ENABLE);//上电ADCADC_Cmd(ADC1,ENABLE);//对ADC进行校准ADC_ResetCalibration(ADC1);//这里会将复位校准状态标志位置1while(ADC_GetResetCalibrationStatus(ADC1));//获取复位校准状态复位校准完后状态位标志为0跳出循环ADC_StartCalibration(ADC1);//开始校准while(ADC_GetCalibrationStatus(ADC1));//等待校准完成ADC_SoftwareStartConvCmd(ADC1,ENABLE);//触发转换选择软件触发}这里使用的是ADC的连续转换扫描模式外部有四个模拟信号输入初始化DMA时前六个函数和传递数组时不同外设地址源地址是ADC1的DR寄存器DMA是从这里转运数据的我们定义一个数组用来存放转换值那么存储器地址目的地址就是数组名注意数据类型要强转为32位无符号整型数据长度选择半字16位长度因为DR寄存器只有低16位有效外设地址不自增、存储器地址自增参考图3来配置传输计数器选择4传输模式选择自动重装模式那么对应只能选择硬件触发DMA_M2M_Disable就是硬件触发最重要的一点要打开ADC1到DMA的通道使用ADC_DMACmd函数ADC就会硬件触发DMA不需要我们手动检测标志位等操作。注意DMA初始化时只能选择通道1(如下图所示)DMA的外设触发源要想是ADC1那么只能走通道1这条路DMA1_Channel1下面是获取DMA转运的数据代码voidADC_GetValue(void){DMA_Cmd(DMA1_Channel1,DISABLE);DMA_SetCurrDataCounter(DMA1_Channel1,4);//重新给传输计数器配值DMA_Cmd(DMA1_Channel1,ENABLE);ADC_SoftwareStartConvCmd(ADC1,ENABLE);//触发转换选择软件触发while(DMA_GetFlagStatus(DMA1_FLAG_TC1)RESET);//等待转运完成DMA_ClearFlag(DMA1_FLAG_TC1);}回顾ADC转换内容ADC开始工作是需要一次软件触发的所以在初始化的最后一条我们还需要加一个软件触发ADC因为我们选择的是ADC的连续转换模式所以只需要触发一次。因为DMA转运也是正常的单次转运所以我们在转运之前也需要重新配置一些传输计数器。因为转运总是在转换后发生的所以这里只需要等待转运标志位即可。以上就是全部代码示例只需要在主函数里调用一次初始化代码然后调用ADC_GetValue函数ADC就会开始转换连续扫描四个通道DMA也同步进行转运ADC转换的值就会放在AD_Value[4]数组里。重要函数DMA_SetCurrDataCounter设置当前数据寄存器设置传输长度DMA_ITConfig开启DMA中断