
简介基于CC2530的光敏传感器代码包面向物联网、嵌入式及无线传感器网络学习者。完整实现光敏电阻信号采集、A/D转换、数据滤波及Zigbee无线上报涵盖从传感器节点到协调器应用层的典型工程结构。压缩包大小14.66MB共1064个文件以C源码、头文件、IAR工程文件.ewp/.eww、编译输出.s51/.r51/.lst/.hex和ZStack协议栈配置及库文件为主可直接导入IAR Embedded Workbench查看、编译与调试。该资源已有4500余人学习内容围绕ZStack-2.5.1a展开涉及GPIO初始化、ADC参数设置、无线组网及数据透传等关键环节适合对照代码梳理CC2530开发流程也可由此外扩温湿度、红外等其他传感器构建更多IoT应用。 做CC2530开发的人迟早会碰到一个场景想在板子上接个光敏传感器判断环境是亮还是暗然后让LED、继电器或者某个外设跟着光照变化。这个需求听起来简单但真正写起基于CC2530的光敏传感器代码时P0口配置、ADC参考电压、寄存器数据拼接每一步都能让新手卡上半天。这篇文章直接把我的裸机实现方案完整放出来从原理到代码再到踩坑记录适合刚开始接触CC2530 ADC采集或者准备在ZigBee项目里加光照监测的朋友。你不需要先跑协议栈先把这块采集逻辑在裸机上打通后面移植到Z-Stack都是水到渠成的事。1. 整体设计思路拆解1.1 为什么我选了光敏电阻方案而不是数字传感器光敏传感器通常有两种主流方案一种是直接用光敏电阻搭配ADC采集电压另一种是用BH1750这类数字光敏传感器通过I2C读回照度值。很多人纠结选哪个我的建议是看需求。BH1750精度高、直接输出Lux代码写起来也简单但I2C时序、器件地址、寄存器配置都需要额外调试。光敏电阻方案就一条分压电路加上CC2530自带的12位ADC不需要外部通信协议硬件成本几乎为零代码量也少得多。考虑这个项目的标题就是“基于CC2530的光敏传感器代码”从纯裸机学习的角度看光敏电阻ADC是最能帮你把CC2530内部资源摸透的组合。它能让你理解P0口复用配置、ADC参考电压选择、采样结果如何拼装这些都是后面做任何模拟量采集电池电压、NTC温度、土壤湿度的通用基础。BH1750这类数字传感器适合在产品化、需要精确照度的阶段替换上来到时候驱动层单独换一个文件就行上层业务逻辑不需要大改。1.2 分压电路怎么搭才稳接线表直接抄光敏电阻的阻值随光照强度变化光照强时阻值降到千欧级别黑暗时阻值升高到兆欧级别。要把它变成CC2530 ADC能读的电压信号就让它和一个固定电阻串联中间抽头接ADC输入引脚形成一个分压电路。我用的电路是这样CC2530的P0.0AIN0作为ADC输入连接分压点。光敏电阻一端接3.3V另一端接分压点。10kΩ固定电阻一端接分压点另一端接GND。在分压点对GND并联一个0.1uF的陶瓷电容软件滤波之外再做一层硬件滤波。这个接法下光照越强光敏电阻阻值越小分压点电压越低ADC读数越小环境越暗ADC读数越大。后面代码里的阈值判断就是按照这个方向写的。如果你希望数值方向反过来把光敏电阻和固定电阻的位置互换就行但注意这样会导致黑暗时分压点接近GND读数接近0阈值判断逻辑要同步反过来。P0.0是CC2530的ADC通道AIN0只需要这一根模拟信号线。板上如果有P0.1到P0.7空着也可以依次作为AIN1到AIN7扩展多个传感器通道。2. 动手写代码前必须搞懂的3个ADC细节2.1 参考电压选错你读到的数据全是假的CC2530的ADC支持多种参考电压在ADCCON3寄存器的高两位SREF里设置。00表示内部1.15V参考电压10表示AVDD5即外部电源引脚我这里用的是3.3V供电。这个选择直接决定ADC输入量程。如果参考电压设为内部1.15V那输入电压超过1.15V后读数直接饱和在4095整个采集失去意义。我实测分压点的电压范围大约是0.2V到2.8V所以必须用AVDD5作为参考量程覆盖到3.3V才够用。代码里我配置为ADCCON3的SREF10对应的寄存器赋值是0xB0中的bit7和bit6。这个0xB0是常用组合后面第3节会逐位拆开看。新手最容易犯的错就是直接用例程里的默认参考电压然后发现数据忽大忽小或者永远在满量程附近。先想清楚你的传感器输出范围再决定参考电压这是ADC采集的首要原则。2.2 P0.0要启用外设功能不是光配置个方向就行CC2530的P0口是复用引脚既可以做普通数字IO也可以连接ADC、UART、定时器等外设。要把P0.0用作AIN0模拟输入必须设置P0SEL寄存器对应位为1让它切换到外设功能。很多人在这里漏了一步结果读到的数据不是0就是随机跳因为引脚还在数字输入模式下电平状态被内部上拉电阻影响。P0SEL寄存器的每一位对应P0口的8个引脚P0.0对应bit0所以初始化时执行P0SEL | 0x01。另外P0INP寄存器决定了引脚的输入模式ADC通道建议配置为三态输入避免内部上拉/下拉电阻对分压点造成影响。在很多例程里这步会被忽略但在高阻抗分压电路里内部上拉会让测量结果产生明显偏移。2.3 12位数据不是直接读ADCL和ADCH拼起来的CC2530的ADC转换结果是12位存到ADCH和ADCL两个8位寄存器里。很多人第一反应是value ADCH 8 | ADCL但我在调试时发现这样读出来的数据完全不对。查看数据手册会发现CC2530的ADC结果寄存器并不是简单的高8位加低8位布局。12位转换结果在寄存器中的排列是ADCL的高6位保存结果低6位ADCH的低6位保存结果高6位。也就是说读取时要这样拼value (ADCL 2) | ((uint16_t)ADCH 6);这个写法把ADCL右移2位去掉无效的低2位再把ADCH整体左移6位两部分合并得到一个0到4095的12位数值。我第一次写代码时没看寄存器布局直接用了左右移8位的传统拼接方式串口输出的数据乱成一团后来对着数据手册的寄存器位图才反应过来。所以这块一定要格外注意它是CC2530 ADC采集最容易出bug的地方。3. 完整可运行的CC2530光敏采集代码3.1 系统初始化与UART0配置为了方便调试我把ADC采集结果通过串口发送到电脑上这样不用接显示屏也能实时观察数值变化。CC2530的UART0我映射到P0.2和P0.3波特率设置为经典的960032MHz外部晶振下使用的寄存器参数是U0BAUD0x5B、U0GCR0x08。初始化部分代码如下#include ioCC2530.h #define ADC_INPUT_PIN 0x01 // P0.0 void InitUart0(void) { PERCFG ~0x01; // UART0使用备用位置1即P0.2/P0.3 P0SEL | 0x0C; // P0.2和P0.3配置为外设功能 U0CSR | 0x80; // UART模式 U0UCR 0x00; // 8位数据无校验1位停止位 U0GCR | 0x08; // 波特率因子高位 U0BAUD 0x5B; // 9600波特率配合32MHz晶振 UTX0IF 0; // 清发送中断标志 }这里要注意PERCFG的位置选择CC2530的USART0可以映射到两个不同的引脚组合P0.2/P0.3是默认位置PERCFG的bit0清零即可。P0SEL的赋值与ADC引脚配置用或运算叠加不会互相覆盖。3.2 多次采样取平均的ADC读取函数我直接封装了一个ReadLightValue函数单次读取P0.0通道并且连续采了8次取平均。光敏电阻的模拟信号变化比较缓慢但ADC本身会有随机噪声尤其是电源波动的时候软件滤波能明显改善稳定性。uint16_t ReadLightValue(void) { uint16_t sum 0; uint8_t i; for (i 0; i 8; i) { ADCCON3 0xB0; // SREFAVDD5, 256分频, 通道AIN0 while (!(ADCCON1 0x80)); // 等待EOC标志位置1转换完成 sum (ADCL 2) | ((uint16_t)ADCH 6); } return sum 3; // 右移3位等价于除以8 }打开ADCCON3这个赋值操作实际上同时也启动了一次单次转换。0xB0的二进制是10110000每一位的含义是bit7到bit6为10表示参考电压选AVDD5bit5到bit4为11表示抽取率256分频采样率偏低但噪声小bit3到bit0为0000表示选择单端输入通道AIN0对应P0.0。使用256分频看似速度慢但对于光照这种缓变信号完全够用而且低采样率本身就有一定的滤波效果读数更稳。如果你后续要采集高速信号可以把SDIV调低但光敏场景没必要。等待转换完成的时候我轮询的是ADCCON1寄存器里的EOC标志位对应bit7。转换完成前为0完成后硬件自动置1代码在while循环里等它变1即可。3.3 主循环与阈值处理逻辑主函数的逻辑很直接初始化串口进入死循环每200ms读一次光敏值通过串口发送出来同时按照之前电路的方向做阈值判断。数值越大代表环境越暗超过阈值就点亮LED。void UartSendChar(char c) { U0DBUF c; while (UTX0IF 0); UTX0IF 0; } void UartSendString(char *str) { while (*str) { UartSendChar(*str); } } void UartSendNumber(uint16_t num) { char buf[6]; char i 0; if (num 0) { UartSendChar(0); return; } while (num 0) { buf[i] 0 (num % 10); num / 10; } while (i 0) { UartSendChar(buf[--i]); } } void main(void) { uint16_t light; InitUart0(); P0SEL | ADC_INPUT_PIN; // P0.0切换为ADC外设功能 P0INP | ADC_INPUT_PIN; // P0.0配置为三态输入 P1DIR | 0x01; // P1.0作为LED输出 while (1) { light ReadLightValue(); UartSendString(Light: ); UartSendNumber(light); UartSendChar(\r); UartSendChar(\n); if (light 2000) // 阈值需要根据实测环境调 { P1_0 0; // 环境暗点亮LED } else { P1_0 1; // 环境亮熄灭LED } DelayMs(200); } }这里有一个关键点P0SEL配置ADC外设时不需要单独配置P0DIR方向外设功能会接管引脚控制。如果你在P0DIR里把P0.0设置为输出反而可能导致引脚驱动冲突ADC采集出来的数据异常。阈值2000不是固定值不同光敏电阻型号、不同分压电阻、不同环境光照下的最终读数都会不一样。我建议先把数据通过串口打印出来记录白天和夜间的实际数值范围再回填到阈值里不要凭感觉写死。DelayMs函数就是普通的循环延时用volatile变量保持循环不被编译器优化掉即可。4. 实测中的常见问题与排查思路4.1 问题速查表我在调试过程中和一些朋友交流发现大家遇到的问题高度相似整理成一张速查表现象大概率原因解决办法ADC读数一直接近4095参考电压选的1.15V内部参考输入电压超量程ADCCON3的SREF位改为10使用AVDD5读数恒为0P0.0没有切换到外设功能或者光敏电阻虚焊开路检查P0SEL串口输出乱码波特率配置与晶振频率不匹配确认32MHz晶振U0BAUD0x5B、U0GCR0x08读数上下跳动超过50电源纹波大或没有硬件滤波电容分压点对地并联0.1uF电容软件上增加采样次数数据随光照变化但方向相反光敏电阻和固定电阻的位置与预期相反对调两个电阻位置或反向修改阈值判断串口完全无输出TX引脚配置错误P0.2/P0.3未切换外设检查PERCFG和P0SEL的低两位是否配置正确4.2 两个最容易迷惑人的坑第一个坑是EOC标志位和中断标志位搞混。ADCCON1寄存器里bit7是EOC标志表示转换完成bit5是ADC中断标志ADCIF。如果中断没有使能ADCIF一直不会自动置位只轮询ADCIF就会永远卡死在等待循环里。我建议直接用EOC判断简单可靠。第二个坑是光敏电阻引脚悬空导致的读数漂移。如果你用的是面包板加杜邦线接触不良时会发现ADC读数在某个值附近抖动得厉害还不是正常的随机噪声。排查方法很简单用手捏住光敏电阻观察数值是否稳定变化如果没有反应大概率是接触问题。焊接过的板子很少出现这个现象。另外还有一点CC2530的ADC对参考电压的稳定性比较敏感如果你的供电用的是电脑USB口直供有时候电源纹波大ADC读数会跟着跳。排除接地问题后加个大电容在3.3V和GND之间通常能改善不少。5. 从这段代码到实际应用的扩展思路5.1 光控小夜灯或者继电器开关把这段代码的主循环里加一个继电器控制即可实现光控开关适合做夜晚自动亮的楼道灯、鱼缸灯或者补光灯。继电器模块一般需要三极管或光耦驱动直接用CC2530的GPIO驱动线圈会烧引脚这个要注意。我在实际做的时候用P1.1控制一个S8050三极管再驱动5V继电器阈值设置成从串口日志里挑出来的临界值再加了大概100的滞回区间。比如阈值2000那么光照值大于2100才开灯小于1900才关灯避免在临界点来回抖动。滞回区间这个东西光控应用里几乎是必须的不加的话傍晚时分继电器会咔嗒咔嗒乱跳很烦人。5.2 接入ZigBee网络做远程光照监测如果你手头的CC2530带功放芯片通常意味着你最终要做ZigBee节点。这时候可以先在裸机上验证好ADC采集逻辑再往Z-Stack协议栈里移植。移植的时候把ADC初始化放到对应任务的初始化函数里采集逻辑可以放在事件处理函数中周期性执行光照值通过AF_DataRequest发送给协调器。协议栈里的ADC读取寄存器操作和裸机完全一致不需要改底层。有一点值得注意Z-Stack的定时任务最小周期一般是毫秒级你不需要像裸机那样搞一个专门的延时等待直接利用协议栈的定时事件驱动采集就好。这样既节省CPU也不影响ZigBee协议栈的无线收包任务。如果考虑低功耗场景可以让节点在休眠状态下定时醒来采集一次光照后立即发送数据再继续休眠实测下来一节电池用几个月不是问题。裸机模型下把所有逻辑跑通后面朝低功耗方向优化也会容易很多。我自己在实际操作中的体会是光敏采集这套代码虽然简单但它牵扯到的知识点很密集。P0口复用、ADC参考电压选择、寄存器数据拼接、UART调试输出这些都是CC2530开发最基础的功夫。建议你拿到代码后先不要急着改功能耐着性子把串口输出的原始数据在不同光照下各采集几分钟摸清数据特征再往下做。等你对ADC的底层行为有感觉了后面再做任何模拟量采集项目都会顺手很多。本文还有配套的精品资源点击获取