zoukankan      html  css  js  c++  java
  • STM32学习笔记(七) ADC模数转换测电平(普通和DMA模式)

      嵌入式系统在微控制领域(温度,湿度,压力检测,四轴飞行器)中占据着重要地位,这些功能的实现是由微处理器cpu(如stm32)和传感器以及控制器共同完成的,而连接他们,使它们能够互相正常交流的正是本小节要讲诉的模块,ADC模数转换外设。下面从最简单的实验说起,逐渐深入了解这个外设。

        本次ADC模数转换设计实现并不复杂,步骤可简化为以下三步:

      1. 接收板上电位器的输入电压

        2. 经过A/D转换获得数字量,并传送给cpu

        3. 通过串口在PC机上输出。

        解析上面三个步骤,分析要求,就会发现ADC、GPIO、USART以及RCC模块就是本次实验所需要的用到的外设,因为除ADC模块,其它外设前面已经学习和实践了,那么理解和学习ADC模块,就可开始程序的设计实现了。

      根据stm32f系列微控制器手册ADC章节

       

     

       

     

        ADC转换的后数字量为12位(分辨率),在参考开发板用户手册和原理图,可知电位器的端口为PC0,输入电压范围0~3.3V,可知精度为3.3/(2^12)V.

        

        查询stm32f107的引脚定义分配,可知PC0对应ADC12_IN10,也就是说采集电位器电压用ADC1和ADC2都可以,但必须采用通道10。

    目前来说,用库函数操作可以避免出现漏错,因此我还是推荐使用库函数配置寄存器,但是了解库函数的含义还是十分有必要的:

    typedef struct
    {
      u32  ADC_Mode;                            //明确ADC1和ADC2的工作方式,独立或其它组合
    
      FunctionalState  ADC_ScanConvMode;      //通道工作方式,单通道还是多通道(扫描)
    
      FunctionalState  ADC_ContinuousConvMode;  //工作在连续还是单次模式(ADC转换工作在连续模式
    
      u32 ADC_ExternalTrigConv;              //A/D转换启动规则
    
      u32  ADC_DataAlign;                      //判断转换数据的对齐方式
    
      u8  ADC_NbrOfChannel;                    //明确规则转换通道的具体数目1~16
    
    }ADC_InitTypeDef

    了解上述结构体代表含义,下面就可以初始化相关寄存器实现ADC外设的配置:

    GPIO_InitTypeDef GPIO_InitStructure;
    ADC_InitTypeDef ADC_InitStructure;
    ADC_DeInit(ADC1); 
        
    //ADC模块外设时钟需在APB2时钟基础上设置,决定单个周期的时钟长度(因为ADC时钟不能大于14MHZ,注意)
    RCC_ADCCLKConfig(RCC_PCLK2_Div4);
    //使能ADC对应GPIO口,外设区域及复用功能时钟 RCC_APB2PeriphClockCmd(RCC_ADC1, ENABLE); //初始化ADC模块对应GPIO GPIO_InitStructure.GPIO_Pin = GPIO_ADC1_Pin; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; GPIO_Init(GPIO_ADC1, &GPIO_InitStructure); ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //ADC1和ADC2工作在独立模式 ADC_InitStructure.ADC_ScanConvMode = ENABLE; //工作在扫描模式
    ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //转换工作在连续模式

    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //转换由软件触发,启动需调用ADC_Cmd程序 ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //ADC数据右对齐 ADC_InitStructure.ADC_NbrOfChannel = 1; //ADC通道数目为1 ADC_Init(ADC1, &ADC_InitStructure); //指定ADC转换的通道和转换周期 ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 1, ADC_SampleTime_55Cycles5);
    #ifdef USE_ADC1_DMA_PER ADC_DMACmd(ADC1, ENABLE);
    //ADC1 DMA请求使能 #endif ADC_Cmd(ADC1, ENABLE); //ADC1使能

    ADC_ResetCalibration(ADC1); //重置ADC1校准寄存器 while (ADC_GetCalibrationStatus(ADC1)) //等待ADC1校准寄存器重置完成 { } ADC_StartCalibration(ADC1); //ADC1进入校准状态 while(ADC_GetCalibrationStatus(ADC1)) //等待ADC1校准完成 { }

    ADC_SoftwareStartConvCmd(ADC1, ENABLE);
    //ADC1软件触发方式启动

    这里有一个重要知识点:ADC通道的规则组和注入组

          在AD转换中,规则组定义的是ADC扫描通道的顺序,按照规则组配置时的采样顺序从小到大依次扫描ADC通道,而注入组的优先级高于规则组,当注入组转换触发时就打断规则组的扫描而执行注入组的通道扫描,具体流程类似于中断中的抢占。本次ADC的转换仅仅使用到一个端口,这些不用考虑,但是在多通道AD/DA采集时,规则组和注入组要根据实际情况进行配置。

    注意:配置通道的规则组和注入组是一定要在使能ADC转换之前的。

    完成了初始化后,剩下的就简单了,只要获得ADC处理后的数字量,在转换成整形变量,就可以通过串口发送接收了,如下:

    //直接获得当前ADC转换后的值,转换并输出,CPU参与传送
    ADValue = ADC_GetConversionValue(ADC1);
    Precent = (ADValue*100/0x1000);
    Voltage = Precent*33;
    printf("
    
     ADCConvertedValue is 0x%x, Percent is %d%%, voltage is %d.%d%dV",
          ADValue,Precent,Voltage/1000,(Voltage%1000)/100,(Voltage%100)/10);
    printf("
     ADC output");
                
    ARM_DELAY(2000000);

    注意:使用了printf函数作为输入输出时,包含头文件#include ”stdio.h” Target下要选择use MicroLib,否则是不会有输出的(串口章节已经说明,重要)

    如此便实现了电位记电压的采集和输出,不过这并不是结束,因为今天我们还要学习另一个同样用途广泛的外设-DMA模块。

        首先我们要知道DMA是干什么的?DMA模块的主要作用是将内存或者外设中的数据自由移动,而不需要cpu的参与,同时通过存储指针的自偏移,实现大量数据的顺序存储(这一点在通讯领域具有重要意义)。和上面一样,学习DMA,肯定首先查询手册了:

       

       从这上面我们可以得出,ADC1对应的传输通道为通道1,在了解下面的结构体后:

    typedef struct
    {
      u32 DMA_PeripheralBaseAddr;     //定义的外设基地址
      u32 DMA_MemoryBaseAddr;         //定义的内存基地址
      u32 DMA_DIR;                    //外设作为数据传输的来源还是目的地
      u32 DMA_BufferSize;             //DMA通道的 DMA缓存的大小,单位为数据单位
      u32 DMA_PeripheralInc;          //外设地址寄存器递增或不变
      u32 DMA_MemoryInc;              //内存地址寄存器递增或不变
      u32 DMA_PeripheralDataSize;     //外设数据宽度
      u32 DMA_MemoryDataSize;         //内存数据宽度
      u32 DMA_Mode;                   //DMA缓存工作方式
      u32 DMA_Priority;               //DMA工作优先级
      u32 DMA_M2M;                    //DMA工作是内存到内存,还是外设到内存
    } DMA_InitTypeDef;

    我们可以得出以下结论:

    定义uint16_t  ADCConvertedValue; //接收内存地址

    1.外设基地址为(uint32_t)&(ADC1->DR)

    2.数据来源于ADC外设,传送地址为内存

    3.工作在循环模式,且外设和内存地址都不自增

    4.数据传输使用DMA1的通道1

    按照上面的结构体依此配置DMA_InitStructrue的各项参数,初始化如下:

    DMA_InitTypeDef DMA_InitStructure;
          
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); 
    DMA_DeInit(DMA1_Channel1);                                                  //复位ADC1对应DMA通道DMA1_Channel1
           
    DMA_InitStructure.DMA_PeripheralBaseAddr =(uint32_t)&(ADC1->DR);            //ADC1规则组转换值寄存器地址作为基地址
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&ADCConvertedValue;        //数据传输至内存的基地址
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;                          //外设作为数据来源地
    DMA_InitStructure.DMA_BufferSize = 1;                                       //可增加地址的的长度
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;            //外设地址不允许自增
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;                    //内存地址不允许自增
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //外设数据为半字16bit
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;         //内存数据为半字16bit
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;                             //DMA工作在循环模式
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;                         //DMA请求优先级高     
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;                                //DMA是外设到内存传递
    DMA_Init(DMA1_Channel1, &DMA_InitStructure);                                  
           
    DMA_Cmd(DMA1_Channel1, ENABLE);                                             /*DMA使能*/

    同时在main函数中添加:

    //将DMA从ADC处传送的数字量经过处理,转换并输出,DMA控制器用于传送
    ADCConvertedValueLocal = ADCConvertedValue;                      
    Precent = (ADCConvertedValueLocal*100/0x1000);
    Voltage = Precent*33;
    printf("
    
     ADCConvertedValue is 0x%x, Percent is %d%%, voltage is %d.%d%dV",
           ADCConvertedValueLocal,Precent,Voltage/1000,(Voltage%1000)/100,(Voltage%100)/10);
    printf("
     ADC DMA output");

    如此便实现了通过ADC通过DMA的传输。

    实验现象如下图:

    代码下载:http://files.cnblogs.com/files/zc110747/5.ADC-DMA.7z

    以下来自外部资料及个人总结,希望对理解DMA模块有用处:

       1.DMA传输将数据从一个地址空间复制到另外一个地址空间,这部分是由DMA控制器实现的,不需要依靠CPU的大量的数据采集传送,节省cpu资源。

       2.DMA工作包含四个过程

         DMA请求-〉DMA响应-〉DMA传输-〉DMA结束

       3.DMA传送方式有以下三种

         (1)停止CPU访内存

         当外围器件有一批数据需要传送时,DMA给CPU发送停止信号,CPU停止访问内存,释放相关总线控制权,DMA获得总线控制权后开始传递数据,完成后将总线控制权交给CPU。一次DMA传送结束。

        优点:控制简单,用于速率很高的组传送

        缺点:内存的效能没有发挥,一部分时间内存处于空闲状态。这是因为DMA传送阶段有很多时间是在读取外设的数据,总线一段时间肯定是空闲的,而这部分时间足够CPU进行内存的访问。

        (2)周期挪用;(ADC转换采用的正是这种方式)

           当I/O设备没有DMA请求时,CPU按程序要求访问内存;一旦I/O设备有DMA请求,则由I/O设备挪用一个或几个内存周期。

        (3)DMA与CPU交替访内存

  • 相关阅读:
    leetcode 309. Best Time to Buy and Sell Stock with Cooldown
    leetcode 714. Best Time to Buy and Sell Stock with Transaction Fee
    leetcode 32. Longest Valid Parentheses
    leetcode 224. Basic Calculator
    leetcode 540. Single Element in a Sorted Array
    leetcode 109. Convert Sorted List to Binary Search Tree
    leetcode 3. Longest Substring Without Repeating Characters
    leetcode 84. Largest Rectangle in Histogram
    leetcode 338. Counting Bits
    git教程之回到过去,版本对比
  • 原文地址:https://www.cnblogs.com/zc110747/p/4738645.html
Copyright © 2011-2022 走看看