简介:面向物联网与无线传感器网络开发者,这份基于CC2530的光敏传感器代码包提供了从传感器数据采集到Zigbee无线传输的完整工程实现。资源覆盖光敏传感器接入、GPIO初始化、A/D转换参数配置、滤波与标度换算,以及Zigbee网络层与应用层通信等关键环节,并附有IAR工程文件与ZStack-2.5.1a协议栈相关源码,适合需要快速上手CC2530外设驱动或参考Zigbee组网代码的读者直接移植与二次开发,能有效缩短物联网原型搭建时间。压缩包内含1064个文件,以C源码、头文件和IAR工程配置(ewp/ewd)为主体,配合hex固件、编译列表等辅助文件,整体大小约14.66MB,目录结构清晰,便于按模块检索定位。项目已有4526人浏览学习,可用于光照控制、环境监测等场景的工程落地,帮助理解传感器模拟量采集、无线节点组织及数据交互的完整链路。
1. 某农业大棚环境监测项目需要部署上百个光照采集节点。当时有两个方案摆在面前:买现成的光照变送器,一个几百块,还得配RS485网关才能接入;另一条路是用手头囤的CC2530模块加上几块钱的光敏电阻模块,自己写一段ADC采集代码,走ZigBee协议栈直接组网上报。我选了后者,结果发现CC2530的12位ADC采集光敏电压本身不难,难的是把电压换算成光照强度,以及让上百个节点在批量生产时读数一致。如果你也正要在CC2530上做光照采集,下面这些关键点拆开讲清楚,从硬件接法、工程配置到代码实现和量产踩坑,让你少走那些我用一晚上测试换来的弯路。
2. 光敏采集的两条硬件路线:直接分压还是用模块
2.1 裸光敏电阻和集成光敏模块的选型对比
常见又靠谱的光敏采集方案有两种。第一种是买光敏电阻,也叫光敏电阻器或硫化镉光敏电阻,自己搭分压电路。光敏电阻的阻值随光照强度变化,暗环境中阻值可达兆欧级别,强光下只有几百欧姆到几千欧姆。把它和一个固定电阻串联,中间节点接CC2530的ADC引脚,阻值变化就转化成了电压变化。
第二种是买集成光敏传感器模块,模块上已经焊好了光敏电阻、比较器和电位器,输出数字高/低电平或模拟电压。这类模块通常用于判断“天黑没黑”这类阈值场景,配合电位器可以调灵敏度。
对于需要连续采集光照强度数值的物联网节点,我建议用第一种:裸光敏电阻加固定阻值电阻做分压,直接进ADC。集成模块的数字输出只有0和1,拿不到中间过程的光照变化;模拟输出模块又往往带有RC滤波,响应慢,采样周期短的时候数值延迟明显。分压电路的好处是自己把控参数,还能用软件做非线性修正。
| 对比项 | 裸光敏电阻+分压电路 | 集成模拟输出模块 | 集成阈值输出模块 |
|---|---|---|---|
| 输出信号 | 模拟电压,需ADC | 模拟电压,带滤波 | 数字高低电平 |
| 成本 | 最低(几毛钱) | 中等(一两块) | 中等 |
| 光照强度连续采集 | 可以做 | 可以做但延迟大 | 不能 |
| 灵活性 | 高,可调分压电阻和偏置 | 低,板上电路固定 | 低 |
| 适合场景 | 环境监测、组网采集 | 快速验证原型 | 路灯控制、夜灯 |
从功耗角度看,光敏电阻分压后,电阻会一直消耗电流。节点如果靠电池供电,要关注这个静态电流。一般做法是选阻值偏大的固定电阻,比如100KΩ到1MΩ之间,把分压支路的电流控制在微安级别,避免把电池过早耗干。
2.2 数据手册里必须先确认的四个参数
拿到CC2530的数据手册,做光敏采集前先看ADC这一章的四个参数,别等代码写完了才发现硬伤。
第一是ADC分辨率。CC2530的ADC是12位逐次逼近型,转换结果可以配置为7位、9位、10位或12位,选择12位时分辨率最高。对光照采集来说,12位在大部分场景下够用,不需要外接高精度ADC芯片。
第二是参考电压的来源。CC2530内部有AVDD引脚参考和内部1.25V参考两种选择,也可以使用AIN0引脚输入外部参考电压。这个选项在初始化ADC寄存器时就要定下来,因为它直接影响换算公式的系数。我习惯用内部参考电压,因为外部基准会更稳定,但很多低成本模块上并没有额外焊接基准源。
第三是ADC的输入阻抗。CC2530的ADC在采样阶段会有一个采样电容,如果信号源内阻太高,采样值会偏低。光敏电阻分压后的等效内阻在暗光环境下可能达到几百KΩ,这会直接导致ADC读数偏低,必须在采样前增加一个缓冲或者把采样时间拉长。
第四是采样时间配置。在ADCCON2寄存器里有采样时间设置项,可选择从第1个到第64个转换周期。信号源内阻越大,需要的采样时间越长,否则采出来的数值会抖得厉害。这四个参数的坑,后面在代码和避坑章节里逐步展开。
2.3 最小硬件接法:分压节点怎么选
一个最简单可靠的光敏采集节点,硬件接法如下:光敏电阻一端接AVDD,另一端接固定电阻,固定电阻的另一端接地。光敏电阻和固定电阻的中间抽头接到CC2530的ADC输入引脚,比如AIN0对应的P0.0口。
选择固定电阻阻值时有一个容易翻车的细节:如果光敏电阻在暗光下阻值达到500KΩ,而固定电阻只有10KΩ,那么抽头电压在暗光下会非常接近AVDD,ADC读数直接顶到满量程,中间光照变化全部被压缩;反过来如果固定电阻取1MΩ,强光下光敏电阻降到1KΩ,抽头电压几乎为0,强光区域的细节又丢了。所以固定电阻阻值最好取光敏电阻在“你最关心的光照范围中间值”附近的阻值,让分压输出在0.3V到2.8V之间变化,留出ADC的线性区间。
硬件接好之后,别忘了在ADC引脚上并联一个0.1μF的去耦电容,抑制电源纹波对采样的干扰。这个电容对后续减少读数跳变有很大帮助。
2.4 接线要点与电源注意事项
接线时有一个细节常被忽略:CC2530的ADC输入引脚最大电压不能超过AVDD。如果系统里用了12V适配器供电,或者光敏电阻分压点直接接到了高于3.3V的电源上,ADC引脚很容易被击穿。所以分压电路的供电一定要和CC2530使用同一个3.3V电源域,不要图方便从主供电直接拉过来。
电源域的问题不仅是选一个电源那么简单。如果系统里同时存在电机、继电器这类大电流负载,它们开关瞬间会在电源线上产生很大的电压波动,而这个波动会直接传递到ADC参考电压上,导致采样值突变。我一般在布局上把光敏电阻分压电路和无线射频部分的电源分别走线,中间串一个磁珠或者小电感做隔离,能明显改善信号质量。
3. 从零搭建工程:环境、协议栈与最小工程骨架
3.1 开发环境与协议栈版本怎么选
CC2530最常见也最省心的开发路径是使用TI官方的Z-Stack协议栈,配合IAR编译器。协议栈的版本选择有讲究:老版本协议栈对CC2530支持完善,资料多,但代码风格陈旧;新版本则往往需要配合更大容量的Flash芯片使用。我在多个项目里的习惯是,优先选择周边同行验证过的稳定组合,而不是盲目追最新。
如果你只是做光敏采集加ZigBee上报,不涉及ZigBee 3.0的复杂组网特性,那么找一套带SampleSwitch或GenericApp示例的Z-Stack版本就够用了。编译前要确认三件事:协议栈的芯片型号是否选择CC2530;IAR的编译器优化等级不要开太激进;以及编译配置里是否正确定义了协调器或终端节点的宏,这决定节点角色。
3.2 最小工程结构:哪些文件不要乱动
打开一个Z-Stack示例工程,你会在App层看到几个关键文件:主逻辑文件、事件处理文件、以及传感器采集相关的文件。做光敏采集,我的做法是在App层新增一个light_sensor.c文件,专门放ADC初始化和采样函数,然后在主循环的事件处理里调用。别去动MAC层和AF层的底层文件,除非你确认自己知道改动的影响范围。
工程里还有一个关键配置文件,里面定义了设备的PAN ID、信道、设备类型等参数。做节点调试时,先把PAN ID固定下来,避免两个实验网络互相发现造成玄学般的串组现象。信道建议先从默认信道开始,等节点数量多了再考虑信道分配问题。
3.3 ADC初始化的最小代码骨架
在应用层里初始化ADC,并不需要像裸机工程那样直接操作寄存器,但理解寄存器命名能帮你排查问题。下面这段代码是我常用的ADC初始化函数,适用于从ADC引脚读取模拟电压:
/* light_sensor.c - ADC初始化与单次采样 */ #include "hal_adc.h" #define LIGHT_ADC_PORT HAL_ADC_CHANNEL_0 /* P0.0 作为模拟输入 */ #define LIGHT_ADC_REF HAL_ADC_REF_AVDD /* 参考电压选AVDD */ #define LIGHT_ADC_RES HAL_ADC_RESOLUTION_12 uint16_t light_sensor_read(void) { uint16_t raw; // 这里的HAL函数会配置ADCCON寄存器并启动一次转换 raw = HalAdcRead(LIGHT_ADC_PORT, LIGHT_ADC_RES); return raw; }这段代码的逻辑简单直接:HalAdcRead一次完成通道选择、参考电压配置和采样转换。但要注意两点:第一,HalAdcRead返回的原始值是二进制补码格式,在输出前需要做格式转换;第二,这个函数是阻塞式的,调用的时候当前任务会被挂住,如果节点在实时性要求高的任务里使用,需要考虑采样时间和任务调度的关系。
参数上,参考电压选AVDD意味着换算公式里要用3.3V做基准。如果你把参考电压改成内部1.25V,那么同样的原始值代表的实际电压不同,公式也要跟着改。建议在校准文档里固定一套参数组合,不要今天用AVDD明天用内部参考。
3.4 把采样结果送到串口:先验证再组网
在把数据通过ZigBee发送之前,我强烈建议先通过串口把采样值打出来,验证硬件连接和ADC配置是否正确。下面是一段简单的串口打印逻辑:
/* 在main循环或事件处理中周期调用 */ void light_sensor_debug(void) { uint16_t raw = light_sensor_read(); uint16_t voltage = (uint32_t)raw * 3300 / 4095; /* 12位ADC,满量程4095 */ // 使用协议栈提供的printf重定向输出 printf("light raw=%d voltage=%dmV\n", raw, voltage); }串口输出正常后,你再去封装ZigBee无线发送才有意义。这一步很多人跳过了,直接改网络发送代码,结果数据到协调器那边怎么都不对,最后排查半天发现是ADC原始读数就不稳定。把验证分步走,能让后面的问题定位容易很多。
3.5 常见编译报错的快速定位
编译Z-Stack工程时,遇到最多的一类报错是找不到头文件路径。刚把工程从一台机器拷贝到另一台机器时,IAR的工程文件中往往保存着绝对路径,换机器后路径失效,编译器会报一堆“unable to open”错误。解决办法是在工程选项里检查预包含路径,把路径改成相对路径形式。
另一类高频报错是内存溢出。CC2530的RAM并不大,如果在应用层里声明了过大的静态缓冲区,链接阶段会报错。做光敏采集时采样数组不要开得太大,8次采样用局部数组就行,不要全局缓存上百个采样点。
还有一个容易被忽视的错误是中断优先级配置冲突。ADC中断和无线收发中断同时使用过高优先级时,可能导致协议栈断言失败。如果你在调试时用了ADC中断而不是轮询方式,需要仔细核对协议栈对中断优先级的要求。
4. 核心代码:电压换算、光照映射与滤波
4.1 分压电路的反推公式与边界计算
拿到ADC原始值之后,第一步是把这个值还原成分压点的电压。如果参考电压是AVDD,也就是3.3V,那么原始值和电压的关系是:
电压 = 原始ADC值 × 3300mV / 4095
但光敏电阻的阻值和电压不是线性关系,阻值和光照强度也不是线性关系。假设分压电路是光敏电阻在上、固定电阻在下,那么分压点电压Vout = VCC × R固定 / (R光敏 + R固定)。知道Vout后,可以反推R光敏的阻值:
R光敏 = VCC × R固定 / Vout - R固定
然后再通过R光敏查光敏电阻的阻值-光照特性曲线,得到具体的光照强度。这一步是许多教程里被跳过的环节——很多代码直接把ADC值当作光照强度上报,这在数据含义上是错误的,后续做数据分析和阈值判断都会出问题。
光敏电阻的阻值-光照曲线通常是一条双对数关系:光照强度每增大一个数量级,阻值大约按比例下降。要把这段曲线放进单片机,常见做法是对曲线做对数拟合,得到公式后把乘除和log计算换成查表插值,因为8051内核做浮点log运算比较慢。
4.2 平均滤波:为什么简单平均值不够
ADC采样得到原始值后,第一个问题是:要不要滤波?我的经验是必须做,但平均值滤波未必是最佳选择。光照变化本身缓慢,但电源噪声、工频干扰和传感器自身抖动都会叠加在信号上,只用一次采样就上报,数据曲线像锯齿一样难看。
常见的滤波方案有三种。第一种是多次采样取平均值,比如连续采8次或16次,直接求算术平均。这个方案简单,但对偶尔出现的尖峰异常值很敏感,一次外部干扰就能把平均值拉偏。
第二种是排序后取中位数。中位数滤波对尖峰噪声有很好的抑制作用,适合光照这类变化缓慢的信号。缺点是排序需要消耗一定运算时间,8次采样排序完全能接受,64次就会明显拖慢任务。
第三种是在时间域上做低通滤波,也叫滑动平均或一阶惯性滤波,公式是当前输出 = α × 当前采样 + (1-α) × 上一次输出。这个方案响应平滑,但会引入相位延迟,光照突变时数据反应慢。
我自己的做法是:8次采样取中位数,再做一步惯性滤波,两者配合。下面的代码展示了一个中位数加惯性滤波的混合方案。
/* 中位数滤波加惯性滤波的示例 */ #define SAMPLE_TIMES 8 static uint16_t last_smooth = 0; static uint16_t med_filter(uint16_t *buf, uint8_t len) { uint8_t i, j; uint16_t tmp; // 简单冒泡排序求中位数,len小的时候性能可接受 for (i = 0; i < len - 1; i++) { for (j = i + 1; j < len; j++) { if (buf[j] < buf[i]) { tmp = buf[i]; buf[i] = buf[j]; buf[j] = tmp; } } } return buf[len / 2]; } uint16_t light_sensor_smooth(void) { uint16_t sample[SAMPLE_TIMES]; uint16_t median; uint16_t smooth; uint8_t i; for (i = 0; i < SAMPLE_TIMES; i++) { sample[i] = light_sensor_read(); } median = med_filter(sample, SAMPLE_TIMES); // 一阶惯性滤波,α取0.7时响应和稳定性的折中 smooth = (uint16_t)(median * 0.7 + last_smooth * 0.3); last_smooth = smooth; return smooth; }这段代码里,中位数部分通过排序去掉异常值,避免单次干扰影响结果;惯性滤波部分让数据在时间序列上平滑过渡。α取0.7是一个经验值,光照响应快但噪声抑制稍弱;如果你发现数据仍然抖动,可以把α降到0.5,代价是光照变化的响应会变慢。调整α需要在现场观察数据曲线的波动情况来决定,不存在一个通吃的值。
我没有把浮点乘法直接写进单片机长期运行,而是在实际代码里把α设为整数定标,比如0.7表示成7,计算时用先乘后除的方式避免8051内核处理浮点数的开销。这一点在长期运行的节点上会体现为功耗和稳定性的差异。
4.3 光照强度的标定与查表映射
要让节点上报的数据单位是勒克斯(lux),需要做标定。标定的方法有两种。
一种是用照度计作为标准设备,让节点和照度计并排放在同一光照环境下,记录ADC原始值和照度计读数的对应关系,从暗到亮采集十几组数据,最后拟合成一条曲线,把曲线离散化成查找表烧进Flash。这个方案准确度最高,适合项目要求光照数据可以跟标准仪器对比的场景。
另一种是直接用光敏电阻数据手册提供的典型曲线。许多光敏电阻厂商会在手册里给出不同照度下的典型阻值表,你可以按这个表格做映射,省去现场标定。但实际效果受光源色温、环境温度影响较大,准确度会打折扣,适合对精度要求不高的项目。
下面是一个简化的查表映射函数的设计思路:
/* 查表法:将ADC值映射到光照等级或近似lux */ /* 实际使用时需要根据你的分压参数重新标定 */ typedef struct { uint16_t adc_value; uint16_t lux_value; } light_map_t; static const light_map_t light_table[] = { { 3800, 10 }, /* 暗光,约10 lux */ { 3000, 50 }, { 2000, 200 }, { 1000, 800 }, { 300, 3000}, /* 强光,约3000 lux */ }; uint16_t light_sensor_to_lux(uint16_t adc) { uint8_t i; /* 查表区间并做线性插值 */ for (i = 0; i < (sizeof(light_table) / sizeof(light_table[0])) - 1; i++) { if (adc >= light_table[i].adc_value && adc < light_table[i + 1].adc_value) { uint32_t span = light_table[i].adc_value - light_table[i + 1].adc_value; uint32_t lux_span = light_table[i + 1].lux_value - light_table[i].lux_value; uint32_t offset = adc - light_table[i].adc_value; return light_table[i].lux_value + (uint16_t)((lux_span * offset) / span); } } return (adc >= light_table[0].adc_value) ? light_table[0].lux_value : light_table[sizeof(light_table) / sizeof(light_table[0]) - 1].lux_value; }这段查表代码的思路是:把ADC值和lux的对应关系固化成一张表,运行时用线性插值方式在两个相邻标定点之间计算。这样做避免了浮点运算,也避免了复杂的对数函数,8051内核跑起来压力小很多。表的数量和间距取决于你实际需要的光照分辨率,如果要做精细的晨昏判断,表的前半段要加密;如果只是区分晴天阴天夜晚,几百个lux一档就够了。
最让我深刻的经验是:不要直接抄别人板子上的标定系数。光敏电阻型号、分压电阻阻值和参考电压的微小差异,会让同一份标定表在同一批板子上出现不同读数。换上自己的硬件,务必重新标定一次,哪怕只是用手机上的照度App配合简单光源快速过一遍,也比直接抄来的表可靠得多。
4.4 通过ZigBee上报:数据帧格式别拍脑袋
光敏数据准备好了,接下来要决定怎么通过ZigBee发出去。最稳健的做法是在App层自定义一个简单的事件数据帧,带上网络短地址、传感器类型、读数和电池电压。不要为了省几个字节把数据帧格式设计得过于紧凑,否则后期加字段会非常痛苦。
/* ZigBee应用层数据帧示例,使用AF_DataRequest发送 */ void light_sensor_send(void) { uint8_t buf[8]; uint16_t raw = light_sensor_smooth(); uint16_t lux = light_sensor_to_lux(raw); buf[0] = 0x01; /* 帧类型:光敏数据 */ buf[1] = LO_UINT16(lux); buf[2] = HI_UINT16(lux); buf[3] = LO_UINT16(raw); buf[4] = HI_UINT16(raw); /* 发送到协调器,使用端点的同一个短地址 */ AF_DataRequest(&light_sensor_dstAddr, &light_sensor_epDesc, SENSOR_REPORT_ID, sizeof(buf), buf, &light_sensor_send_seq, AF_DISCV_ROUTE, AF_DEFAULT_RADIUS); }这里的数据帧是应用层的自定义格式,上层可以按帧类型解析。注意发送前确认目标短地址和端点描述符是否正确配置,否则数据发出去收不到。调试时最容易犯的错误是在不同节点上使用了不同的端点号,导致协调器过滤掉数据帧。
发送周期也要根据光照变化特点设置。室内光照变化慢,1分钟一次足够;户外光照变化快一些,10秒到30秒一次也够了。太频繁的发送会消耗节点电量,而且在ZigBee网络里会让路由器频繁转发,影响整个网络寿命。
5. CC2530光敏采集避坑记录:现象、原因与解决
5.1 读数一直跳变,采样值前后差异几百
现象:串口打印的ADC值忽高忽低,相邻两次采样差几百,接入ZigBee网络后数据波动更大。
原因:最常见的三个根源。第一是ADC引脚悬空或者没有去耦电容,引脚受环境噪声干扰严重;第二是采样时间配置太短,信号源内阻偏高时采样电容没有充满;第三是参考电压选了AVDD,但电源本身纹波大,参考电压也在跟着抖动。
解决:先在ADC引脚并联0.1μF到1μF的电容,再把ADCCON2的采样时间配置到最长的64个周期,同时检查电源模块的输出纹波。如果用的是电池供电,可以加一个10μF钽电容和0.1μF陶瓷电容组合做电源去耦。按顺序做完这三步,读数跳变基本能压到几十个数值以内。
5.2 低功耗模式唤醒后,ADC读数直接变成满量程
现象:节点平时休眠省电,定时唤醒后读取光敏值,发现读数一直顶着4095,但复位之后读数恢复正常。
原因:这是典型的外设初始化不完整问题。低功耗模式下ADC模块处于关闭状态,唤醒后没有重新初始化ADC通道和参考电压,采样电容也没有充电,读出来的值自然不对。协议栈的休眠唤醒回调函数里,很多开发者只恢复了无线模块,忽略了外设的重新初始化。
解决:在唤醒回调函数中加入ADC的初始化调用,确保每次唤醒后重新配置参考电压、采样时间和通道。我习惯写一个light_sensor_power_on()函数,把ADC相关寄存器配置全部重新写一遍,而不是依赖于上电时的初始状态。这个函数在复位后的主流程里也调用一次,保证两条路径行为一致。
5.3 串口测试没问题,但协调器收到的数据少一半
现象:节点串口上打印的数据正常,通过ZigBee发送的数据丢包严重,协调器收到的包不完整或者干脆只有零星几包。
原因:很大概率是发送缓冲区处理不当。串口打印时数据直接发送,不占网络缓冲区;但ZigBee的AF_DataRequest是排队发送的,如果你在事件处理循环里连续高频调用发送函数,会把协议栈的发送缓冲队列占满,后续的包直接被丢弃。另外,终端节点发送时如果父节点正处于休眠状态,也会导致发送失败。
解决:发送前检查上一次请求是否已完成,可以通过跟踪序列号或者使用发送完成回调来抑制重入。建议把发送周期拉长到5秒以上,并且在发送失败时不要立刻重发,而是延迟几秒再重试。对于低功耗终端节点,还要确认父节点的休眠策略不会拦截你的数据。
5.4 批量生产的板子,读数一致性差
现象:同一批板子放在同一个光照条件下,读出的lux值各不相同,偏差甚至超过20%。
原因:光敏电阻本身离散性就很大,同一批次的不同器件在同一照度下阻值差别可以有15%以上。如果分压电阻用的是精度为5%的普通电阻,误差会叠加上去;再加上每颗芯片参考电压的微小差异,最终表现出来的就是批量读数分散。
解决:在硬件设计阶段把分压电阻换成1%精度的金属膜电阻,并且要求光敏电阻供应商提供同一批次的阻值分档数据。批量生产时,在产线上增加一道简单标定工序:用一个标准光源照射产品,读取原始值,把这个值作为校准系数烧写到每台设备的Flash里,之后软件读取时不直接使用ADC值,而是乘以校准系数去修正偏差。
提示:产线标定系数一旦写入Flash,后续更换光敏电阻后要重新标定,不要沿用旧系数。
这一道产线工序看上去增加了时间成本,但它能救回整批货的一致性。没有这道工序的话,数据到了平台侧还得再靠软件做二次修正,麻烦得多。我的原则是:能用硬件和产线解决的问题,不要留到软件里猜。
5.5 协调器收不到数据但串口显示发送正常
现象:节点串口打印的发送函数已经执行,返回值显示成功,但协调器端收不到任何光敏数据包,或者只能在复位后收到一两包。
原因:这种现象往往不是发送路径的问题,而是地址和端点配置不一致。发送函数的调用者认为目标的短地址是协调器的0x0000,但实际编译到固件里的短地址是一个随机值,因为协调器在组网时可能没有使用预配置的短地址。另一种常见原因是应用层的端点描述符在协调器和终端节点的定义不同,协调器过滤数据时把包丢弃了。
解决:先在调试串口上把目标短地址和端点号打印出来,和协调器端实际显示的邻居短地址做比对。如果用协调器启动的静态PAN,可以在协议栈配置里为协调器指定短地址0x0000,并在终端节点发送时固定使用这个地址。端点号则要求两边使用同一个数值,不要在某个节点上为了凑内存改了端点ID。
6. 进阶技巧:让CC2530光照数据更可信的三个做法
先说说定时采样里的一个细节。光照数据缓慢变化,但你想兼顾响应速度与功耗,可以设置一个1秒的定时器事件,在事件处理函数里判断距离上次发送是否超过设定周期,未到期就只采样不发送。这种做法比简单地在主循环里delay要合理得多,节点可以做别的事情,功耗也更低。
另一个技巧是多通道数据联合使用。我在做某农业大棚项目时,发现单纯的lux值容易因遮挡和污损产生错误判断。后来在同一个节点上加入了一个备用光敏电阻,放在不同朝向,上报时同时发送两个通道的值。平台侧看到两路值同时大幅变化才判定为真实光照变化,一路异常另一路正常就标记为可疑数据。这个思路不复杂,但能让数据可信度上一个台阶。
最后说一个校准习惯:在现场把节点架好之后,不要急着收数据。用手机上的光照App或者手持照度计,放在节点旁边记录当前光照值,同时让节点打印原始ADC值,记录下来。这样每个点位都有了一组对照数据,后期数据回溯时这些记录特别有用。我一般会在每批节点安装完成后留出一小时做对照记录,并把记录表格存档。
这些看起来不起眼的习惯,在实际运行中帮我排掉了不少隐蔽故障。有一次某台节点上报的数据持续偏高,翻对照记录发现那台节点安装位置的附近后来加装了一盏补光灯,光照环境实际上确实变了。没有对照记录的时候,这种问题往往会让开发者怀疑是传感器坏了,直接走弯路。希望这篇关于CC2530光敏传感器代码的拆解,能让你在动手做的时候少踩一些我踩过的坑,也帮你把光照采集这件事做成可信赖的功能模块。
本文还有配套的精品资源,点击获取