1. 项目概述:STM32光敏传感器数据采集系统
在嵌入式开发中,环境光检测是一个基础但重要的功能场景。我最近用STM32F103C8T6(俗称"蓝莓派")配合光敏电阻模块,实现了一套简单可靠的光强检测系统。这个项目特别适合刚接触STM32 ADC功能的新手,通过CubeMX配置和少量代码就能看到实时效果。
系统核心功能包括:
- 通过PB1引脚(ADC1_IN9)采集光敏电阻的模拟信号
- 将12位ADC原始值(0-4095)转换为实际电压(0-3.3V)
- 计算相对亮度百分比(0-100%)
- 通过SEGGER RTT实时输出数据(也可替换为串口打印)
提示:光敏电阻的特性是阻值随光照增强而降低,因此ADC值会随亮度增加而减小。我们在代码中做了反向映射处理,使输出值直观反映亮度变化。
2. 硬件设计与环境搭建
2.1 硬件选型与接线
核心组件清单:
- 主控:STM32F103C8T6最小系统板(72MHz Cortex-M3)
- 传感器:4针光敏模块(带LM393比较器)
- 调试工具:ST-Link V2下载器
- 可选:USB-TTL模块(如果用串口输出)
接线示意图:
code复制光敏模块 STM32
-------------------
VCC → 3.3V
GND → GND
AO → PB1 (ADC1_IN9)
DO → 不接(数字输出暂未使用)
注意:模块工作电压范围3.3-5V,与STM32对接时建议统一使用3.3V,避免电平不匹配。
2.2 CubeMX关键配置
-
ADC配置:
- Mode:Independent mode
- Scan Conversion Mode:Disabled
- Continuous Conversion Mode:Disabled
- DMA:不使用(单次读取无需DMA)
- Resolution:12Bits
- Data Alignment:Right
- Channel 9 Sampling Time:239.5 Cycles(提高采样精度)
-
时钟配置:
- HCLK:72MHz(ADC时钟不超过14MHz,需分频)
- ADC Prescaler:PCLK2分频6(72MHz/6=12MHz)
-
GPIO配置:
- PB1模式:Analog(无需上/下拉)
3. 软件实现与代码解析
3.1 ADC驱动层(adc.c)
CubeMX生成的初始化代码已经满足基本需求,重点关注两个参数:
c复制sConfig.Channel = ADC_CHANNEL_9; // 使用通道9(PB1)
sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; // 采样时间约20μs
经验:对于光敏电阻这种高阻抗信号源,适当增加采样时间可以提高稳定性。实测中239.5周期比默认的1.5周期噪声更小。
3.2 光敏数据处理(gm.c/gm.h)
核心算法包含三个关键函数:
3.2.1 ADC原始值读取
c复制uint16_t GM_Read_ADC_RawValue(void) {
HAL_ADC_Start(&hadc1);
if(HAL_ADC_PollForConversion(&hadc1, 100) == HAL_OK) {
return HAL_ADC_GetValue(&hadc1);
}
HAL_ADC_Stop(&hadc1);
return 0;
}
3.2.2 电压值转换
c复制float GM_Convert_To_Voltage(uint16_t adc_value) {
return (float)adc_value * 3.3f / 4095; // Vref=3.3V, 12bit ADC
}
3.2.3 亮度百分比计算
c复制uint8_t GM_Convert_To_Brightness(uint16_t adc_value) {
// 反向映射:光强↑ → 电阻↓ → ADC值↓ → 亮度%↑
uint32_t brightness = (100 * (4095 - adc_value)) / 4095;
return (brightness > 100) ? 100 : brightness;
}
调试技巧:实际测试中发现,不同型号光敏电阻的响应曲线可能非线性。如需精确测量,建议采集多组数据后做曲线拟合。
3.3 主程序逻辑(main.c)
主循环中实现数据采集-转换-输出的完整流程:
c复制while(1) {
adc_raw = GM_Read_ADC_RawValue(); // 读取原始值
adc_voltage = GM_Convert_To_Voltage(adc_raw); // 转电压
brightness = GM_Convert_To_Brightness(adc_raw); // 转亮度
// 三种输出方式任选其一:
SEGGER_RTT_printf(0,"Raw:%d\tVoltage:%.2fV\tBrightness:%d%%\n",
adc_raw, adc_voltage, brightness);
// 或HAL_UART_Transmit(&huart1, buffer, len, timeout);
// 或HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, (brightness>50)?GPIO_PIN_SET:GPIO_PIN_RESET);
HAL_Delay(500); // 采样间隔
}
4. 校准与优化实践
4.1 硬件校准方法
-
暗校准:
- 完全遮光时,旋转模块上的蓝色电位器,使DO输出刚好从高电平变为低电平
- 此时AO输出的电压值即为"全暗"基准
-
亮校准:
- 用手电筒直射传感器,记录此时的ADC最小值
- 在代码中调整ADC_MAX_VALUE常量
4.2 软件滤波方案
原始数据可能包含噪声,推荐添加滑动平均滤波:
c复制#define FILTER_SIZE 5
uint16_t adc_filter_buf[FILTER_SIZE];
uint8_t filter_index = 0;
uint16_t GM_Get_Filtered_Value() {
adc_filter_buf[filter_index] = GM_Read_ADC_RawValue();
filter_index = (filter_index + 1) % FILTER_SIZE;
uint32_t sum = 0;
for(uint8_t i=0; i<FILTER_SIZE; i++) {
sum += adc_filter_buf[i];
}
return sum / FILTER_SIZE;
}
4.3 阈值触发应用
结合比较器输出实现自动控制:
c复制if(brightness > 70) {
// 光线过强,启动遮光帘
HAL_GPIO_WritePin(RELAY_GPIO_Port, RELAY_Pin, GPIO_PIN_SET);
} else if(brightness < 30) {
// 光线不足,开启补光灯
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);
}
5. 常见问题排查
5.1 ADC值始终为0
- 检查接线:PB1是否虚焊?模块VCC/GND是否接反?
- 验证CubeMX配置:ADC时钟分频是否过大?GPIO模式是否为Analog?
- 测量AO引脚电压:遮光时应有0.5-1V,强光时接近0V
5.2 数值跳动严重
- 增加软件滤波(如前述滑动平均)
- 在AO引脚添加0.1μF电容到GND
- 适当降低ADC采样速率(增加SamplingTime)
5.3 亮度百分比不准确
- 重新校准最大/最小ADC值
- 尝试非线性映射(如查表法)
- 更换质量更好的光敏模块(推荐ROHM的PIC-0212)
6. 项目扩展方向
-
多传感器网络:
c复制// 在CubeMX中启用多个ADC通道 sConfig.Channel = ADC_CHANNEL_9; // 光敏1 HAL_ADC_ConfigChannel(&hadc1, &sConfig); sConfig.Channel = ADC_CHANNEL_8; // 光敏2 HAL_ADC_ConfigChannel(&hadc1, &sConfig); -
低功耗模式:
- 配置ADC触发为TIM定时启动
- 主循环中进入STOP模式,由定时器中断唤醒
-
物联网集成:
c复制// 通过ESP8266发送数据到云平台 char msg[64]; sprintf(msg,"{\"light\":%d}", brightness); ESP8266_Send("AT+CIPSEND=0,%d\r\n", strlen(msg));
这个项目最让我惊喜的是光敏电阻的灵敏度——用手电筒照射时,ADC值能在100-3000之间快速变化。实际部署时发现,模块上的比较器电位器如果调得太敏感,会导致DO引脚频繁跳变。后来在软件中加了20ms的消抖延迟,问题迎刃而解。