ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

STM32F103驱动BH1750光传感器:模拟I2C实战与避坑指南

STM32F103驱动BH1750光传感器:模拟I2C实战与避坑指南 做嵌入式这行手里没几个用得上的传感器驱动总觉得不踏实。BH1750这个光强度传感器算是环境光检测里的常青树了I2C接口、数字输出、价格便宜做智能灯光、屏幕亮度自适应、温室补光控制都能派上用场。我这次在STM32F103上把它完整驱动起来实测结果是满意的把整个调试过程和踩过的坑都记录下来给正在搞同类方案的朋友一个参考。这个驱动适合谁看如果你手上正好有STM32F103最小系统板、一块BH1750模块想快速跑通I2C通信拿到真实光照度数值或者你之前用模拟I2C时遇到过数据不稳、读出来全是0xFF之类的问题这篇内容能帮你省掉不少弯路。文章里涉及的代码基于标准外设库编写用GPIO模拟I2C时序没有依赖具体开发板的型号移植到其他F1系列芯片上也就是改改引脚定义的事。1. 整体方案设计思路为什么选模拟I2C而不是硬件I2C先说结论STM32F103的硬件I2C外设不是不能用但它对时序的要求比较苛刻中断优先级、总线错误恢复、时钟延展Clock Stretching处理不好就容易卡死。网上关于F103硬件I2C翻车的帖子一抓一大把说的都是同一个问题——I2C事件中断里稍微多执行几条指令就超时总线忙标志清不掉。我这次做BH1750驱动目标是拿过来就能用、稳定不折腾所以直接用GPIO模拟I2C也就是俗称的软件I2C。1.1 方案选型软件I2C的三个核心优势第一个优势是时序完全可控。BH1750的I2C时钟频率最高支持到400kHzFast Mode但它的从机响应时间、内部ADC转换时间都有固定参数。用GPIO模拟我可以精确控制每个SCL高低电平的延时想快想慢自己说了算不用担心硬件外设的自动时序把你带进沟里。第二个优势是引脚选择自由。硬件I2C1默认只能挂在PB6/PB7I2C2是PB10/PB11如果这组引脚被其他外设占用了硬件I2C就废了。软件模拟就灵活多了任意两个GPIO口都能当SCL和SDA布局布板的时候自由度大很多。第三个优势是调试直观。硬件I2C出问题时你要查的事件标志一大堆——SB、ADDR、BTF、TXE、RXNE光读状态寄存器就能绕晕。软件I2C的逻辑完全是自己写的每个时序步骤都在代码里摆着出了bug直接看波形或者串口打印就能定位效率高得多。1.2 硬件准备最小系统板和引脚分配我手上这块板子是STM32F103C8T6的最小系统板也就是俗称的BluePill板型板载8MHz晶振、AMS1117稳压、USB转串口芯片外设资源对跑BH1750来说绰绰有余。BH1750模块是常见的Grove接口小板板载4.7kOhm上拉电阻模块上的VCC、GND、SCL、SDA四个引脚直接接过去就能用。引脚分配我选的是PB6做SCL、PB7做SDA虽然我用的是软件I2C但故意选这两个引脚是有一个小心思——万一后面想切到硬件I2C1硬件上不用改线只改代码就行。这一点在实际项目中很实用前期先用软件I2C快速验证功能后期量大了再优化成硬件I2C完全无缝切换。注意BH1750模块的VCC可以接3.3V也可以接5V模块上有电平转换逻辑但如果你的板子IO口只有3.3V容忍度建议还是统一用3.3V供电避免长期运行烧坏GPIO。2. 核心细节解析I2C时序与BH1750寄存器机制写驱动之前必须先把BH1750的通信协议和寄存器机制搞透彻。这个传感器的命令少得可怜不像有些外设动辄几十个寄存器本质上它就是一个发命令、收数据的从机但每一个命令字节的含义和数据读取的时序都有讲究。2.1 I2C基础时序起始、停止、应答、非应答I2C通信靠的就是一根时钟线SCL和一根数据线SDA的配合。起始条件START是SCL高电平时SDA从高拉低停止条件STOP是SCL高电平时SDA从低拉高。数据线SDA的电平变化只能发生在SCL低电平时SCL高电平时SDA要保持稳定这就是I2C的规矩。每次传输8位数据后接收方要回一个应答位。主机发送完数据后从机拉低SDA表示ACK应答从机发送数据时主机拉高SDA表示NACK非应答表示我读够了别再发了。BH1750读取数据时读完高字节后主机返回ACK读完低字节后主机返回NACK并发送停止条件这个顺序不能乱。2.2 BH1750的地址机制0x23还是0x5CBH1750的从机地址是7位由ADDR引脚的电平决定。ADDR接地时地址是0x23ADDR接VCC时地址是0x5C。在I2C总线上实际发送的字节是7位地址左移1位 读写位所以写操作发送0x460x23 1 | 0读操作发送0x470x23 1 | 1。我用的模块上ADDR引脚默认接地所以地址用0x23这点在做多设备挂载时要特别留意总线上挂两个BH1750时就是把一个ADDR接高、一个接低来区分。2.3 BH1750命令集从断电到连续测量BH1750的命令通过单字节写入常见的几个命令整理如下命令字功能说明0x01Power On上电初始化从Power Down模式唤醒0x02Power Down进入低功耗待机模式0x10连续H分辨率模式1 lux分辨率典型测量时间120ms0x11连续H分辨率模式20.5 lux分辨率典型测量时间120ms0x13连续L分辨率模式4 lux分辨率典型测量时间16ms0x20一次H分辨率模式测量一次后自动回到Power Down0x21一次H分辨率模式2同上分辨率更高0x23一次L分辨率模式同上测量速度最快0x07复位清除测量结果寄存器仅在Power On状态下有效我这次测试用的是0x10连续H分辨率模式这也是绝大多数应用场景的最佳选择——分辨率1lux量程够宽速度适中。如果做灯光控制这类需要快速响应的场景可以用0x13连续L分辨率模式16ms就能出一组数据但精度只有4lux。2.4 数据换算公式为什么是除以1.2BH1750内部的光电二极管将光照转化为电流经过ADC转换为16位数字量但这个数字量还不是直接的lux值。手册里给出的换算公式是光照度lux 读取到的16位数据 / 1.2。这个1.2的系数是传感器出厂校准时针对标准光源通常是荧光灯或C光源标定的结果已经考虑到了传感器表面封装透光率的影响。需要注意的是如果修改了测量时间寄存器MTreg命令字0x40~0x5F这个系数会发生变化数据手册提供了一张换算表和对应的公式实际lux值 读取的原始值 × 0.6 × 69 / MTreg值 / 1.2默认MTreg是69所以公式就简化为 原始值 / 1.2。我测试时保持默认MTreg不折腾这个寄存器换算就一条除法语句搞定。3. 实操过程驱动代码一步步写出来这一章直接上代码我把整个驱动拆成三个文件bh1750.h头文件、bh1750.c驱动实现、main.c测试代码结构清晰方便你直接抄作业。代码基于STM32标准外设库这是F103最主流的开发方式。3.1 底层I2C时序GPIO模拟的实现细节先抽象出底层I2C的引脚控制。我用宏定义规划好引脚SDA引脚需要支持输入输出切换SCL只做输出。// bh1750.h #ifndef __BH1750_H #define __BH1750_H #include stm32f10x.h // 引脚定义改成你自己的接线 #define BH1750_SCL_PORT GPIOB #define BH1750_SCL_PIN GPIO_Pin_6 #define BH1750_SDA_PORT GPIOB #define BH1750_SDA_PIN GPIO_Pin_7 // I2C时钟频率控制调整延时以适配不同主频 #define I2C_DELAY_US 10 // BH1750器件地址ADDR引脚接地时的7位地址 #define BH1750_ADDR 0x23 // BH1750命令字 #define BH1750_POWER_DOWN 0x02 #define BH1750_POWER_ON 0x01 #define BH1750_RESET 0x07 #define BH1750_CONT_H 0x10 // 连续H分辨率模式 #define BH1750_CONT_H2 0x11 // 连续H分辨率模式2 #define BH1750_CONT_L 0x13 // 连续L分辨率模式 #define BH1750_ONCE_H 0x20 // 一次H分辨率模式 #define BH1750_ONCE_H2 0x21 // 一次H分辨率模式2 #define BH1750_ONCE_L 0x23 // 一次L分辨率模式 void BH1750_Init(void); void BH1750_WriteCmd(uint8_t cmd); uint16_t BH1750_ReadData(void); float BH1750_ReadLux(void); #endif然后是GPIO模拟I2C的底层时序函数。我用了开漏输出加上外部上拉的配置这样在读取SDA电平之前只需要把GPIO切到输入模式。代码里我写了延时函数用的是简单的循环延时在实际工程中如果你开了SysTick可以换成更精确的微秒延时。// bh1750.c #include bh1750.h static void I2C_Delay(void) { uint16_t i; for (i 0; i 100; i) ; // 具体循环次数根据主频调整 } static void SCL_High(void) { GPIO_SetBits(BH1750_SCL_PORT, BH1750_SCL_PIN); } static void SCL_Low(void) { GPIO_ResetBits(BH1750_SCL_PORT, BH1750_SCL_PIN); } static void SDA_High(void) { GPIO_SetBits(BH1750_SDA_PORT, BH1750_SDA_PIN); } static void SDA_Low(void) { GPIO_ResetBits(BH1750_SDA_PORT, BH1750_SDA_PIN); } // SDA引脚设为输入模式 static void SDA_Input(void) { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin BH1750_SDA_PIN; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(BH1750_SDA_PORT, GPIO_InitStructure); } // SDA引脚设为输出模式 static void SDA_Output(void) { GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin BH1750_SDA_PIN; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_OD; // 开漏输出 GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(BH1750_SDA_PORT, GPIO_InitStructure); } // I2C起始条件 static void I2C_Start(void) { SDA_Output(); SDA_High(); SCL_High(); I2C_Delay(); SDA_Low(); I2C_Delay(); SCL_Low(); I2C_Delay(); } // I2C停止条件 static void I2C_Stop(void) { SDA_Output(); SDA_Low(); SCL_High(); I2C_Delay(); SDA_High(); I2C_Delay(); }写字节和读字节是I2C通信的核心。发送字节时数据从高位到低位一位位放到SDA上每个位在SCL的上升沿被从机采样。接收字节时主机在SCL高电平期间读取SDA的电平状态。// 主机发送一个字节返回从机的ACK/NACK状态 static uint8_t I2C_WriteByte(uint8_t data) { uint8_t i; uint8_t ack; SDA_Output(); for (i 0; i 8; i) { if (data 0x80) SDA_High(); else SDA_Low(); data 1; I2C_Delay(); SCL_High(); I2C_Delay(); SCL_Low(); I2C_Delay(); } // 释放SDA总线等待从机应答 SDA_Input(); I2C_Delay(); SCL_High(); I2C_Delay(); ack GPIO_ReadInputDataBit(BH1750_SDA_PORT, BH1750_SDA_PIN); SCL_Low(); I2C_Delay(); return ack ? 1 : 0; // 返回0表示收到ACK } // 主机接收一个字节ackFlag为1时发送ACK为0时发送NACK static uint8_t I2C_ReadByte(uint8_t ackFlag) { uint8_t i; uint8_t data 0; SDA_Input(); for (i 0; i 8; i) { I2C_Delay(); SCL_High(); I2C_Delay(); data 1; if (GPIO_ReadInputDataBit(BH1750_SDA_PORT, BH1750_SDA_PIN)) data | 0x01; I2C_Delay(); SCL_Low(); } // 主机发送应答位 SDA_Output(); if (ackFlag) SDA_Low(); // ACK else SDA_High(); // NACK I2C_Delay(); SCL_High(); I2C_Delay(); SCL_Low(); I2C_Delay(); SDA_Low(); return data; }这里有个重要的细节需要注意I2C_WriteByte里面我用了SDA_Input()释放总线去读取应答位。因为GPIO配置成了开漏输出输出高电平其实就是释放总线的意思所以也可以在这之前把SDA设成高电平而不切换模式。但为了代码逻辑更清晰我在读取应答位时切换到了输入模式。两种方式实测都OK但切换模式的方式更规范因为输入模式下读取引脚电平时不会受到输出数据寄存器的影响。3.2 BH1750命令控制与数据读取有了底层I2C时序操作BH1750就是水到渠成的事了。写命令、读数据两个函数走起// 初始化BH1750上电并设置为连续H分辨率模式 void BH1750_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; // 使能GPIOB时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 初始化SCL为推挽输出 GPIO_InitStructure.GPIO_Pin BH1750_SCL_PIN; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(BH1750_SCL_PORT, GPIO_InitStructure); // 初始化SDA为开漏输出外部已接上拉电阻 GPIO_InitStructure.GPIO_Pin BH1750_SDA_PIN; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_OD; GPIO_Init(BH1750_SDA_PORT, GPIO_InitStructure); // 先释放总线确保总线处于空闲状态 SDA_High(); SCL_High(); I2C_Delay(); // 上电唤醒 BH1750_WriteCmd(BH1750_POWER_ON); // 复位 BH1750_WriteCmd(BH1750_RESET); // 设置为连续高分辨率模式 BH1750_WriteCmd(BH1750_CONT_H); } // 向BH1750写入一个命令 void BH1750_WriteCmd(uint8_t cmd) { I2C_Start(); I2C_WriteByte((BH1750_ADDR 1) | 0x00); // 写地址 I2C_WriteByte(cmd); // 写命令 I2C_Stop(); } // 读取BH1750的16位测量数据 uint16_t BH1750_ReadData(void) { uint16_t data 0; uint8_t highByte, lowByte; I2C_Start(); I2C_WriteByte((BH1750_ADDR 1) | 0x01); // 读地址 // 先读高字节主机返回ACK highByte I2C_ReadByte(1); // 再读低字节主机返回NACK lowByte I2C_ReadByte(0); I2C_Stop(); data (highByte 8) | lowByte; return data; }这里我特别留意了一个容易被忽略的点I2C_WriteByte返回的ACK状态我没有使用。正常情况下写地址后如果BH1750在线会返回ACK这里可以加个判断做健壮性处理但测试阶段为了简化就不做复杂处理了。后面在常见问题章节我会专门讲如何用这个返回值做设备在位检测。3.3 主函数测试串口输出实测光照度测试代码很简单初始化B H1750和串口1后循环读取光照度并通过串口打印。我用的是USART1波特率1152008位数据位1位停止位。// main.c #include stm32f10x.h #include usart.h #include bh1750.h // 串口重定向fputc方便用printf输出 int fputc(int ch, FILE *f) { while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) RESET); USART_SendData(USART1, (uint8_t)ch); return ch; } int main(void) { float lux; USART1_Init(115200); BH1750_Init(); printf(BH1750 Light Sensor Test...\r\n); while (1) { lux BH1750_ReadLux(); printf(Lux: %.2f\r\n, lux); Delay_ms(500); } }对应的换算函数和延时函数也要补上// 读取并换算光照度返回单位lux float BH1750_ReadLux(void) { uint16_t raw; float lux; raw BH1750_ReadData(); lux (float)raw / 1.2f; return lux; }这里要注意BH1750在连续测量模式下每次读取数据前不需要重新发命令直接发起读操作就行。但如果用的是一次测量模式每次读取前都要重新发一次测量命令否则读到的是上一次的数据这个差别在开发时要搞清楚。3.4 串口打印的坑为什么已测试OK还要注意波特率在实测过程中串口打印是重要的辅助手段但串口这块有几个坑我先替你们踩了。第一个坑是USB转串口芯片的波特率容差。我用的是板载CH340芯片115200波特率下实测非常稳定但有些便宜的USB转串口线在921600等高波特率下会出现乱码。调试时如果你发现串口输出奇怪的字符先把波特率降到9600或38400试试这能帮你区分是主频配置问题还是转换芯片问题。第二个坑是printf重定向。上面的代码用fputc重定向到USART1这个方案在MDK和IAR下都通用但记得包含stdio.h头文件并且在MDK的C/C选项中勾选Use MicroLIB否则printf会占用大量代码空间F103C8T6的64KB Flash分分钟给你塞满。4. 实测结果与精度验证代码写完下载到板子用STLINK V2通过SWD接口下载这里提醒一句如果你用STLINK下载失败检查一下BOOT0引脚是不是被拉高了。BOOT0接低电平才能进入正常Flash启动模式有些最小系统板的BOOT0没有预置下拉电阻需要手动接GND否则DAP/STLINK下载会报错这在热词里也是高频问题。4.1 实测数据记录我把传感器放在不同光照环境下用串口助手记录了一组数据测试环境BH1750读数raw换算光照度lux备注密闭暗箱无光0 ~ 20 ~ 1.6传感器前无遮挡室内日光灯下1m86 ~ 9271 ~ 76桌面照度窗台阳光直射上午10点12400 ~ 1260010333 ~ 10500读数偶有波动手机LED手电筒紧贴32000 ~ 3300026666 ~ 27500接近量程户外阴天楼顶6400 ~ 68005333 ~ 5667光线稳定第一组暗箱实验值得说一下BH1750在完全无光环境下读数并不严格是0偶尔能读到1~2的数值这个属于正常现象换算下来不到2lux人眼已经完全察觉不到这种强度的光。如果你做的是无光触发类的阈值判断建议在代码里加一个死区判断比如读数小于5时直接当0处理避免灯光在临界点反复抖动。4.2 不同分辨率模式对比我把连续H分辨率模式0x10和连续H分辨率模式20x11做了对比测试在室内日光灯环境下连续读取20次记录最大值和最小值H分辨率模式最小值68最大值76波动范围8个lux。H分辨率模式2最小值66最大值78波动范围12个lux。从数据上看H分辨率模式2的理论分辨率更高0.5 lux但实际应用中的波动反而更大一些这是因为分辨率提高后ADC对微小光照波动的敏感度也提高了微小的环境光变化都会被捕捉到。所以在实际项目中如果不是特别暗的环境低于5 lux用标准H分辨率模式就够了。4.3 测量边界与响应时间验证BH1750的响应时间在手册里标注的是H分辨率模式典型120msI实测从完全遮挡传感器状态突然切换到手机手电照射读取到的数值在第二组数据约250ms后就稳定下来了响应速度完全够用。但有一点要注意传感器的感光窗口很小方向性很强如果传感器表面贴了保护膜或者外壳遮挡了感光窗口读数会明显偏低而且不一定是线性的下降这点在做产品结构设计时一定要做实地校准。5. 常见问题与排查技巧实录这一章全是硬货整理了我这次调试过程和以往做其他I2C传感器时遇到的典型问题每一条都给出了排查思路和解决方法。5.1 I2C通信失败读到的数据全是0xFF或0x00这是传感器调试最经典的问题。数据全是0xFF最可能的原因是从机没有拉低SDA总线做应答——要么地址不对要么从机没上电要么SDA和SCL接反了。数据全是0x00大概率是数据传输过程中丢位了常见原因是SCL频率太高从机每个时钟周期来不及处理。排查时先用示波器或逻辑分析仪看SCL和SDA的波形如果能看到起始条件、地址字节和明确的NACK问题基本就锁定在地址或时序上。我的排查顺序是首先用万用表量模块VCC和GND是否正常供电然后确认ADDR引脚的接电平是否正确再用软件反复发送Power On命令并读取ACK返回值。如果ACK始终异常把I2C_Delay的延时加大一倍再试排除时序过快的问题。5.2 环境光变化但读数纹丝不动遇到这种情况先怀疑传感器的感光窗口是不是被遮挡了。BH1750的感光窗口在模块正面的小方块区域有的模块上面贴了一层保护膜使用前要撕掉。另外如果传感器背面贴到了金属或者发热元件温漂也会导致读数异常BH1750虽然温漂不大但长期在高温环境下精度会下降。还有一个容易被忽视的点连续测量模式下如果你调用了BH1750_WriteCmd(BH1750_POWER_DOWN)传感器会在测量完当前周期后停止更新数据下次读取到的依然是旧的测量值。此时必须重新发送Power On命令并等待一个测量周期才能恢复到连续测量状态。5.3 多传感器挂载ADDR引脚与总线冲突如果你要在同一条I2C总线上挂两个BH1750一个模块的ADDR接GND另一个的ADDR接VCC这样地址就分别是0x23和0x5C互不冲突。但要注意两个模块的SDA和SCL要并联到总线上VCC和GND也要统一。如果模块自带上拉电阻并联后相当于两个上拉电阻并联总线上的等效上拉阻值会变小I2C通信仍然可以工作但如果你挂载的设备很多上拉电阻并联后总线上拉能力过强某些从机可能拉不动SDA电平导致通信异常。这种情况需要把每个模块上的上拉电阻去掉只在总线上保留一对上拉电阻即可。5.4 STLINK下载失败与BOOT引脚的关系这个坑我在测试过程中也踩过。用STLINK V2通过SWD接口下载程序时如果下载器报了类似No target connected或Connection error的错误先查BOOT0引脚。BOOT0为高电平时芯片会进入ISP引导模式SWD接口虽然依然连接但调试器可能无法正常控制内核。把BOOT0接到GND再试试基本能解决问题。另外确认STLINK的驱动安装正确在设备管理器里能看到ST-Link Debug设备老版本的STLINK容易在Win10/Win11下出现驱动兼容问题建议直接装最新版驱动。5.5 数据波动大且毫无规律排除供电问题后优先检查I2C总线上是否干扰噪声过大。连接线过长超过20cm且没有用屏蔽措施时SCL上的毛刺容易造成数据错位。解决办法是尽量缩短传感器到MCU的连线距离或者把I2C速率降下来从我当前设置的I2C_DELAY_US加大到20甚至30微秒实测稳定性和波形质量都会提升。如果走线必须很长可以考虑用带屏蔽的4芯线屏蔽层接GND。6. 优化与扩展思路驱动跑通了、数据也验证了但项目做完不等于事情就结束了。我在实际项目中把BH1750玩出了一点新花样这里分享几个经过验证的扩展方向。6.1 连续测量与低功耗设计结合如果你的产品用电池供电BH1750有一个很实用的特性——一次测量模式One-Time Mode。在这种模式下传感器测量完一次数据后自动进入Power Down状态电流消耗降到约1uA以下唤醒后再次发送测量命令又能快速出数据非常适合低功耗应用。配合STM32的停机模式Stop Mode可以实现每10秒唤醒一次、读取光照度、回到睡眠的低功耗循环。我做过一个温湿度加光感的数据采集中继器3.7V锂电池供电采用这种周期唤醒机制整体平均功耗控制在百微安级别一块1000mAh电池能跑好几个月。核心代码就是把Init时的连续测量命令改成一测一次读取前先发测量命令、延时150ms、再读数据。6.2 从标准库到HAL库移植现在新项目大多用HAL库我这份标准库代码要移植过去也不难。底层I2C时序函数直接复用只需要把GPIO_SetBits替换成HAL_GPIO_WritePin把RCC_APB2PeriphClockCmd替换成__HAL_RCC_GPIOB_CLK_ENABLE()main里的延时换成HAL_Delay()逻辑完全不用动。如果你打算用STM32CubeMX配工程把PB6和PB7配成GPIO_Output然后在用户代码段里粘贴我这份模拟I2C驱动五分钟就能跑起来。6.3 数据滤波处理滑动平均让读数更稳光照传感器在实际环境中使用的痛点之一是数据抖动尤其是光源为50Hz交流驱动的荧光灯时光强会有100Hz的波动成分BH1750的测量周期刚好可能采到波峰或波谷造成读数跳变。我用的最简单的效果好方案是滑动平均滤波——维护一个长度为10的环形缓冲区每次采样后移除最旧的数据求平均。实测施加这个滤波后室内灯光下的读数波动范围从±8lux降到了±2lux以内。如果环境变化很快又需要快速响应可以改成加权平均最新的数据权重更高。这种滤波方案在感光夜灯这类应用里很实用既保证稳定性又不会让灯反应迟钝。涉及代码太长我就不贴了核心思路就是新值0.3 旧均值0.7每次迭代更新计算量小效果好。我个人在实际测试中的一个体会是BH1750的驱动难度不高难点全在I2C底层和外部环境干扰上。只要底层的时序准确、硬件连接干净这个传感器的数据是相当可靠的。如果你照着这篇文章去调一次跑通的可能性很大万一遇到问题回头翻一翻常见问题那一章大部分坑都能对号入座。后续如果有时间我计划把这套驱动加到FreeRTOS下做成一个独立任务再配合联动控制写个小Demo到时候再来分享。
返回列表