目录

Overview:

光敏传感器

ADC

12 位逐次逼近型 ADC

18 个通道

可测量 16 个外部和 2 个内部信号源

最大转换速率 1MHz

不要让 ADC 的时钟超过 14M

原理图

配置代码

初始化

1. ADC配置

2. 接收输入的GPIO配置

ADC业务

1. 触发单通道ADC转换

2. 取多次的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 的初始化配置

  1. 设置ADC分频因子,确保ADC时钟不超过14MhZ
  2. 开启 ADC3 时钟并复位
  3. 配置工作模式为独立模式(非双 ADC 模式)
  4. 设置为单通道、单次转换模式
  5. 禁用外部触发,使用软件触发转换
  6. 配置数据右对齐,设置转换通道数为 1
  7. 完成 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的配置。

  1. 打开GPIO时钟
  2. 配置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);	
	} 
}

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐