【STM32】ADC采集光敏数据(不看库函数手册进行配置)
ADC采集光敏数据(不看库函数手册进行配置)
前言
在写一个工程的某一个功能的时候,我们都会对照库函数手册进行对该模块功能的配置,但是,有的时候,中文版的库函数手册在翻译过来的时候会有翻译上的出入,所以为了工程的可行性、进度性,我们可以不通过库函数手册,用另一种方法进行配置,请耐心往下看,完整的代码会在最后写出!
1、STM32库函数源代码说明
1.1首先我们先新建两个.c和.h新的文件,
我们用的是ADC1通道3,(这里不进行解释),所以我们要把PA3的引脚配置成模拟模式,因为连接ADC的所有通过I/O引脚,都必须为模拟输入模式。
那在模拟输入之前,我们要开GPIO A组的时钟,那我们开时钟的时候,怎么不通过手册去开时钟呢?
我们的规则是:先查看.h,(查看该功能的.h文件),比如RCC,我要使能时钟,那么就要先找stm32f10x_rcc.h()
就在该模块的“+”号这里打开,找到rcc.h,
那打开rcc.h,之后我们怎么看文件呢?注意,因为rcc比较特殊,因为rcc里面没有结构体,使能rcc没有结构体,打开.h后,直接倒着看,拉到文件最下面的文字,最下面存放的是rcc相关函数的声明。
找到这个,因为我们用的是APB2,我们就直接复制过去到工程,
void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);
2、然后右键单击RCC_APB2PeriphClockCmd,跳转过去,它里面会有一个函数调用说明。
点击后,进入到rcc.c文件看到其函数说明。
解释:brief:表示的是说明,摘要的意思,在这里相当于函数说明;
param:参数的意思,说的是,你在这里用的函数的是哪个形参;
那我们用的是GPIO A口的时钟,所以我们按照上面的图片的里面的 RCC_APB2Periph_GPIOA,我们直接复制下来,替换掉即可,
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
这样就完成一个函数的编写,使能GPIO A,下一步就到GPIO结构体,
2、GPIO配置
(RCC没有结构体,记住,因为他不需要使用结构体初始化,所以我们可以直接调用结构体初始化),但是GPIO是有结构体的,需要初始化结构体,查看的方法不一样,当然见面的步骤是一样的,先是找到gpio.h文件,这时候,注意,不需要像rcc一样拉到最下面,gpio的结构体是在上面的(因为rcc特殊)
往下滑,可以看到很多参数的宏定义:
再往下就可以看到结构体(截图到的是GPIO的结构体)
备注:一旦设计到结构体的,就按照这个方法来,从上往下看,不涉及到结构体的,就拉到最下面就可以了。
接下来,我们找到结构体类型,复制到工程
这样我们就可以直接进行配置
注意:找到这些结构体之后,这些填充的参数也是要在你找到结构体的上面或下面就可以找到,比如,第一个要我们填充的就是GPIO的模式,是要模拟输入的,
剩下的两个不再说明,目前完整的如下
void GZ_Init(void)//用的是PA3引脚,ADC1通道3
{
GPIO_InitTypeDef gz;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
gz.GPIO_Mode = GPIO_Mode_AIN;//模拟输入
gz.GPIO_Pin = GPIO_Pin_3;
gz.GPIO_Speed = GPIO_Speed_50MHz;
}
这时候,当你结束初始化结构体,还要对函数进行初始化,方法是拉到函数的最下面进行查找,
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
我们的操作是右键单击跳到 GPIO_Init的gpio.c文件,
圈的解释是: where x can be (A..G)to select the GPIO peripheral. 其中x可以是(A.g)来选择GPIO外围设备。然后取地址就可以完成这一步的操作。
GPIO_Init( GPIOA, &gz);//根据初始化的参数进行配置
到这里我们的引脚已经完成了模拟输入。
这里漏了一步,我们ADC设置的时候要设置预分频值,在rcc.h文件最底下可以找到,
可以分6/8分频都可以,我们通右键点击跳转过去就可以知道该填充的值;
RCC_ADCCLKConfig(RCC_PCLK2_Div6);//72/6=12Mhz,不能超过14MHZ
到此,我们完成RCC的配置之后,我们是要用ADC功能的,这时候就要初始化ADC功能。
3、ADC初始化
前面的步骤和上面GPIO一样,加相关的功能的.h文件,adc.h
ADC结构体如下:
对结构体初始化前,我们要把结构体成员复制过来 ADC_InitTypeDef,
定义好之后,就可以得出其结构体的成员,如上。
得出ADC结构体如下:
adc1_gz.ADC_ContinuousConvMode = 是否连续模式,我们启动为连续模式
adc1_gz.ADC_DataAlign = 数据对齐方式,左还是右对齐,我们选择右对齐
adc1_gz.ADC_ExternalTrigConv = 是否为外部触发,我们选择软件触发模式
adc1_gz.ADC_Mode = 独立模式还是双模式,我们选择双模式
adc1_gz.ADC_NbrOfChannel = 你需要装换的通道数量
adc1_gz.ADC_ScanConvMode = 选择单通道还是多通道,因为本次是光敏电阻功能,就需要单通道就好了。
接下来需要我们对结构体成员参数进行填充,在这之前,我们不知道要填充什么样的参数值,我们可以对照右边的英文进行翻译即可得出填充的信息。
完整的ADC结构体成员配置参数如下:
adc1_gz.ADC_ContinuousConvMode = DISABLE;//单次转换
adc1_gz.ADC_DataAlign = ADC_DataAlign_Right;//数据右对齐
adc1_gz.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//选软件启动
adc1_gz.ADC_Mode = ADC_Mode_Independent;//独立模式
adc1_gz.ADC_NbrOfChannel = 1;//设置转换的通道数量
adc1_gz.ADC_ScanConvMode = DISABLE;//配置为单通道模式
完成以后在adc.h文件拉到最下面,找到初始化函数,对其进行初始化即可,然后再开使能时钟就可以了,因为时钟操作能GPIO一样,我们就或“|”,GPIO时钟。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_ADC1, ENABLE);
初始化完之后,ADC就需要进行先校准才可以正常采集数据(这个工程在这里不解释,可以看往期文章或者百度)
4、ADC 复位校准
注意:1、建议在每次上电后执行一次校准;
2、启动校准前,ADC必须处于关机状态超过至少两个ADC时钟周期。
好的,因为这里他说了,要通过设置ADC_CR2寄存器的CAL位进行启动。所以我们就要看CAL寄存器,
CAL在位2,
由此可以知道,写1才开始校准,0结束。
接下来,对校准函数初始化,一样的步骤,回到adc.h文件,找到一下四个校准函数;这四个函数都要用到,
void ADC_ResetCalibration(ADC_TypeDef* ADCx);
解释:重置指定的ADC的校准寄存器
FlagStatus ADC_GetResetCalibrationStatus(ADC_TypeDef* ADCx);
解释:获取ADC重置校准寄存器状态
void ADC_StartCalibration(ADC_TypeDef* ADCx);
解释:开始指定ADC的校准程序
FlagStatus ADC_GetCalibrationStatus(ADC_TypeDef* ADCx);
解释:获取指定的ADC的校准状态
写到这里,你会觉得ADC很麻烦。确实麻烦,但是只要你配置完一个ADC,后面的ADC只需要改变通道的引脚就可以了,所以只能是是,前期麻烦了点,后期就简单多了。
好的,接下来,我们就对校准器进行编写改造,为了节省时间,小编已对其校准器部分写好并解释如下:
开始的时候
FlagStatus ADC_GetResetCalibrationStatus(ADC_TypeDef* ADCx);
这是一个flag的状态,那就是要有返回值,一般是成功set或者失败reset,这个操作只需要右键点击ADC_GetResetCalibrationStatus跳转过去就可以查看得出来,这里就不多加啰嗦。
那么复位就是等待它为1,那就用while语句,如果是1,那就取反就可以了,因为如果是1,复位成功返回的是set,set=成功=1;1取反,就是跳出,就是不需要再等了。(这样说能理解吧!)
整理如下:
ADC_ResetCalibration(ADC1);//复位校准器
while ( ADC_GetResetCalibrationStatus(ADC1));//复位校准器状态
ADC_StartCalibration(ADC1);//开始校准
while(ADC_GetCalibrationStatus(ADC1));//等待校准
初始化完成,接下来就需要进行采集数据,并将采集到的数据进行转换。
5、采集数据
这里我们结合上述的内容,我们可以知道,我们采集的是PA3连接ADC通道3通过的转换数据。
上述,我们已经不预分频处理好了,接下来,我们就要对其注入组和规则组进行设置(注入和规则相当于抢占和响应优先级)。转换结束之后,EOC位会被置1,因为我们选择的是单次转换模式
简单而言,如果我们选择的是规则通道,那EOC置1,选择的是规则通道,那么JEOC置1.
如果是单次转换模式,如果你采用了规则通道,数据转换完后,EOC位会被置1;单次转换模式下,采用的是注入模式,数据转换完成后,JEOC置1.
所以通道不一样,等待的标志位不一样,开始的时候,我们不确定使用的是注入还是规则通道,那应该用什么方法去确定呢?这个怎么说呢,意思是注入还是规则,由你自己决定。那怎么决定呢,实际上,它是将规则通道和注入通道分别封装成了一个函数,你要用哪个,调用哪个通道去采集就好了。
因为我们这个光敏的工程,只需要一个通道,设置成什么通道由你决定。(我们这次用规则通道)
一样的步骤,stm32f10x_adc.h,去找到规则通道
然后我们新建一个光照通道采集的函数如下:
然后步骤一样,单击右键跳转进入就可以看到参数配置,
四个参数分别是:设置为ADC几号? ADC1;
uint8_t ADC_Channel 设置为通道几号? 通道13;
uint8_t Rank 设置的是转换的通道数量顺序1-16,就是第几个开始转换,转换的顺序;
uint8_t ADC_SampleTime 设置的是转换的时间;(这些参数值的配置都可以早stm32f10x_adc.c找到)
//单次转换,需要我们调用函数
void get_gz_val(void)
{
ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 1, ADC_SampleTime_41Cycles5);//规则通道配置函数
}
接着,我们之前初始化的时候是选择软件启动,所以就要调用使能指定的ADC软件转换启动功能的函数,那就是我们回到_adc.c去找这个函数,这个就不复制了,自己看到这里,就会找了。
好的,接下来,我们选择的规则组转换完之后会置1,因为我们不可能直接返回数据,要等转换完以后,(等状态位 置1,说明数据才转换完成,这样才能返回数据,当然转换也是需要时间的)
在获取前,要检查ADC标志位是否置1;那就要去找标志位函数,就是获取完成的标志位,才能够返回想要的数据。
while(!(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC)==SET));
读取数据:
返回最近一次ADC规则组的转换数据。
这里注意,因为它是无符号的返回值,所以我们要改成u16,不然程序会报错,把void get_gz_val
改成 u16 get_gz_val即可。这样就返回了。完整如下:
//单次转换,需要我们调用函数
u16 get_gz_val(void)
{
ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 1, ADC_SampleTime_41Cycles5);//规则通道配置函数
ADC_SoftwareStartConvCmd(ADC1,ENABLE);//启动软件
while(!(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC)==SET));//等待EOC位置1
return ADC_GetConversionValue(ADC1);//返回ADC1通道采集到的数据
}
PS:记得函数声明哦,别写着写着忘记声明了。接下来在main函数里面进行初始化调用就可以进行测试了。最后,计算光照亮度还有一个公式:
光照=100-(获取到的数据()/4096*100);
6、实验现象
7、完整代码
gz.c
#include "gz.h"
void GZ_Init(void)//用的是PA3引脚,ADC1通道3
{
GPIO_InitTypeDef gz;
ADC_InitTypeDef adc1_gz;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_ADC1, ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);//72/6=12Mhz,不能超过14MHZ
gz.GPIO_Mode = GPIO_Mode_AIN;//模拟输入
gz.GPIO_Pin = GPIO_Pin_3;
gz.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init( GPIOA, &gz);
adc1_gz.ADC_ContinuousConvMode = DISABLE;//单次转换
adc1_gz.ADC_DataAlign = ADC_DataAlign_Right;//数据右对齐
adc1_gz.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//选软件启动
adc1_gz.ADC_Mode = ADC_Mode_Independent;//独立模式
adc1_gz.ADC_NbrOfChannel = 1;//设置转换的通道数量
adc1_gz.ADC_ScanConvMode = DISABLE;//配置为单通道模式
ADC_Init(ADC1, &adc1_gz);//初始化
ADC_Cmd(ADC1, ENABLE); //使能时钟
ADC_ResetCalibration(ADC1);//复位校准器
while ( ADC_GetResetCalibrationStatus(ADC1));//复位校准器状态
ADC_StartCalibration(ADC1);//开始校准
while(ADC_GetCalibrationStatus(ADC1));//等待校准
}
//单次转换,需要我们调用函数
u16 get_gz_val(void)
{
ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 1, ADC_SampleTime_41Cycles5);//规则通道配置函数
ADC_SoftwareStartConvCmd(ADC1,ENABLE);//启动软件
while(!(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC)==SET));//等待EOC位置1
return ADC_GetConversionValue(ADC1);//返回ADC1通道采集到的数据
}
gz.h
#ifndef _GZ_H_
#define _GZ_H_
#include "stm32f10x.h"
#include "io_bit.h"
void GZ_Init(void);
u16 get_gz_val(void);
#endif
mian.c(关键部分,因为还有其他功能,太多太乱,不完全复制了)
while(1)
{
Delay_ms(1000);
Gz = 0;
liangdu = 100-(Get_Gz_Data(3)/4096.0*100);
printf("光照强度:%02d%%\r\n",liangdu);
}
总结:再不看手册的时候,可以按照这样的方法来,当然习惯了手册的话,这个就是你的辅助方法了,希望我的方法能对初学的入门的朋友有所帮助,哪里写的不好的话,大家可以指正!谢谢,最后,大家可以稍微关注我一下,谢谢!
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)