
简介一份基于TI CC2530与光敏传感器的无线光照采集工程面向物联网、嵌入式及Zigbee无线传感网初学者用于掌握传感器驱动、数据采集与无线组网技能。项目以IAR为开发环境结合ZStack-2.5.1a协议栈完整实现光敏电阻信号调理、A/D转换、数字滤波及Zigbee组网通信覆盖初始化配置、参考电压与采样通道选择、定时触发转换再到终端节点发送、路由器/协调器接收的完整数据链路。压缩包共1064个文件约14.66MB包含182个C源码、170个头文件以及众多IAR工程文件ewp/ewd、编译中间文件和hex固件可直接导入工程进行编译烧录与二次开发。工程内附路由与协调器代码、Zigbee协议栈配置及调试辅助文件有助于理解网络建立、节点入网、数据路由与透传等关键环节。目前已有4530人学习下载代码结构清晰、注释明确是从外设驱动到无线应用的一体化参考范例。1. 项目定位CC2530 光敏传感器代码能干什么假设你手头有一块基于 CC2530 的 ZigBee 节点板想把它做成一个能感知环境光照度的传感器节点却卡在 ADC 采样不稳定、数据怎么塞进 ZigBee 帧、协调器收不到数据这一连串问题上。这份「基于CC2530的光敏传感器代码」就是用来解决这类落地问题的它把光敏电阻或光敏二极管模块接到 CC2530 的 ADC 通道完成电压采集、滤波、阈值判断再通过 ZigBee 协议栈把光照数据周期性地上报给协调器。对于做智能家居、农业大棚光照监测、实验室环境记录的同学来说这套代码可以直接移植到自己的节点上改几个引脚和参数就能跑起来不用从零啃协议栈源码。整个工程基于 TI 的 Z-Stack 协议栈代码风格偏向实际项目中的裸机与协议栈混合写法ADC 部分用寄存器操作无线上报部分走标准端点与簇接口。它既不是那种只有main里点个灯的玩具例程也不是把你困在某款开发板里的封闭工程而是把「采集 - 处理 - 上报」这条链路完整打通了。对新手来说照着烧录就能看到串口输出对熟练的开发者来说可以快速把光敏通道替换成其他模拟量传感器或者把上报帧改成自己定义的格式。最值得看的是中间那层数据处理光敏电阻的模拟电压随光照变化但直接读 ADC 结果往往抖动得让人怀疑人生这套代码里做了多次采样均值滤波并且给出了光照等级换算的参考公式让你拿到的不只是原始 ADC 值而是可以直接使用的环境光照量。接下来我会从硬件电路讲起再把代码拆开看最后带你把工程跑起来并分享几个实际调试中容易踩的坑。2. 硬件与协议栈在 CC2530 上采集光照度的基础设计2.1 CC2530 的 ADC 与光敏电阻分压电路CC2530 内部集成了一个 12 位 ADC支持 8 个输入通道其中AIN0到AIN7对应 P0_0 到 P0_7 引脚。光敏传感器模块的常见接法是光敏电阻串联一个固定电阻构成分压电路把光照变化转化为电压变化然后把这个电压送入 ADC 输入引脚。比如我常用的接法光敏电阻一端接 VDD另一端接 10kΩ 下拉电阻到 GND中间抽头接到 P0_6也就是AIN6通道。光照增强时光敏电阻阻值下降P0_6 电压升高光照减弱时电压降低ADC 读数线性反映这个电压。分压电阻的取值很关键。如果选 10kΩ在典型室内光照几百 Lux下光敏电阻阻值大约在 5kΩ 到 20kΩ 之间变化输出电压幅度能覆盖 ADC 量程的中段不会出现低照度时读数被噪声吞掉、高照度时饱和的情况。若你的光照范围特别窄可以把分压电阻换成 100kΩ 可调电阻先跑一轮实测把中间电压调到接近 1.5V这样左右动态范围都留足余量。在代码里ADC 配置主要涉及几个寄存器ADCCON1控制转换触发方式和采样结束标志ADCCON2选择参考电压、采样位数和输入通道ADCCON3在单次转换模式下指定起始通道。这套代码使用单次转换模式参考电压选择AVDD5内部电源12 位分辨率启动方式为手动ADCCON1 | 0x40。需要注意CC2530 的 ADC 参考电压可选项包括内部 1.25V、AVDD5 和外部参考。对光敏电阻分压电路一般分压输出电压不会超过 3V所以选 AVDD5 足够直接用读到的电压值反推光照时也更直观。2.2 ZigBee 协议栈中传感器节点的角色划分在一套典型的 ZigBee 网络中有协调器Coordinator、路由器Router和终端设备End Device三类角色。光敏传感器节点通常作为终端设备或路由器节点存在。如果节点固定位置、需要经常休眠省电那就配置成终端设备如果节点还需要为其他节点转发数据那就配置成路由器。CC2530 的 Z-Stack 工程里这个角色是在f8wConfig.cfg文件中通过ZDO_COORDINATOR、RTR_NWK等宏定义来决定的。这套光敏传感器代码默认配置为终端设备适合电池供电的独立节点。协议栈中最核心的抽象是「端点Endpoint」和「簇Cluster」。每个设备可以有多个端点每个端点对应一组应用描述。光敏传感器通常在端点 8 上注册一个输入簇比如用自定义簇 ID0xFF01或者直接使用标准 HA 配置里的「占用Occupancy」类簇。代码里发送数据时会调用AF_DataRequest()函数把待发送的光照数据打包成afDataConfirm_t格式然后通过 APS 层发往协调器。理解这个流程不需要把整个 Z-Tack 源码读完只需要知道传感器采集的数字量必须放进一个afAckRequest_t结构体的cmd.Data数组里指定目标地址和端点协议栈就会自动完成后续封包和路由。从工程文件结构来看这套代码不是单个.c文件而是完整的 IAR 工程包含ZMain、Components、Projects、Source等目录。其中Source目录下有几个关键文件SensorApp.c、SensorApp.h、OSAL_Sensor.c和ADC_Sensor.c。OSAL_Sensor.c是协议栈操作系统的任务注册入口在osalInitTasks()里添加了光敏传感器任务初始化函数SensorApp.c里则处理周期采样事件和无线数据发送。下面我先把目录结构列出来然后进入核心代码讲解。2.3 代码目录结构从裸机驱动到协议栈应用层拿到这份代码后首先看到的不是一堆散落的.c文件而是一个完整可导入的 IAR for 8051 工程目录。顶层目录大致如下CC2530_LightSensor_Project/ ├── Components/ │ ├── hal/ │ │ ├── target/ │ │ │ └── CC2530EB/ │ │ │ ├── hal_adc.c │ │ │ └── hal_adc.h │ │ ├── include/ │ │ └── common/ │ ├── stack/ │ │ ├── af/ │ │ ├── nwk/ │ │ ├── sapi/ │ │ └── zcl/ │ └── osal/ │ ├── common/ │ └── include/ ├── Projects/ │ ├── zstack/ │ │ └── Samples/ │ │ └── LightSensor/ │ │ ├── CC2530DB/ │ │ │ ├── LightSensor.eww │ │ │ ├── LightSensor.ewp │ │ │ └── f8wConfig.cfg │ │ └── Source/ │ │ ├── SensorApp.c │ │ ├── SensorApp.h │ │ ├── OSAL_Sensor.c │ │ └── ADC_Sensor.c └── Tools/ └── 烧录脚本/这里的hal_adc.c是 TI 底层库提供的 ADC 驱动但这份代码里的ADC_Sensor.c没有直接调用它而是选择直接操作寄存器这样更容易控制采样时序也方便把光敏传感器的采样逻辑独立出来。Projects/zstack/Samples/LightSensor/CC2530DB下是真正的工程文件用 IAR 打开.eww就能加载整个 workspace。工程中定义了一个周期性事件SENSOR_SAMPLE_EVT当 OSAL 系统启动后应用层事件处理函数会周期性进入该事件分支触发一次 ADC 采样然后经过滤波和数据处理最后调用AF_DataRequest()上报。这个设计把「硬件采样」和「无线协议」完全解耦ADC_Sensor.c只负责拿到数值SensorApp.c只负责把数值变成 ZigBee 报文。后面你要换传感器只需要替换ADC_Sensor.c内部的采集函数即可应用层代码几乎不用动。3. 核心模块拆解光敏采集、滤波与上报链路3.1 光敏电阻电压读取ADC 采样配置与寄存器操作在ADC_Sensor.c里光敏采样的入口函数是Sensor_ReadLightADC()它负责完成所有寄存器配置并返回原始 ADC 结果。CC2530 的 ADC 有两条路径一条是单次转换一条是连续转换。这里用的是单次转换好处是功耗低、逻辑简单适合传感器周期性唤醒的场景。代码直接抄了底层的启动流程但把通道和参考电压的配置抽成了可改参数方便换引脚。#include ioCC2530.h #define LIGHT_SENSOR_ADC_CH 6 // P0_6 对应 AIN6 #define ADC_REF_AVDD5 0x00 // 参考电压选择 AVDD5 #define ADC_12_BIT 0x08 // 12位分辨率 unsigned int Sensor_ReadLightADC(void) { unsigned int adcValue 0; // 1. 配置 ADC 通道、分辨率、参考电压 ADCCON2 (ADC_12_BIT | ADC_REF_AVDD5 | LIGHT_SENSOR_ADC_CH); // 2. 触发单次转换 ADCCON1 | 0x40; // 3. 等待转换完成 while (!(ADCCON1 0x80)); // 4. 读取 12 位结果高 8 位在 ADCL低 8 位在 ADCH adcValue (ADCL 2) | ((unsigned int)ADCH 6); return adcValue; }逻辑说明第 1 步把ADCCON2按位组合ADC_12_BIT这个值 0x08 实际上对应的是ADCCON2中SHDIV字段里表示 12 位模式的位置以及启动转换时需要的额外配置。第 2 步通过置ADCCON1的 bit6 来启动一次转换。第 3 步循环等待ADCCON1的 bit7 变为 1表示采样完成。第 4 步是 CC2530 特有的结果拼接方式ADCL里低 2 位是无效位所以右移两位丢弃ADCH是结果的高 8 位左移 6 位拼到adcValue的 bit6 到 bit13最终得到 0~4095 的 12 位无符号数。参数说明LIGHT_SENSOR_ADC_CH决定从哪个引脚采集如果你把光敏模块接到了 P0_0就把这个值改成 0。ADC_REF_AVDD5选择参考电压为 3.3VAVDD5 实际就是芯片工作电压。对 12 位分辨率来说adcValue / 4096 * 3.3V就是光敏电阻分压点的电压值。这段代码没有做多次采样因为单次采样可能带上电源噪声所以我们在下一节用软件滤波来处理。3.2 数字滤波均值滤波与阈值映射直接读回来的 ADC 值通常不稳定尤其是用电池供电或者附近有电机、继电器干扰时数值会在 ±50 范围内乱跳。这套代码在SensorApp.c里实现了一个简单的滑动均值滤波连续采样 5 次去掉最大值和最小值然后对剩余 3 次求平均。这个思路比简单平均抗尖峰干扰又比卡尔曼滤波简单得多适合 MCU 资源有限的场景。#define SAMPLE_COUNT 5 unsigned int Sensor_FilterLightADC(void) { unsigned int raw[SAMPLE_COUNT]; unsigned int sum 0; unsigned int min, max; unsigned char i; // 连续采集 SAMPLE_COUNT 次 for (i 0; i SAMPLE_COUNT; i) { raw[i] Sensor_ReadLightADC(); // 增加一点延时让 ADC 内部电容充分放电 asm(NOP); } // 找最大值和最小值 min raw[0]; max raw[0]; for (i 1; i SAMPLE_COUNT; i) { if (raw[i] min) min raw[i]; if (raw[i] max) max raw[i]; } // 跳过最大最小值求和 sum 0; for (i 0; i SAMPLE_COUNT; i) { if (raw[i] ! min raw[i] ! max) { sum raw[i]; } } // 返回平均中间值个数是 3 return sum / (SAMPLE_COUNT - 2); }逻辑说明第一次循环里连读 5 次 ADC每次读完后插入一个 NOP 空指令目的是给 ADC 采样保持电容一点时间避免连续读时通道切换带来的串扰。第二次循环找出最大和最小值第三个循环求和时跳过这两个极端值最后除以 3得到的结果就比单次采样的稳定很多。这个滤波方法在光照缓慢变化的场景下不会丢失实时性因为光照本身就不是快速变化的量5 次采样消耗的总时间不到 1ms对 ZigBee 上报周期来说影响可以忽略。拿到滤波后的 ADC 值下一步是映射成光照等级。常见做法是把 ADC 值按电压线性映射成 0~100 的百分比光照强度因为光敏电阻本身就是非线性元件精确计算 Lux 需要标定曲线。代码里给了一个实用的映射函数unsigned char Sensor_CalcLightLevel(unsigned int adcValue) { unsigned int volt_mv; unsigned char level; // 参考电压 3.3V12 位分辨率换算成 mV volt_mv (unsigned long)adcValue * 3300 / 4096; // 分压关系VLux VDD * R2 / (R1 R2)根据实测范围定阈值 if (volt_mv 500) level 0; // 黑暗 else if (volt_mv 1200) level 1; // 弱光 else if (volt_mv 2200) level 2; // 一般室内 else level 3; // 明亮 return level; }这里我把光照分成了 4 档阈值对应的电压值是我在某次做模拟项目X时按 10kΩ 分压电阻实测来的室内关灯时电压约 300mV开台灯时约 800mV正常日光灯下约 1800mV靠窗白天能到 2800mV 以上。你移植到不同环境后可以用串口把volt_mv打印出来根据自己的场景重新划分档位。如果你要更细腻的连续值就不要用等级直接把volt_mv或 adcValue 放到数据帧里上报即可。3.3 无线上报基于 ZigBee 协议栈的端点与簇设计光敏数据处理好之后要交给协议栈发送。SensorApp.c里用了一个自定义事件SENSOR_REPORT_EVT来周期触发上报。之所以不放在hal层直接调串口是想让这个节点能真正成为一个独立的 ZigBee 设备。下面这段是上报的核心函数static void Sensor_SendReport(void) { afAddrType_t dstAddr; uint8 lightData[2]; // 组装上报数据高字节为光照等级低字节为 ADC 原始值高 8 位 lightData[0] Sensor_GetLightLevel(); lightData[1] (uint8)(Sensor_FilterLightADC() 4); // 目标地址协调器端点的单播地址 dstAddr.addrMode (afAddrMode_t)Addr16Bit; dstAddr.addr.shortAddr 0x0000; // 协调器的短地址 dstAddr.endpoint 0x08; // 协调器上对应的端点 // 调用协议栈发送接口 AF_DataRequest(dstAddr, SensorTaskDesc, SENSOR_LIGHT_CLUSTERID, 2, lightData, NULL, Sensor_AckCallback, AF_DISCV_ROUTE); }逻辑说明dstAddr是协议栈定义的地址结构体Addr16Bit表示使用 16 位短地址寻址0x0000是协调器固定短地址。endpoint设为 8两边都要一致。SENSOR_LIGHT_CLUSTERID是我在头文件里定义的 0xFF01用于区分不同数据类型。AF_DataRequest的参数依次是目标地址、任务描述符、簇 ID、数据长度、数据指针、确认序号NULL 表示不申请重传、回调函数、路由发现选项。发送完成后结果会通过Sensor_AckCallback回调返回你可以在这个回调里检查状态是不是afStatus_SUCCESS。关于数据帧格式这个设计把光照等级和 ADC 高位分开一个字节存等级方便协调器直接做逻辑判断另一个字节存 ADC 值方便上位机复现原始电压。如果你要发送完整 12 位 ADC 值通常要拆成两个字节并在帧头放一个标识。这版代码为了简练只发了高 8 位但保留了扩展思路。所有上报逻辑都由 OSAL 的事件驱动。在OSAL_Sensor.c的任务初始化里启动了一个定时器void Sensor_TaskInit(void) { // 注册应用层事件处理函数 SensorTaskId osal_set_event(Sensor_TaskID, SENSOR_REPORT_EVT); }然后在事件回调里每 10 秒触发一次上报。周期修改也很简单在SensorApp.c的Sensor_ProcessEvent函数里重新调用osal_start_timerEx即可。后续改造我会专门讲怎么改成 1 秒或 30 秒。4. 环境搭建与运行把代码烧进 CC2530 的实际步骤4.1 IAR 工程导入与编译器配置这套代码是标准的 IAR for 8051 工程不是 Keil 或 GCC 工程。你需要安装 IAR Embedded Workbench for 8051版本建议 8.10 以上对 CC2530 支持比较好。拿到源码后先用 IAR 打开Projects/zstack/Samples/LightSensor/CC2530DB/LightSensor.eww。如果打开后编译报错最常见原因是工程里的芯片型号没选对需要在 Project - Options - General Options - Target 里选择Chip: CC2530F256。接着检查编译器优化等级。Z-Stack 官方例程里默认优化等级是High - Speed但光敏传感器代码里涉及寄存器操作如果开启高优化可能把ADCCON1的判断语句优化掉导致代码停在 while 循环里。我一般把这个选项改成Low或Medium稳定性优先。具体位置Options - C/C Compiler - Optimizations - Level选Low。还要注意一个坑f8wConfig.cfg文件里配置了协调器、路由器、终端设备的编译选项。如果你要烧录的是终端节点确保宏定义ZDO_COORDINATOR没有被启用否则节点会试图建网。工程里已经把这部分配置好了但你自己新建工程时经常会遇到「节点一直无法加入网络」的问题原因就在这里。4.2 烧录工具与仿真器连接烧录 CC2530 有三种常见方式通过 TI 的 SmartRF04EB 仿真器、通过 CC Debugger或者通过串口 Bootloader。这套代码默认走仿真器下载因为编译完成后可以直接用 IAR 的 Debug 按钮下载。接线很简单仿真器的 10 针 JTAG 接口对接到开发板的 P0_2DD、P0_3DC和复位脚。注意不要接反有的板子丝印是 DC 在上有的是 DD 在上我第一次接反时直接把仿真器保护熔丝烧了血泪经验。如果你的板子上没有 JTAG 接口只有串口那就需要先烧一版 Bootloader。但大多数 CC2530 模块都会预留 JTAG所以直接用仿真器最稳妥。烧录时 IAR 会提示选择仿真器型号选TI CC2530后如果下载失败先检查仿真器驱动是否安装通常用TI SmartRF Flash Programmer测试一下能不能识别芯片。4.3 串口调试与协调器端数据验证这套代码的终端节点本身没有串口输出因为我们默认它通过 ZigBee 无线上报。要验证数据你需要一个协调器节点负责接收并打印数据。最简单的验证方法用一个 CC2530 协调器开发板烧录 TI 官方例程GenericApp或者任何能打印接收数据的代码把串口接到电脑波特率设为 115200然后上电组网。给光敏传感器节点供电后它会自动扫描信道并加入协调器所在的网络。加入成功后协调器端的串口会看到类似Light Level: 2, Raw: 0x045A的打印信息。如果你手头没有协调器代码也可以暂时把光敏节点的AF_DataRequest注释掉改成调用二维码串口打印函数比如HAL_UART_WriteString(Light Level:...)先验证采集部分再验证无线部分。这样分步调试出问题时更容易定位。下面我给出一个常见的手动触发采样调试方法在SensorApp.c的按键事件里每次按 SW1 键强制调用一次Sensor_SendReport()这样即使没等到上报周期也能立刻看到数据。具体在按键回调里加上if (keys HAL_KEY_SW1) { Sensor_SendReport(); }这个简单改动在调试时非常实用相当于给无线上报增加了一个手动触发开关不用傻等那 10 秒。5. 避坑与常见问题排查我在 CC2530 光敏项目里踩过的坑5.1 读到的 ADC 值一直是 4095 或 0现象光敏传感器在正常光照下读回来的原始 ADC 值不是 4095 就是 0完全没有中间值。不管怎么改变光照结果都不变。原因这个问题几乎都是引脚配置错误导致的。CC2530 的 P0 引脚既可以做 GPIO也可以做模拟输入默认状态下是 GPIO 功能。如果不把引脚寄存器P0SEL对应位置为 1ADC 根本采集不到外部信号读出来的结果就可能是电源电压或者 0。另外一个常见原因是分压电路没共地或者光敏电阻两端接反导致电压直接落在 VDD 或 GND 上。解决在Sensor_ReadLightADC()开头加上引脚模拟功能配置P0SEL | (1 LIGHT_SENSOR_ADC_CH); // 配置为外设功能ADC输入同时用万用表测一下你接的那个抽头电压确认电压在 0.1V~3.0V 之间随光照变化。如果电压不变检查光敏电阻是不是焊错位置了。5.2 光照强度突变但上报数据没变化现象拿手电筒直接照光敏电阻串口打印的光照等级仍然停留在 2 或 3要过好几秒才反应过来甚至一直不变。原因这套代码里的均值滤波函数Sensor_FilterLightADC()没有考虑瞬态响应。如果你在 10 秒上报周期里只调用一次滤波每次滤波内部连续采 5 次但光照可能在两次采样间已经跳变。另外如果SENSOR_SAMPLE_EVT事件里没有及时重新启动定时器上报周期会变得越来越长看起来就是数据不更新。解决在事件处理函数里每次上报结束后必须重新osal_start_timerEx启动下一次事件。正确的写法是if (events SENSOR_REPORT_EVT) { Sensor_SendReport(); // 重新启动10秒定时器 osal_start_timerEx(Sensor_TaskID, SENSOR_REPORT_EVT, 10000); return (events ^ SENSOR_REPORT_EVT); }如果你需要更快响应可以把Sensor_FilterLightADC()的连续采样次数减少到 3 次并把上报周期改成 2 秒。光照传感器本身不需要 1ms 级响应2 秒足够感知大部分环境变化。5.3 ZigBee 组网后数据收不到现象节点和协调器指示灯都表明已经入网但协调器串口就是打印不出光敏数据。节点上的AF_DataRequest返回状态一直是afStatus_NO_ROUTE或者afStatus_FAILED。原因大多数情况下是端点号不匹配。协调器如果只注册了端点 1而光敏节点发往端点 8数据会被协议栈丢弃。其次可能是簇 ID 不一致协调器端没有注册SENSOR_LIGHT_CLUSTERID这个簇应用层不会把数据交给用户处理。再有就是源地址模式搞错了有的协调器程序用的组播地址而光敏节点发的是单播短地址。解决先抓包看节点是否真的发出了数据帧。如果你没有 USB 抓包器就在协调器的接收回调里打印所有收到的未知端点数据临时改成广播发送试试// 临时将目标地址改为广播 dstAddr.addrMode (afAddrMode_t)AddrBroadcast; dstAddr.addr.shortAddr 0xFFFF;这样做能确认协议栈本身通不通。如果广播能收到数据说明问题在单播路由或端点配置再回头核对两边的端点号和簇 ID。之前我做某跨平台系统时就是因为协调器工程里默认端点号是 1而我自己定的 8导致整整查了两天。5.4 电池供电后采集电压漂移严重现象用 USB 供电时读数稳定换成两节 AA 电池供电光照等级频繁跳变ADC 值上下浮动接近 200。原因电池供电时电源电压会随着负载变化而跌落尤其当 ZigBee 发射瞬间电流高达 40mA电池电压瞬间被拉低。而代码里 ADC 参考电压选的AVDD5即参考电压跟随电源电压变化所以光合值就随电源波动漂移。这是 CC2530 这类内置 ADC 的常见痛点。解决三种方案。一是改用内部 1.25V 参考电压这样 ADC 结果不会随 VDD 漂移但你的分压电路输出必须不超过 1.25V否则会溢出需要重新分压到适合范围。二是软件校准在每次上报前读一次电源电压用 ADC 值除以参考电压归一化。三是先射后看把模块放在可调电源下从 3.0V 到 3.6V 扫一遍找到偏移规律然后在代码里做电压补偿。这套代码默认用 AVDD5如果是做电池供电节点我强烈建议你把ADC_REF_AVDD5改成内部参考电压0x10并把分压电阻调大。当你看到光照等级稳定了这个坑才算真正迈过去。6. 二次开发思路把单点光敏节点改造成可用的小系统6.1 修改采样周期与数据包结构若要让这套代码适应你自己的项目第一步就是改上报周期。在SensorApp.c中找到osal_start_timerEx调用把 10000 改成你需要的值即可单位是毫秒。注意这个定时器是单次触发的所以必须在事件处理中重新启动否则只上报一次。如果你要同时上报光照、温度和湿度可以扩展lightData数组长度并把不同传感器数据依次填入同时协调器端也要调整解析逻辑。6.2 增加阈值报警和控制输出光敏传感器最常见的业务场景不是单纯上报而是天黑自动开灯、大白天拉窗帘。可以在Sensor_CalcLightLevel返回等级后加一个阈值判断#define LIGHT_THRESHOLD_DARK 0 if (Sensor_GetLightLevel() LIGHT_THRESHOLD_DARK) { // 触发继电器输出或者发送开灯控制命令 Sensor_ControlLight(ON); } else { Sensor_ControlLight(OFF); }Sensor_ControlLight可以是直接控制 P1_0 引脚的 GPIO 函数也可以是向另一个 ZigBee 开关节点发送控制帧。如果是后者只需把目标地址从协调器改成开关节点的短地址簇 ID 换成一个控制簇即可。这样就把一个采样节点升级成了控制节点。6.3 低功耗优化休眠与定时唤醒CC2530 的终端设备支持 PM2 休眠模式休眠电流微安级非常适合电池供电。你要做的改造是在Sensor_SendReport()完成后调用osal_pwrmgr_task_state(Sensor_TaskID, PWRMGR_CONSERVE)让协议栈进入低功耗模式同时用睡眠定时器替代软件定时器。等休眠时间到了芯片自动唤醒。这个改造有几个注意点必须把POWER_SAVING宏打开且不能用带 GPSK 的 SensorApp 方式否则整个任务都会阻塞。我把它写成了一个简单的流程表格步骤改动位置关键代码/配置使能低功耗f8wConfig.cfgPOWER_SAVING宏设为 TRUE任务管理SensorApp.c上报完成后调用PWRMGR_CONSERVE唤醒源使用睡眠定时器osal_start_timerEx会自动处理ADC 采样唤醒后重新初始化因为休眠期间 GPIO 和 ADC 配置会丢失确实休眠会带来一个麻烦CC2530 从 PM2 唤醒后GPIO 配置不会保留必须重新设置引脚模式否则 ADC 会失灵。所以我在Sensor_FilterLightADC()里每次采集前都会重新调用P0SEL | (1 LIGHT_SENSOR_ADC_CH);这一步宁可多花一条指令也不想起床后一直读到 0。从那以后每次改造这套代码我都强制走一遍「确认引脚模式、确认定时器重启、确认端点匹配」这三步。就算改的是别人的工程也一定要先烧一版原版代码验证数据通路正常再动手换传感器。这个习惯帮我避开过无数个隐藏的坑。希望这篇文章的细节能帮到你让这套 CC2530 光敏传感器代码少走几段弯路。本文还有配套的精品资源点击获取