赞
踩
DMA东西不多,但是有点绕。并且DMA是非常重要且常用的数据搬用外设。今天简单学习下。
DMA是可以直接访问stm32的所有存储器,例如Flash,SRAM等。所以你通过CPU配置了DMA,它就可以自动去各个外设搬运数据到指定的地方,CPU就不用来做这搬运数据的活了,只需要在指定的地方读取CPU所需要的数据即可。
Flash一般是只读,如果要将程序写入Flash,还需要先擦除页,再写入,这是另外一部分内容。然后还要将Boot引脚置对应的高低电平。这些在手册中都能找到。
对于软件触发和特定硬件触发,一般软件触发用在存储器到存储器,因为我们只需要把数据搬运过去,所以软件触发就好比,一直快速地去触发DMA,让它一下就把数据都搬运完。但是硬件触发一般用在外设到存储器,因为外设的数据到来是不定时的,需要有一定的信号 ,不能软件触发一下把所有都搬运完,说不定那个时候外设数据还没采集到呢!综上,软件触发一般用在存储器到存储器;硬件触发一般用在外设到存储器。
这些地址在数据手册中都可以查找到。
ROM(只读存储器)和RAM(随机存取存储器)是两种重要的存储器类型
所以stm32单片机的存储地址0-FFFFFFFF,这是个非常大的存储空间,4GB。但是事实上,有很多的空间都是空的,stm32本身只有几kb的容量。可以看到都是有对应的地址的,外设的地址就是
0x4000 0000开始。可以在单片机程序中,对变量取地址,再强转,把他的地址拿出来看看和这个有什么联系。
- int a;
- const int b;
- int main(void)
- {
- OLED_Init();
- while(1)
- {
- OLED_ShowHexNum(1, 1, (uint32_t)&a, 8);
- OLED_ShowHexNum(2, 1, (uint32_t)&ADC1->DR, 8);
- OLED_ShowHexNum(3, 1, (uint32_t)&b, 8);
- }
- }
显示这几个变量的地址
发现和我们阅读手册的地址是吻合的。
虽然DMA有多个通道,但是DMA的总线却只有一条,所以所有的通道只能分时复用这个总线。然后通过总线的仲裁器来判断优先级,来决定谁先过。
起始地址就是填你需要搬用的数据的源头以及目的地的地址;数据宽度则是一次搬用多大的数据,有8位,16位,32位;地址是否自增,指的是搬用完一次之后是否自增地址;传输计数器,就是要转运数据的次数,每转运一次,计数器就会减1;而自动重装载器,就是在传输计数器变为0的时候重新赋值给传输计数器,达到自动开启下一轮的搬运;M2M就是选择硬件触发或者软件触发,所以软件触发和自动重装一起用,软件触发是一直触发DMA,这样DMA就停不下了;在修改传输计数器的值的时候必须先关闭DMA,修改完毕后再打开,手册里面规定的。
不同的通道对应不同的外设请求信号。要注意。
这个是针对两个传输站点不同的数据类型来设置的。
概括:小的转大的,高位舍弃;大的转小的,高位补零
将数组A中的数据每秒+1后,转运到数组B中,+1后延迟一小会,方便看实验现象。
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
- DMA_InitTypeDef DMA_InitStructure;
- DMA_InitStructure.DMA_MemoryBaseAddr = AddrB; //存储器地址
- DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte ; //数据8位
- DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //地址是否自增
- DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA; //外设地址
- DMA_InitStructure.DMA_PeripheralDataSize = DMA_MemoryDataSize_Byte; //数据8位
- DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable; //地址是否自增
- DMA_InitStructure.DMA_BufferSize = Size; //计数器
- DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //传输方向
- DMA_InitStructure.DMA_M2M = DMA_M2M_Enable ; //选择硬件触发或软件触发,打开软件触发
- DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ; //选择DMA的传输模式为不连续,连续or单次
- DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA优先级
-
- DMA_Init(DMA1_Channel1,&DMA_InitStructure);
上面6个为存储器的配置,分别配置地址;数据位为8位;地址自增;
再配置BufferSize,就是计数器,初始化数组有4个数据,所以配置为4;再配置传输的方向,选择外设为源端,即外设传递数据到存储器;配置为软件触发;DMA的模式选择为不连续模式;到这DMA的配置就完成了。
DMA_Cmd(DMA1_Channel1, ENABLE);
当使能DMA后,立刻开始数据转运,转运完成后(计数器为0后)停止。所以需要我们再次写入BufferSize值。
- void MyDMA_Transfer(void)
- {
- DMA_Cmd(DMA1_Channel1, DISABLE); //DMA失能,在写入传输计数器之前,需要DMA暂停工作
- DMA_SetCurrDataCounter(DMA1_Channel1, 4); //写入传输计数器,指定将要转运的次数
- DMA_Cmd(DMA1_Channel1, ENABLE); //DMA使能,开始工作
-
- while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET); //等待DMA工作完成
- DMA_ClearFlag(DMA1_FLAG_TC1); //清除工作完成标志位
- }
先关闭DMA,调用函数写入计数器值;然后再开启DMA;然后检查DMA传输完成标志位(还可以检查传输一半等标志位),最后清除标志位等待下一次。
所以每调用一次函数,就转运一次数据。
循环模式的话,是不需要判断标志位,因为循环模式会自动重新装载计数器的值。所以不需要调用函数来开启转运,直接修改DMA的配置即可。
- void DMA_User_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size)
- {
- MyDMA_Size = Size;
- RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);//开启DMA的时钟
-
- DMA_InitTypeDef DMA_InitStructure;
- DMA_InitStructure.DMA_MemoryBaseAddr = AddrB; //存储器地址
- DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte ; //数据8位
- DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //地址是否自增
- DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA; //外设地址
- DMA_InitStructure.DMA_PeripheralDataSize = DMA_MemoryDataSize_Byte; //数据8位
- DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable; //地址是否自增
- DMA_InitStructure.DMA_BufferSize = Size; //计数器
- DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //传输方向
- DMA_InitStructure.DMA_M2M = DMA_M2M_Enable ; //选择硬件触发或软件触发,打开软件触发。硬件触发一般和外设配合
- DMA_InitStructure.DMA_Mode = DMA_Mode_Circular ; //选择DMA的传输模式为不连续,连续or单次
- DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA优先级
-
- DMA_Init(DMA1_Channel1,&DMA_InitStructure);
- DMA_Cmd(DMA1_Channel1, ENABLE); //打开DMA,转运后等待DMA关闭,重新写入计数器
- }
修改DMA的模式为循环模式既可。开启一次DMA即可,不需要反复地去开启关闭修改BufferSize值。
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
- RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //开启DMA1的时钟
-
- RCC_ADCCLKConfig(RCC_PCLK2_Div6); //选择时钟6分频,ADCCLK = 72MHz / 6 = 12MHz
-
- GPIO_InitTypeDef GPIO_InitStructure;
- GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN;
- GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 |GPIO_Pin_3;
- GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
- GPIO_Init(GPIOA,&GPIO_InitStructure);
-
- ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);
- 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);
-
- ADC_InitTypeDef ADC_InitStructure;
- ADC_InitStructure.ADC_ContinuousConvMode=ENABLE;
- ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right;
- ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;
- ADC_InitStructure.ADC_Mode=ADC_Mode_Independent;
- ADC_InitStructure.ADC_NbrOfChannel=4;
- ADC_InitStructure.ADC_ScanConvMode=ENABLE;
- ADC_Init(ADC1,&ADC_InitStructure);
-
- /*DMA初始化*/
- DMA_InitTypeDef DMA_InitStructure;
- DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; //外设基地址,ADC1储存数据的地址
- DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //外设数据宽度,选择半字,对应16为的ADC数据寄存器
- DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址自增,选择失能,始终以ADC数据寄存器为源
- DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value; //存储器基地址,给定存放AD转换结果的全局数组AD_Value
- DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //存储器数据宽度,选择半字,与源数据宽度对应
- DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //存储器地址自增,选择使能,每次转运后,数组移到下一个位置
- DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //数据传输方向,选择由外设到存储器,ADC数据寄存器转到数组
- DMA_InitStructure.DMA_BufferSize = 4; //转运的数据大小(转运次数),与ADC通道数一致
- DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //模式,选择循环模式,与ADC的连续转换一致
- DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //存储器到存储器,选择失能,数据由ADC外设触发转运到存储器
- DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //优先级,选择中等
- DMA_Init(DMA1_Channel1, &DMA_InitStructure); //将结构体变量交给DMA_Init,配置DMA1的通道1
-
- DMA_Cmd(DMA1_Channel1,ENABLE);
- ADC_Cmd(ADC1, ENABLE); //使能ADC1,ADC开始运行
- ADC_DMACmd(ADC1,ENABLE);
-
- ADC_ResetCalibration(ADC1); //固定流程,内部有电路会自动执行校准
- while (ADC_GetResetCalibrationStatus(ADC1) == SET);
- ADC_StartCalibration(ADC1);
- while (ADC_GetCalibrationStatus(ADC1) == SET);
- ADC_SoftwareStartConvCmd(ADC1, ENABLE); //软件触发AD转换一次
ADC正常配置,我想要硬件全自动,那我就选择开启连续转换,并且我4路的ADC通道,我再开启扫描模式,一次扫描4个ADC通道,再进行转换,再扫描,再转换。
DMA配置要注意,外设的地址自增应该关闭,这样才能保持我们的数据源头一直是ADC1,这样印证了ADC的数据覆盖问题,我们需要DMA一直不断地把数据搬走,不然就覆盖了。然后要开启数据目的地地地址自增,这样才能分开存入我们想要存入地地方。然后开启循环模式。关闭软件触发,开启硬件触发,硬件地触发源为ADC1,这边没有特定参数配置DMA的触发源。
但是可以开启ADC到DMA的通道,这样ADC一有数据过来,DMA就搬用,一有数据过来DAM就搬运。
然后我们调用目的地的数组就可以查看ADC的数据了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。