• STM32之DMA


    简介

    DMA Direct Memory Access )直接存储器存取
    (可以直接访问STM32内部存储器,如SRAM、程序存储器Flash和寄存器等)
    DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源(外设指的是外设的寄存器)
    12 个独立可配置的通道: DMA1 7 个通道), DMA2 5 个通道)
    每个通道都支持软件触发和特定的硬件触发
    (特定的:每个DMA通道的硬件触发源不一样)
    (软件触发:会一股脑的把这批数据以最快的速度全部转运完成)
    (硬件触发:硬件触发一次,转运一次)
    (存储器到存储器的转运一般使用软件触发,外设到存储器的数据转运一般使用硬件触发)
    STM32F103C8T6 DMA 资源: DMA1 7 个通道)

     存储器映像

    ROM:只读存储器,是一种非易失性、掉电不丢失的存储器

    RAM:随机存储器,是一种易失性、掉电丢失的存储器

    DMA框图 

    使用DMA进行数据转运可以归为一类问题—— 从某个地址取内容,然后再放到另一个地址去

            为了高效有条理地访问存储器,设计了一个总线矩阵,其左端是主动单元,拥有存储器的访问权,右边是被动单元,它们的存储器只能被左边的主动单元读写。

            主动单元中内核有DCode和系统总线,可以访问右边的存储器。DCode专门访问Flash。

            DMA需要转运数据,所以DMA也有访问的主动权,DMA1、DMA2和以太网都有各自的DMA总线。DMA1有七个通道,各个通道可以分别设置它们转运数据的源地址和目的地址,这样它们就可以各自独立地工作了。

           虽然多个通道可以独立转运数据,但是因为DMA的总线只有一条,所以的通道都只能分时复用这一条DMA总线,如果产生冲突,那就会由仲裁器,根据通道的优先级决定先转运哪一条通道的数据。(另外总线矩阵中也有一个仲裁器,如果DMA和CPU都要访问同一个目标,那么DMA就会暂停CPU的访问,以防止冲突)。

        因为DMA作为一个外设,它也有相对应的配置寄存器——AHB从设备。所以DMA即是总线矩阵的主动单元,可以读写各自存储器,也是AHB总线上的被动单元。   

     DMA请求:即触发,这条线路右边的触发源是各个外设,所以这个DMA请求就是DMA的硬件触发源(比如ADC转换完成,需要触发DMA转运数据时,就会通过这条线路,向DMA发出硬件触发信号)

    基本结构

     起始地址:有外设端的起始地址和存储器端的起始地址,决定里的数据从哪里来到哪里去的

    数据宽度:指定一次转运要按多大的数据宽度来进行,可以选择字节Byte(8位,uint8_t)、半字HalfWord(16位,uint16_t)和字Word(32位,uint32_t)

    地址是否自增:指定一次转运完成后,下一次转运是不是要把地址移动到下一个位置去,相当于指针p++(比如ADC扫描模式,用DMA进行数据转运,外设地址是ADC_DR寄存器,寄存器这边显然地址是不用自增的,如果自增,下一次转运就跑到别的寄存器那里去了;存储器这边地址就需要自增了,每转运一次都需要往后挪个位置,不然就会导致下次再转把上次的数据覆盖掉)

    传出计数器:是个自减的计数器,输入一个数字,每传输一次数据,计数就自减,当计数为0时,停止转换;

    自动重装器:(即转换完一轮是否需要再来一轮)当传输计数器计数为0时,自动重装器就可以决定是否从计数的初始置重新开始计数,不重装,就是正常的单次模式,如果重装,则是循环模式。如果是ADC扫描模式+连续转换,为了配合ADC,则DMA也需要使用循环模式;

    触发控制:分为硬件触发和软件触发,有M2M这个参数控制选择哪一种。

    软件触发:以最快的速度连续不断地触发DMA,争取最快速把传输计数器清零,完成这一轮转换,不可以和自动重装器(循环模式)搭配使用,因为这样下去就是无限循环触发DMA,运用在存储器到存储器转运的情况

    硬件触发:可以选择ADC、串口、定时器等,一般与外设有关的转运才会使用硬件触发,因为外设的转运(ADC转换完成,串口接收到信号等等)都有一个时机,当达到这个时机时触发DMA的转运。

    DMA转运的条件

    1、开关控制,DMA_Cmd必须使能;

    2、就是传输计数器必须大于0;

    3、必须有触发信号

    (当传输计数器为0时,无论怎么样都不能再触发DMA了,想要再次触发,就必须关闭开关控制,即给DMA_Cmd一个DISABLE信号,关闭DMA,再给传输计数器写入一个大于0的数,再打开开关控制,才能再次进行DMA转运,需要注意的是当传输计数器为0时,且开关控制在ENABLE状态下,是不能给传输计数器写入值的,需要关闭开关才可以写入)

    DMA请求

    硬件触发必须使用对应的通道,软件触发则随意

    那么一个通道如何判断是哪个外设发出的请求信号呢,会有相关函数初始化相关外设的通道

    数据宽度与对齐

    总结:大数据转到小的里去,舍弃高位,小数据转到大的里去,高位补0

    举个栗子

    数据转运+DMA

    需要把DataA的数据转运到DataB上去

    应该如何配置DMA的数据

    首先外设地址填DataA,存储器地址填DataB;数据宽度皆为uint8_t;地址是否自增:是;

    传输计数器给7;不需要自动重装,软件触发(因为是存储器到存储器的转运,不需要等待时机)

     ADC扫描模式+DMA

    扫描模式下,ADC会自动转换从序列1到最后的序列的数据,不需要地址自增,而DMA储存器需要地址自增;因为ADC转换完一个通道后既不会置标志位,也不会触发中断,按理说是无法体现“时机”的,但是据研究表明ADC转换完一个序列的数据后,会自动触发DMA的数据转运,所以这里选择硬件触发。ADC的扫描模式不使用DMA,功能会受到很大的限制。

     拓展

    利用结构体访问寄存器的地址(原理详见江科大P247min)

    比如要访问ADC1的DR寄存器

    1. #include "stm32f10x.h" // Device header
    2. #include "Delay.h"
    3. #include "OLED.h"
    4. int main(void)
    5. {
    6. OLED_Init();
    7. //要输入地址信息就需要强制转换为该类型
    8. OLED_ShowHexNum(1, 1, (uint32_t)&ADC1->DR, 8);
    9. while(1)
    10. {
    11. }
    12. }

    OLED上显示地址为:4001244C,这个地址是固定的,可以通过数据手册查出来的。

    先打开数据手册的2.3存储器映像,可查到ADC1的地址起始位为0x40012400

    然后再打开11.12.15ADC寄存器地址映像,可查到ADC_DR的偏移量为4C

    综合可得到ADC1_DR的地址为0x4001244C。

    如果想查某个寄存器的地址,可以先通过查存储器映像,查出其起始地址,然后再在外设的寄存器总表中查其偏移量,即可计算得出其地址了。

    代码实操

    DMA数据转运

    了解相关函数

    老朋友了

    1. void DMA_DeInit(DMA_Channel_TypeDef* DMAy_Channelx);
    2. void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct);
    3. void DMA_StructInit(DMA_InitTypeDef* DMA_InitStruct);
    4. void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState);

    中断输出使能

    void DMA_ITConfig(DMA_Channel_TypeDef* DMAy_Channelx, uint32_t DMA_IT, FunctionalState NewState);
    

     DMA设置当前数据寄存器(即给传输计数器写数据的)

    void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber); 
    

     DMA获取当前数据寄存器(返回传输计数器的值)

    uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);
    
    1. //获取标志位状态
    2. FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);
    3. //清除标志位
    4. void DMA_ClearFlag(uint32_t DMAy_FLAG);
    5. //获取中断状态
    6. ITStatus DMA_GetITStatus(uint32_t DMAy_IT);
    7. //清除中断挂起位
    8. void DMA_ClearITPendingBit(uint32_t DMAy_IT);

    按照下图编写代码

    先建立一个数组作为传输的数据,建立另一个数组用于接收数据

     再建立一个在System中建立模块(因为DMA不涉及外围硬件电路)

    1、RCC开启DMA时钟

    1. //开启时钟,DMA是AHB总线的设备
    2. RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

    2、调用DMA_Init初始化各个参数

    1. //DMA初始化
    2. DMA_InitTypeDef DMA_InitStructure;
    3. //外设站点的起始地址、数据宽度、是否自增
    4. DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA;//数组名,需要传输数据的数组
    5. DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//以字节为单位传输
    6. DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;//自增
    7. //存储器的起始地址、数据宽度、是否自增
    8. DMA_InitStructure.DMA_MemoryBaseAddr = AddrB;//存储数据的数组
    9. DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    10. DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    11. //传输方向
    12. //SRC-从外设站点传输到存储器站点,DST-从存储器传输到外设站点
    13. DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    14. //缓冲器大小(传输计数器)
    15. DMA_InitStructure.DMA_BufferSize = BufferSize;
    16. //传输模式(是否启用自动重装)(不自动重装)
    17. DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    18. //是否是存储器到存储器(硬件触发还是软件触发)
    19. //Enable-软件触发,Disable-硬件触发
    20. DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;
    21. //优先级(一个随便选
    22. DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
    23. //y决定是哪个DMA,x决定是DMA的哪个通道
    24. DMA_Init(DMA1_Channel1, &DMA_InitStructure);

    3、开关控制

    	DMA_Cmd(DMA1_Channel1, ENABLE);

    (还可以调用中断函数)

    得初始化函数

    1. #include "stm32f10x.h" // Device header
    2. //(传出数据的数组,传入数据的数组,传输数据的数量)
    3. void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t BufferSize)
    4. {
    5. //开启时钟,DMA是AHB总线的设备
    6. RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
    7. //DMA初始化
    8. DMA_InitTypeDef DMA_InitStructure;
    9. //外设站点的起始地址、数据宽度、是否自增
    10. DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA;//数组名,需要传输数据的数组
    11. DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//以字节为单位传输
    12. DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;//自增
    13. //存储器的起始地址、数据宽度、是否自增
    14. DMA_InitStructure.DMA_MemoryBaseAddr = AddrB;//存储数据的数组
    15. DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    16. DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    17. //传输方向
    18. //SRC-从外设站点传输到存储器站点,DST-从存储器传输到外设站点
    19. DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    20. //缓冲器大小(传输计数器)
    21. DMA_InitStructure.DMA_BufferSize = BufferSize;
    22. //传输模式(是否启用自动重装)(不自动重装)
    23. DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    24. //是否是存储器到存储器(硬件触发还是软件触发)
    25. //Enable-软件触发,Disable-硬件触发
    26. DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;
    27. //优先级(一个随便选
    28. DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
    29. //y决定是哪个DMA,x决定是DMA的哪个通道
    30. DMA_Init(DMA1_Channel1, &DMA_InitStructure);
    31. DMA_Cmd(DMA1_Channel1, ENABLE);
    32. }

    在主函数中调用一下测试结果

    1. #include "stm32f10x.h" // Device header
    2. #include "Delay.h"
    3. #include "OLED.h"
    4. #include "MyDMA.h"
    5. uint8_t DataA[]={0x01, 0x02, 0x03, 0x04};//需要转运的数据
    6. uint8_t DataB[4];//用于存储数据的数组
    7. int main(void)
    8. {
    9. OLED_Init();
    10. //转运前
    11. OLED_ShowHexNum(1, 1, DataA[0], 2);
    12. OLED_ShowHexNum(1, 4, DataA[1], 2);
    13. OLED_ShowHexNum(1, 7, DataA[2], 2);
    14. OLED_ShowHexNum(1, 10, DataA[3], 2);
    15. OLED_ShowHexNum(2, 1, DataB[0], 2);
    16. OLED_ShowHexNum(2, 4, DataB[1], 2);
    17. OLED_ShowHexNum(2, 7, DataB[2], 2);
    18. OLED_ShowHexNum(2, 10, DataB[3], 2);
    19. MyDMA_Init((uint32_t)DataA, (uint32_t)DataB, 4);
    20. //转运后
    21. OLED_ShowHexNum(3, 1, DataA[0], 2);
    22. OLED_ShowHexNum(3, 4, DataA[1], 2);
    23. OLED_ShowHexNum(3, 7, DataA[2], 2);
    24. OLED_ShowHexNum(3, 10, DataA[3], 2);
    25. OLED_ShowHexNum(4, 1, DataB[0], 2);
    26. OLED_ShowHexNum(4, 4, DataB[1], 2);
    27. OLED_ShowHexNum(4, 7, DataB[2], 2);
    28. OLED_ShowHexNum(4, 10, DataB[3], 2);
    29. while(1)
    30. {
    31. }
    32. }

    第一二行分别是转运前的DataA和DataB,第三四行分别是转运前的DataA和DataB 

     测试得出转运结果没问题

    当需要转运的数据发生变化时,需要重新转运则就会需要重新给传输计数器写入新的值,这还需要关闭DMA的开关,再打开其开关。

    得出传输函数

    1. void MyDMA_Transfer(void)
    2. {
    3. //关闭DMA,写入传输计数器的值
    4. DMA_Cmd(DMA1_Channel1, DISABLE);
    5. DMA_SetCurrDataCounter(DMA1_Channel1, Size);
    6. //开启DMA
    7. DMA_Cmd(DMA1_Channel1, ENABLE);
    8. //获取标志位状态(即是否完成传输)
    9. while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);
    10. //清除标志位(方便下次继续传输)
    11. DMA_ClearFlag(DMA1_FLAG_TC1);
    12. }

    同时也需要修改主函数

    1. #include "stm32f10x.h" // Device header
    2. #include "Delay.h"
    3. #include "OLED.h"
    4. #include "MyDMA.h"
    5. uint8_t DataA[]={0x01, 0x02, 0x03, 0x04};//需要转运的数据
    6. uint8_t DataB[4];//用于存储数据的数组
    7. int main(void)
    8. {
    9. OLED_Init();
    10. OLED_ShowString(1, 1, "DataA:");
    11. OLED_ShowString(3, 1, "DataB:");
    12. MyDMA_Init((uint32_t)DataA, (uint32_t)DataB, 4);
    13. //显示两个数组的地址
    14. OLED_ShowHexNum(1, 8, (uint32_t)DataA, 8);
    15. OLED_ShowHexNum(3, 8, (uint32_t)DataB, 8);
    16. while(1)
    17. {
    18. //模拟数据的变化
    19. for(int i=0;i<4;i++)
    20. {
    21. DataA[i]++;
    22. }
    23. OLED_ShowHexNum(2, 1, DataA[0], 2);
    24. OLED_ShowHexNum(2, 4, DataA[1], 2);
    25. OLED_ShowHexNum(2, 7, DataA[2], 2);
    26. OLED_ShowHexNum(2, 10, DataA[3], 2);
    27. OLED_ShowHexNum(4, 1, DataB[0], 2);
    28. OLED_ShowHexNum(4, 4, DataB[1], 2);
    29. OLED_ShowHexNum(4, 7, DataB[2], 2);
    30. OLED_ShowHexNum(4, 10, DataB[3], 2);
    31. Delay_ms(1000);
    32. MyDMA_Transfer();
    33. OLED_ShowHexNum(2, 1, DataA[0], 2);
    34. OLED_ShowHexNum(2, 4, DataA[1], 2);
    35. OLED_ShowHexNum(2, 7, DataA[2], 2);
    36. OLED_ShowHexNum(2, 10, DataA[3], 2);
    37. OLED_ShowHexNum(4, 1, DataB[0], 2);
    38. OLED_ShowHexNum(4, 4, DataB[1], 2);
    39. OLED_ShowHexNum(4, 7, DataB[2], 2);
    40. OLED_ShowHexNum(4, 10, DataB[3], 2);
    41. Delay_ms(1000);
    42. }
    43. }

    这样DataA模拟了数据会变化的情况,这样就实现了每过一秒DataA的数据都+1,然后每过一秒转运一次DataA的数据到DataB中。

    DMA+AD多通道

    单次转换+扫描模式

    在AD多通道的代码基础上做修改

    在ADC使能之前加入DMA初始化的代码

    部分需要稍作修改

    在配置完GPIO口后添加

    1. //选择规则组的输入通道
    2. ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
    3. ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);
    4. ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5);
    5. ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5);

    再把ADC修改为扫描模式,扫描前4个通道 

    1. //扫描模式(ENABLE-扫描模式 or DISABLE-非扫描模式)
    2. ADC_InitStructure.ADC_ScanConvMode = ENABLE;
    3. //通道数目(指定在扫描模式下指定用到几个通道0~16)
    4. //在非扫描模式下,填任何数值都没用
    5. ADC_InitStructure.ADC_NbrOfChannel = 4;

     DMA初始化——首先外设站点的起始地址改为ADC1的DR寄存器(存储数据的寄存器)

    再改为以半字为单位传输,外设站点的自增改为Disable

    再建立一个数组接收传输的数据,并强制转换其地址填入存储器的起始地址中,同样的改为以半字为单位输入,保持地址自增

    传输计数器给4(因为接入的一个电位器和三个传感器作为实验对象)

    软件触发改为硬件触发(ADC1触发)

    1. //DMA初始化
    2. DMA_InitTypeDef DMA_InitStructure;
    3. //外设站点的起始地址、数据宽度、是否自增
    4. //ADC1的地址
    5. DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
    6. //以半字为单位传输
    7. DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
    8. //非自增
    9. DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    10. //存储器的起始地址、数据宽度、是否自增
    11. DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)ADCValue;//存储数据的数组
    12. DMA_InitStructure.DMA_MemoryDataSize = DMA_PeripheralDataSize_HalfWord;
    13. DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    14. //传输方向
    15. //SRC-从外设站点传输到存储器站点,DST-从存储器传输到外设站点
    16. DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    17. //缓冲器大小(传输计数器)
    18. DMA_InitStructure.DMA_BufferSize = 4;
    19. //传输模式(是否启用自动重装)(不自动重装)
    20. DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    21. //是否是存储器到存储器(硬件触发还是软件触发)
    22. //Enable-软件触发,Disable-硬件触发
    23. DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    24. //优先级(一个随便选
    25. DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
    26. //y决定是哪个DMA,x决定是DMA的哪个通道
    27. DMA_Init(DMA1_Channel1, &DMA_InitStructure);
    28. //打开DMA
    29. DMA_Cmd(DMA1_Channel1, ENABLE);

     但是在打开DMA后,并不能立即配合ADC传输数据,因为通道一还有其他外设,所以需要添加一个在ADC中开启DMA输出信号的函数

    void ADC_DMACmd(ADC_TypeDef* ADCx, FunctionalState NewState);
    

     

    ADC_DMACmd(ADC1, ENABLE);

    再修改一下获取转换值的函数

    1. void AD_GetValue(void)
    2. {
    3. //关闭DMA,写入传输计数器的值
    4. DMA_Cmd(DMA1_Channel1, DISABLE);
    5. DMA_SetCurrDataCounter(DMA1_Channel1, 4);
    6. //开启DMA
    7. DMA_Cmd(DMA1_Channel1, ENABLE);
    8. //软件触发(启动)
    9. ADC_SoftwareStartConvCmd(ADC1, ENABLE);
    10. //获取标志位状态(即是否完成传输)
    11. while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);
    12. //清除标志位(方便下次继续传输)
    13. DMA_ClearFlag(DMA1_FLAG_TC1);
    14. }

    然后再在主函数中调用,查看结果

    1. #include "stm32f10x.h" // Device header
    2. #include "Delay.h"
    3. #include "OLED.h"
    4. #include "AD.h"
    5. int main(void)
    6. {
    7. OLED_Init();
    8. AD_Init();
    9. OLED_ShowString(1, 1, "AD0:");
    10. OLED_ShowString(2, 1, "AD1:");
    11. OLED_ShowString(3, 1, "AD2:");
    12. OLED_ShowString(4, 1, "AD3:");
    13. while(1)
    14. {
    15. AD_GetValue();
    16. OLED_ShowNum(1, 5, ADValue[0], 4);
    17. OLED_ShowNum(2, 5, ADValue[1], 4);
    18. OLED_ShowNum(3, 5, ADValue[2], 4);
    19. OLED_ShowNum(4, 5, ADValue[3], 4);
    20. Delay_ms(100);
    21. }
    22. }

     此时会出现BUG,表现为两个值不稳定,两个值为0,这是因为我们在配置DMA时,把外设站点的输出单位直接复制到了存储器的输出单位中

    	DMA_InitStructure.DMA_MemoryDataSize = DMA_PeripheralDataSize_HalfWord;
    

    此时就会出现显示BUG,而且这个BUG需要非常细心的寻找才可能找到

    改回来后显示就没问题了。

    AD.c

    1. #include "stm32f10x.h" // Device header
    2. uint16_t ADValue[4];
    3. void AD_Init(void)
    4. {
    5. //开启时钟
    6. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    7. RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
    8. //DMA是AHB总线的设备
    9. RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
    10. //6分频
    11. RCC_ADCCLKConfig(RCC_PCLK2_Div6);
    12. //配置GPIO口
    13. GPIO_InitTypeDef GPIO_InitStructure;
    14. //AIN模拟输入
    15. //在AIN模式下GPIO口无效,即断开GPIO口
    16. //防止GPIO输入输出对模拟电压造成干扰(ADC专属模式)
    17. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    18. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
    19. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    20. GPIO_Init(GPIOA, &GPIO_InitStructure);
    21. //选择规则组的输入通道
    22. ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
    23. ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);
    24. ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5);
    25. ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5);
    26. //初始化ADC(单次转换,扫描模式)
    27. ADC_InitTypeDef ADC_InitStructure;
    28. //ADC工作模式(独立模式)
    29. ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
    30. //数据对齐(右对齐)
    31. ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
    32. //外部触发转换选择(外部触发源选择)(None,内部软件触发)
    33. ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
    34. //连续转换模式(ENABLE-连续模式 or DISABLE-非连续模式)
    35. ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
    36. //扫描模式(ENABLE-扫描模式 or DISABLE-非扫描模式)
    37. ADC_InitStructure.ADC_ScanConvMode = ENABLE;
    38. //通道数目(指定在扫描模式下指定用到几个通道0~16)
    39. ADC_InitStructure.ADC_NbrOfChannel = 4;
    40. ADC_Init(ADC1, &ADC_InitStructure);
    41. //DMA初始化
    42. DMA_InitTypeDef DMA_InitStructure;
    43. //外设站点的起始地址、数据宽度、是否自增
    44. //ADC1的地址
    45. DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
    46. //以半字为单位传输
    47. DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
    48. //非自增
    49. DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    50. //存储器的起始地址、数据宽度、是否自增
    51. DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)ADValue;//存储数据的数组
    52. DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
    53. DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    54. //传输方向
    55. //SRC-从外设站点传输到存储器站点,DST-从存储器传输到外设站点
    56. DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    57. //缓冲器大小(传输计数器)
    58. DMA_InitStructure.DMA_BufferSize = 4;
    59. //传输模式(是否启用自动重装)(不自动重装)
    60. DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    61. //是否是存储器到存储器(硬件触发还是软件触发)
    62. //Enable-软件触发,Disable-硬件触发
    63. DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    64. //优先级(一个随便选
    65. DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
    66. //y决定是哪个DMA,x决定是DMA的哪个通道
    67. DMA_Init(DMA1_Channel1, &DMA_InitStructure);
    68. //打开DMA
    69. DMA_Cmd(DMA1_Channel1, ENABLE);
    70. //开启ADC到DMA输出信号
    71. ADC_DMACmd(ADC1, ENABLE);
    72. //开启ADC电源
    73. ADC_Cmd(ADC1, ENABLE);
    74. //校准
    75. //复位校准
    76. ADC_ResetCalibration(ADC1);
    77. //获取复位校准状态
    78. //标志位为1时,表示正在进行复位校准
    79. //标志位为0时,表示复位校准结束,则我们要保证复位校准成功
    80. //当复位校准未完成就一直循环等待其完成
    81. while(ADC_GetResetCalibrationStatus(ADC1) == SET);
    82. //开始校准
    83. ADC_StartCalibration(ADC1);
    84. //获取开始校准状态
    85. while(ADC_GetCalibrationStatus(ADC1) == SET);
    86. }
    87. void AD_GetValue(void)
    88. {
    89. //关闭DMA,写入传输计数器的值
    90. DMA_Cmd(DMA1_Channel1, DISABLE);
    91. DMA_SetCurrDataCounter(DMA1_Channel1, 4);
    92. //开启DMA
    93. DMA_Cmd(DMA1_Channel1, ENABLE);
    94. //软件触发(启动)
    95. ADC_SoftwareStartConvCmd(ADC1, ENABLE);
    96. //获取标志位状态(即是否完成传输)
    97. while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);
    98. //清除标志位(方便下次继续传输)
    99. DMA_ClearFlag(DMA1_FLAG_TC1);
    100. }

    main.c

    1. #include "stm32f10x.h" // Device header
    2. #include "Delay.h"
    3. #include "OLED.h"
    4. #include "AD.h"
    5. int main(void)
    6. {
    7. OLED_Init();
    8. AD_Init();
    9. OLED_ShowString(1, 1, "AD0:");
    10. OLED_ShowString(2, 1, "AD1:");
    11. OLED_ShowString(3, 1, "AD2:");
    12. OLED_ShowString(4, 1, "AD3:");
    13. while(1)
    14. {
    15. AD_GetValue();
    16. OLED_ShowNum(1, 5, ADValue[0], 4);
    17. OLED_ShowNum(2, 5, ADValue[1], 4);
    18. OLED_ShowNum(3, 5, ADValue[2], 4);
    19. OLED_ShowNum(4, 5, ADValue[3], 4);
    20. Delay_ms(100);
    21. }
    22. }

    多次转换+扫描模式

    只需把ADC改为多次扫描,DMA自动重装,把软件触发直接放到初始化函数中,不需要GetValue函数也可执行

    1. #include "stm32f10x.h" // Device header
    2. uint16_t ADValue[4];
    3. void AD_Init(void)
    4. {
    5. //开启时钟
    6. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    7. RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
    8. //DMA是AHB总线的设备
    9. RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
    10. //6分频
    11. RCC_ADCCLKConfig(RCC_PCLK2_Div6);
    12. //配置GPIO口
    13. GPIO_InitTypeDef GPIO_InitStructure;
    14. //AIN模拟输入
    15. //在AIN模式下GPIO口无效,即断开GPIO口
    16. //防止GPIO输入输出对模拟电压造成干扰(ADC专属模式)
    17. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    18. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
    19. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    20. GPIO_Init(GPIOA, &GPIO_InitStructure);
    21. //选择规则组的输入通道
    22. ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
    23. ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);
    24. ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5);
    25. ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5);
    26. //初始化ADC(单次转换,扫描模式)
    27. ADC_InitTypeDef ADC_InitStructure;
    28. //ADC工作模式(独立模式)
    29. ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
    30. //数据对齐(右对齐)
    31. ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
    32. //外部触发转换选择(外部触发源选择)(None,内部软件触发)
    33. ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
    34. //连续转换模式(ENABLE-连续模式 or DISABLE-非连续模式)
    35. ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
    36. //扫描模式(ENABLE-扫描模式 or DISABLE-非扫描模式)
    37. ADC_InitStructure.ADC_ScanConvMode = ENABLE;
    38. //通道数目(指定在扫描模式下指定用到几个通道0~16)
    39. ADC_InitStructure.ADC_NbrOfChannel = 4;
    40. ADC_Init(ADC1, &ADC_InitStructure);
    41. //DMA初始化
    42. DMA_InitTypeDef DMA_InitStructure;
    43. //外设站点的起始地址、数据宽度、是否自增
    44. //ADC1的地址
    45. DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
    46. //以半字为单位传输
    47. DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
    48. //非自增
    49. DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    50. //存储器的起始地址、数据宽度、是否自增
    51. DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)ADValue;//存储数据的数组
    52. DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
    53. DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    54. //传输方向
    55. //SRC-从外设站点传输到存储器站点,DST-从存储器传输到外设站点
    56. DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    57. //缓冲器大小(传输计数器)
    58. DMA_InitStructure.DMA_BufferSize = 4;
    59. //传输模式(是否启用自动重装)(不自动重装)
    60. DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
    61. //是否是存储器到存储器(硬件触发还是软件触发)
    62. //Enable-软件触发,Disable-硬件触发
    63. DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    64. //优先级(一个随便选
    65. DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
    66. //y决定是哪个DMA,x决定是DMA的哪个通道
    67. DMA_Init(DMA1_Channel1, &DMA_InitStructure);
    68. //打开DMA
    69. DMA_Cmd(DMA1_Channel1, ENABLE);
    70. //开启ADC到DMA输出信号
    71. ADC_DMACmd(ADC1, ENABLE);
    72. //开启ADC电源
    73. ADC_Cmd(ADC1, ENABLE);
    74. //校准
    75. //复位校准
    76. ADC_ResetCalibration(ADC1);
    77. //获取复位校准状态
    78. //标志位为1时,表示正在进行复位校准
    79. //标志位为0时,表示复位校准结束,则我们要保证复位校准成功
    80. //当复位校准未完成就一直循环等待其完成
    81. while(ADC_GetResetCalibrationStatus(ADC1) == SET);
    82. //开始校准
    83. ADC_StartCalibration(ADC1);
    84. //获取开始校准状态
    85. while(ADC_GetCalibrationStatus(ADC1) == SET);
    86. //软件触发(启动)
    87. ADC_SoftwareStartConvCmd(ADC1, ENABLE);
    88. }

    这样就也可以实现目标

    同时还可以再优化代码,添加一个定时器,定时器触发ADC,ADC触发DMA转运

  • 相关阅读:
    非root权限下run qemu-kvm
    10个设计人士应该关注的国内外资源网站
    QT QFrame类
    区块链安全应用------压力测试
    Spring学习|使用JavaConfig实现bean配置、代理模式:静态代理模式、动态代理模式(通俗易懂)
    SPDK线程模型
    Linux权限
    网络安全总结
    力扣-消失的数字(两种方法)
    带你快速概览MySQL 整体架构
  • 原文地址:https://blog.csdn.net/m0_74460550/article/details/133323995