当前位置:   article > 正文

七、Stm32学习-DMA-循环模式与非循环模式_stm32的dma连续模式

stm32的dma连续模式

DMA东西不多,但是有点绕。并且DMA是非常重要且常用的数据搬用外设。今天简单学习下。

1.DMA简介

DMA是可以直接访问stm32的所有存储器,例如Flash,SRAM等。所以你通过CPU配置了DMA,它就可以自动去各个外设搬运数据到指定的地方,CPU就不用来做这搬运数据的活了,只需要在指定的地方读取CPU所需要的数据即可。

Flash一般是只读,如果要将程序写入Flash,还需要先擦除页,再写入,这是另外一部分内容。然后还要将Boot引脚置对应的高低电平。这些在手册中都能找到。

对于软件触发和特定硬件触发,一般软件触发用在存储器到存储器,因为我们只需要把数据搬运过去,所以软件触发就好比,一直快速地去触发DMA,让它一下就把数据都搬运完。但是硬件触发一般用在外设到存储器,因为外设的数据到来是不定时的,需要有一定的信号 ,不能软件触发一下把所有都搬运完,说不定那个时候外设数据还没采集到呢!综上,软件触发一般用在存储器到存储器;硬件触发一般用在外设到存储器。

2.存储器映像

这些地址在数据手册中都可以查找到。

ROM(只读存储器)和RAM(随机存取存储器)是两种重要的存储器类型

所以stm32单片机的存储地址0-FFFFFFFF,这是个非常大的存储空间,4GB。但是事实上,有很多的空间都是空的,stm32本身只有几kb的容量。可以看到都是有对应的地址的,外设的地址就是

0x4000 0000开始。可以在单片机程序中,对变量取地址,再强转,把他的地址拿出来看看和这个有什么联系。

  1. int a;
  2. const int b;
  3. int main(void)
  4. {
  5. OLED_Init();
  6. while(1)
  7. {
  8. OLED_ShowHexNum(1, 1, (uint32_t)&a, 8);
  9. OLED_ShowHexNum(2, 1, (uint32_t)&ADC1->DR, 8);
  10. OLED_ShowHexNum(3, 1, (uint32_t)&b, 8);
  11. }
  12. }

显示这几个变量的地址

发现和我们阅读手册的地址是吻合的。

3. DMA结构框图

虽然DMA有多个通道,但是DMA的总线却只有一条,所以所有的通道只能分时复用这个总线。然后通过总线的仲裁器来判断优先级,来决定谁先过。

4.DMA的基本结构

 起始地址就是填你需要搬用的数据的源头以及目的地的地址;数据宽度则是一次搬用多大的数据,有8位,16位,32位;地址是否自增,指的是搬用完一次之后是否自增地址;传输计数器,就是要转运数据的次数,每转运一次,计数器就会减1;而自动重装载器,就是在传输计数器变为0的时候重新赋值给传输计数器,达到自动开启下一轮的搬运;M2M就是选择硬件触发或者软件触发,所以软件触发和自动重装一起用,软件触发是一直触发DMA,这样DMA就停不下了;在修改传输计数器的值的时候必须先关闭DMA,修改完毕后再打开,手册里面规定的。

5.DMA请求

不同的通道对应不同的外设请求信号。要注意。

6.数据宽度与对齐

这个是针对两个传输站点不同的数据类型来设置的。

概括:小的转大的,高位舍弃;大的转小的,高位补零

7.程序编写-DMA数据转运-非循环模式

将数组A中的数据每秒+1后,转运到数组B中,+1后延迟一小会,方便看实验现象。

(1)开启DMA的外设时钟

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);

(2)DMA的配置

  1. DMA_InitTypeDef DMA_InitStructure;
  2. DMA_InitStructure.DMA_MemoryBaseAddr = AddrB; //存储器地址
  3. DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte ; //数据8位
  4. DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //地址是否自增
  5. DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA; //外设地址
  6. DMA_InitStructure.DMA_PeripheralDataSize = DMA_MemoryDataSize_Byte; //数据8位
  7. DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable; //地址是否自增
  8. DMA_InitStructure.DMA_BufferSize = Size; //计数器
  9. DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //传输方向
  10. DMA_InitStructure.DMA_M2M = DMA_M2M_Enable ; //选择硬件触发或软件触发,打开软件触发
  11. DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ; //选择DMA的传输模式为不连续,连续or单次
  12. DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA优先级
  13. DMA_Init(DMA1_Channel1,&DMA_InitStructure);

上面6个为存储器的配置,分别配置地址;数据位为8位;地址自增;

再配置BufferSize,就是计数器,初始化数组有4个数据,所以配置为4;再配置传输的方向,选择外设为源端,即外设传递数据到存储器;配置为软件触发;DMA的模式选择为不连续模式;到这DMA的配置就完成了。

(3)DMA的使能

DMA_Cmd(DMA1_Channel1, ENABLE);

当使能DMA后,立刻开始数据转运,转运完成后(计数器为0后)停止。所以需要我们再次写入BufferSize值。

(4)编写修改DMA的BufferSize值(计数器值)函数

  1. void MyDMA_Transfer(void)
  2. {
  3. DMA_Cmd(DMA1_Channel1, DISABLE); //DMA失能,在写入传输计数器之前,需要DMA暂停工作
  4. DMA_SetCurrDataCounter(DMA1_Channel1, 4); //写入传输计数器,指定将要转运的次数
  5. DMA_Cmd(DMA1_Channel1, ENABLE); //DMA使能,开始工作
  6. while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET); //等待DMA工作完成
  7. DMA_ClearFlag(DMA1_FLAG_TC1); //清除工作完成标志位
  8. }

先关闭DMA,调用函数写入计数器值;然后再开启DMA;然后检查DMA传输完成标志位(还可以检查传输一半等标志位),最后清除标志位等待下一次。

所以每调用一次函数,就转运一次数据。

8.程序编写-DMA数据转运-循环模式

循环模式的话,是不需要判断标志位,因为循环模式会自动重新装载计数器的值。所以不需要调用函数来开启转运,直接修改DMA的配置即可。

  1. void DMA_User_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size)
  2. {
  3. MyDMA_Size = Size;
  4. RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);//开启DMA的时钟
  5. DMA_InitTypeDef DMA_InitStructure;
  6. DMA_InitStructure.DMA_MemoryBaseAddr = AddrB; //存储器地址
  7. DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte ; //数据8位
  8. DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //地址是否自增
  9. DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA; //外设地址
  10. DMA_InitStructure.DMA_PeripheralDataSize = DMA_MemoryDataSize_Byte; //数据8位
  11. DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable; //地址是否自增
  12. DMA_InitStructure.DMA_BufferSize = Size; //计数器
  13. DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //传输方向
  14. DMA_InitStructure.DMA_M2M = DMA_M2M_Enable ; //选择硬件触发或软件触发,打开软件触发。硬件触发一般和外设配合
  15. DMA_InitStructure.DMA_Mode = DMA_Mode_Circular ; //选择DMA的传输模式为不连续,连续or单次
  16. DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA优先级
  17. DMA_Init(DMA1_Channel1,&DMA_InitStructure);
  18. DMA_Cmd(DMA1_Channel1, ENABLE); //打开DMA,转运后等待DMA关闭,重新写入计数器
  19. }

修改DMA的模式为循环模式既可。开启一次DMA即可,不需要反复地去开启关闭修改BufferSize值。

9.程序编写-DMA配合ADC数据转运-循环模式

  1. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
  2. RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
  3. RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //开启DMA1的时钟
  4. RCC_ADCCLKConfig(RCC_PCLK2_Div6); //选择时钟6分频,ADCCLK = 72MHz / 6 = 12MHz
  5. GPIO_InitTypeDef GPIO_InitStructure;
  6. GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN;
  7. GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 |GPIO_Pin_3;
  8. GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
  9. GPIO_Init(GPIOA,&GPIO_InitStructure);
  10. ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);
  11. ADC_RegularChannelConfig(ADC1,ADC_Channel_1,2,ADC_SampleTime_55Cycles5);
  12. ADC_RegularChannelConfig(ADC1,ADC_Channel_2,3,ADC_SampleTime_55Cycles5);
  13. ADC_RegularChannelConfig(ADC1,ADC_Channel_3,4,ADC_SampleTime_55Cycles5);
  14. ADC_InitTypeDef ADC_InitStructure;
  15. ADC_InitStructure.ADC_ContinuousConvMode=ENABLE;
  16. ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right;
  17. ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;
  18. ADC_InitStructure.ADC_Mode=ADC_Mode_Independent;
  19. ADC_InitStructure.ADC_NbrOfChannel=4;
  20. ADC_InitStructure.ADC_ScanConvMode=ENABLE;
  21. ADC_Init(ADC1,&ADC_InitStructure);
  22. /*DMA初始化*/
  23. DMA_InitTypeDef DMA_InitStructure;
  24. DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; //外设基地址,ADC1储存数据的地址
  25. DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //外设数据宽度,选择半字,对应16为的ADC数据寄存器
  26. DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址自增,选择失能,始终以ADC数据寄存器为源
  27. DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value; //存储器基地址,给定存放AD转换结果的全局数组AD_Value
  28. DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //存储器数据宽度,选择半字,与源数据宽度对应
  29. DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //存储器地址自增,选择使能,每次转运后,数组移到下一个位置
  30. DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //数据传输方向,选择由外设到存储器,ADC数据寄存器转到数组
  31. DMA_InitStructure.DMA_BufferSize = 4; //转运的数据大小(转运次数),与ADC通道数一致
  32. DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //模式,选择循环模式,与ADC的连续转换一致
  33. DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //存储器到存储器,选择失能,数据由ADC外设触发转运到存储器
  34. DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //优先级,选择中等
  35. DMA_Init(DMA1_Channel1, &DMA_InitStructure); //将结构体变量交给DMA_Init,配置DMA1的通道1
  36. DMA_Cmd(DMA1_Channel1,ENABLE);
  37. ADC_Cmd(ADC1, ENABLE); //使能ADC1,ADC开始运行
  38. ADC_DMACmd(ADC1,ENABLE);
  39. ADC_ResetCalibration(ADC1); //固定流程,内部有电路会自动执行校准
  40. while (ADC_GetResetCalibrationStatus(ADC1) == SET);
  41. ADC_StartCalibration(ADC1);
  42. while (ADC_GetCalibrationStatus(ADC1) == SET);
  43. ADC_SoftwareStartConvCmd(ADC1, ENABLE); //软件触发AD转换一次

ADC正常配置,我想要硬件全自动,那我就选择开启连续转换,并且我4路的ADC通道,我再开启扫描模式,一次扫描4个ADC通道,再进行转换,再扫描,再转换。

DMA配置要注意,外设的地址自增应该关闭,这样才能保持我们的数据源头一直是ADC1,这样印证了ADC的数据覆盖问题,我们需要DMA一直不断地把数据搬走,不然就覆盖了。然后要开启数据目的地地地址自增,这样才能分开存入我们想要存入地地方。然后开启循环模式。关闭软件触发,开启硬件触发,硬件地触发源为ADC1,这边没有特定参数配置DMA的触发源。

但是可以开启ADC到DMA的通道,这样ADC一有数据过来,DMA就搬用,一有数据过来DAM就搬运。

然后我们调用目的地的数组就可以查看ADC的数据了。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/584454
推荐阅读
相关标签
  

闽ICP备14008679号