
1. 为什么我放着几十块的成品不用非要自己攒一台光功率计先交代一下背景我平时做激光调试、光纤对光和光衰减实验比较多经常要测一个光束到底有多少毫瓦。按理说闲鱼上几十块就能买到一台手持光功率计省事又便宜但我最终还是决定自己从零做一台而且后来发现这个决定一点都不亏。原因有这么几个。第一成品的探头大多是固定波长的通常是850nm、1310nm、1550nm这种通信波段而我手里玩的是可见光激光器和LED波长对不上测出来的数值参考意义不大。第二成品仪器的显示单位、量程、采样速度都是写死的我想看个峰值变化或者记录一段光强曲线还得额外接上位机麻烦。第三也是最实际的我对光功率计的测量原理一直停留在光电二极管运放这种概念层面从来没有真正自己搭过一条完整的链路趁着做仪器的机会把光电转换、跨阻放大、ADC采集、校准标定这些环节全都过一遍比看十篇原理文章都有用。这就是这篇博文的出发点不讲空泛的原理直接给你一套能跑起来的方案。整台仪器包含光电探测头、放大电路、数字显示三部分物料成本大约三十块钱焊接难度属于入门到中等之间代码部分我用的Arduino Nano加一个16位ADC模块你手里如果有其他单片机也能很轻松移植。做完之后可以实现大约1nW到1mW量级的光功率测量配合激光笔粗标定后精度大概能到±10%以内——对于DIY级别的光学实验来说这个精度已经够用了。如果你手头正好有激光二极管、LED、光纤或者任何需要确认到底有没有光、光有多强的场景这台仪器都能派上用场。哪怕你只是想练手模电和单片机这个项目也把每一个关键节点都串起来了做完之后你会对光信号怎么变成电压、电压怎么变成数字这条链路有非常直观的感受。2. 测量链路拆解从光子到屏幕上的数字每一步都在干什么在动手焊电路之前我得先把整条链路讲清楚。光功率计并不是什么玄学仪器它本质上就是一个光转电流、电流转电压、电压转数字的过程。你理解了这条链路上每一个环节在干什么后面看电路图和代码的时候就不会懵。2.1 光电二极管把光子变成电流测量光的第一个环节是光电二极管。光子打到PN结上如果能量足够就会激发出电子-空穴对在外加电场或者内建电场的作用下形成电流。这个电流非常微弱通常以纳安nA到微安μA为单位和入射光功率成正比。光电二极管有两种常用的工作模式光伏模式和光导模式。光伏模式是零偏置也就是二极管两端不加反向电压好处是暗电流极小、线性度好适合测量微弱光光导模式是加反向偏置电压响应速度更快但暗电流会变大。对于光功率计这种以测量强度为主要目的的仪器我建议用光伏模式简单、稳定、线性度足够好。我用的是BPW34这是一颗非常经典的PIN光电二极管感光面积大约7平方毫米波长响应范围覆盖400nm到1100nm对可见光和近红外都有反应。它的响应度在850nm附近大约是0.6A/W也就是说每入射1瓦光功率输出0.6安培电流换成1毫瓦光就是0.6毫安——这个数值听起来不小但实际测量中我们经常遇到的是微瓦甚至纳瓦级别的光对应的电流就是微安甚至纳安级别直接测量非常困难。2.2 跨阻放大把皮安级电流变成可读电压微弱电流怎么测最简单的办法是让电流经过一个电阻用电压表测电阻两端的压降然后根据欧姆定律反推电流。但这么做有个问题如果直接用万用表的电压档去测量表的输入阻抗不够大而且信号源阻抗太高很容易受干扰。所以这里要用到跨阻放大器TIATransimpedance Amplifier。它的核心结构就是一个运放加一个反馈电阻电流从光电二极管的阴极流入运放的反相输入端反馈电阻接在反相输入端和输出端之间。由于运放的虚短特性反相输入端电压被强制等于同相输入端电压电流只能流过反馈电阻因此输出电压等于输入电流乘以反馈电阻——V_out I_in × R_f。这个结构最大的好处是光电二极管两端的电压被钳位在零伏左右相当于工作在完美的光伏模式同时输出端是一个低阻抗电压源后级想接ADC还是接万用表都非常方便。反馈电阻选得越大增益越高能测的光就越微弱。比如R_f10MΩ输入1μA电流时输出就是10V输入1nA电流时输出是10mV依然可读。但增益不是越大越好后面我会详细说带宽、噪声和反馈电容的问题这是新手最容易翻车的地方之一。2.3 ADC与显示把电压翻译成光功率有了电压信号接下来就是数字化。我的方案里用了ADS1115这是一颗16位ADCI2C接口内置可编程增益放大器PGA可以测量±0.256V到±6.144V的差分信号分辨率最高能达到微伏级别。相比Arduino Nano自带的10位ADCADS1115的精度和噪声表现都好了不止一个档次尤其适合测量微弱光对应的低电压。有了数字量之后还需要结合响应度、反馈电阻、运放增益、校准系数等一系列参数反推出实际的光功率数值。这个过程在代码里实现我会在后面的章节给出完整逻辑。如果你不想外接ADC直接用Arduino Nano内置的10位ADC也不是不行但测量下限会差很多微弱光信号会被量化噪声淹没所以我还是强烈建议加上ADS1115十几块钱换来的性能提升非常值得。3. 器件选型与参数计算照着清单买就行但得知道为什么是它这一章我直接把整个项目的物料清单和选型逻辑摆出来。你如果只是想要一份购物清单直接照抄就行如果你想搞明白为什么选这个不选那个下面每一小节都写清楚了理由。3.1 光探测器BPW34和光敏电阻选哪个先说结论做光功率计优先选光电二极管而不是光敏电阻。光敏电阻的阻值确实会随光照变化但它有几个硬伤一是响应速度慢几十毫秒级别的变化根本跟不上二是阻值变化和光强之间不是线性关系标定非常麻烦三是温度系数大换个环境读数就漂。所以光敏电阻更适合做光控开关这类定性检测不适合做定量测量。光电二极管这边除了BPW34还有几种常见的替代选择。我整理了一个对比表格器件响应波长范围响应度暗电流特点BPW34400-1100nm~0.6A/W850nm约2nAPIN结构响应快性价比高DIY首选硅光电池400-1100nm更高0.4-0.5A/W大面积较大面积大适合强光测量弱光不行OPT101可见光-近红外内置运放输出电压—集成度高但价格贵带宽低普通LED反向接法波长范围窄很低很小只能测窄波段当探测器用精度有限BPW34在可见光段的响应度会低于850nm处的峰值大约在0.4~0.5A/W左右但这不影响DIY使用因为我们会做整机标定标定之后这个差异会被修正掉。真正要注意的是暗电流BPW34在零偏置下的暗电流大约是2nA对应到10MΩ反馈电阻上会产生约20mV的底数电压这个底数可以在软件里做调零处理问题不大。3.2 运算放大器和反馈电阻的搭配思路运放是整个模拟前端的核心选型时主要看三个参数输入偏置电流、输入失调电压和带宽。输入偏置电流决定了你能测多小的电流。如果运放的偏置电流是100nA而你想测的信号电流只有1nA那信号早就被偏置电流淹没了。好在现在JFET输入的运放很常见输入偏置电流能做到皮安级别非常适合跨阻放大。我推荐的运放是OPA2380或者OP07这类精密运放OPA2380的输入偏置电流只有皮安级带宽也够用。但考虑到很多读者手头不一定有这些片子我也做了另一组测试用LM358也就是网上最常见、几毛钱一片的通用运放配合10MΩ反馈电阻实测下来做光功率计粗测其实也能用只是底噪会大一些弱光段的线性度也差点意思。如果你手头只有LM358完全可以先搭起来后续再换更好的运放。反馈电阻的选择是整个设计的关键参数。我的方案里用了两组增益通过一个拨码开关或者继电切换反馈电阻低增益档用100kΩ适合测量几十微瓦以上的光高增益档用10MΩ适合测量纳瓦到微瓦级别的微弱光。如果你需要兼顾更宽的量程可以加第三档1MΩ切换逻辑和软件里的量程设置对应上就行。反馈电阻还有一个容易忽略的注意点电阻本身的寄生电容会和反馈电阻形成极点导致运放自激振荡。解决办法是在反馈电阻上并联一个几皮法的小电容我实测用4.7pF到10pF比较合适既能抑制振荡又不会严重降低带宽。3.3 供电方案和整体结构供电方面运放需要双电压或者至少是单电源供电。我的方案里用了两节18650电池串联得到7.2V经过一个简单的稳压电路后给运放供电数字部分用AMS1117-5.0把电压稳到5V给Arduino Nano和OLED供电。你也可以直接用9V电池加7805或者USB供电加电荷泵产生负压方式很多不一定要照抄我的。我自己用的供电结构是这样的两节18650进DCDC模块升到12V再用一个7812线性稳压给运放。7812的输出噪声比开关电源小很多这是模拟电路的基本素养——给精密模拟前端供电尽量别直接用开关电源否则纹波会耦合进测量信号里。至于整机结构我建议先面包板验证确认没问题再焊洞洞板。探头部分如果预算允许可以用一个带螺纹的铝管做遮光筒光电二极管装在管子尾部前面留一个接口方便固定光纤或者加衰减片。这个遮光筒非常关键它能挡住环境杂散光否则你的测量结果会严重漂移。4. 电路连接与PCB布局元件不多但走线一点都不能随意电路本身的元件数量不多但这类高阻抗微弱信号电路的布局走线非常讲究。我在这章先给出具体的连接关系然后讲清楚哪些地方容易出问题以及我踩过的坑。4.1 核心电路连接关系整个仪器的信号链路如下光电二极管阳极接地阴极接运放反相输入端反馈电阻和补偿电容并联接在反相输入端和输出端之间运放同相输入端接地运放输出接到ADS1115的AIN0ADS1115通过I2C接到ArduinoArduino再接OLED显示屏。我把所有连接列成一张表方便你照着接线起点终点说明光电二极管阴极运放反相输入端2脚信号输入光电二极管阳极地光伏模式零偏置反馈电阻100kΩ反相输入端 ↔ 输出端低增益档反馈电阻10MΩ反相输入端 ↔ 输出端高增益档补偿电容4.7pF与反馈电阻并联防自激振荡运放同相输入端3脚地参考点运放输出1脚ADS1115的AIN0电压信号ADS1115的SCL/SDAArduino A5/A4I2C通信ADS1115的ADDRGNDI2C地址设为0x48OLED SCL/SDAArduino A5/A4与ADS1115共用I2C总线4.2 高阻抗节点的布局要求所有连到运放反相输入端的部分都是高阻抗节点——光电二极管阴极、反馈电阻左端、补偿电容左端这几个引脚一定要尽量短尽量减少裸露的焊盘面积。原因是高阻抗节点对电场干扰极其敏感你手指靠近都能让输出跳几个毫伏。我在洞洞板上焊接时特地让运放紧挨着光电二极管的引脚反馈电阻也用贴片件直接跨在运放的1脚和2脚之间走线不超过5毫米。相比之下如果把这些元件分散排开、用飞线连噪声会大好几倍测量微瓦以下的光功率时数字会像心电图一样乱跳。另外还有个细节反馈电阻旁边别走数字信号线特别是I2C和PWM信号。数字信号的跳变沿会通过寄生电容耦合到高阻抗节点上在输出端形成毛刺。我的做法是把模拟地分割出来数字部分和模拟部分在电源入口处单点汇合这个分地策略在微弱信号测量中几乎是必须的。4.3 先用Multisim仿真验证再上电焊接如果你对电路没把握强烈建议先在Multisim里把跨阻放大级搭出来仿真一下。Multisim里有虚拟光电二极管模型虽然没有完全对应BPW34的但可以用电流源串联一个二极管来模拟或者干脆用交流电流源加直流偏置来仿真不同光照下的响应。仿真时重点观察两件事一是放大倍数对不对输入1μA电流时输出是否接近10V用10MΩ反馈电阻二是有没有自激振荡用示波器观察输出端的波形如果出现高频等幅振荡那就是补偿电容没加或者加得不够。用Multisim提前踩这些坑能省下不少焊板子的时间。仿真通过之后再上洞洞板。我的建议是先焊接模拟前端上电用万用表测运放输出遮住光电二极管时读数应该接近0V用手电筒照射时读数应该明显变化。这个基本功能确认没问题再接数字部分这样可以避免两边都有问题时分不清到底哪边出错。5. 固件代码与校准数据烧录就能用但校准得自己来硬件搭好之后代码是决定这台光功率计好不好用的关键。我用的开发环境是Arduino IDE核心逻辑包括ADC读取、量程判断、工程值计算和OLED显示。下面分段展开每个部分都会给出可用的代码片段。5.1 主程序框架从ADC读数到光功率值整个固件的思路是先从ADS1115读取模拟输入电压然后根据当前档位的反馈电阻和光电二极管的响应度把电压换算成电流再换算成光功率。#include Wire.h #include Adafruit_ADS1015.h #include U8g2lib.h Adafruit_ADS1115 ads; U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset*/ U8G2_PIN_NONE); // 硬件参数 const float R_F 10000000.0; // 当前档位反馈电阻10MΩ const float RESPONSIVITY 0.6; // 光电二极管响应度 A/W850nm const float CAL_COEF 1.0; // 校准系数标定后写入 float readOpticalPower() { // ADS1115读取差分电压增益为±4.096V int16_t raw ads.readADC_SingleEnded(0); float voltage ads.computeVolts(raw); // 减去遮光时的底数由调零流程得到 voltage - zeroOffset; // 电压换算电流I V / R_F float current voltage / R_F; // 电流换算光功率P I / RESPONSIVITY * CAL_COEF float power (current / RESPONSIVITY) * CAL_COEF; return power; }这里有个容易混淆的点电压减掉底数这一步我用的zeroOffset是遮光状态下读取的电压值。因为运放存在输入失调电压加上反馈电阻带来的偏置实测遮光时输出往往不是绝对的0V而是几毫伏到几十毫伏不等。这个值必须在每次开机或者换挡之后重新采一次存下来后面所有测量都要先减掉它。5.2 量程自动切换和OLED显示逻辑因为我们的硬件上有两档增益低增益100kΩ、高增益10MΩ程序里就要做自动量程切换。一个比较朴素的实现思路是先用当前档位测量如果读数超过满量程的90%就切到低增益档如果读数低于满量程的5%就切到高增益档。切换通过控制继电器或者模拟开关实现。float measured readOpticalPower(); // 自动量程切换根据实测值判断是否换挡 if (measured HIGH_LIMIT currentGain HIGH_GAIN) { switchGain(LOW_GAIN); // 切到低增益档 } else if (measured LOW_LIMIT currentGain LOW_GAIN) { switchGain(HIGH_GAIN); // 切到高增益档 } // OLED显示自动选择合适的分档显示 if (measured 1e-6) { displayValue(measured * 1e6, uW); // 微瓦 } else if (measured 1e-9) { displayValue(measured * 1e9, nW); // 纳瓦 } else { displayValue(measured * 1e12, pW); // 皮瓦 }OLED我用的是0.96寸SSD1306驱动库选u8g2因为它在中文字库和数字显示上的灵活性都更好。显示逻辑不复杂就是实时刷新当前数值和单位。我再加了一个一维数组做滑动平均滤波每5次测量取一次平均能明显降低噪声造成的读数跳动。5.3 校准系数一台仪器准不准全靠这一步校准是光功率计最核心的环节。DIY设备没有标准光源但我们完全可以用一个已知功率的激光笔来做粗标定。我的操作方法是这样的找一支标称10mW的532nm绿光激光笔先用商用光功率计或者干脆根据激光笔规格测出它实际输出到某个固定距离上的功率值记为P_ref。然后用我们DIY的仪器在同一位置测量得到未校准的读数P_raw。那么校准系数CAL_COEF就是P_ref/P_raw。把CAL_COEF用EEPROM存起来以后每次测量都乘上这个系数。#include EEPROM.h float calCoef; float zeroOffset; void saveCalibration(float coef) { EEPROM.put(0, coef); } void loadCalibration() { EEPROM.get(0, calCoef); if (isnan(calCoef) || calCoef 0) calCoef 1.0; // 默认值保护 }这里必须提醒一句激光笔的功率标称值和实际值往往有偏差有些廉价笔甚至虚标一倍以上。所以最好的办法是借一台靠谱的功率计做一次对比标定。如果没有这个条件那就调低预期粗标定之后至少能做到不同光强之间的相对变化是可信的这对很多实验来说已经够用了。5.4 如何验证代码和硬件是否正常工作代码跑起来之后第一步验证用的是最简单的办法用手电筒照光电二极管看OLED上的数值是否明显变化。如果数值跟着光源的远近亮暗变化说明整条链路已经通了如果数值纹丝不动要按优先级排查先测运放供电是否正常再测运放输出有没有电压最后检查ADC的I2C地址和接线。我自己踩过的一个坑是OLED和ADS1115共用I2C总线时忘记给SCL和SDA接上拉电阻导致两个器件都识别不到。Arduino的内部上拉在I2C上不一定够强尤其是线比较长的时候建议外部加4.7kΩ上拉VCC接3.3V或5V都行。6. 实测标定与常见坑我把容易翻车的点提前替你踩了硬件代码都齐了之后真正的战场在于实测。这个过程里我发现的问题比原理文章里讲的要多得多。下面列的每一条都是实际踩过的值得反复看。6.1 遮光底数不是零必须先做调零第一次上电测试时我遮住光电二极管OLED上显示的数值不是0而是大约3.2μW。这个底数来源有几个运放的输入失调电压、反馈电阻的热噪声、光电二极管的暗电流还有环境光从遮光筒缝隙漏进来的部分。解决思路是软件调零。在开机时或者按下调零按键后程序自动采集100次遮光状态下的读数取平均作为zeroOffset存下来。之后每次测量都减去这个值。但要注意zeroOffset不是永远不变的。温度变化会引起运放失调电压漂移档位切换后不同阻值的反馈电阻也会引入不同的底数所以每次换挡后最好重新调零。我做的方案很简单每次按键调零同时把当前档位和zeroOffset一起存进EEPROM下次开机如果档位没变直接调用存储的调零值。6.2 用激光笔做粗标定流程和注意事项这里我把标定的完整流程走了一遍供你参考。我用的标定光源是一支标称100mW、实际峰值约83mW的450nm蓝光激光笔因为功率较大我这里先加了一片中性密度滤光片衰减到约1mW量级再让DIY光功率计测量。操作步骤是这样的打开激光笔预热5分钟保证输出功率稳定。用遮光筒接住光束调整位置让光斑完全落在光电二极管的感光面上。记录DIY仪器的当前读数P_raw。计算校准系数CAL_COEF P_ref / P_raw。写入EEPROM然后用另一个未知功率的光源验证一下校准是否有效。实际结果我商店里买的一支标称5mW、实际约4.8mW的红色激光笔校准后测得4.6mW误差约4%。这个精度在DIY设备里我个人是很满意的。要注意的是标定时光斑必须完全覆盖在感光面上不能有一部分光照到遮光筒壁上否则读数会偏低。另外激光笔的光斑比较集中功率密度高建议在光电二极管前加一片漫射片或者小积分球让光均匀地落在感光面上这样能降低位置敏感性测量重复性会好很多。6.3 强光下的饱和与保护高增益档10MΩ反馈虽然能测微弱光但遇到强光会迅速饱和。按照BPW34的响应度1mW光功率产生的电流是0.6mA流过10MΩ电阻会产生6000V的输出电压——当然运放不可能输出这么高结果是直接打到电源轨上长时间这样工作会让放大器输出极度过载虽然一般不会烧毁运放但恢复时需要时间响应可能变得迟钝。所以面对可能的强光要先从低增益档开始测试然后再切到高档位。如果只是偶尔误操作运放一般能承受几秒钟的过载但如果你经常忘记换挡建议在运放输出端加一个双向钳位二极管比如BAT54S到电源轨防止输出过压损坏后面的ADC虽然ADS1115的输入本身也有保护二极管但多一层保护总不是坏事。6.4 微弱光测量时的噪声控制到了我最想分享的部分nW量级的微弱光测量噪声才是最大的敌人。即使有了16位ADC和JFET运放示波器上看输出依然会有几十毫伏的噪声换算成功率就是几十纳瓦的波动。我试过几个改善方向效果各不相同软件滑动平均是性价比最高的把采样次数提到16次以上显示数字会稳定很多缺点是响应变慢。缩短遮光筒内部的光路减少杂散光进入探测器这是最有效的硬件措施。给运放和反馈电阻加屏蔽罩用一个接地的铝箔包裹住能降低几个dB的工频干扰。如果用开关电源供电换成线性稳压电源输出纹波明显降低读数也稳了。还有一个经常被忽略的点把ADS1115的采样率设置成最低档8SPS开启内置的PGA增益为16倍。这样虽然单次测量变慢但分辨率会大幅提升配合滑动平均我在微弱光段的读数稳定度提升了将近一个数量级。6.5 最后分享一个调试的小技巧调试这类微弱信号电路时我最常用的工具不是示波器而是一个廉价的USB声卡加虚拟示波器软件。把运放输出接到声卡的麦克风输入注意串一个大电容隔直软件里就能看到微伏级别的时域波形。这个办法能非常直观地看到50Hz工频干扰、高频自激和随机噪声比万用表好用太多。对于这类低频微弱信号声卡的采样率完全够用。整个项目做完我最深的体会是光功率计这个东西看起来简单真正把微弱信号处理做好涉及的知识点其实横跨了光学、模拟电路、数字采样和校准测试四个领域。但正因为如此它才是特别值得亲手做一遍的项目——做完之后你再看到任何光电仪器都会下意识地去想它的前端是怎么设计的这个习惯会伴随你之后所有的电子DIY项目。如果你也照着做了一台遇到测量不稳定、读数飘、或者换挡不工作这类问题欢迎来交流我很乐意分享更详细的解决办法。