☰
基于LDR光敏电阻与舵机的手工光照仪表制作教程
2026/10/8 14:31:02 网站建设 项目流程

光照这个东西,大到温室大棚补光判断、小到阳台植物该不该挪地方,天天都会遇到。以前我老是用眼睛估,结果经常到了中午才发现该遮阳了。后来我用手边的MakerBuddy IoT Kit配上一颗LDR光敏电阻和一个舵机,做了个能"实时摆给你看"的LDR Light Level Gauge——光一变,舵机指针就跟着转,读数直接从表盘上读出来,不用连电脑、不用看串口。这个项目特别适合刚接触物联网套件和传感器的朋友,成本低、接线少、代码也不绕,跑通一遍就能把模拟输入、数学映射、PWM控制这三个最常用的底层机制全摸一遍。

1. 项目缘起:为什么用LDR加舵机做光照检测

1.1 常见光照测量方案的取舍

市面上一提测光照,通常会冒出三个方案:光敏电阻(LDR)、光敏二极管/光敏三极管、数字光照传感器(比如BH1750)。数字传感器确实精度高、输出直接用I2C读,几行代码就能出lux,但问题也在这——它太省事了,模拟链路上那点事你还是不懂。光敏二极管则需要加运放做电流转电压,对新手来说接线和调试成本偏高。

LDR看起来"土",但它在模拟电路层面的价值无可替代。它就是一个随光照变化阻值变化的电阻,你只需要一个固定电阻跟它分压,中间抽头接到主控板的模拟输入引脚,就能读到电压变化。整个信号链路从光电转换到电压采样再到软件映射,每一步都是可见、可测、可调的,这比直接拿I2C读出来要直观得多。这也是我坚持用LDR做这个项目的第一理由:它逼你把"模拟量是怎么变成数字量的"这件事彻底吃透。

1.2 为什么表盘用舵机而不是LED或数码管

有了光照数值之后,显示方式的选择也很有意思。最省事的做法是用串口打印,再加三个LED表示亮暗,但那种方案信息量太少,而且跟"仪表"两个字沾不上边。用OLED确实能显示具体数字,但又要引入I2C屏,和用BH1750一样属于"一步到位"式做法,缺少一个你能亲手调校的机械环节。

舵机的优势在于它把抽象数值变成了物理角度。光照从暗到亮,指针从0度扫到180度,那种机械联动感会让你非常直观地感觉到"数据在动"。而且舵机控制本身就是物联网和机器人项目里躲不开的一环,无非是PWM脉冲宽度的问题。用Servo库几行代码就能转,但真正理解里面1500微秒对应90度、500微秒对应0度、2500微秒对应180度,是另一回事。这个项目正好让你把舵机的脾气摸清楚。

1.3 MakerBuddy IoT Kit在项目里扮演什么角色

MakerBuddy IoT Kit这套东西,我的理解是它定位在"硬件开发板+常用传感器+接线配件"的一体化教学套件上,主控板通常走Arduino兼容路线,也带WiFi模块,方便以后往上接物联网应用。在这个项目里,它提供了几个关键东西:可编程的主控板、稳定的电源接口、面包板和杜邦线、以及一堆能反复插拔的传感器模块,LDR如果有现成模块就直接插,没有就给一颗裸的LDR配合面包板搭建。

选它而不是单独买单片机加各种散件,最大的好处是省去反复查找引脚定义和供电电压的麻烦。套件里的板子一般已经把供电、下载电路都做齐了,插上USB线就能烧录。对于新手来说,少一个"为什么板子没反应"的排查点,就是少劝退一个爱好者。而且你之后做WiFi温湿度计、远程开关灯,这套东西还能复用。

2. 从传感器到表盘:光照变化是怎么变成角度变化的

2.1 分压电路与LDR的非线性特性

LDR又叫光敏电阻,它在暗处的阻值可以到1MΩ以上,在强光下可能只有几百欧。我手里这颗5mm的LDR,暗态实测大概1.2MΩ,拿台灯贴近照的时候能压到800Ω左右。但任何一个LDR的电阻和光照之间都不是线性关系,而是近似按幂函数变化,这意味着如果你直接把阻值当成光照强度来用,暗光区的数据会被压得很扁,亮光区又会很快饱和。

标准接法是LDR和固定电阻串联,接在3.3V(或5V)和GND之间,两个电阻的中间点引到ADC引脚。固定电阻阻值怎么选?我一般会用跟LDR常见亮阻同一个数量级的,常用10kΩ。这个值的讲究在于:固定电阻太大,亮光时分压变化不明显;太小,暗光时分压区间又太窄。10kΩ在大多数室内场景下能兼顾两端的灵敏度。当然这只是起点,后面我会讲怎么用软件校正来补救。

分压公式也很简单:ADC引脚得到的电压 Vout = Vcc × Rfixed / (Rldr + Rfixed)。LDR阻值下降时,Vout上升。所以在暗处读数偏低,在亮处读数偏高。这就产生了一个关键问题:ADC读到的数值越大,代表光照越强,这和你想要的"光照越强角度越大"刚好是一致的,省掉了反相处理,但如果你接反了固定电阻和LDR的位置,那逻辑就反了,指针会变成一个暗处指向高亮度的反常表。

2.2 模拟信号采样与ADC的理解

主控板的ADC引脚把电压转成数字量。如果板子用的是10位ADC,读数范围是0到1023;如果是ESP32的12位ADC,范围是0到4095。两者之间差四倍,代码里做映射之前先确认自己是几位的,不然算出来的角度会跑到很离谱的位置。

我在实操中习惯先把原始值稳定下来再谈映射。做法是连续读100次,取平均,这样能滤掉一部分市电频闪和环境噪声引起的微小跳动。注意平均次数不是越多越好,100次已经够稳,但响应速度会略微变慢。对于静态环境下的光照仪表来说,这点延迟无所谓。这个项目追求的是视觉上的稳定感,不是高速脉冲响应。

2.3 舵机角度控制与Servo库的底层逻辑

舵机标配是SG90这类微型舵机,三根线分别是棕色GND、红色5V(有的套件是VCC)、橙色信号线。信号线要求20ms左右的周期,其中高电平脉宽在500微秒到2500微秒之间,对应0度到180度。Servo库把这些细节都封装好了,你只需要写 myservo.write(angle),库会把0到180映射成对应的脉宽。

但有几个坑我踩过必须说。其一,不是所有板子都适合用Servo库,部分基于ESP32的套件用Servo库会有定时器冲突问题,如果你发现舵机一插上板子就重启或者不转,可能需要用ESP32专用的LEDC通路来替代。其二,舵机启动瞬间电流能达到几百毫安,如果直接用主板的3.3V引脚供电,很容易把板子拉崩。我在这个项目里是把舵机电源单独接到5V供电的,信号线也共地,这样最稳。

3. 硬件搭建:物料清单、电路连接与供电细节

3.1 完整物料清单

做这个项目不需要太多东西,基本都是套件里现成的,但你最好先列个清单核对一遍:

  • MakerBuddy IoT Kit主控板(Arduino兼容或ESP32均可)
  • 面包板一块加若干公对公杜邦线
  • LDR光敏电阻一颗
  • 10kΩ电阻一颗
  • 5V舵机一颗,最常见就是SG90
  • 一个USB电源(优先用5V 1A以上的电源适配器,避免只靠电脑USB口同时带板子和舵机)
  • 可选:小纸板加记号笔做一个表盘刻度

注意LDR模块和裸元件的区别:如果套件里给的是LDR模块(一般是三针接口,VCC、GND、AO),那就不需要面包板做分压,模块上已经焊好了固定电阻和比较器,直接接模拟口就行。但如果是裸LDR,就老老实实搭分压电路。我这份教程按裸LDR来写,因为最通用。

3.2 接线步骤与方法

第一步,把LDR的一条引脚接到3.3V或5V。第二步,把10kΩ电阻一端接到LDR的另一条引脚,电阻另一端接到GND。第三步,在LDR和电阻连接的那条节点上引出一条线,接到主控板的模拟输入引脚,比如A0。示意图就是电源正极—LDR—节点—电阻—GND,节点引出到ADC。

这里有一个容易犯的错:有些人会把LDR两头分别接电源和GND,中间不接电阻,结果一上电就短路或读数奇奇怪怪。分压电路必须要有两个电阻串联,LDR只是其中一个,另外一个电阻必不可少。

舵机接线就简单了:信号线接到支持PWM输出的数字引脚,比如D9;电源线接5V;地线接GND。务必让舵机的地和LDR分压电路的地在同一个公共地线上,这就是项目里说的共地,如果主控板的GND和舵机电源的GND没有连在一起,信号参考电压就不一致,舵机会乱动。

3.3 供电设计:为什么我不建议把舵机直接怼在主控板5V上

这个项目看着功耗小,但舵机工作时峰值电流不低。我把SG90堵转测试过一次,电流能冲到700mA左右,一般的USB口加上板载稳压芯片要同时给主控、传感器和舵机供电,很容易出现电压跌落。电压一跌落,最直观的表现是舵机在启动瞬间咔咔响而不动,或者LDR的ADC读数也跟着波动。

所以我建议分两路供电:主控板用USB口或独立的5V适配器供电,舵机的VCC也接到同一个5V电源,但要直接从电源接线端引出来,而不是通过板子的引脚。如果条件实在有限,也可以从主控板的5V引脚带一个舵机,但前提是只带这一个舵机且不给板子接额外负载,然后观察串口输出是否稳定。做项目不要想当然,实测电压才是王道。

4. 代码实现:让光照数值变成看得见的指针角度

4.1 代码流程设计

代码整体上是一个循环:读ADC、滤波、映射角度、写舵机、延时。中间的难点是映射。直接把0到1023映射到0到180看起来简单,实测效果会非常差,因为亮光区ADC读数可能集中在800到1023这很窄的区间,指针会瞬间从100度蹿到180度,暗光区又半天不动。

更好用的方案有分段线性映射和对数映射。对数映射更适合模拟人眼对光照的感知特性:人眼觉得"暗一格"对应的光照增量在亮光区和暗光区是完全不同的。这里我用对数映射作为主方案,先把原始读数归一化到0到1之间,取对数后再缩放、偏移,得到一个适合转成角度的值。

4.2 完整代码与逐段说明

下面这段代码基于ESP32版MakerBuddy套件,但注释里我标出了Arduino UNO需要改的地方。如果你用的是10位ADC,把4095改成1023就行。

#include <Servo.h> #define LDR_PIN A0 #define SERVO_PIN 9 #define SAMPLE_TIMES 100 Servo pointerServo; void setup() { Serial.begin(115200); pointerServo.attach(SERVO_PIN); // 让舵机先回到刻度最左端 pointerServo.write(0); delay(500); } float readLightLevel() { long sum = 0; for (int i = 0; i < SAMPLE_TIMES; i++) { sum += analogRead(LDR_PIN); delay(2); } float avg = sum / (float)SAMPLE_TIMES; float normalized = avg / 4095.0; // 如果是UNO,改成 /1023.0 normalized = constrain(normalized, 0.01, 1.0); // 防止log(0) float logVal = log(normalized) / log(2.71828); float mapped = map(logVal * 1000, -4600, 0, 0, 180); mapped = constrain(mapped, 0, 180); return mapped; } void loop() { float angle = readLightLevel(); pointerServo.write((int)angle); Serial.print("Light ADC: "); Serial.println(analogRead(LDR_PIN)); Serial.print("Angle: "); Serial.println(angle); delay(300); }

这里为什么 logVal 要乘1000再用map?因为log(normalized)范围在黑暗时大概是ln(0.01)=-4.6到ln(1)=0之间,直接映射小数点太多,不好调试。乘1000后范围就是-4600到0,用Arduino的map函数就方便了。这个Trick的实际意义是避免写一堆浮点乘法,也让映射参数的调整更直观。

map函数本身在Arduino里是整数运算,所以我把浮点值先乘1000再转成整数范围。如果你想调灵敏度,只需要改logVal乘的系数和map的第二、第三个参数。比如想加大暗光区分辨率,可以让map范围下限从-4600改成-3000,这样暗光区角度变化范围更大,但亮光区会提前饱和。权责自负,这就是调参的乐趣。

4.3 表盘刻度的画法与视觉标定

代码写完烧进去之前,用记号笔在纸板上画一个扇形刻度会很有仪式感。我的做法是把纸板剪成半圆,用0度、90度、180度作为三个大刻度,然后让舵机分别写0、90、180,每到一个角度就在指针实际停留的位置画一道线,再标注"暗"、"适中"、"亮"。

这里有个小技巧:不要让代码直接写180,因为部分舵机在机械限位附近会堵转,发出那种嗡嗡的电流声。我实际会让映射上限设为170,留出一点余量,避免舵机齿轮长期顶着限位。登两次角度校正后,再在刻度盘上写字,就得到一个完全贴合你舵机实际偏差的仪表。便宜的SG90个体差异很大,纸面标定比写在代码里的理想角度更可靠。

5. 校准与调优:让仪表读数真正可信

5.1 手动标定光源等级

软件跑起来后,接下来就是校准。校准之前要明白一个概念:这个仪表测的不是精确lux,而是一种相对光照等级。你完全可以用三个场景来给它定义等级:关灯拉窗帘的抽屉内部记为"暗",室内自然光不开灯记为"室内",台灯贴近照射记为"亮"。然后打开串口监视器观察对应的原始ADC值,记录下来。

我记录的典型值:暗处ADC约为80到120,室内约为1500到2000,台灯强光下约为3800到4000。每个环境的光都不一样,所以你的值大概率和我不同。这个记录的意义在于,后续你可以针对这台设备自己的环境特性调整映射的上下限,而不是我的。

5.2 用对数映射解决低光灵敏度问题

很多新手第一版会直接 map(analogRead(0), 0, 4095, 0, 180),用起来最大的感受是暗处几乎不动、亮处一下打满。原因就是前面说的LDR非线性。对数映射的本质是把一个接近幂函数变化的曲线,压缩成更接近线性的感知曲线。这样在暗处指针也会有可观察的移动,不会像非线性版本那样"前半段僵死"。

如果你觉得对数映射还不够直感,也可以退一步用分段线性映射:小于某阈值用一组比例放大,大于某阈值用另一组比例缩小。效果类似,但代码要写两条if分支。我建议先跑对数版,实测几个位置再决定改不改。校准的最终目标只有一个:在你关心的光照区间内,指针能明显、平滑地反映变化。

5.3 舵机指针抖动与响应速度的取舍

舵机跟着ADC数值走,如果ADC数值本身在光照不变时还有微小波动,指针就会一直小幅"碎抖",看久了很烦躁。解决有两层:软件上把前面代码里的平均次数从10次加到100次,并给角度输出加一个死区判断,也就是只有新旧角度差值大于2度时才执行write,否则保持旧角度。这样做以后,指针基本能在稳定光照下安静停住。

硬件上还要检查一下舵机供电是否干净。如果舵机供电和主控共用USB口,舵机的电流波动可能会影响ADC参考电压,进而让读数出现50Hz级波动。这种情况在软件里很难完全滤掉,最好从源头解决:给舵机独立的5V供电,或者至少在主控板5V和舵机VCC之间串一个100uF电解电容,也能改善一部分。

6. 常见问题与排查实录

6.1 光照没变读数乱跳

排查优先级先从供电开始。我遇到过一次特别典型的:用笔记本电脑USB口给整个系统供电,笔记本在充电模式切换时,板子的ADC参考电压会跟着USB电平轻微波动,导致读数忽高忽低。换成稳定适配器后恢复正常。软件上则把采样次数提高,100次平均后仍然抖动的,多半是硬件问题而不是代码问题。

还有一个原因是LDR引脚接触不良。面包板用久了氧化之后,LDR引脚插上去时好时坏,表现为读数每隔几秒跳到某个随机值。处理办法是换一个面包板插孔位置,或者直接把LDR引脚焊到一个小转接板上再插。

6.2 舵机不动、乱摆或啸叫

舵机纹丝不动先检查三件事:共地了没有,信号线是不是接到了Servo库支持的PWM引脚上,舵机电源是否够。我最常遇到的是把信号线插到了不支持PWM的模拟引脚或者插接反了。SG90这类舵机的信号接反不会立刻烧毁,但会发热,时间一长就可能损伤内部驱动芯片。

乱摆和啸叫往往是供电不足的一个表现。舵机启动瞬间电流很大,如果电源电压被拉低,控制信号和反馈信号逻辑都会出问题。解决方案就是独立供电,或者在舵机电源正负之间并联一个大电容。啸叫如果是转到180度左右才出现,一般是到了机械限位,调代码让最大角度落到170度即可。

6.3 LDR读数一直满量程或一直为零

满量程通常原因是对着强光且LDR阻值到极限,但如果你拿遮光物挡上还是满量程,那就是接线问题。检查一下LDR和固定电阻的串联顺序,是不是某个引脚接到了3.3V/5V上,然后ADC点有没有和另一个引脚短接。如果读数一直是零,则更可能是ADC点没接到主控板,或者电阻接到了电源而LDR接到了地,导致分压点电压固定为0。测量节点电压是最好的定位方式。

现象可能原因排查顺序
读数跳变供电不稳、LDR接触不良先换电源,再查面包板
舵机不动信号线接错、未共地、供电不足看信号Pin,测舵机VCC
强光读数≈最大值但挡光不变分压电阻位置错误重新检查串联节点
暗处指针快速跳到亮端LDR和电阻位置接反交换LDR/电阻位置
通电后板子重启舵机电流拉低系统电压独立供电,加大电容

6.4 关于套件与扩展方向

最后说一个我个人体会很深的地方:做这个项目不一定非要照着教程买到一模一样的套件。MakerBuddy这类IoT Kit的好处是板载模块齐全,但如果手头只有一块ESP8266和单独买来的LDR、舵机,也完全能做,只要确认引脚兼容、电压匹配就行。

做完了表盘指示之后,这套思路还可以往上叠加:用套件自带的WiFi模块把光强数据送到局域网,手机浏览器就能看;加一个LDR阵列,做一个简易太阳追踪灯的反馈部分;或者让舵机反过来带动遮光帘,用光照阈值自动拉窗帘。这个项目最值钱的地方不在指针本身,而是你把"传感器-数据处理-机械执行"这条完整链路跑通了,以后玩别的传感器和执行器,思路都是这一套。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询