LDR 光敏电阻自动灯:分压电路、双阈值与 12V 继电器

Ilustrasi kartun LDR bulat bermuka merah jambu pucat dengan corak serpentin perak berdiri di atas breadboard putih di sebelah lampu jalan mini bertiang wayar perak dengan LED 5 mm jernih bercahaya kuning, berlatarkan langit siang teal di kiri dan langit malam navy berbulan sabit di kanan

天黑自动亮的灯,是由一颗 LDR 光敏电阻和一颗固定电阻在 Arduino 的 A0 脚上组成一个分压电路做出来的:analogRead 读的是这个分压的中点,而代码里的两个阈值 —— 一个管亮、一个管灭 —— 才是让灯不会在黄昏那一刻一闪一闪的关键。LDR 本身不产生电压,它只改变电阻;把电阻翻译成读得到的电压的,是那颗固定电阻。

做一盏光敏电阻自动灯,需要哪些零件?

这个作品的主角是 LDR(光敏电阻) —— 一颗正面呈淡粉色、上面爬着一道银色蜿蜒电极的小圆片,两根笔直的裸线脚,没有 PCB,也没有正负极。圆片的直径每一批可能略有出入,而这在这里改变不了什么,因为你不会去抄别人的电阻值 —— 你是在串口监视器上量自己手上这一颗,再拿那个数字去定阈值。清单上其余的东西,只是给它一个大脑、一盏灯,还有一颗让它的读数有意义的电阻。

选配 —— 现成模块的捷径、继电器升级、数据线与电阻套件

ItemPriceQty
Light Sensor Module Photosensitive Light Detection LDR For Arduino IOT ApplicationLight Sensor Module Photosensitive Light Detection LDR For Arduino IOT ApplicationLIGHT3WRM3.95
Relay Module 3.3V 5V 12V 1/2/4/8 Ways Optocoupler Trigger Relay Module 1 2 4 8 Channel Relay Module - 5V RELAY MODULE(1WAY)Relay Module 3.3V 5V 12V 1/2/4/8 Ways Optocoupler Trigger Relay Module 1 2 4 8 Channel Relay Module - 5V RELAY MODULE(1WAY)1WRELAYRM3.15
Data Cable Type-A Type-C MicroUSB Type-B 0.5m 1m 30cm 0.3m 100cm Data Transfer Upload Code - TYPE-A TO TYPE-B (1.0M)Data Cable Type-A Type-C MicroUSB Type-B 0.5m 1m 30cm 0.3m 100cm Data Transfer Upload Code - TYPE-A TO TYPE-B (1.0M)ATBC100RM4.90
Electronic Component Set Beginner Electrical Learning Kit For Arduino Beginner Learning - ELECTRICAL STARTER KITElectronic Component Set Beginner Electrical Learning Kit For Arduino Beginner Learning - ELECTRICAL STARTER KITELECTRKRM39.95

光敏传感器模块是开箱即用的捷径:它的阈值是用板上的电位器调的,不在代码里。5V 继电器模块则是把迷你 LED 升级成真正的 12V 直流灯用的。USB A 转 B 线是 Uno 那个方形大插座认的形状 —— 抽屉里没有这种线的话就加一条。电子入门套件里有整条整条的电阻,照片上看得到标着 220 和 100K 的那两条,正好就是这个作品要的两个阻值,此外还配了 LED、杜邦线和 830 孔面包板;电阻包缺货的时候,这是好用的备选。

要两个阻值:分压用 100 kΩ,给 LED 限流用 220 Ω。(10 kΩ 也能用;下一节会说明为什么 100 kΩ 在黄昏那一段的读数宽裕得多。)这些都是常见值,1/4 W 电阻包里有,你的零件抽屉里多半也有,或者在电子入门套件那几条标好阻值的电阻带上。透明圆头的 5 mm 白色 LED 就是我们的「路灯」;旁边想再加一个小小的状态指示灯的话,3 mm 红色 LED 就够了。

有一件事要一开始就讲清楚:这个电路响应的是光线强弱,不是人。想要有人经过才亮的灯?那是运动传感器的活 —— 请看我们的 HC-SR501 人体感应教程。这两者常常搭在一起用:PIR 负责判断「有人」,LDR 负责判断「天黑了」。

LDR 为什么非要接成分压电路不可?

LDR 是一片光电导材料:打到它表面的光子会释放出载流子,所以光越强,电流可走的路径就越多,电阻也就跟着往下掉。在明亮的室内光下,这一类 LDR 只有几 kΩ;到了全黑,它会一路爬到几百 kΩ,直逼 1 MΩ。这个跨度很大 —— 但它是电阻的跨度。

问题在于,Arduino 的模拟脚量不了电阻。analogRead 是一支电压表:它把引脚上的电压拿去和 5 V 基准比较,返回 0 到 1023 之间的一个数字,每一档约 4.9 mV。芯片内部并没有一个电流源,能主动去「问」一个电阻有多大。所以,如果 LDR 从 5 V 直接接到 A0、其他什么都不接,那么几乎没有电流流过它 —— 按 ATmega328P 的数据手册,引脚漏电流最大只有 1 µA —— 于是 LDR 上几乎没有压降,A0 就一直坐在接近 5 V 的地方,读数不管正午还是半夜都卡在 1023 附近。反过来只接到 GND 呢,你会得到一个镜像:同样那点漏电流小到撑不起 LDR 上的电压,引脚被死死拉在 0 V 附近,读数卡在量程的最低端。两种接法,LDR 的电阻都从来没有变成一个数字。

分压电路用一颗固定电阻解决了这件事。把 LDR 从 5 V 接到某一点,再把一颗固定电阻 —— 先拿 10 kΩ 当例子 —— 从同一点接到 GND:现在有一股小电流同时流过两者,而中点的电压就是 5 V × 10 kΩ ÷(RLDR + 10 kΩ)。LDR 上上下下的电阻,现在推着这个电压上上下下,而这正是 ADC 读到的东西。这里还白送一个好处:同一条 5 V 电源轨既供着分压,是 ADC 的基准,所以电源轨稍微塌一点 —— 笔电 USB 对比墙充 —— 比例的两边是同时塌的,读数几乎不动。你校准好的阈值,换个电源照样有效。

LDR 电阻 大致的光线 A0 电压(10 kΩ 电阻) analogRead 换 100 kΩ 的 analogRead
5 kΩ 大白天 3.33 V 682 974
20 kΩ 明亮的室内 1.67 V 341 852
50 kΩ 刚起黄昏 0.83 V 170 682
100 kΩ 黄昏 0.45 V 93 512
300 kΩ 入夜之后 0.16 V 33 256
1 MΩ 全黑 0.05 V 10 93

最后两列回答的是「我到底该用哪个阻值」。分压电路最灵敏的时候,是固定电阻的阻值接近你想区分的那个光照下的 LDR 电阻。用 10 kΩ,50 kΩ–300 kΩ 这一段黄昏只对应 170 到 33 —— 137 档。换成 100 kΩ,同一段光线对应的是 682 到 256,也就是 426 档,放阈值的空间宽了三倍。原因就写在公式里:当固定电阻比 LDR 小得多,5 V 几乎全部落在 LDR 上,中点被死死挤在 0 V 附近,于是电阻变化再大,读数也只挪几档。两个值都能用,但这篇教程里的作品用的是 100 kΩ,因为那段最宽裕的区间,正好落在真正要做判断的光照上。

两格卡通示意图讲解分压电路:淡粉色带银色纹路的 LDR 在上、蓝色电阻在下,夹在红色电源轨和灰色接地符号之间,橙色的液柱显示黄色太阳下电压高、月牙下电压低
下面那颗固定电阻不会变。光线一强,LDR 的电阻掉下来,中点的电压就升上去 —— A0 读到的正是它。

LDR、LED 和 Arduino Uno 要怎么接?

这个作品的所有零件都坐在400 孔面包板上,再由公对公杜邦线把每一行接到 Uno 的母座上。先把电送到板上:一条杜邦线从 Uno 的 5V 母座接到面包板的红线轨,一条从 GND 接到蓝线轨。之后每一处 5 V 和 GND 都从电源轨上取,而不是几根线去抢板子上那一个母座。

接线 接到 为什么
Uno 5V 面包板红线轨 分压电路的供电
Uno GND 面包板蓝线轨 ADC 和 LED 共同的零参考
LDR 第一只脚 红线轨 LDR 没有极性 —— 哪一只脚都行
LDR 第二只脚 一列空的孔位(就叫它 A 列) 分压电路的中点
100 kΩ 电阻 A 列 → 蓝线轨 分压的下半边;少了它,A0 就卡在接近 5 V
杜邦线 A 列 → Uno A0 ADC 读的就是中点电压
Uno D9 220 Ω 电阻的一只脚 点亮灯的输出脚
220 Ω 电阻另一只脚 LED 长脚(正极) 限制 LED 的电流
LED 短脚(负极) 蓝线轨 把 LED 这条回路接回 GND

虽然 LDR 本身没有极性,这两个元件的先后顺序却是要紧的:LDR 在 5 V 那一侧、固定电阻在 GND 那一侧,意味着越亮读数越大。把两者对调,分压照样成立,只是读数反了过来 —— 数字大代表暗 —— 于是下面那份代码里的两个阈值会整个反着动。如果你的灯白天亮、晚上灭,先检查这个顺序,别急着去动任何数字。

那颗 220 Ω 电阻不是装饰。白色 LED 的压降大约 3.1 V,所以 220 Ω 承担剩下的 1.9 V,把电流定在大约 9 mA —— 舒舒服服地待在 ATmega328P 每脚 20 mA 的限额以下。LED 不接电阻直接插到引脚上,那么限流的就只剩引脚自己的内阻,结果是烧掉 LED、烧掉引脚,或者两个一起。

还有一个很容易被忽略的安装决定:让 LDR 背对着 LED。如果 LDR 看得见自己那盏灯,灯一亮就会把光照读数顶上去,而你的电路刚刚亲手做出一个把自己关掉的反馈回路。

在它变成一盏真正的灯之前还有一件事:这个电路得整晚活着,而不是只在你笔电开着的时候活着。Uno 可以用同一条线接任何一个 USB 手机充电器供电,也可以从板边那个黑色圆孔插座接 7–12 V 的直流电源。因为灯的状态完全由当下的光照读数决定,所以断电再上电之后,没有任何东西需要重新设定。

LDR 和电阻在面包板上到底怎么摆,想先看一遍的话:

阈值要怎么校准,为什么需要两个数字?

阈值这个数字取决于你选的电阻、你手上这一颗 LDR,以及这盏灯将来要装的那个位置的光线。把下面这份代码上传,串口监视器开 9600 波特,把电路放到它真正要待的地方,然后看你希望灯切换的那个时刻,屏幕上跑出来的是什么数字。那就是你的阈值。

接下来是把一盏利落的黄昏自动灯和一盏烦人的灯区分开来的地方。如果你只用一个阈值,慢慢暗下来的黄昏光线会在那个数字附近待上好几十分钟。ADC 噪声带来的几档抖动、飘过的一片云、邻居家的灯,任何一样都足够让它来回穿越那条线,于是你的灯开始闪。

两个阈值就解决了。读数掉到 AMBANG_ON灯才亮,但要等读数升到更高的 AMBANG_OFF灯才灭。在这两个数字之间,什么都不变 —— 当前状态原样保持。这就是迟滞(hysteresis),而这道间隔必须比两样东西都宽:你平常读数的抖动幅度,以及灯本身造成的读数跳升。第二样要实测:在同样的环境光下,分别记下强制点亮和强制熄灭时的读数,两个阈值之间的间隔必须大于这个差值。

这份代码还多加了一层:光线的变化必须持续三秒,灯才动作。迟滞管的是阈值附近的抖动;计时器管的是车灯扫过、手掌掠过这类短暂事件。

两格卡通对比图:左边青色的光线曲线带着抖动四次穿过一条深蓝色虚线,旁边是忽亮忽灭的灯泡图标和橙色叉号;右边同一条曲线只穿过橙线与深蓝线之间那条带一次,配青色勾号
左:只有一个阈值,抖动的黄昏光线来回穿过它 —— 灯就闪。右:两个阈值中间留出间隔,只切换一次。
/*
  光敏电阻(LDR)自动感光灯 — Arduino Uno
  LDR + 100k 固定电阻 = A0 上的分压电路。
  读数大 = 亮,读数小 = 暗
  (LDR 在 5V 那一侧,固定电阻在 GND 那一侧)。

  两个阈值(hysteresis 迟滞)+ 持续时间,让灯
  不会在黄昏一闪一闪。
*/

const uint8_t PIN_LDR   = A0;   // 分压电路的中点
const uint8_t PIN_LAMPU = 9;    // LED(经 220R)或继电器的 IN 脚

// 串口监视器(Serial Monitor)校准之后,改这两个数字。
const int AMBANG_ON  = 400;     // 低于此值 = 够暗了 -> 亮灯
const int AMBANG_OFF = 550;     // 高于此值 = 够亮了 -> 灭灯

const unsigned long TEMPOH_SAH = 3000UL;  // 光线必须维持 3 秒
const uint8_t SAMPEL = 16;                // 取平均,滤掉 ADC 噪声

bool lampuNyala = false;
bool sedangTunggu = false;
unsigned long mulaBerubah = 0;

int bacaPurata() {
  long jumlah = 0;
  for (uint8_t i = 0; i < SAMPEL; i++) {
    jumlah += analogRead(PIN_LDR);
    delay(2);
  }
  return (int)(jumlah / SAMPEL);
}

void setup() {
  pinMode(PIN_LAMPU, OUTPUT);
  digitalWrite(PIN_LAMPU, LOW);
  Serial.begin(9600);
  Serial.println(F("Kalibrasi: tutup LDR dgn jari = gelap, sinar lampu = terang"));
}

void loop() {
  int cahaya = bacaPurata();

  // Hysteresis 迟滞:每个状态各有自己的阈值,所以在
  // AMBANG_ON 和 AMBANG_OFF 之间,什么都不会变。
  bool mahuNyala = lampuNyala;
  if (!lampuNyala && cahaya < AMBANG_ON)  mahuNyala = true;
  if (lampuNyala  && cahaya > AMBANG_OFF) mahuNyala = false;

  // 持续时间:车灯扫过或手影掠过,都撑不了这么久。
  if (mahuNyala != lampuNyala) {
    if (!sedangTunggu) {
      sedangTunggu = true;
      mulaBerubah = millis();
    } else if (millis() - mulaBerubah >= TEMPOH_SAH) {
      lampuNyala = mahuNyala;
      digitalWrite(PIN_LAMPU, lampuNyala ? HIGH : LOW);
      sedangTunggu = false;
    }
  } else {
    sedangTunggu = false;          // 光线又回去了,计时作废
  }

  Serial.print(F("cahaya="));
  Serial.print(cahaya);
  Serial.print(F("  lampu="));
  Serial.println(lampuNyala ? F("ON") : F("OFF"));
  delay(200);
}

在 Uno 上,这份代码占 2,718 字节 Flash(32 KB 的 8%),静态 RAM 194 字节,所以后面你想再加一块显示屏或者一个时钟,空间绰绰有余。bacaPurata() 取 16 次读数再取平均:单次 ADC 读数会晃动几档,取平均就是在这点抖动还来不及去骚扰阈值之前把它抹掉。

说到这里,要诚实地看一眼我们的光敏传感器模块。它是一块三针的 HW-072 板 —— VCC、GND 和 DO —— 板上有一颗蓝色电位器和一颗 LM393 比较器:电位器在硬件里定下一个切换点,DO 只给你「是或否」这一种答案。它没有 AO 脚,所以在它身上,analogRead 和代码里的两个阈值都不成立。作为一个开箱即用的光控开关,它干净又利落(板上那排 90° 弯的公排针要配公对母杜邦线)—— 但要自己掌控「亮」和「灭」之间那道间隔,赢的还是裸 LDR。

要怎么用继电器升级去带 12 V 的灯?

5V 继电器模块把迷你 LED 换成真正的直流灯。那块红色板子上有一颗蓝色的松乐继电器和两组蓝色螺丝端子:控制侧标着 IN、DC- 和 DC+,负载侧标着 NO、COM 和 NC。两边都是螺丝端子,所以公对公杜邦线的针头直接压在螺丝下面就行 —— 不用焊。

DC+ 接 5 V 电源轨,DC- 接 GND 电源轨,IN 接 D9。先把 LED 和那颗 220 Ω 电阻拔掉,然后 IN 直接接到 D9,中间不加电阻 —— IN 是信号输入,板上已经带了它自己的串联电阻,它不是需要你去限流的负载。板子角落有一组标着 L 和 H 的跳线,用来选择 IN 脚是低电平触发还是高电平触发。上面那份代码是发 HIGH 去点灯的,所以这个跳线要坐在 H 那一对针上。在我们的产品照片里它本来就在那个位置,所以通常没有什么要动的 —— 接线前看一眼确认即可,因为万一它跑到 L,你的灯就会白天亮、晚上灭。负载那一侧更详细的说明,在我们的继电器模块安全接线教程里。

安全,而且这一段不是可选建议。继电器外壳上印着两个额定值,10A 250VAC 和 10A 30VDC,在这里有关的是第二个 —— 它远远超过任何一盏 12 V 花园灯会拉的电流。这个作品全程只走低压直流 —— 最高就是 12 V 的灯,而且要有它自己的电源。千万不要把 240 V 市电接到这块面包板电路上。面包板上的市电线无法做绝缘防护,没有任何触电隔离,一根线松脱就足以致命。市电的活是持牌电工的活,不是 Arduino 作品的活。

继电器只负责切换 —— 它不供电。你的 12 V 电源进 COM,NO 出去接灯的正极,灯的负极回到电源的负极。请注意这句话里没有什么:没有一根线从 12 V 电源接到 Uno 的 GND。继电器的触点是一片被磁力吸动的金属,和它的线圈是隔开的,所以 12 V 回路和 5 V 回路根本不需要共地。这正是它和三极管开关的差别 —— 后者是一定要共地的。如果你的系统本来就跑在 12 V 上,比如太阳能板或者车上,那么12V 线圈那个版本更合适,因为它直接由那条 12 V 电源轨供电;它没办法由 Uno 的 5 V 脚驱动。

卡通插图:红色继电器板上有蓝色继电器方块和两组蓝色螺丝端子,左边连着蓝色 Arduino 板的边缘,右边连着发出黄光的白色灯具和灰色直流电源,左上角是打了橙色禁止标记的灰色市电插头
继电器让你能带亮得多的 12 V 直流灯。240 V 市电不属于面包板作品 —— 那是电工的活。

我们常看到的错误

LDR 直接接到 A0。这是最常见的那一种:LDR 从 5 V 接到 A0,就没了。少了那颗接到 GND 的固定电阻,几乎没有电流流过 LDR,于是 LDR 上几乎没有压降:引脚坐在接近 5 V 的地方,读数卡在 1023 附近,完全不跟着光线走。那颗固定电阻不是配件 —— 它是这个传感器的一半。

从网上抄阈值数字。别人的阈值是从他们的电阻、他们那颗 LDR 和他们那个房间里算出来的;上面代码里的 400 和 550 是为 100 kΩ 分压给的一个起点,好让代码先跑起来,而不是答案。

灯一闪一闪就怪 LDR。黄昏时闪烁,几乎总是意味着只用了一个阈值、迟滞间隔留得太窄,或者 LDR 看得见自己那盏灯 —— 先把间隔拉宽,再把 LDR 转到别的方向。如果连全黑的时候读数都很怪,那就在怪代码之前,先用万用表直接量一下 LDR 的电阻;我们的数字万用表怎么用那一篇会告诉你怎么量。

常见问题

LDR 为什么不能直接接到 A0 脚?

因为 analogRead 量的是电压,不是电阻。少了那颗接到 GND 的固定电阻,几乎没有电流流过 LDR,于是它上面几乎没有压降:A0 就坐在接近 5 V 的地方,读数卡在 1023 附近,不管天亮还是天黑。把电阻变成电压的,正是那颗固定电阻。

LDR 分压该用哪个阻值 —— 10 kΩ 还是 100 kΩ?

两个都能用。分压最灵敏的时候,是固定电阻的阻值接近你想区分的那个光照下的 LDR 电阻;黄昏时那是几十到几百 kΩ,所以 100 kΩ 给出的读数区间更宽裕。

我的自动灯为什么在黄昏一闪一闪?

因为你的代码只用了一个阈值,而黄昏的光线会在那个数字附近待很久。改用两个阈值,并且确保两者之间的间隔大于灯自己造成的读数跳升。

可以用光敏传感器模块来做 analogRead 吗?

我们这块三针的 HW-072 模块只引出 VCC、GND 和 DO,所以你是用 digitalRead 去读它,阈值则用板上的电位器来调。想在代码里做两个阈值,就用裸 LDR 配分压电路。

这个电路可以点 240 V 的灯吗?

不行。这个电路是为低压直流设计的 —— LED,或者经继电器模块、由它自己的电源供电的 12 V 灯。市电接线需要正规的隔离、保护和外壳,而在马来西亚那是持牌电工的工作。

最后更新:2026 年 8 月。有问题?WhatsApp 联系我们

Leave a Reply

Your email address will not be published. Required fields are marked *