STM32 | ADC 获取 光敏传感器数值
·
目录
Overview:
光敏传感器
光敏传感器是利用光敏元件将光信号转换为电信号的传感器。我们在STM32的一个GPIO上接了一个光敏二极管(光敏电阻)。它对光的变化非常敏感,不同光照情况下,通过的电流不同,所以我们在这条电路上串接一个电阻,就可以转变成模拟电压的变化。然后通过ADC读取电压值,交给单片机来处理。(光线越强,电压越低,光线越暗,电压越高)
光敏二极管也叫光电二极管。
光敏二极管与半导体二极管在结构上是类似的,其管芯是一个具有光敏特征的 PN 结,具有单向导电性,因此工作时需加上反向电压。
- 无光照时,有很小的饱和反向漏电流,即暗电流,此时光敏二极管截止。
- 当受到光照时,饱和反向漏电流大大增加,形成光电流,它随入射光强度的变化而变化。
当光线照射 PN 结时,可以使 PN 结中产生电子一空穴对,使少数载流子的密度增加。
这些载流子在反向电压下漂移,使反向电流增加。因此可以利用光照强弱来改变电路中的电流。
ADC
接在STM32 的 ADC 是 12 位逐次逼近型的模拟数字转换器,18 个通道,可测量 16 个外部和 2 个内部信号源。
最大的转换速率为 1Mhz ,也就是转换时间为 1us (在 ADCCLK=14M, 采样周期为 1.5 个 ADC 时钟下得到),不要让 ADC 的时钟超过 14M ,否则将导致结果准确度下降。
解释一下:
12 位逐次逼近型 ADC
- 12 位:这是 ADC 的精度指标。
- 简单说:12 位表示 ADC 能把 “0~3.3V” 这个电压范围,分成
2^12 = 4096个等份。- 计算:最小可分辨的电压(量化步长)= 3.3V / 4096 ≈ 0.805mV。比如输入电压是 1.65V,转换后的数字值就是 1.65V / 0.805mV ≈ 2050(十进制),或 0x7F2(十六进制)。
- 对比:如果是 8 位 ADC,只能分成 256 份,步长≈12.9mV,精度远低于 12 位 —— 这也是 STM32F407 ADC 的核心优势。
- 逐次逼近型:这是 ADC 的工作原理,也是嵌入式芯片最常用的 ADC 类型。
- 通俗理解:就像猜数字游戏 —— 先猜中间值(2048),看输入电压比这个值大还是小,再缩小范围继续猜,直到锁定精确值。
- 特点:速度快、精度高、功耗适中,完美匹配 STM32 的工业级应用场景(比 “积分型 ADC” 快,比 “流水线型 ADC” 成本低)。
18 个通道
可测量 16 个外部和 2 个内部信号源
- 通道:可以理解为 ADC 的 “输入接口”,18 个通道就是 18 个 “输入端口”,但物理引脚只有 16 个外部的,另外 2 个是芯片内部的。
- 16 个外部通道:对应 STM32 的特定 GPIO 引脚(比如 PA0~PA7、PB0~PB1 等),用来测量外部传感器(如温湿度、电压、电流)的模拟信号。
- 2 个内部通道:芯片自带的参考信号,无需外接引脚:
- 内部温度传感器:测量芯片自身的温度;
- 内部参考电压(Vrefint):约 1.2V,用于校准 ADC、提高测量精度。
- 实用价值:不用外接多路选择器,就能同时采集多个外部模拟信号,比如同时测温度、湿度、电池电压,简化硬件电路。
最大转换速率 1MHz
转换时间 1μs(ADCCLK=14M,采样周期 1.5 个 ADC 时钟)
- ADCCLK:ADC 的专用时钟,是 ADC 工作的 “心跳”,这个时钟不能随便调,有严格上限(14MHz)。
- 转换速率 1MHz:表示 ADC 每秒最多能完成 100 万次转换,对应单次转换时间≈1μs(1/1000000 秒)。
- 转换时间的计算(核心):ADC 的单次转换 = 采样时间 + 转换时间(逐次逼近时间)。STM32F407 的 ADC 转换固定需要12.5 个 ADCCLK 时钟周期(12 位模式),加上采样周期(这里是 1.5 个周期),总周期 = 1.5+12.5=14 个。当 ADCCLK=14MHz 时,单个时钟周期 = 1/14μs≈0.0714μs。总转换时间 = 14 × (1/14μs) = 1μs → 对应速率 = 1/1μs=1MHz。
- 通俗理解:ADC 先 “采样”(把输入电压存到电容里),再 “转换”(逐次逼近算数字值),14 个时钟周期刚好完成一次,14MHz 时钟下就是 1μs 完成一次转换。
不要让 ADC 的时钟超过 14M
否则结果准确度下降
- 原因:STM32F407 的 ADC 硬件设计时,内部的采样电容、比较器等电路的响应速度有限。
- 如果 ADCCLK>14MHz(比如调到 16MHz),时钟周期变短,采样和转换的时间不足:
- 采样阶段:电容来不及充满输入电压,采样值不准;
- 转换阶段:逐次逼近的比较器来不及稳定,计算出的数字值偏差变大。
- 后果:测量结果出现误差,比如实际输入 1V,转换后可能显示 0.95V 或 1.05V,甚至跳变严重。
- 实操建议:配置 ADC 时钟时,必须通过分频把 ADCCLK 限制在 14MHz 以内(比如系统时钟 168MHz,ADC 预分频器设为 12,168/12=14MHz)。
原理图


配置代码
初始化
1. ADC配置
Adc3_Init():负责 ADC3 的初始化配置
- 设置ADC分频因子,确保ADC时钟不超过14MhZ
- 开启 ADC3 时钟并复位
- 配置工作模式为独立模式(非双 ADC 模式)
- 设置为单通道、单次转换模式
- 禁用外部触发,使用软件触发转换
- 配置数据右对齐,设置转换通道数为 1
- 完成 ADC 校准流程
void Adc3_Init(void)
{
ADC_InitTypeDef ADC_InitStructure;
// 设置ADC分频因子,确保ADC时钟不超过14MHz
RCC_ADCCLKConfig(RCC_PCLK2_Div6); // 将APB2时钟(72MHz)分频为12MHz(72/6=12MHz)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC3, ENABLE ); //使能ADC3通道时钟
RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC3,ENABLE); //ADC复位
RCC_APB2PeriphResetCmd(RCC_APB2Periph_ADC3,DISABLE);//复位结束
ADC_DeInit(ADC3); //复位ADC3,将外设 ADC3的全部寄存器重设为缺省值
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //ADC工作模式: 独立模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE; //模数转换工作在单通道模式
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //模数转换工作在单次转换模式
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //转换由软件而不是外部触发启动
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //ADC数据右对齐
ADC_InitStructure.ADC_NbrOfChannel = 1; //顺序进行规则转换的ADC通道的数目
ADC_Init(ADC3, &ADC_InitStructure); //根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器
ADC_Cmd(ADC3, ENABLE); //使能指定的ADC3
ADC_ResetCalibration(ADC3); //使能复位校准
while(ADC_GetResetCalibrationStatus(ADC3)); //等待复位校准结束
ADC_StartCalibration(ADC3); //开启AD校准
while(ADC_GetCalibrationStatus(ADC3)); //等待校准结束
}
2. 接收输入的GPIO配置
上面只是ADC的初始化配置,还需要进行对连接光敏电阻的GPIO的配置。
- 打开GPIO时钟
- 配置GPIO模式为为模拟输入
void Lsens_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOF,ENABLE);//使能PORTF时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //PF8
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //模拟输入引脚
GPIO_Init(GPIOF, &GPIO_InitStructure);
// 调用上面的ADC初始化
Adc3_Init();
}
ADC业务
1. 触发单通道ADC转换
Get_Adc3: 用于读取ADC3中某个通道的ADC值
//获得ADC3某个通道的值
//ch:通道值 0~16
//返回值:转换结果
u16 Get_Adc3(u8 ch)
{
//设置指定ADC的规则组通道,一个序列,采样时间
/*
ADC_SampleTime_239Cycles5:采样时间设置为 239.5 个 ADC 时钟周期。
采样时间越长:抗干扰能力越强,适合缓慢变化的信号(如光敏传感器)。
采样时间越短:适合快速变化的信号,但可能引入噪声。
*/
ADC_RegularChannelConfig(ADC3, ch, 1, ADC_SampleTime_239Cycles5 ); //ADC3,ADC通道,采样时间为239.5周期
ADC_SoftwareStartConvCmd(ADC3, ENABLE); //使能指定的ADC3的软件转换启动功能
while(!ADC_GetFlagStatus(ADC3, ADC_FLAG_EOC ));//等待转换结束
return ADC_GetConversionValue(ADC3); //返回最近一次ADC3规则组的转换结果
}
2. 取多次的ADC平均值
将 ADC 值(0-4000)线性映射到光照强度百分比(0-100)
- 值 = (100 - ( temp_val / 40 ) )
| 值 | 状态 | ADC 值 | 光敏电阻阻值 |
| 0 | 最亮 | 4000 | 低 |
| 100 | 最暗 | 0 | 高 |
#define LSENS_READ_TIMES 10 //定义光敏传感器读取次数,读这么多次,然后取平均值
#define LSENS_ADC_CHX ADC_Channel_6 //定义光敏传感器所在的ADC通道编号
//读取Light Sens的值
//0~100:0,最暗;100,最亮
u8 Lsens_Get_Val(void)
{
u32 temp_val=0;
u8 t;
for(t=0; t<LSENS_READ_TIMES; t++)
{
temp_val += Get_Adc3(LSENS_ADC_CHX); //读取ADC值
delay_ms(5);
}
temp_val /= LSENS_READ_TIMES;//得到平均值
// ADC 满量程为 4095,但实际电路可能因上拉电阻分压导致最大值低于 4095。
// 限制为 4000 可避免后续计算溢出。
if(temp_val>4000)
{
temp_val=4000;
}
// 将 ADC 值(0-4000)线性映射到光照强度百分比(0-100)
// 0:最亮(对应 ADC 值 4000,光敏电阻阻值低)
// 100:最暗(对应 ADC 值 0,光敏电阻阻值高)
return (u8)(100-(temp_val/40));
}
主函数中调用:
void ADC_task(void *pvParameters)
{
short temp;
while(1)
{
temp=Lsens_Get_Val(); //得到值
printf("temp = %d \r\n",temp);
LED0=!LED0;
delay_ms(3000);
}
}
更多推荐

所有评论(0)