实验五:传感器数据收发实验
一句话理解
这不是"照着步骤做一遍"的实验,而是一次设计型训练:工程里有一套能跑通的基线代码,你的任务是 ① 读懂它 ② 改造它 ③ 验证你的改造——代码要自己看、自己改、自己测,具体怎么改成什么样,由你设计。
| 项目 | 内容 |
|---|---|
| 实验名称 | 传感器数据收发(设计与实现) |
| 建议学时 | 90 分钟 |
| 分组 | 2 人一组 |
| 实验类型 | 设计型 |
| 实验板 | CC2530 实验板 2 块(1 块协调器,1 块传感器节点) |
⚠️ 本实验不是"照着步骤做一遍"
工程里有一套能跑通的基线代码。你的任务是三件事:
① 读懂它 ② 改造它 ③ 验证你的改造
代码要自己看、自己改、自己测。 每个任务都给了"改哪里"的提示和"怎么算通过"的验收标准,但具体怎么改、改成什么样,由你设计。
一、实验目的
- 读懂 ZCL 属性上报的完整实现链路:采样 → 更新属性 → 组装报文 → 无线发送 → 接收解析
- 能独立定位并修改上报周期、上报条件等关键参数,并说明改动的后果
- 掌握"改前 / 改后对比"的验证方法:用可观测的现象证明你的改动确实生效了
二、实验原理(你要读懂的机制)
2.1 谁是发送方,谁是接收方 —— 这是最关键的一点
本实验里两个角色的分工是不对称的:
| 角色 | 固件配置 | 干什么 | 串口打印什么 |
|---|---|---|---|
| 传感器节点 | RouterEB(或 EndDeviceEB) | 采样 → 上报 | 自己采集到的数值 |
| 协调器 | CoordinatorEB | 接收 → 显示 | 收到的、别人发来的数值 |
⚠️ 协调器不会去采样
它没有接传感器,也不会主动去读 —— 它只负责"听"。
这不是巧合,是代码里刻意做的。读代码时请找出:哪些地方用条件编译把"采样/上报"排除在协调器之外? (提示:Components\hal\common\hal_drivers.c 和 Source\zcl_sampletemperaturesensor.c 各有一处)
2.2 上报的是什么:三个 ZCL 属性
上报的不是"一条自定义消息",而是 ZCL(ZigBee Cluster Library)属性:
| 簇(Cluster) | 簇 ID | 属性 | 数据来源 | 单位 |
|---|---|---|---|---|
| Illuminance Measurement | 0x0400 | MeasuredValue | 光敏传感器 | ADC 原始值 0~1023 |
| Temperature Measurement | 0x0402 | MeasuredValue | DHT11 | 0.01 ℃ |
| Relative Humidity Measurement | 0x0405 | MeasuredValue | DHT11 | 0.01 %RH |
注意单位:串口上的 2650 不是 2650 ℃,而是 26.50 ℃。
2.3 数据是怎么流动的
┌──────────── 传感器节点(RouterEB)────────────┐
│ 周期任务(每 3 秒): │
│ ① 读光敏 ADC(P0_5) → 光照值 │
│ ② 读 DHT11(P0_4) → 温度值、湿度值 │
│ ③ 更新三个 ZCL 属性 │
│ ④ 串口打印 ← 发送端看到的 │
│ ⑤ 判断"有没有变化" → 有变化才发上报报文 │
└───────────────────┬───────────────────────────┘
│ 空口(ZigBee 无线)
│ Report Attributes × 3
▼
┌──────────── 协调器(CoordinatorEB)───────────┐
│ 收到报文 → 解析 → 串口打印收到的数值 │
│ ← 接收端看到的 │
│ → 同时在 OLED 上显示 Rx Temp: │
└───────────────────────────────────────────────┘2.4 上报机制的三个可调参数(任务改造的入口)
读代码时,把这三件事弄清楚,后面的任务就好做了:
| 参数 | 当前值 | 在哪里 | 改成什么会影响 |
|---|---|---|---|
| 多久上报一次 | 3 秒 | zcl_sampletemperaturesensor.h 的 SAMPLETEMPERATURESENSOR_REPORT_PERIOD | 报文密度、实时性、功耗 |
| 什么样才上报 | 三个值有任一变化 | GetAndReportEx() 里的 changed 标志 | 空口流量、功耗 |
| 发给谁 | 协调器 0x0000 | ReportAttr() 里的 destAddr | 数据的去向 |
2.5 本实验的四个关键约定
| 约定 | 值 |
|---|---|
| 上报周期 | 3 秒(SAMPLETEMPERATURESENSOR_REPORT_PERIOD = 3000) |
| 上报条件 | 数值有变化才发(但串口每周期都打印) |
| 上报目标 | 协调器,短地址 0x0000 |
| 串口参数 | 38400 / 8 / 1 / 无 |
三、代码导读(动手改之前必须读完)
3.1 你要看的文件
工程路径:
TemperatureSensor工程源码分析\TemperatureSensor工程源码分析\Z-Stack 3.0.1\
Projects\zstack\HomeAutomation\SampleTemperatureSensor\| 文件 | 你要从这里看懂什么 |
|---|---|
Source\zcl_sampletemperaturesensor.c | 主战场:采样、上报、串口打印、接收解析 |
Source\zcl_sampletemperaturesensor_data.c | 属性表:哪些属性可读、可写、可上报 |
Source\zcl_sampletemperaturesensor.h | 端点号、事件位、上报周期宏 |
Source\OSAL_SampleTemperatureSensor.c | 任务表与初始化顺序(了解即可) |
Components\hal\target\CC2530EB\HW_DHT11\hal_dht11.h | DHT11 的状态码定义 |
3.2 五个核心函数(先找到它们,再往下读)
| 函数 | 在哪个文件 | 干什么 | 对应本实验哪个环节 |
|---|---|---|---|
zclSampleTemperatureSensor_GetAndReportEx() | .c | 周期任务:采样 + 更新属性 + 判断变化 + 上报 | 发送端全流程 |
zclSampleTemperatureSensor_ReportAll() | .c | 决定上报哪几个簇 | 发送 |
zclSampleTemperatureSensor_ReportAttr() | .c | 组一条上报报文并发送 | 发送 |
zclSampleTemperatureSensor_PrintReport() | .c | 发送端的串口输出格式 | 观测 |
zclSampleTemperatureSensor_PrintRxReport() | .c | 协调器端的接收解析与输出 | 接收 |
小技巧:在 IAR 里用
Ctrl+Shift+F(全局搜索)搜函数名,能直接跳到定义处。
3.3 代码走查问题(必须书面回答,计入成绩)
带着这些问题去读代码,读不懂就先别改:
GetAndReportEx()里有一个changed变量。它什么时候被置为TRUE?总共几处?最后它是怎么决定"要不要上报"的?ReportAll()为什么要调用三次ReportAttr()?一次不行吗?(提示:想想 ZCL 对一条上报报文的限制)ReportAttr()里destAddr.addr.shortAddr被设成了多少?这个地址代表谁?PrintRxReport()为什么被放在#ifdef ZCL_REPORT ... #endif里面?而PrintReport()却在#else分支里?这个条件编译的边界在哪里?_data.c的属性表里,温度属性的权限写着ACCESS_CONTROL_READ | ACCESS_REPORTABLE。ACCESS_REPORTABLE是干什么的?如果去掉它会怎样?- 协调器的采样代码为什么被排除?找出两处条件编译,写出行号。
- DHT11 读到数据后,为什么代码里写
dat.temp * 100才赋给属性? - 发送端每 3 秒都在串口打印,那接收端是不是每 3 秒也一定收到?为什么?
四、开发环境与基线验证
4.1 工程与两个配置
| 板子 | IAR 配置 | 角色 |
|---|---|---|
| A 板 | CoordinatorEB | 协调器(接收端) |
| B 板 | RouterEB | 传感器节点(发送端) |
4.2 编译与烧写
- IAR 打开
CC2530DB\SampleTemperatureSensor.eww - 左上角配置下拉框选
CoordinatorEB→Project → Rebuild All→ 连 A 板 →Download and Debug - 配置切到
RouterEB→Rebuild All→ 连 B 板 →Download - 两块板都要擦除 NV
为什么要擦 NV? 板子里可能残留上次组网的参数(PANID、信道、父节点)。不擦的话,节点会拿着旧参数反复尝试加入一个已经不存在的网络,可能十几分钟都连不上。
擦除方法:IAR
Project → Download → Erase memory,或用 TI Flash Programmer 的Erase。
4.3 上电顺序(顺序错了实验会失败)
① 先只给 A 板(协调器)上电 → 等 10~15 秒,让它把网络建起来
② 再给 B 板(传感器节点)上电 → 等 20~30 秒,让它加入网络4.4 串口设置
两块板都要接串口(两个窗口并排摆):
| 参数 | 值 |
|---|---|
| 波特率 | 38400 |
| 数据位 / 停止位 / 校验位 | 8 / 1 / 无 |
| 流控 | 无 |
怎么确定 COM 口? 拔掉某块板 → 设备管理器"端口"里少了哪个 COM 就是它。 两块板的 COM 号通常不一样,别选错。
4.5 ★ 先验证基线(改代码之前必须做)
在动手改任何代码之前,先把原样的固件跑通,确认你能看到正常现象。
B 板(发送端)应每 3 秒打印:
------ SENSOR REPORT ------
DHT11 : OK
Temp : 2650 (0.01 C)
Humi : 6100 (0.01 %RH)
Light : 512 (ADC 0~1023)
---------------------------A 板(接收端)应打印:
<<< RX REPORT src=0x796F cluster=0x0402
Temperature = 2650 (0.01 C)
<<< END
<<< RX REPORT src=0x796F cluster=0x0405
Humidity = 6100 (0.01 %RH)
<<< END
<<< RX REPORT src=0x796F cluster=0x0400
Illuminance = 512 (ADC 0~1023)
<<< END逐项确认:
| 检查 | 期望 |
|---|---|
B 板 DHT11 : | 显示 OK |
B 板 Temp / Humi / Light | 数值合理(温度 15~35 ℃、湿度 30~90 %RH、光照 0~1023) |
| A 板是否有输出 | 有 RX REPORT,且 src 有值 |
| A 板的数值 | 与 B 板同一时刻的数值完全相等 |
用环境变化验证数据是真的:
| 动作 | 两端都应出现的现象 |
|---|---|
| 对着 DHT11 哈气(或靠近热水杯) | Humi 明显上升,Temp 略升 |
| 手电筒照光敏电阻 | Light 明显下降(注意是下降!) |
| 遮住光敏电阻 | Light 明显上升 |
为什么"越亮读数越小"? 光敏电阻接在分压电路低侧:光越强 → 阻值越小 → 分压点电压越低 → ADC 越小。这是硬件决定的,不是错误。
把基线数据记入表 1。
⚠️ 基线跑不通就不要改代码 —— 否则出了问题分不清是你的改动还是原来的问题。
五、实验任务
5.0 任务总览
| 编号 | 任务 | 改动位置 |
|---|---|---|
| 1 | 修改上报周期 | .h 的宏 SAMPLETEMPERATURESENSOR_REPORT_PERIOD |
| 2 | 改为无条件周期上报 | .c 的 GetAndReportEx() |
每次改完都要:①
Rebuild All② 重新烧写对应板 ③ 用可观测现象验证 ④ 记录数据。 每次只改一处,改完验证完再改下一处 —— 不要一次改一堆。
任务 1:修改上报周期
设计要求
把上报周期从 3 秒改成 1 秒,然后再改成 10 秒,观察差异。
改哪里
Source\zcl_sampletemperaturesensor.h 里的宏:
#define SAMPLETEMPERATURESENSOR_REPORT_PERIOD 3000提示
- 这个宏在
zcl_sampletemperaturesensor.c里被用作osal_start_timerEx()的周期(在Init()里启动,在事件循环里重新装载) - 找到这两个地方,理解"周期任务"是怎么自我重新触发的
- ⚠️ 不要小于 1000 ms:DHT11 本身需要一定的采样间隔,读太快会频繁出错
验收标准
| 周期 | 60 秒内 B 板打印次数(理论值) | 实测 |
|---|---|---|
| 3000 ms(原值) | 约 20 次 | |
| 1000 ms | 约 60 次 | |
| 10000 ms | 约 6 次 |
回答:周期变短会带来什么代价?(从无线信道占用、功耗、DHT11 读取耗时三个角度说)
任务 2:改为"无条件周期上报"
设计要求
当前代码是"三个值有任一变化才上报"。请改成"每个周期都上报"。
改哪里
Source\zcl_sampletemperaturesensor.c → zclSampleTemperatureSensor_GetAndReportEx()
该函数末尾现在是这样的逻辑:
/* 有变化才上报 */
if (changed)
{
zclSampleTemperatureSensor_ReportAll();
}提示
- 最简做法是把判断条件直接去掉;但更好的做法是先想清楚:
changed这个变量在整个函数里起了什么作用?去掉它会不会有副作用? - 想一想:串口打印和无线上报是两条独立的路径吗?改动只影响哪一条?
验收标准
| 场景 | 改前 | 改后 |
|---|---|---|
| 环境稳定(不哈气、光照不变),观察 60 秒 | A 板收不到或很少收到 | A 板应收到约 20 轮 |
回答:
- 改完之后,空口报文的数量是增加还是减少?大约增加多少倍?
- 如果这是一块电池供电的节点,你会选哪种策略?为什么?
六、数据记录表
表 1 基线验证(改代码之前)
| 序号 | 发送端 Temp | 接收端 Temperature | 发送端 Humi | 接收端 Humidity | 发送端 Light | 接收端 Illuminance | src |
|---|---|---|---|---|---|---|---|
| 1 | |||||||
| 2 | |||||||
| 3 |
发送端与接收端数值是否完全一致? □ 是 □ 否 src = 0x__________ 你是如何确认它就是发送端的地址的? ____________________
表 2 任务 1 —— 上报周期
| 周期设置 | 60 秒内 B 板打印次数 | 60 秒内 A 板收到轮次 | 是否符合预期 |
|---|---|---|---|
| 3000 ms(原值) | |||
| 1000 ms | |||
| 10000 ms |
表 3 任务 2 —— 上报条件
| 上报条件 | 稳定环境下 60 秒内 A 板收到轮次 | 与发送端打印次数之比 |
|---|---|---|
| 有变化才上报(原值) | ||
| 无条件周期上报(改后) |
表 4 环境变化的影响(基线阶段)
| 动作 | 变化前 Temp/Humi/Light | 变化后 Temp/Humi/Light | 变化方向 |
|---|---|---|---|
| 哈气 / 靠近热水 | / / | / / | |
| 强光照 | / / | / / | |
| 遮光 | / / | / / |
七、思考题
- 谁在发送、谁在接收? 协调器会不会主动去读传感器?为什么这么设计?
- 为什么一次上报会变成三条报文? 如果三个属性属于同一个簇,会是几条?
Temp : 2650代表多少摄氏度? ZCL 为什么用它作为单位,而不是直接传 26?- 为什么光越强
Light反而越小? 结合分压电路说明。 - "数值不变就不上报"有什么好处? 你把它改成无条件上报后,代价是什么?
ReportAttr()里的destAddr决定了什么? 如果网络里有 5 个传感器节点,协调器靠报文的哪个字段区分它们?- 串口打印和无线上报是两件独立的事吗? 用你在任务 1、2 中的实测数据说明。
- 如果两块板都上电了但协调器一直收不到数据,按可能性从高到低列出你的排查顺序。
八、常见问题排查
| 现象 | 可能原因 | 处理 |
|---|---|---|
| 编译报错 | 语法/类型错误 | 看 IAR 的 Error[...] 信息里的文件名和行号,双击跳过去 |
编译警告 Pe177: declared but never referenced | 你删掉了某个函数的调用,但前置声明还留着 | 把没用的声明也注释掉,或恢复调用 |
| 串口完全没输出 | COM 口选错;波特率不对;端口没打开 | 拔插板子确认 COM;波特率 38400;先"关闭"再"打开" |
| 只有乱码 | 波特率不对 | 确认 38400(不是 115200) |
发送端 DHT11 : 不是 OK | 传感器没接好 / 超量程 | 换环境再试 |
| 发送端正常,接收端一直没有输出 | ① 传感器没入网 ② 两块板不在同一网络 | 按 4.3 的顺序上电;两块板都擦 NV 后重来 |
| 改了代码但现象没变 | 忘记 Rebuild All;烧错了板;没擦 NV | 重新 Rebuild + 确认烧到正确的那块板 |
| 烧完就死机 / 不打印 | 你的改动导致内存或数组越界 | 用 Rebuild All 的全部警告逐条排查;先恢复原代码确认基线正常 |
| OLED 一直空白 | 正常(协调器只在收到温度上报时写一行) | 收到上报后应显示 Rx Temp: |
| 板上电没反应 | USB 线不能传数据 | 换一根 USB 线(很多线只充电不传数据) |
九、实验报告要求
- 实验目的(简写 2~3 条)
- 实验设备:板子数量、COM 口、固件配置
- 代码走查答案:第三.3 节的 8 个问题全部书面回答
- 任务完成情况:每个任务写清 —— 你改了什么(贴改动前后代码)→ 为什么这么改 → 实测结果
- 实验数据:第六节的表 1 ~ 表 4
- 实验现象:至少贴 2 段真实的串口输出(发送端 + 接收端),直接复制或截图,不要手抄
- 思考题:第七节 8 题全答
- 实验结论:用你自己的话说清"数据是怎么从传感器走到协调器的"
- 问题与体会:遇到的坑、怎么定位的(这一项计入成绩,写清楚比写漂亮重要)
格式:手写或打印均可。代码改动请贴 diff 形式(改前 / 改后对照)。
附录 A:两端串口输出格式速查
发送端(RouterEB / EndDeviceEB)—— 每 3 秒一次
------ SENSOR REPORT ------
DHT11 : OK
Temp : 2650 (0.01 C)
Humi : 6100 (0.01 %RH)
Light : 512 (ADC 0~1023)
---------------------------接收端(CoordinatorEB)—— 每收到一条报文的时刻
<<< RX REPORT src=0x796F cluster=0x0402
Temperature = 2650 (0.01 C)
<<< END
<<< RX REPORT src=0x796F cluster=0x0405
Humidity = 6100 (0.01 %RH)
<<< END
<<< RX REPORT src=0x796F cluster=0x0400
Illuminance = 512 (ADC 0~1023)
<<< ENDA 板上的
src是发送方的短地址。数值对照一致,就证明这条数据确实来自那块传感器板。
附录 B:关键函数索引(改代码时查这里)
| 你想改什么 | 去哪改 |
|---|---|
| 上报多久一次 | .h → SAMPLETEMPERATURESENSOR_REPORT_PERIOD |
| 上报什么条件 | .c → GetAndReportEx() 的 changed 相关逻辑 |
| 上报哪几个簇 | .c → ReportAll() |
| 一条报文怎么组 | .c → ReportAttr() |
| 上报发给谁 | .c → ReportAttr() 里的 destAddr |
| 发送端打印格式 | .c → PrintReport() |
| 接收端打印/处理逻辑 | .c → PrintRxReport()、ProcessInReportCmd() |
| 属性表(有哪些属性、权限、类型) | _data.c → zclSampleTemperatureSensor_Attrs[] |
| 光照值的换算 | .c → GetAndReportEx() 里处理光照那一段 |
| DHT11 状态码含义 | hal_dht11.h → HAL_DHT11_SC_* |
附录 C:三个簇/属性速查
| 名称 | 簇 ID | 属性名 | 串口标签 | 单位 |
|---|---|---|---|---|
| 光照测量 | 0x0400 | MeasuredValue | Illuminance / Light | ADC 0~1023 |
| 温度测量 | 0x0402 | MeasuredValue | Temperature / Temp | 0.01 ℃ |
| 相对湿度测量 | 0x0405 | MeasuredValue | Humidity / Humi | 0.01 %RH |
换算:Temp : 2650 = 26.50 ℃ │ Humi : 6100 = 61.00 %RH
附录 D:一页速查卡
┌────────────────────────────────────────────────┐
│ 传感器数据收发 · 速查卡 │
├────────────────────────────────────────────────┤
│ 固件 A板(协调器)=CoordinatorEB │
│ B板(传感器)=RouterEB │
│ 串口 38400 / 8 / 1 / 无 │
│ 上电 先 A 等10~15秒 → 再 B 等20~30秒 │
│ 必做 两块板都擦 NV;先验证基线再改代码 │
├────────────────────────────────────────────────┤
│ 上报周期 = SAMPLETEMPERATURESENSOR_REPORT_PERIOD │
│ 上报条件 = GetAndReportEx() 里的 changed 标志 │
│ 上报目标 = ReportAttr() 里的 destAddr(0x0000) │
│ 属性表 = _data.c 的 Attrs[] │
├────────────────────────────────────────────────┤
│ 温度 = 显示值 ÷ 100 ℃ │
│ 湿度 = 显示值 ÷ 100 %RH │
│ 光照 = ADC 原始值;越亮越小 │
├────────────────────────────────────────────────┤
│ 改完必做:Rebuild All → 烧对板 → 擦NV → 对比验证 │
└────────────────────────────────────────────────┘