STM32学习:探索ADC(模拟数字转换器)的奥秘

目录

ADC的定义及其类型

ADC-单通道独立规则模式

对于该模式的理解:

通道及ADC分配:

时钟配置:

GPIO配置:

ADC模式配置:

校准:

读取ADC:

代码:野火的开源代码


由于大二学生一枚,水平有限,文中自己的理解难免出错,恳请道友发现后能批评❤️

未完待更…

ADC的定义及其类型

全称:ADC = Analog to Digital Converter

中文翻译:模数转换器

作用:模拟信号转换成数字信号的电路。

方法步骤:采样-量化-编码


模拟信号:模拟信号主要是指幅度相位都连续的电信号。个人理解:连续嘛自然就不能突然中间有跳点,跳跃间断点知道吧,函数连续知道吧,不能断了。在每个时刻都得有值。这个相位连续不就是时间上连续,一个瞬时时刻对应一个值。把时间作为横轴,幅值作为纵轴,那么模拟信号的图像应该是一条连续的线(一笔画嘛,不懂找宋浩去)。

数字信号:自然定义与模拟信号相反啦(手疼不想写了)。个人理解:首先虽然是间断的,但是间断得分种类啊,幅值得是有限值,要是取值无限那就不叫数字信号嘞,好像叫抽样信号吧。简单回忆一下数学假如x取值为(0,1)叫不叫取值有限???

采样是啥:每隔一定时间的信号样值序列来代替原来在时间上连续的信号,也就是在时间上将模拟信号离散化。个人理解:通俗来讲不就是每隔一段时间采集一小段电压嘛😒。

量化是啥:用有限个幅度值近似原来连续变化的幅度值,把模拟信号的连续幅度变为有限数量的有一定间隔的离散值。个人理解:本来是一小段是弯的,现在用一小段直的代替,相当于积分思想。

编码是啥:按照一定的规律,把量化后的值用二进制数字表示,然后转换成二值或多值的数字信号流。个人理解:为啥要编码嘞,那搞一堆数字好还是就用0,1好咧,肯定二进制好啦。

分辨率:即输入信号值的最小变化。个人理解:通俗点来说,假如你的ADC是12BIT的,那么其分辨率就是gif.latex?2%5E%7B12%7D,假设可测量的电压范围为[0,5V]那么,其精度为5的1024等份伏。

参考电压:为ADC提供基准电压,是ADC所能测量电压的最大值,可由片内或片外提供。


类型:

  • 逐次逼近型:中速,直接型,成本不高,大概是咱本科接触最多的
  • 并联比较型:    快速,直接型,能耗大,成本高
  • 双积分型: 慢速,间接型,低耗,低成本

  • 逐次逼近型:逐次逼近型ADC是另一种直接ADC,它也产生一系列比较电压VR,但与并联比较型ADC不同,它是逐个产生比较电压,逐次与输入电压分别比较,以逐渐逼近的方式进行模数转换的。逐次逼近型ADC每次转换都要逐位比较,需要(n+1)个节拍脉冲才能完成,所以它比并联比较型ADC的转换速度慢,比双分积型ADC要快得多,属于中速ADC器件(摘自百度百科)。由于这玩意用的比较广泛,且32中的ADC就是逐次比较型,稍微细致谈下基本工作方式:


    逐次逼近型的基本思想其实就是一个二分法的思想,比如,现在一头猪的重量未知,但是嘞,咱们这边有这么几个秤砣,gif.latex?%5E%7B%7Dgif.latex?2%5E%7B7%7Dkg, gif.latex?2%5E%7B6%7Dkg, gif.latex?2%5E%7B5%7Dkg, gif.latex?2%5E%7B4%7Dkg, gif.latex?2%5E%7B3%20%7Dkg, gif.latex?2%5E%7B2%20%7Dkg, gif.latex?2%5E%7B1%7Dkg, gif.latex?2%5E%7B0%7Dkg。当然啦,这猪的重量在这些个秤砣的总重范围内。好,咱们先放一个最重的。秤砣状态给记录一下,放上去记录为1,否则为0。放最重的比较结果是猪重。记录如下

    第一次比较
    gif.latex?2%5E%7B7%7Dkg gif.latex?2%5E%7B6%7Dkg gif.latex?2%5E%7B5%7Dkg gif.latex?2%5E%7B4%7Dkg gif.latex?2%5E%7B3%7Dkg gif.latex?2%5E%7B2%7Dkg gif.latex?2%5E%7B1%7Dkg gif.latex?2%5E%7B0%7Dkg
    1 0 0 0 0 0 0 0

     比较完了,发现猪重,怎么办?再加秤砣。

    第二次比较
    gif.latex?2%5E%7B7%7Dkg gif.latex?2%5E%7B6%7Dkg gif.latex?2%5E%7B5%7Dkg gif.latex?2%5E%7B4%7Dkg gif.latex?2%5E%7B3%7Dkg gif.latex?2%5E%7B2%7Dkg gif.latex?2%5E%7B1%7Dkg gif.latex?2%5E%7B0%7Dkg
    1 1 0 0 0 0 0 0

     好,发现猪被弹起来了,咋办,把刚刚加上去的拿掉换个刚刚一半重的秤砣。

    第三次比较
    gif.latex?2%5E%7B7%7Dkg gif.latex?2%5E%7B6%7Dkg gif.latex?2%5E%7B5%7Dkg gif.latex?2%5E%7B4%7Dkg gif.latex?2%5E%7B3%7Dkg gif.latex?2%5E%7B2%7Dkg gif.latex?2%5E%7B1%7Dkg gif.latex?2%5E%7B0%7Dkg
    1 0 1 0 0 0 0 0

     结果猪重了,再加…以此类推。那么最后得到的结果是不是就是这个八位二进制的数了。。

    那么在电路中如何实现这样的机制嘞,这里复习几个重要的器件。那么这个比较谁重的在该电路中叫比较器,用法很简单啦。而上表其实就是所谓的寄存器用于存放二进制数据的,这也就是笔者举这个例子的原因啦。当然,比较器输入的是模拟量,因此我们需要把逐次逼近寄存器(SAR)里面的数值转化为一个模拟量电压输出,从而与所采样的值进行比较,这便需要一个数模转化器(DAC),在采样过后,需要保证这一小段的模拟量不变,那么就需要一个保持器。

    7c05f94fc7614f009af24edb262e566b.png

    概念示意图

    839e0b9ce9094a1281ca4c62c92f9c64.jpeg

    更加详细一点的(摘自网络)

     这些内容基本数电模电教材上有啦,不懂自学吧。重点基本知识:触发器,移位寄存器,比较器。


    并联比较型: 采用各量级同时并行比较,各位输出码也是同时并行产生,所以转换速度快是它的突出优点,同时转换速度与输出码位的多少无关。并联比较型ADC的缺点是成本高、功耗大。因为n位输出的ADC,需要2n个电阻,(2n-1)个比较器和D触发器,以及复杂的编码网络,其元件数量随位数的增加,以几何级数上升。所以这种ADC适用于要求高速、低分辩率的场合。(摘自百度百科)。备注:本人没用过。只是作为了解。

    双积分型:属于间接型ADC,它先对输入采样电压和基准电压进行两次积分,以获得与采样电压平均值成正比的时间间隔,同时在这个时间间隔内,用计数器对标准时钟脉冲(CP)计数,计数器输出的计数结果就是对应的数字量。双积分型ADC优点是抗干扰能力强;稳定性好;可实现高精度模数转换。主要缺点是转换速度低,因此这种转换器大多应用于要求精度较高而转换速度要求不高的仪器仪表中(摘自百度百科)。备注:本人没用过。只是作为了解。

    ADC-单通道独立规则模式


    对于该模式的理解:

    单通道嘛,简单,就一个输入;啥是独立嘞,3个ADC自己采集自己分配到的通道,跟别人没关系,规则嘛,简单来说就是老老实实的干活,配置成单次转换就采集一次就完了,循环模式嘞就按规律一直采集。不打扰其他模式工作,比较老实。


    通道及ADC分配:

    笔者用的是STM32F103VET6,对于大容量产品有ADC1,2,3。ADC1呢一般是主ADC,还是比较牛的。ADC模块嘞共有18个通道,对于本模式中,其中可供外部使用的有16个。另外两个干嘛了?参考手册给了答案。

     

    eb5a1de2a9a046acadd78c0b23868b55.png

     那么具体的通道对应的引脚可以查阅数据手册。


    时钟配置:

             由时钟控制器提供的ADCCLK时钟和PCLK2(APB2时钟)同步。RCC控制器为ADC时钟提供一个 专用的可编程预分频器。且ADC_CLK最大不得超过14MHZ。

    备注:笔者在此就犯了一个错误,只是开启了片上外设ADC时钟,但未配置其时钟导致ADC无法正常工作。即:RCC_ADCCLKConfig(uint32_t RCC_PCLK2)。

    542dd239ad64421983041962bcb30b92.png

    ADC时钟树(参考手册)


    GPIO配置:

    GPIO配置的基本过程:开时钟 → 配引脚 → 配模式 → 配速度(对于输出而言输入则不用)。

    由于ADC采集的是模拟量,因此模式须配置为模拟输入,对应固件库GPIO_Mode_AIN。

    0ddcbc32b5cb4374ac8259c68eef9172.png

    模拟模式的信号走向


    ADC模式配置:

    这里没啥好讲的,有代码的看看就行了。但是需要注意,一般像这种外设都有个自己的开关ADC_Cmd(ADC_TypeDef* ADCx, FunctionalState NewState);然后使用软件触发的,需要用软件触发ADC_SoftwareStartConvCmd(ADC_TypeDef* ADCx, FunctionalState NewState);当然别忘了给ADC分配通道。


    校准:

    ADC自带了硬件校准,这样做是为了消除电容组带来的静态误差,是不是听着很牛逼,其实不用特别定量分析电路的,知道电容能储电就欧克,由于电容电压不能突变,所以当电路中电压变化时,可能会影响到电压的采集。这里直接贴图了:应该能看懂

    dd719b7539664514a4ccfde679e673b7.png

     备注:这里笔者自己写代码的时候就没有写校准,得到的值自然没那么精确啦。还有,笔者写校准的时候,把程序写卡死了,这里是错误代码:

    	// 开启ADC ,并开始转换
    	ADC_Cmd(ADCx_1, ENABLE);	
    	// 初始化ADC 校准寄存器  
    	ADC_ResetCalibration(ADCx_1);
    	// 等待校准寄存器初始化完成
    	while(ADC_GetResetCalibrationStatus(ADCx_1) == SET);	
    	// ADC开始校准
    	ADC_StartCalibration(ADCx_1 == SET);
    	// 等待校准完成
    	while(ADC_GetCalibrationStatus(ADCx_1));


    读取ADC:

    这里需要注意,调用固件库ADC_GetConversionValue(ADC_TypeDef* ADCx);得到的值仅仅是ADC_DR里面的值,并不是实际的电压值,电压值怎么算,就得深刻了解参考电压,分辨率。上公式:  实际电压 =  (ADC_DR)/分辨率 *(正参考电压-负参考电压)。显然我的这负参考电压为0。然后还要特别注意这个ADC1寄存器的数据大小16位,但实际上只用了12位。当然还要会寄存器操作啦,比如说咋用寄存器操作读数据。

    查询方式读取:当设置位单次转换模式,比较好理解,结束了即自动产生一个转换完成标志位(EOC)。加个判断语句,读完之后再软件置位即可再采集,不会乱。

    那么连续转换嘞?哼哼,这就比较麻烦了,反正我是没有查标志位成功过。可以采用DMA读取就可以啦,或者是中断读取就可以啦,那么这里DMA就不细说了,后期再单独说明。这个中断配置要开启ADC自己的中断允许位,然后嘞配置优先级自然用到(NVIC)啦。好,也不说了。😂


    代码:野火的开源代码

    需要的时候自提啦!主函数就不贴了,不傻应该也会。

    // ADC 编号选择
    // 可以是 ADC1/2,如果使用ADC3,中断相关的要改成ADC3的
    #define    ADC_APBxClock_FUN             RCC_APB2PeriphClockCmd
    #define    ADCx                          ADC2
    #define    ADC_CLK                       RCC_APB2Periph_ADC2
    
    // ADC GPIO宏定义
    // 注意:用作ADC采集的IO必须没有复用,否则采集电压会有影响
    #define    ADC_GPIO_APBxClock_FUN        RCC_APB2PeriphClockCmd
    #define    ADC_GPIO_CLK                  RCC_APB2Periph_GPIOC  
    #define    ADC_PORT                      GPIOC
    #define    ADC_PIN                       GPIO_Pin_1
    // ADC 通道宏定义
    #define    ADC_CHANNEL                   ADC_Channel_11
    
    // ADC 中断相关宏定义
    #define    ADC_IRQ                       ADC1_2_IRQn
    #define    ADC_IRQHandler                ADC1_2_IRQHandler
    
    //#define    ADC_IRQ                       ADC3_IRQn
    //#define    ADC_IRQHandler                ADC3_IRQHandler
    
    
    
    /**
      * @brief  ADC GPIO 初始化
      * @param  无
      * @retval 无
      */
    static void ADCx_GPIO_Config(void)
    {
    	GPIO_InitTypeDef GPIO_InitStructure;
    	
    	// 打开 ADC IO端口时钟
    	ADC_GPIO_APBxClock_FUN ( ADC_GPIO_CLK, ENABLE );
    	
    	// 配置 ADC IO 引脚模式
    	// 必须为模拟输入
    	GPIO_InitStructure.GPIO_Pin = ADC_PIN;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    	
    	// 初始化 ADC IO
    	GPIO_Init(ADC_PORT, &GPIO_InitStructure);				
    }
    
    /**
      * @brief  配置ADC工作模式
      * @param  无
      * @retval 无
      */
    static void ADCx_Mode_Config(void)
    {
    	ADC_InitTypeDef ADC_InitStructure;	
    
    	// 打开ADC时钟
    	ADC_APBxClock_FUN ( ADC_CLK, ENABLE );
    	
    	// ADC 模式配置
    	// 只使用一个ADC,属于独立模式
    	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
    	
    	// 禁止扫描模式,多通道才要,单通道不需要
    	ADC_InitStructure.ADC_ScanConvMode = DISABLE ; 
    
    	// 连续转换模式
    	ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
    
    	// 不用外部触发转换,软件开启即可
    	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
    
    	// 转换结果右对齐
    	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
    	
    	// 转换通道1个
    	ADC_InitStructure.ADC_NbrOfChannel = 1;	
    		
    	// 初始化ADC
    	ADC_Init(ADCx, &ADC_InitStructure);
    	
    	// 配置ADC时钟为PCLK2的8分频,即9MHz
    	RCC_ADCCLKConfig(RCC_PCLK2_Div8); 
    	
    	// 配置 ADC 通道转换顺序和采样时间
    	ADC_RegularChannelConfig(ADCx, ADC_CHANNEL, 1, 
    	                         ADC_SampleTime_55Cycles5);
    	
    	// ADC 转换结束产生中断,在中断服务程序中读取转换值
    	ADC_ITConfig(ADCx, ADC_IT_EOC, ENABLE);
    	
    	// 开启ADC ,并开始转换
    	ADC_Cmd(ADCx, ENABLE);
    	
    	// 初始化ADC 校准寄存器  
    	ADC_ResetCalibration(ADCx);
    	// 等待校准寄存器初始化完成
    	while(ADC_GetResetCalibrationStatus(ADCx));
    	
    	// ADC开始校准
    	ADC_StartCalibration(ADCx);
    	// 等待校准完成
    	while(ADC_GetCalibrationStatus(ADCx));
    	
    	// 由于没有采用外部触发,所以使用软件触发ADC转换 
    	ADC_SoftwareStartConvCmd(ADCx, ENABLE);
    }
    
    static void ADC_NVIC_Config(void)
    {
      NVIC_InitTypeDef NVIC_InitStructure;
    	// 优先级分组
    	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
    
      // 配置中断优先级
      NVIC_InitStructure.NVIC_IRQChannel = ADC_IRQ;
      NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
      NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
      NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
      NVIC_Init(&NVIC_InitStructure);
    }
    
    
    /**
      * @brief  ADC初始化
      * @param  无
      * @retval 无
      */
    void ADCx_Init(void)
    {
    	ADCx_GPIO_Config();
    	ADCx_Mode_Config();
    	ADC_NVIC_Config();
    }
    
    
    
    void ADC_IRQHandler(void)
    {	
    	if (ADC_GetITStatus(ADCx,ADC_IT_EOC)==SET) 
    	{
    		// 读取ADC的转换值
    		ADC_ConvertedValue = ADC_GetConversionValue(ADCx);
    	}
    	ADC_ClearITPendingBit(ADCx,ADC_IT_EOC);
    }
    
    
    

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32学习:探索ADC(模拟数字转换器)的奥秘

    发表评论