STM32第十一课:ADC采集光照

文章目录

  • 需求
  • 一、ADC概要
  • 二、实现流程
  • 1.开时钟,分频,配IO
  • 2.配置ADC工作模式
  • 3.配置通道
  • 4.复位,AD校准
  • 5.数值的获取
  • 三、需求的实现
  • 总结

  • 需求

    通过ADC转换实现光照亮度的数字化测量,最后将实时测量的结果打印在串口上。
    `


    一、ADC概要

       ADC全称是Analog-to-Digital Converter模数转换器,一般我们把模拟信号(Analog signal) 用A来进行简写,数字信号(digital signal) 用D来表示。
      自然界中绝大部分都是模拟信号,例如压力或温度的测量,为了方便储存,处理和传输,我们会通过ADC把模拟信号转化成数字形式给计算机处理。将模拟转换成数字的形式有两个步骤:采样和量化。
      本例中就是将光照亮度这种模拟量转换为具体的数字量。

    本次使用的ADC:


    二、实现流程

    1.开时钟,分频,配IO

    先打开原理图,找到该光敏电阻的位置。

    由该电路可知VAL测量的是该光敏电阻的分压,而随着光照的变化,该光敏电阻的电压也会发生实时的波动。
    此时我们就利于该光敏电压的变化来实现需求。
    先找到CPU上对应的引脚

    由上图可知该模块对应的引脚为PA5,ADC为ADC12_IN5,代表该引脚PA5是ADC1/2的通道5。
    此时我们就开GPIOA的时钟和ADC1的通道(1,2都行,无所谓)
    代码如下:

    	RCC->APB2ENR |= 0x01<<9;//ADC1通道
    	RCC->APB2ENR |= 0x01<<2;//使能GPIOA
    

    下面就要进行分频了,由于本次使用的ADC的特征为12分辨率,而APB2所传输的频率为72M,所以此时我们要进行6分频(72 ÷ 6 = 12)

    	RCC->CFGR &= ~(0x03<<14);
    	RCC->CFGR |= (0x02<<14);//6分频
    

    最后进行PA5引脚的模式配置,由于要获得该引脚的电压值,而该电压值为动态变化的模拟量,所以此处要将模式置为模拟输入模式(0000)

    GPIOA->CRL &= ~(0x0F<<20);//配置成模拟输入
    

    2.配置ADC工作模式

    首先打开手册找ADC1的控制寄存器(CR1,CR2),一个一个查看,看是否需要配置。

    一般常用的是第8位扫描模式

    不过此处只传输光照一个变量,所以可以不开置零就行。

    双模式选择也是必要的,此处选独立模式就行,因为只用这一个ADC1。
    到这里ADC1的CR1寄存器的基本配置就算完成了。
    下面来看ADC1的CR2寄存器。


    先来看第20位规则通道的外部触发转换模式。规则通道组每转换一次,代表着ADC1把数据传输到DR规则组通道数据寄存器上,该寄存器为16位,并且每传输一次,数据就会被覆盖一次。
    此处我们选择开启1:使用外部时间启动转换

    再来看19-17位,规则通道组转换的外部触发条件。
    我们这里选择111:SWSTART(软件触发)因为是通过软件代码置位来触发。

    第十一位数据对齐的模式要选择为右对齐,方便后续操作。

    第一位的连续转换可开可关,因为只有光照一个量。

    最后使能一下第0位:开启ADC并启动转换。

    	//3、配置ADC的工作模式
    	ADC1->CR1 &= ~(0x0F<<16);//独立模式
    	ADC1->CR1 &= ~(0x01<<8);//不开扫描
    	ADC1->CR2 |= 0x01<<20;//选择开启外部触发
    	ADC1->CR2 |= 0x07<<17;//触发方式swsatrt(软件触发)
    	ADC1->CR2 &= ~(0x01<<11);//选择数据右对齐
    	ADC1->CR2 &= ~(0x01<<1);//关闭连续转换
    	ADC1->CR2 |= 0x01<<0;//ADC使能
    

    3.配置通道

    由于该引脚PA5对应的是ADC12_IN5,所以我们只需要配置通道5即可。
    配置通道在ADC规则序列寄存器和ADC采样时间寄存器中。
    先找到SQR1寄存器


    ADC规则序列寄存器负责通道数量的选择,共有16个,由于我们只用通道5,所以此时我们将L配置成0000,只配只配一个通道。

    接下来配置我们选的SQ1通道,将其配成通道0x05。

    最后配置一下采样周期,周期越大越准,所以我选择了111:239.5周期。

    	//配置一个通道:通道5,第一个转换,采样周期最大(239.5)
    	ADC1->SQR1 &= ~(0x0F<<20);//规则组通道只转换一个(配置通道数量)
    	//具体某个通道的配置
    	ADC1->SQR3 &= ~(0x1F<<0);//0-5位清0
    	ADC1->SQR3 |= 0x05<<0;//选择第一个转换通道5
    	ADC1->SMPR2 |= 0x07<<15;//采样周期最大(239.5)
    

    4.复位,AD校准

    校准可有可无,不过为了更加保险,我还是加上了。
    复位校准在CR2寄存器的第3位。

    AD校准在CR2寄存器的第2位。

    每次校准后会自动置位0,所以此处while(1)等待非0,若为1就等待,为0就校准完成,继续往下执行。

    	ADC1->CR2 |= 0x01<<3;//启动复位校准
    	//等待复位校准结束
    	while((ADC1->CR2&(0x01<<3))!=0)//判断寄存器的位3是不是等于1
    	{}
    	ADC1->CR2 |= 0x01<<2;//启动AD校准
    	//等待AD校准结束
    	while((ADC1->CR2&(0x01<<2))!=0)//判断寄存器的位2是不是等于1,是1就等待
    	{}
    

    5.数值的获取

    对于数值的获取,我是单独写了个函数来执行,放便主函数调用并发送给串口。
    想要获取数据,就要让ADC的CR2寄存器的第22位置1转换一下。

    每转换一次,就代表着ADC1把数据传输到DR规则组通道数据寄存器上,该寄存器为16位,并且每传输一次,数据就会被覆盖一次。
    所以此时我们让ADC的CR2寄存器的第22位置为1

    那么什么时候代表转换完了?此时就要查看ADC的状态寄存器SR了


    可以看到,每一次转换结束时,ADC_SR寄存器的第一位就会置1,并且不用我们去清零,每当我们去ADC_DR读取数据时,就会自动清除。
    那么此时我们就可判断转换结束位的0,1来进行数据的读取了。
    最后,将读取到的光照强度数据打印即可。(之前已经给printf重定向了,会自动打印到串口中)

    void GetLightValue()
    {
    	uint16_t Light=0;
    	//让规则通道转换一次
    	ADC1->CR2 |= 0x01<<22;
    	while((ADC1->SR&(0x01<<1))==0)//判断寄存器的位2是不是等于1,是0就等待转换完成
    	{}
    	Light = ADC1->DR; //读规则组通道数据寄存器
    	printf("光照强度参数 = %d \r\n",Light);
    }
    
    

    三、需求的实现

    关键代码如下:
    main.c

    #include "stm32f10x.h"
    #include "usart.h"
    #include "stdio.h"
    #include "delay.h"
    #include "string.h"
    #include "pwm.h"
    #include "adc.h"
    
    int main()
    {
    		NVIC_SetPriorityGrouping(5);//两位抢占两位次级
        Usart1_Config(); 
    	  SysTick_Config(72000);
    	  RGBpwm_Config();
    	  uint8_t cai_count=0;
    	  uint16_t cont=0;
    	  Adc_Config();
        while(1)
        {	
    	
    	
    			if(ledcnt[0]>=ledcnt[1]){//过去500ms
    			ledcnt[0]=0;
    					GetLightValue();
    		}
        }
    }
    

    adc.c

    #include "ADC.h"
    
    void Adc_Config(void)
    {
    	//PA5
    	//1、设置ADC的时钟(开时钟和时钟分频6分频)
    	RCC->APB2ENR |= 0x01<<9;//ADC1通道
    	RCC->APB2ENR |= 0x01<<2;//使能GPIOA
    	
    	RCC->CFGR &= ~(0x03<<14);
    	RCC->CFGR |= (0x02<<14);//6分频
    	//2、配置IO模式(模拟输入)
    	GPIOA->CRL &= ~(0x0F<<20);//配置成模拟输入
    	//3、配置ADC的工作模式
    	ADC1->CR1 &= ~(0x0F<<16);//独立模式
    	ADC1->CR1 &= ~(0x01<<8);//不开扫描
    	ADC1->CR2 |= 0x01<<20;//选择开启外部触发
    	ADC1->CR2 |= 0x07<<17;//触发方式swsatrt(软件触发)
    	ADC1->CR2 &= ~(0x01<<11);//选择数据右对齐
    	ADC1->CR2 &= ~(0x01<<1);//关闭连续转换
    	ADC1->CR2 |= 0x01<<0;//ADC使能
    	//配置一个通道:通道5,第一个转换,采样周期最大(239.5)
    	ADC1->SQR1 &= ~(0x0F<<20);//规则组通道只转换一个(配置通道数量)
    	//具体某个通道的配置
    	ADC1->SQR3 &= ~(0x1F<<0);//0-5位清0
    	ADC1->SQR3 |= 0x05<<0;//选择第一个转换通道5
    	ADC1->SMPR2 |= 0x07<<15;//采样周期最大(239.5)
    
    	ADC1->CR2 |= 0x01<<3;//启动复位校准
    	//等待复位校准结束
    	while((ADC1->CR2&(0x01<<3))!=0)//判断寄存器的位3是不是等于1
    	{}
    		ADC1->CR2 |= 0x01<<2;//启动AD校准
    	//等待AD校准结束
    	while((ADC1->CR2&(0x01<<2))!=0)//判断寄存器的位2是不是等于1,是1就等待
    	{}
    }
    
    void GetLightValue()
    {
    	uint16_t Light=0;
    	//让规则通道转换一次
    	ADC1->CR2 |= 0x01<<22;
    	while((ADC1->SR&(0x01<<1))==0)//判断寄存器的位2是不是等于1,是0就等待转换完成
    	{}
    	Light = ADC1->DR; //读规则组通道数据寄存器
    	printf("光照强度参数 = %d \r\n",Light);
    }
    
    

    adc.h

    #ifndef _ADC_H_
    #define _ADC_H_
    #include "stm32f10x.h"
    #include "stdio.h"
    void GetLightValue();
    
    void Adc_Config(void);
    #endif
    		
    

    delay.c

    #include "stm32f10x.h"
    #include "delay.h"
    
    uint32_t systicktime=0;
    
    uint16_t ledcnt[2]={0,1000};//500ms   每个任务执行的时间
    uint16_t led2cnt[2]={0,2000};//700ms
    uint16_t keycnt[2]={0,10};//10ms检测一次
    void SysTick_Handler(void)//1ms调用一次
    {
    	//不需要清中断挂起位
    	systicktime++;
    	ledcnt[0]++;
    	led2cnt[0]++;
    	keycnt[0]++;
    }
    
    void Delay_ms(uint32_t time)
    {
        uint32_t nowtime = systicktime;
    		while(systicktime < time+nowtime);
    }
    
    void Delay_nus(uint32_t time)
    {
        uint32_t i=0;
        for(i=0;i<time;i++){
            delay1us();
        }    
    }
    
    void Delay_nms(uint32_t time)
    {
        uint32_t i=0;
        for(i=0;i<time;i++){
             Delay_nus(1000);//延时1ms
        }    
    }
    
    

    delay.h

    #ifndef _DELAY_H_
    #define _DELAY_H_
    #include "stm32f10x.h"
    
    #define delay1us() {__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\
        __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\
        __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\
        __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\
        __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\
        __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\
        __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\
        __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();\
        __NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();__NOP();}
    
    extern uint16_t ledcnt[2];
    extern uint16_t led2cnt[2];
    extern uint16_t keycnt[2];		
    		
    void Delay_nus(uint32_t time);
    void Delay_ms(uint32_t time);
    void Delay_nms(uint32_t time);
    #endif
    		
    

    总结

    1.先看该光敏电阻的电路图,分析如何获取光照的数值。
    2.想到可以通过ADC转换得到光照的树数值,开始学习ADC的知识。
    3.先看ADC的功能描述,然后开时钟,分频,配IO。
    4.看手册中的ADC的控制寄存器,一个一个查看,看看究竟需要配置那些。
    5.看该引脚的ADC是那个通道的,开始配置通道。
    6.都配置完后进行复位校准和数据获取函数的编写。
    7.最后在主函数按照需求调用即可。

    作者:小白橘颂

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32第十一课:ADC采集光照

    发表回复