4、传感器集成:光敏电阻与ADC采集、人体红外传感器(PIR)检测、根据环境亮度自动调节灯光
这一章,我们开始给灯控系统装上「眼睛」和「耳朵」。
说白了,就是让灯能感知环境亮度和人体存在。我刚开始做智能家居项目时,觉得传感器就是读个数值而已,后来踩了不少坑才明白——传感器选型、电路设计、采样策略,每一步都藏着细节。
4.1 光敏电阻与ADC采集
光敏电阻,也叫光依赖电阻(LDR)。它的原理很简单:光照越强,电阻越小;光照越弱,电阻越大。我们通过测量电阻变化,就能反推出环境亮度。
但微控制器不能直接读电阻值,得用ADC(模数转换器)把模拟电压转成数字量。我习惯用分压电路来接光敏电阻。
4.1.1 典型分压电路
把光敏电阻和一个固定电阻串联,中间抽头接ADC引脚。固定电阻选10kΩ比较通用。供电电压3.3V或5V都行,但ADC参考电压要匹配。
电路连接示意:
VCC (3.3V)
│
├── LDR (光敏电阻)
│
├── ADC_PIN (中间抽头)
│
├── R_fixed (10kΩ)
│
GND
ADC读到的值跟光照强度成反比。数值越大,说明环境越暗。嗯,这里要注意:不同型号的光敏电阻,暗电阻和亮电阻范围差别很大。我遇到过用国产5516和进口5537,同样的电路,读数差了30%。
4.1.2 ADC采样代码示例
以ESP32为例,它的ADC是12位的,范围0~4095。我一般做多次采样取平均,滤掉噪声。
#include <driver/adc.h>
#define LDR_PIN ADC1_CHANNEL_0 // GPIO36
#define SAMPLE_COUNT 10
void setup() {
Serial.begin(115200);
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(LDR_PIN, ADC_ATTEN_DB_11);
}
void loop() {
uint32_t sum = 0;
for (int i = 0; i < SAMPLE_COUNT; i++) {
sum += adc1_get_raw(LDR_PIN);
delay(10);
}
uint16_t avg = sum / SAMPLE_COUNT;
Serial.printf("LDR ADC Value: %d\n", avg);
delay(500);
}
你想想看,如果只采一次,刚好碰到电源纹波尖峰,读数就飘了。我吃过这个亏,后来一律用滑动平均。
4.2 人体红外传感器(PIR)检测
PIR传感器检测人体移动,靠的是热释电效应。人经过时,红外辐射变化被感应,输出高电平。静止不动时,输出低电平。
市面上常见的HC-SR501模块,我用了不下两百个。它有三个关键调节:
| 调节项 | 作用 | 我的推荐值 |
|---|---|---|
| 灵敏度 | 检测距离(3~7米) | 室内用中档,约5米 |
| 延时时间 | 触发后保持高电平的时间 | 5~10秒,避免频繁翻转 |
| 光敏控制 | 是否只在暗光下工作 | 关闭,由主控统一判断 |
4.2.1 PIR读取代码
#define PIR_PIN 14
void setup() {
pinMode(PIR_PIN, INPUT);
Serial.println("PIR warming up...");
delay(60000); // 等待稳定
Serial.println("PIR ready.");
}
void loop() {
int motion = digitalRead(PIR_PIN);
if (motion == HIGH) {
Serial.println("Motion detected!");
// 触发亮灯逻辑
} else {
Serial.println("No motion.");
}
delay(200);
}
这里有个细节:PIR输出是数字信号,直接接GPIO就行。但如果你用3.3V的MCU接5V的PIR模块,记得加电平转换。我烧过一块ESP32的引脚,就是因为没注意这个。
4.3 根据环境亮度自动调节灯光
现在传感器数据都有了,怎么联动?
我的策略很简单:
- 环境暗 + 有人移动 → 开灯
- 环境亮 → 关灯(不管有没有人)
- 环境暗 + 无人移动超过N秒 → 关灯
说白了,就是「亮度优先,人体触发」。你想想看,白天灯开着也没意义,还费电。
4.3.1 完整联动逻辑
#define LIGHT_THRESHOLD 2500 // ADC值大于此认为环境暗
#define TIMEOUT_SECONDS 30 // 无人后延时关灯
uint16_t ldr_value;
int motion_state;
unsigned long last_motion_time = 0;
bool light_on = false;
void loop() {
// 1. 读取传感器
ldr_value = read_ldr_average();
motion_state = digitalRead(PIR_PIN);
// 2. 更新最后移动时间
if (motion_state == HIGH) {
last_motion_time = millis();
}
// 3. 判断是否开灯
bool should_light_on = false;
if (ldr_value > LIGHT_THRESHOLD) {
// 环境暗
if (motion_state == HIGH) {
should_light_on = true;
} else {
// 无人移动,检查超时
if (millis() - last_motion_time < TIMEOUT_SECONDS * 1000) {
should_light_on = true;
}
}
}
// 4. 控制灯光
if (should_light_on && !light_on) {
digitalWrite(RELAY_PIN, HIGH);
light_on = true;
Serial.println("Light ON");
} else if (!should_light_on && light_on) {
digitalWrite(RELAY_PIN, LOW);
light_on = false;
Serial.println("Light OFF");
}
delay(100);
}
4.4 本章知识体系
下面这张图,把传感器、ADC、PIR和灯光控制的整体流程串起来了。我画图时习惯把数据流向标清楚,调试时一眼就能看出问题出在哪一环。
4.5 避坑与优化建议
最后,分享几个我实际项目中总结的经验:
- ADC参考电压要稳。如果MCU用电池供电,电压会漂,ADC读数也跟着漂。我一般用内部参考电压或外接基准芯片。
- PIR不要正对空调或暖气。热风会让PIR误触发。我遇到过客户说灯半夜自己亮,结果发现是空调出风口对着传感器吹。
- 光敏电阻不要装在灯罩正下方。灯一开,光敏电阻直接照到,读数变亮,灯又关了,形成振荡。我习惯把传感器引到灯体侧面或单独开孔。
- 超时时间别设太短。10秒以内的话,人稍微停一下灯就灭了,体验很差。30秒到1分钟比较合理。