STM32 DMA+AD多通道

news2024/9/30 5:32:25

单片机学习!


目录

一、DMA配置步骤

 二、ADC配置步骤

三、DMA+AD多通道框图

四、DMA+AD多通道函数设计详细步骤

4.1 开启RCC时钟

4.2 配置GPIO

4.3 配置多路开关

4.4 结构体初始化ADC

 4.5 DMA参数初始化配置

4.5.1 外设站点的三个参数

4.5.2 存储器站点的三个参数

4.5.3 传输方向

4.5.4 传输计数器

4.5.5 是否使用自动重装

4.5.6 选择触发源

4.5.7 通道优先级

4.5.8 DMA_Init

4.6 DMA开关控制

4.7 开启ADC到DMA的输出

4.8 开启ADC电源

4.9 ADC进行校准

4.9.1 复位校准

4.9.2 等待复位校准完成

4.9.3 开始校准

4.9.4 等待校准完成

4.10 启动AD转换与DMA转运函数设计

4.10.1 ADC软件触发转换。

4.10.2 传输计数器赋值

4.10.3 标志位查看/清除

4.10.4 启动函数代码

五、DMA+AD多通道代码

总结


一、DMA配置步骤

初始化步骤:

        第一步,RCC开启DMA的时钟。

        第二步,参数初始化配置。直接调用DMA_Init,初始化各个参数,包括外设和存储器站点的起始地址、数据宽度、地址是否自增、方向、传输计数器、是否需要自动重装、选择触发源、通道优先级。这些所有的参数,通过一个结构体,就可以配置好了。

        第三步,开关控制,调用DMA_Cmd函数,给指定的通道使能,就完成了。

  • 如果选择的是硬件触发,需要在对应的外设调用一下XXX_DMACmd函数,开启一下触发信号的输出;
  • 如果需要DMA的中断,那就调用DMA_ITConfig,开启中断输出。再在NVIC里配置相应的中断通道,然后写中断函数就可以了。

        最后,在运行的过程中,如果转运完成,传输计数器清0了,这时再想给传输计数器赋值的话,就DMA失能 -> 写传输计数器 -> DMA使能,这样就行了。

 二、ADC配置步骤

        参照结构框图,在原理上将ADC外设运转起来。

第一步:开启RCC时钟,包括ADC和GPIO的时钟。另外ADCCLK的分频器也需要配置。

第二步:配置GPIO,把需要用到的GPIO配置成模拟输入的模式。

第三步:配置多路开关,把通道接入到规则组列表里。

第四步:配置ADC转换器,库函数中用结构体来配置电路参数,包括ADC是单次转换还是连续转换;扫描还是非扫描;有几个通道;触发源是什么;数据对齐是左对齐还是右对齐。

  • 如果需要模拟看门狗,可以用几个库函数来配置阈值和监测通道;
  • 如果需要开启中断,在中断输出控制里用 ITConfig 函数开启对应的中断输出,然后再在NVIC里配置一下优先级。这样就能触发中断了。

最后:开关控制,调用一下ADC_Cmd函数,开启ADC.

        在开启ADC之后,根据STM32手册的建议,还可以对ADC进行一下校准,这样可以减小误差。

        在ADC工作的时候,如果想要软件触发转换,有库函数可以触发;如果想读取转换结果,也会有函数可以读取结果。

三、DMA+AD多通道框图

         下图对应的任务是ADC扫描模式+DMA。图左边是ADC扫描模式的执行流程,有7个通道,触发一次后,7个通道依次进行AD转换。然后转换结果都放到ADC_DR数据寄存器里面。DMA要做的就是,在每个单独的通道转换完成后,进行一个DMA数据转运,并且目的地址进行自增。这样数据就不会被覆盖了。

        DMA基本结构参数配置:

首先是外设站点和存储器站点的起始地址、数据宽度、地址是否自增这三个参数。

  1. 外设地址应该写入 ADC_DR 这个寄存器地址,存储器地址可以在 SRAM 中定义一个数组 ADValue ,然后把 ADValue 的地址当作存储器的地址;
  2. 数据宽度,因为 ADC_DR 和 SRAM 数组需要的数据类型都是uint16_t,所以数据宽度都是16位的半字传输;
  3. 地址是否自增,应该是外设地址不自增,存储器地址自增。

第二方向参数,根据任务图,应该是外设站点转运到存储器站点了。

第三传输计数器是否要自动重装

        传输计数器这里有7个通道,所以计数7次。

        计数器是否自动重装,这需要看ADC的配置。

ADC如果是单次扫描,那DMA的传输计数器可以不自动重装,转换一轮就停止;

ADC如果是连续扫描,那DMA的传输计数器就可以使用自动重装,在ADC启动下一轮转换的时候,DMA也启动下一轮的转运,ADC和DMA同步工作。

第四触发选择部分,这里 ADC_DR 的值是在ADC单个通道转换完成后才会有效,所以DMA转运的时机,需要和ADC单个通道转化完成同步。那DMA的触发要选择ADC的硬件触发。

        硬件触发这里需要说明一下。在ADC扫描模式下,每个单独的通道转换完成后,没有任何标志位,也不会触发中断,所以程序不太好判断某一个通道转换完成的时机是什么时候。这里虽然单个通道转换完成后,不产生任何标志位和中断,但是应该会产生DMA请求,去触发DMA转运。

最后,给DMA使能,调用DMA_Cmd函数之后,数据就会从 ADC_DR 数据寄存器转运到 SRAM 数组了。

        一般来说,DMA最常见的用途就是配合ADC的扫描模式,因为ADC扫描模式有个数据覆盖的特征,或者可以说这个数据覆盖的问题是ADC固有的缺陷,而这个缺陷也使得ADC和DMA成为了最常见的伙伴。ADC对DMA的需求是非常强烈的,其它一些外设使用DMA可以提高效率,是锦上添花的操作,但是不使用也是可以的,顶多损失一些性能。但是ADC的扫描模式,如果不使用DMA,功能都会受到很大的限制。所以ADC和DMA的结合最为常见。

四、DMA+AD多通道函数设计详细步骤

        这里代码使用的是ADC的扫描模式,加DMA数据转运,执行流程如图:

4.1 开启RCC时钟

        第一步:开启RCC时钟。包括ADC和GPIO的时钟。另外ADCCLK的分频器也需要配置。

代码示例:

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);//开启ADC1的时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//还需要开启PA0口的时钟
    RCC_ADCCLKConfig(RCC_PCLK2_Div6);//配置ADCCLK

        RCC_APB2PeriphClockCmd函数开启ADC1的时钟,ADC都是APB2上的设备,所以这里用APB2开启时钟的函数。

        RCC_APB2PeriphClockCmd函数用于开启PA0口的时钟。

        以上时钟就配置好了,还需要配置ADCCLK,用RCC_ADCCLKConfig/函数来配置,函数有四个参数分别是2、4、6、8分频:

  • RCC_PCLK2_Div2: ADC clock = PCLK2/2
  • RCC_PCLK2_Div4: ADC clock = PCLK2/4
  • RCC_PCLK2_Div6: ADC clock = PCLK2/6
  • RCC_PCLK2_Div8: ADC clock = PCLK2/8

        函数参数配置好之后,ADC的CLOCK=PCLK2/2、PCLK2/4、PCLK2/6、PCLK2/8,参数的PCLK2就是APB2时钟的意思。

        代码示例中选择6分频,分频之后,ADCCLK=72MHz/6=12MHz

4.2 配置GPIO

        第二步:配置GPIO,把需要用到的GPIO配置成模拟输入的模式。

代码示例:

    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.GPIO_Mode= GPIO_Mode_AIN;//选择模拟输入
    GPIO_InitStruct.GPIO_Pin= GPIO_Pin_0;
    GPIO_InitStruct.GPIO_Speed= GPIO_Speed_50MHz;
    GPIO_Init(GPIOA,&GPIO_InitStruct);

        代码示例中选择GPIO_Mode_AIN模拟输入这个模式,在GPIO_Mode_AIN模式下,GPIO是无效的,断开GPIO口,防止GPIO口的输入输出对模拟电压造成干扰。GPIO_Mode_AIN模式就是ADC的专属模式。

4.3 配置多路开关

        第三步:配置多路开关,把通道接入到规则组列表里。

        用ADC_RegularChannelConfig函数可选择规则组的输入通道。

函数参数:

  • 第1个参数:选择ADC。
  • 第2个参数:指定通道,通道0~通道17.
  • 第3个参数:Rank,规则组序列器里的次序,在1~16之间。若只有PA0一个通道,使用的是非扫描模式,那指定的通道就放在第一个序列1的位置。
  • 第4个参数:指定通道的采样时间,采样时间参数根据需求调整,需要更快的转换,就选择小的参数;需要更稳定的转换,就选择大的参数。

        如果想在序列2的位置写入其他通道,就可以复制一下这个代码,把序列数改成2,然后指定你想要的通道,若还需要继续填充序列,可以再复制这个函数,修改序列和通道,另外每个通道也可以设置不同的采样时间,在函数最后一个参数修改即可。

代码示例:

	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);

        代码示例的配置是:在ADC1中,在规则组菜单列表的第一个位置,写入通道0、1、2、3通道,指定通道的采样时间参数选择的采样时间为55.5个ADCCLK的周期。

        因为需要扫描PA0到PA3这4个通道,所以调用四次ADC_RegularChannelConfig函数。通道0放在序列1的位置;通道1放在序列2的位置;通道2放在序列3的位置;通道3放在序列4的位置。

        需要转运的1~4号空位,填上了0~3这4个通道。通道和次序可以任意修改,修改后最终结果存放的顺序也会相应变化。

4.4 结构体初始化ADC

        第四步、用结构体初始化ADC。这里使用ADC,单次转换,扫描模式。

代码示例:

    ADC_InitTypeDef ADC_InitStructure;
    ADC_InitStructure.ADC_Mode=ADC_Mode_Independent;//ADC的工作模式
    ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right;//数据对齐
    ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;//外部触发转换选择
    ADC_InitStructure.ADC_ContinuousConvMode=DISABLE;//连续/单次转换模式
    ADC_InitStructure.ADC_ScanConvMode=ENABLE;//扫描/非扫描转换模式
    ADC_InitStructure.ADC_NbrOfChannel=4;//通道数目

    ADC_Init(ADC1,&ADC_InitStructure);

        ADC_Mode 是ADC的工作模式,这个参数是配置ADC是工作在独立模式还是双ADC模式,代码示例选择独立模式。

        ADC_DataAlign 数据对齐,这里介绍是指定ADC数据是左对齐还是右对齐。

  • ADC_DataAlign_Right右对齐;
  • ADC_DataAlign_Left左对齐 

        ADC_ExternalTrigConv 外部触发转换选择,就是触发控制的触发源,定义用于启动规则组转换的外部触发源。参数对应结构框图中的外部触发源选择。代码示例选择 ADC_ExternalTrigConv_None 参数,就是不使用外部触发,也就是使用内部软件触发的意思。

        ADC_ContinuousConvMode 连续/单次转换模式,这个参数可以选择是ENABLE连续转换模式还是DISABLE单次转换模式。

        ADC_ScanConvMode 扫描/非扫描转换模式,这个参数可以选择是ENABLE扫描模式(多通道)还是DISABLE非扫描模式(单通道)。

        ADC_NbrOfChannel 通道数目,这个是在指定扫描模式下,总共会有几个通道需要扫描,参数必须在1~16之间。这个参数仅在扫描模式下使用。因为需要扫描PA0到PA3这4个通道,所以通道数目填4.

        后三个参数设置可以对应四种模式:

  • 单次转换非扫描模式。
  • 连续转换非扫描模式。
  • 单次转换扫描模式。
  • 连续转换扫描模式。

这里配置的是单次转换,扫描模式。

 4.5 DMA参数初始化配置

        第五步,参数初始化配置。直接调用DMA_Init,初始化各个参数。

        参数包括外设和存储器站点的起始地址、数据宽度、地址是否自增、方向、传输计数器、是否需要自动重装、选择触发源、通道优先级。

        这些所有的参数,通过一个结构体,就可以配置好了。

代码示例:

    DMA_InitTypeDef DMA_InitStructure;
    DMA_InitStructure.DMA_PeripheralBaseAddr=(uint32_t)&ADC1->DR;//起始地址
    DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_HalfWord;//数据宽度
    DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Disable;//是否自增
    DMA_InitStructure.DMA_MemoryBaseAddr=AddrB;//起始地址
    DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte;//数据宽度
    DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;//是否自增
    DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralSRC;//传输方向
    DMA_InitStructure.DMA_BufferSize=Size;//传输计数器
    DMA_InitStructure.DMA_Mode=DMA_Mode_Normal;//否使用自动重装
    DMA_InitStructure.DMA_M2M=DMA_M2M_Enable;//选择硬件触发还是软件触发
    DMA_InitStructure.DMA_Priority=DMA_Priority_Medium;//优先级
    DMA_Init(DMA1_Channel1,&DMA_InitStructure);

4.5.1 外设站点的三个参数

外设站点的三个参数:

  1. 起始地址
  2. 数据宽度
  3. 地址是否自增

1. 起始地址
        DMA_PeripheralBaseAddr 起始地址,外设站点的基地址。这在里要写一个32位的地址,比如 0x20000000 这样的地址。

        外设站点的起始地址是DMA转运的源头,AD转换完成后数据结果都放在ADC_DR寄存器里。所以外设站点的起始地址就填ADC_DR寄存器的地址。

        ADC1的DR寄存器地址是0x4001 244C,所以可以直接填0x4001244C。但是库函数已经算好了具体地址,一般寄存器地址也不需要算好后填实际值,所以可以对用 ADC1->DR 的方法取ADC_DR寄存器地址,再强转为uint32_t类型。也就是这样: (uint32_t)&ADC1->DR ,得到的结果就是0x4001244C。

代码示例:

    DMA_InitStructure.DMA_PeripheralBaseAddr=(uint32_t)&ADC1->DR;//起始地址

这样外设站点的地址就完成了。

2. 数据宽度
        DMA_PeripheralDataSize数据宽度,函数定义中介绍是,指定数据宽度的参数可以是以下值:

  • DMA_PeripheralDataSize_Byte            Byte,字节,就是uint8_t ; 
  • DMA_PeripheralDataSize_HalfWord    HalfWord,半字,就是uint16_t ;
  • DMA_PeripheralDataSize_Word           Word,字,就是uint32_t 。

        这里需要DR寄存器低16位的数据,所以就填入DMA_PeripheralDataSize_HalfWord,半字。

3. 地址是否自增
        DMA_PeripheralInc 地址是否自增。函数定义中解释是,指定外设地址是自增或者不是。

参数取值:

  • DMA_PeripheralInc_Enable  自增
  • DMA_PeripheralInc_Disable   不自增

        这里不自增,始终转运同一个位置的数据,所以这里选择 DMA_PeripheralInc_Disable 。


这样外设站点的参数就配置好了:

	DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;//起始地址
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//数据宽度
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//是否自增

4.5.2 存储器站点的三个参数

存储器站点的三个参数:

  1. 起始地址
  2. 数据宽度
  3. 地址是否自增

1. 起始地址

        DMA_MemoryBaseAddr 起始地址,存储器站点的基地址。存储器地址可以在 SRAM 中定义一个数组 ADValue ,然后把 ADValue 的地址当作存储器的地址。

        这里需要把数据存在SRAM数组里,所以现在代码块最前面定义一个数组AD_Value,然后在这里把AD_Value数组的地址作为目的地,把数组的地址强转为uint32_t,

示例:

    DMA_InitStructure.DMA_MemoryBaseAddr=(uint32_t)AD_Value;

这样存储器站点的地址就完成了。

2. 数据宽度
        DMA_MemoryDataSize 数据宽度,和外设站点一样也选择HalfWord参数,以半字传输。

  • DMA_MemoryDataSize_Byte           Byte,字节,就是uint8_t ;
  • DMA_MemoryDataSize_HalfWord   HalfWord,半字,就是uint16_t ;
  • DMA_MemoryDataSize_Word          Word,字,就是uint32_t 。

3. 地址是否自增

        DMA_MemoryInc 地址是否自增,存储器地址是自增的,每转运一次挪一个坑。这里选择地址自增。

  • DMA_MemoryInc_Enable   自增
  • DMA_MemoryInc_Disable   不自增

这样存储器站点的参数就配置好了:

    DMA_InitStructure.DMA_MemoryBaseAddr=(uint32_t)AD_Value;//起始地址
    DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_HalfWord;//数据宽度
    DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;//是否自增

4.5.3 传输方向

        DMA_DIR 传输方向,函数定义解释是,指定外设站点是源端还是目的地。

  • DMA_DIR_PeripheralDST  外设站点作为DST,destination,目的地,外设站点作为目的地,其实就是传输方向是存储器站点到外设站点。  
  • DMA_DIR_PeripheralSRC  外设站点作为SRC,source,源头,外设站点作为源头,也就是传输方向是外设站点到存储器站点。

        传输方向是外设站点到存储器站点,所以这里选择 DMA_DIR_PeripheralSRC参数,外设站点作为数据源。

代码示例:

    DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralSRC;//传输方向

4.5.4 传输计数器

        DMA_BufferSize 缓存区大小,其实就是传输计数器。

        DMA_BufferSize 在函数定义解释是,以数据单元指定缓存区大小,数据单元等于外设数据宽度或者存储器数据宽度,数据宽度取决于传输方向。

        以数据单元指定缓存区大小,就是说需要传送几个数据单元,这个数据单元等于传输源站点的DataSize,简单理解就是,DMA_BufferSize就是传输计数器,指定传输几次。

        可以查看DMA_Init函数的源码,DMA_BufferSize参数其实就是直接赋值给了传输计数器的寄存器,它的取值是0~65535.

        因为有4个ADC通道,传输4次,所以这里DMA_BufferSize给4。

代码示例:

    DMA_InitStructure.DMA_BufferSize=4;//传输数量给4

这样传输次数就完成了。

4.5.5 是否使用自动重装

        DMA_Mode 传输模式,其实就是指定传输计数器是否使用自动重装。

        DMA_Mode 在函数定义中的解释是,指定操作方式有对应参数取值列表。

        配置 DMA_Mode 参数还有一个注意事项,函数定义中写的是循环模式,也就是自动重装,不能应用在存储器到存储器的情况下。也就是之前博文说的,自动重装和软件触发不能同时使用,如果同时使用,DMA就会连续触发,永远也不会停下来。

  • DMA_Mode_Circular  循环模式,就是传输计数器自动重装  
  • DMA_Mode_Normal  正常模式,就是传输计数器不自动重装,自减到0后停下来。

        这里可以给正常的单次模式,也可以自动重装的循环模式。这里就先将 DMA_Mode 的配置选择正常模式DMA_Mode_Normal。

代码示例:

    DMA_InitStructure.DMA_Mode=DMA_Mode_Normal;//否使用自动重装

4.5.6 选择触发源

        DMA_M2M 选择是否存储到存储器,其实就是选择硬件触发还是软件触发。

        DMA_M2M 在函数定义中的解释是,DMA是否应用于存储器到存储器的转运模式,存储器到存储器的转运模式就是软件触发。

  • DMA_M2M_Enable    使用软件触发。
  • DMA_M2M_Disable   不使用软件触发,也就是使用硬件触发。

        这里不使用软件触发,需要硬件触发,触发源为ADC1。

        因为 ADC_DR 的值是在ADC单个通道转换完成后才会有效,DMA转运的时机,需要和ADC单个通道转化完成同步。每个通道的AD转换好了,叫DMA一下,DMA再去转运数据,这样才是合适的时机。

        所以选择 DMA_M2M_Enable 使用软件触发。

代码示例:

    DMA_InitStructure.DMA_M2M=DMA_M2M_Enable;//选择触发源

4.5.7 通道优先级

        DMA_Priority 优先级,按照参数要求,给一个优先级。

        DMA_Priority 在函数定义中的解释是,指定通道的软件优先级。

  • DMA_Priority_VeryHigh    非常高  
  • DMA_Priority_High     高
  • DMA_Priority_Medium    中等   
  • DMA_Priority_Low     低

        这里配置DMA_Priority_Medium,中等。

代码示例:

     DMA_InitStructure.DMA_Priority=DMA_Priority_Medium;//优先级

4.5.8 DMA_Init

DMA_Init 函数参数配置:

        第一个参数:DMAy_Channelx,y可以是1或2,用来选择是哪个DMA;对于DMA1,x可以是1~7,或者对于DMA2,x可以是1~5.x用来选择是哪一个通道。

        DMA_Init函数的第一个参数,既选择了是哪个DMA,也选择了是DMA的哪个通道。

        这里通道不能任意选择,需要看DMA1请求映像图。

可以看到ADC1的硬件触发只接在了DMA1的通道1上。所以这里必须要使用DMA1的通道1,其它通道都不行。所以 DMAy_Channelx,这里y写为1,选择DMA1;x选择通道,x给1,通道1.

        第二个参数的位置放 DMA_InitStructure 结构体的地址,这样就是把结构体指定的参数,配置到DMA1的通道1里面去。

代码示例:

DMA_Init(DMA1_Channel1,&DMA_InitStructure);

4.6 DMA开关控制

        第六步,开关控制,调用DMA_Cmd函数,给指定的通道使能,就完成了。    

代码示例:

    DMA_Cmd(DMA1_Channel1,ENABLE);

        使能DMA之后,来看一下DMA转运的三个条件:

  • 第一个,传输计数器不为0,满足;
  • 第三个,DMA使能,满足;
  • 第二个,触发源有信号,不满足。因为这里配置的是硬件触发,ADC还没启动,就不会有触发信号。

        所以这里DMA使能之后,不会立刻工作。最后在ADC使能之前还有一个事需要做,就是开启ADC到DMA的输出。

4.7 开启ADC到DMA的输出

        第七步,开启ADC到DMA的输出。有三个硬件触发源,具体使用哪个,取决于哪个硬件触发源的DMA输出开启了。

        这里调用 ADC_DMACmd 函数,开启DMA触发信号。

代码示例:

    ADC_DMACmd(ADC1,ENABLE);

4.8 开启ADC电源

        第五步、开启ADC电源,调用一下ADC_Cmd函数,开启ADC。

代码示例:

ADC_Cmd(ADC1,ENABLE);

        以上配置完后ADC准备就绪。

4.9 ADC进行校准

        在开启ADC电源之后,根据手册的建议,还需要对ADC进行校准,校准分为以下四步。

  1. 复位校准
  2. 等待复位校准完成
  3. 开始校准
  4. 等待校准完成

4.9.1 复位校准

代码示例:

ADC_ResetCalibration(ADC1);//复位校准

4.9.2 等待复位校准完成

代码示例:

while(ADC_GetResetCalibrationStatus(ADC1)==SET);

        ADC_GetResetCalibrationStatus函数是返回复位校准的状态,要等待复位完成的话,还需要加一个while循环,若没校准完成的话,就在这个while空循环里一直等待。

        获取的标志位和是否校准完成的对应关系需参考函数定义寄存器说明

函数定义:

        ADC_GetResetCalibrationStatus函数定义中返回值的说明是,ADC复位校准寄存器的状态,SET或RESET。

函数代码:

/**
  * @brief  Gets the selected ADC reset calibration registers status.
  * @param  ADCx: where x can be 1, 2 or 3 to select the ADC peripheral.
  * @retval The new state of ADC reset calibration registers (SET or RESET).
  */
FlagStatus ADC_GetResetCalibrationStatus(ADC_TypeDef* ADCx)
{
  FlagStatus bitstatus = RESET;
  /* Check the parameters */
  assert_param(IS_ADC_ALL_PERIPH(ADCx));
  /* Check the status of RSTCAL bit */
  if ((ADCx->CR2 & CR2_RSTCAL_Set) != (uint32_t)RESET)
  {
    /* RSTCAL bit is set */
    bitstatus = SET;
  }
  else
  {
    /* RSTCAL bit is reset */
    bitstatus = RESET;
  }
  /* Return the RSTCAL bit status */
  return  bitstatus;
}

        在此函数代码中也可以看出来,它获取的就是CR2寄存器里的RSTCAL标志位。

寄存器说明:

        在ADC的CR2寄存器里RSTCAL复位校准位的说明是该位由软件设置并由硬件清除,在校准寄存器被初始化后该位将被清除。

        所以该标志位的用法就是软件置改位为1,那硬件就会开始复位校准,当复位校准完成后,该位就会由硬件自动清0.

        因为校准的第一条代码ADC_ResetCalibration(ADC1);开始复位校准,就是将RSTCAL标志位置1,然后获取复位校准状态,就是读取RSTCAL标志位这一位,所以在读取这一位的时候:

  • 如果它是1,那就需要一直空循环等待;
  • 如果它变为0了,那就说明复位校准完成,可以跳出等待了。

        所以校准第二条代码while(ADC_GetResetCalibrationStatus(ADC1)==SET);中while的条件就是,获取标志位函数 ADC_GetResetCalibrationStatus 的返回值是不是==SET,如果等于SET,while条件为真,就会一直空循环。一旦标志位被硬件清0了,这个空循环就会自动跳出来。这样就实现了等待复位校准完成的效果。这里==SET也是可以省略的,因为返回值SET直接作为条件和是不是==SET作为条件效果是一样的。

4.9.3 开始校准

代码示例:

ADC_StartCalibration(ADC1);

        调用ADC_StartCalibration函数就开始校准了,之后内部电路就会自动进行校准。

4.9.4 等待校准完成

    while(ADC_GetCalibrationStatus(ADC1)==SET);//调用函数获取校准状态

        调用ADC_GetCalibrationStatus函数获取校准状态,也需要将函数放于while循环内,和校准的第二步同理,循环条件是,校准标志位是不是==SET,这样就可以等待校准是否完成了。

4.10 启动AD转换与DMA转运函数设计

        上文描述的代码设置使ADC和DMA配合工作的配置完成了。以下设计启动AD转换,获取DMA转运结果的函数块。

4.10.1 ADC软件触发转换。

        因为现在ADC还是单次模式,所以还需要软件触发一下ADC开始。其他的ADC函数就不需要了。

        调用ADC_SoftwareStartConvCmd函数,实现软件触发。

代码示例:

ADC_SoftwareStartConvCmd(ADC1,ENABLE);

        调用ADC_SoftwareStartConvCmd函数之后就可以触发,ADC就已经开始进行转换了。转换需要一段时间。

4.10.2 传输计数器赋值

        因为DMA也是正常的单次模式,所以在触发ADC之前,需要再启动一次DMA转运,就需要重新再重新写入一下传输计数器。

重新给传输计数器赋值必须要

  1. 先给DMA失能;
  2. 然后给传输计数器赋值;
  3. 最后再给DMA使能。

代码示例:

void MyDMA_Transfer(void)
{
    DMA_Cmd(DMA1_Channel1,DISABLE);
    DMA_SetCurrDataCounter(DMA1_Channel1,4);
    DMA_Cmd(DMA1_Channel1,ENABLE);

    ......
}

1. 调用DMA_Cmd函数,使DMA失能。

  • 第一个参数还是选择DMA1的通道1;
  • 第二个参数给DISABLE。

2. 然后就可以给传输计数器赋值了,调用DMA_SetCurrDataCounter函数。

  • 第一个参数,选择DMA和通道。
  • 第二个参数,指定要给传输计数器写入的值,也就是传输次数,给4。

3. 最后,再次调用DMA_Cmd函数,给DMA使能。

  • 第一个参数还是选择DMA1的通道1;
  • 第二个参数给ENABLE。

4.10.3 标志位查看/清除

        最后,等待ADC转换和DMA转运完成。因为DMA转运总是在ADC转换之后的,所以可以写入等待DMA转运完成的代码,等待ADC转换完成的代码就不需要了。

        等待转运完成,可以通过查看标志位来确定转运是否完成。

        因为转运也是要花一些时间的,等待转运完成调用 DMA_GetFlagStatus函数可以查看标志位。

        DMA_GetFlagStatus函数中总共四种标志位,以DMA1的通道1举例,其它所有的通道,都是这4种标志位。

  • DMA1_FLAG_GL1:全局标志位
  • DMA1_FLAG_TC1:转运完成标志位
  • DMA1_FLAG_HT1:转运过半标志位
  • DMA1_FLAG_TE1:转运错误标志位

代码示例:

    while(DMA_GetFlagStatus(DMA1_FLAG_TC1)==RESET);
    DMA_ClearFlag(DMA1_FLAG_TC1);

        这里需要检查DMA1通道1转换完成的标志位,所以选择DMA1_FLAG_TC1参数。

        转运完成之后,标志位会置1,所以需要加一个while循环,等待这个标志位==RESET,如果没有完成,就一直循环等待,这样就实现了等待转运完成的效果了。

        标志位置1之后,不要忘记清除标志位,这个标志位需要手动清除。调用 DMA_ClearFlag函数。

4.10.4 启动函数代码

代码示例:

void AD_GetValue(void)//函数不需要参数和返回值
{
    ADC_SoftwareStartConvCmd(ADC1,ENABLE);
    DMA_Cmd(DMA1_Channel1,DISABLE);
    DMA_SetCurrDataCounter(DMA1_Channel1,4);
    DMA_Cmd(DMA1_Channel1,ENABLE);
    while(DMA_GetFlagStatus(DMA1_FLAG_TC1)==RESET);
    DMA_ClearFlag(DMA1_FLAG_TC1);
}

        这样当调用一下AD_GetValue函数,ADC开始转换,连续扫描4个通道,DMA也同步进行转运,AD转换结果依次放在AD_Value数组里。

五、DMA+AD多通道代码

代码示例:

uint16_t AD_Value[4];

void AD_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);//开启ADC1的时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启PA0口的时钟
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);//开启DMA的时钟

    RCC_ADCCLKConfig(RCC_PCLK2_Div6);
    
    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.GPIO_Mode= GPIO_Mode_AIN;
    GPIO_InitStruct.GPIO_Pin= GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;
    GPIO_InitStruct.GPIO_Speed= GPIO_Speed_50MHz;
    GPIO_Init(GPIOA,&GPIO_InitStruct);
    
    
    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_Mode=ADC_Mode_Independent;//ADC的工作模式
    ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right;//数据对齐 
    ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;//外部触发转换选择
    ADC_InitStructure.ADC_ContinuousConvMode=DISABLE;//连续/单次转换模式
    ADC_InitStructure.ADC_ScanConvMode=ENABLE;//扫描/非扫描转换模式
    ADC_InitStructure.ADC_NbrOfChannel=4;//通道数目
    ADC_Init(ADC1,&ADC_InitStructure);


    DMA_InitTypeDef DMA_InitStructure;
    DMA_InitStructure.DMA_PeripheralBaseAddr=(uint32_t)&ADC1->DR;//起始地址
    DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_HalfWord;//数据宽度
    DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Enable;//是否自增
    DMA_InitStructure.DMA_MemoryBaseAddr=(uint32_t)AD_Value;//起始地址
    DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_HalfWord;//数据宽度
    DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;//是否自增
    DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralSRC;//传输方向
    DMA_InitStructure.DMA_BufferSize=4;//传输数量给4
    DMA_InitStructure.DMA_Mode=DMA_Mode_Normal;//传输模式
    DMA_InitStructure.DMA_M2M=DMA_M2M_Disable;//选择是否存储到存储器
    DMA_InitStructure.DMA_Priority=DMA_Priority_Medium;//优先级
    DMA_Init(DMA1_Channel1,&DMA_InitStructure);
    DMA_Cmd(DMA1_Channel1,ENABLE);//使能

    ADC_DMACmd(ADC1,ENABLE);//开启DMA触发信号

    ADC_Cmd(ADC1,ENABLE);//开启ADC电源

    ADC_ResetCalibration(ADC1);//复位校准
    while(ADC_GetResetCalibrationStatus(ADC1)==SET);
    ADC_StartCalibration(ADC1);//开始校准
    while(ADC_GetCalibrationStatus(ADC1)==SET);//获取校准状态
}

void AD_GetValue(void)//函数不需要参数和返回值了
{
    ADC_SoftwareStartConvCmd(ADC1,ENABLE);
    DMA_Cmd(DMA1_Channel1,DISABLE);
    DMA_SetCurrDataCounter(DMA1_Channel1,4);
    DMA_Cmd(DMA1_Channel1,ENABLE);
    while(DMA_GetFlagStatus(DMA1_FLAG_TC1)==RESET);
    DMA_ClearFlag(DMA1_FLAG_TC1);
    
}


总结

        以上就是今天要讲的内容,本文仅仅简单介绍了DMA+AD多通道的函数设计。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2178769.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Tomcat 调优技巧(Tomcat Tuning Tips)

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:Linux运维老纪的首页…

IDEA关联Tomcat

一、Tomcat服务器 web服务器,就是运行web项目的容器 即运行java代码的一个容器 webapp(web应用程序) --> 就是我们写的javaweb项目 Tomcat 是Apache 软件基金会(Apache Software Foundation)下的一个核心项目,免费开源、并支持Servlet 和J…

yolov8/9/10模型在垃圾分类检测中的应用【代码+数据集+python环境+GUI系统】

yolov8/9/10模型在垃圾分类检测中的应用【代码数据集python环境GUI系统】 yolov8/9/10模型在垃圾分类检测中的应用【代码数据集python环境GUI系统】 背景意义 随着计算机视觉技术和深度学习算法的快速发展,图像识别、对象检测、图像分割等技术在各个领域得到了广泛…

DL_语义分割(学习笔记)

文章目录 图像分割1 常见分类1.1 语义分割1.2 实例分割1.3 全景分割 2 语义分割2.1 模型评价指标2.2 常用数据集2.3 转置卷积2.4 膨胀卷积2.5 感受野2.6 双线性插值2.7 FCN 图像分割 1 常见分类 1.1 语义分割 定义:【只判断类别,无法区分个体】 语义分…

Matlab实现麻雀优化算法优化回声状态网络模型 (SSA-ESN)(附源码)

目录 1.内容介绍 2.部分代码 3.实验结果 4.内容获取 1内容介绍 麻雀搜索算法(Sparrow Search Algorithm, SSA)是一种新兴的群体智能优化算法,灵感来源于麻雀的觅食行为及其在面临危险时的预警机制。SSA通过模拟麻雀的这些自然行为来寻找问题…

[Docker学习笔记]利用Dockerfile创建镜像

Dockerfile 指令 指令作用from继承基础镜像maintainer镜像制作者信息(可缺省)run用来执行shell命令expose暴露端口号cmd启动容器默认执行的命令entrypoint启动容器真正执行的命令volume创建挂载点env配置环境变量add复制文件到容器copy复制文件到容器workdir设置容器的工作目录…

蓝卓亮相中国工博会,打造以数据驱动的智能工厂

9月28日,以“工业聚能,新质领航”为主题的第24届中国国际工业博览会(以下简称“工博会”)在国家会展中心(上海)圆满拉下帷幕。本届工博会共设9大专业展区,吸引了来自全球28个国家和地区的2600余…

针对考研的C语言学习(定制化快速掌握重点4)

typedef的使用 简化变量类型 逻辑结构 集合结构:无关系 线性结构:一对一 树形结构:一对多 图形结构:多对多 存储结构 顺序存储和链式存储(考代码) 顺序存储优点:1.可以实现随机存取。2.…

针对考研的C语言学习(定制化快速掌握重点5)

顺序表 特点: 写代码主要就是增删改查!!! 写代码的边界性非常重要以及考研插入和删除的位置都是从1开始,而数组下标是从0开始 【注】下标和位置的关系 线性表最重要的是插入和删除会涉及边界问题以及判断是否合法 …

【Spring Boot 入门二】Spring Boot中的配置文件 - 掌控你的应用设置

一、引言 在上一篇文章中,我们开启了Spring Boot的入门之旅,成功构建了第一个Spring Boot应用。我们从环境搭建开始,详细介绍了JDK的安装以及IDE的选择与配置,然后利用Spring Initializr创建了项目,分析了项目结构&am…

资质申请中常见的错误有哪些?

在申请建筑资质的过程中,企业可能会犯一些常见的错误,以下是一些需要避免的错误: 1. 资料准备不充分: 申请资质需要提交大量的资料,包括企业法人资料、财务报表、业绩证明等。资料不齐全或不准确都可能导致申请失败。…

多线程(一):线程的基本特点线程安全问题ThreadRunnable

目录 1、线程的引入 2、什么是线程 3、线程的基本特点 4、线程安全问题 5、创建线程 5.1 继承Thread类,重写run 5.1.1 创建Thread类对象 5.1.2 重写run方法 5.1.3 start方法创建线程 5.1.4 抢占式执行 5.2 实现Runnable,重写run【解耦合】★…

MySQL-数据库设计

1.范式 数据库的范式是⼀组规则。在设计关系数据库时,遵从不同的规范要求,设计出合理的关系型数 据库,这些不同的规范要求被称为不同的范式。 关系数据库有六种范式:第⼀范式(1NF)、第⼆范式(…

【Mysql】SQL语言基础

1、SQL的概述 SQL全称:Structured Query Language,是结构化查询语言,用于访问和处理数据库的标准的计算机语言。SQL语言1974年由Boyce和Chamberlin提出,并首先在IBM公司研制的关系数据库系统systemr上实现。 美国国家标准局&#x…

亚信安全发布第34期《勒索家族和勒索事件监控报告》

本周态势快速感知 本周全球共监测到勒索事件91起,近三周勒索事件数量较为稳定。从整体上看,Ransomhub是影响最严重的勒索家族;Play和ElDorado恶意家族也是两个活动频繁的恶意家族,需要注意防范。本周,土耳其公司巴克皮…

小红书2024秋招后端开发(Java工程师、C++工程师等)

前几天做了美团,OPPO的秋招笔试题,然后又做了一场小红书,总体难度我觉得都差不多,涉及到的知识点要么是语法模拟,或者就是一些基础算法,所以这样看秋招编程题还是很简单的,对于笔试我们还要把除…

深刻理解Redis集群(下):Redis 哨兵(Sentinel)模式

背景 现在对3个节点的sentinel进行配置。sentinel的配置文件在redis的安装目录中已经存在,只需要复制到指定的位置即可。 sentinel是独立进程,有对应的脚本来执行。 基于之前的redis 一主二从的架构,我们继续启动3个sentinel进程。 哨兵模式的…

使用微服务Spring Cloud集成Kafka实现异步通信

在微服务架构中,使用Spring Cloud集成Apache Kafka来实现异步通信是一种常见且高效的做法。Kafka作为一个分布式流处理平台,能够处理高吞吐量的数据,非常适合用于微服务之间的消息传递。 微服务之间的通信方式包括同步通信和异步通信。 1&a…

GPU参数指标

以英伟达的A800卡为例,简单聊聊GPU卡的核心参数指标,A800的核心指标主要有5个,为算力、显存大小、显存带宽、功耗情况和卡间互联速率。 性能:则可以理解为货车对不同货物类型的马力大小,决定能“拉动”多少重量的货&…

实用工具推荐---- PDF 转换

直接上链接:爱PDF |面向 PDF 爱好者的在线 PDF 工具 (ilovepdf.com) 主要功能如下: 全免费!!!!