5.1 按键实验
本节导读
LED 会亮了,但只会"自己动"太没意思——让按键来指挥 LED!
本实验目标(与原站一致):按下按键 → LED 闪烁。学会按键的使用套路后,你就能让开发板"听懂"人手了。
一、先搞清一件事:按键编号到底是多少?
很多同学在这里栽跟头——宏名 HAL_KEY_SW_x 和开发板上的按键不是"想当然"对应,它由工程里的 hal_key.c 映射决定。
Z-Stack 3.0 默认的按键宏(hal_key.h)
#define HAL_KEY_SW_1 0x01 // Joystick up(五向键·上)
#define HAL_KEY_SW_2 0x02 // Joystick right(五向键·右)
#define HAL_KEY_SW_5 0x04 // Joystick center(五向键·中)
#define HAL_KEY_SW_4 0x08 // Joystick left(五向键·左)
#define HAL_KEY_SW_3 0x10 // Joystick down(五向键·下)
#define HAL_KEY_SW_6 0x20 // Button S1(独立按键 S1)
#define HAL_KEY_SW_7 0x40 // Button S2(独立按键 S2)重点:SW_1~SW_5 是五向键(用得少),SW_6 和 SW_7 才是独立按键!所以别拿 HAL_KEY_SW_1 当独立按键用——它默认是五向键"上"。
引脚对接配置(hal_board_cfg.h)
HAL 用 PUSH1/PUSH2 把"独立按键"接到具体引脚:
/* S1 按键 = P0_1(原站配套板与 GEC2530 底板一致) */
#define PUSH1_BV BV(1)
#define PUSH1_SBIT P0_1
// 按键按下时的电平极性(按下为低电平 → ACTIVE_LOW)
#define PUSH1_POLARITY ACTIVE_LOW对照 GEC2530 底板原理图(Sheet 8):P0_1 上接的独立按键是 S4。所以在本站:S4(P0_1)= Z-Stack 默认的 S1 键 =
HAL_KEY_SW_6——本章实验就用它,与原站一致。
⚠️ 想用板上其他独立按键(如 S3/P1_3、S5/P1_2)?先查你工程的
hal_key.c,看它把哪个引脚映射到哪个HAL_KEY_SW_x,再把代码里的宏换成实际的宏。不要猜。
二、三步用法:登记 → 写回调 → 处理
第 1 步:应用层登记"我要收按键消息"
按键受 OSAL 调度,用之前必须在应用层注册。在 zclSampleSw_Init() 里(SampleSwitch 默认已有一行):
void zclSampleSw_Init(byte task_id) {
// ……原有初始化代码……
RegisterForKeys(zclSampleSw_TaskID); // 注册:按键消息发给我
}为什么必须 RegisterForKeys()?
一句话:按键消息需要一个"收件地址",RegisterForKeys() 就是提前把地址登记给系统。不登记,按键事件就"无处可投"。
① 按键消息不是直接发给你的
按键按下 → HAL 检测(HalKeyPoll)→ 系统回调 OnBoard_KeyCallback(打包消息)
→ osal_msg_send(发给谁?) → 应用层收到 KEY_CHANGEHAL 层不认识应用层(分层设计),它把按键事件交给中间人 OnBoard;而 OnBoard 打包好消息后,必须知道投递地址才能发出去。
② RegisterForKeys() 干的就是"登记地址"(OnBoard.c 源码)
static uint8 RegisteredTaskID = NO_TASK; // ★ 全局只有一个"收件人"变量
uint8 RegisterForKeys(uint8 task_id) {
if (RegisteredTaskID == NO_TASK) { // 只允许登记一次
RegisteredTaskID = task_id; // 把你的任务 ID 记为收件人
return TRUE;
}
return FALSE;
}按键发生时:
void OnBoard_KeyCallback(uint8 keys, uint8 shift) {
// ……打包 KEY_CHANGE 消息……
if (RegisteredTaskID != NO_TASK) { // 有登记过收件人吗?
osal_msg_send(RegisteredTaskID, msg); // 有 → 投递
}
// 没有登记 → 消息直接丢弃!
}③ 不用的后果:RegisteredTaskID 保持 NO_TASK,消息被直接丢弃 → 应用层永远收不到 KEY_CHANGE → 按键"按了没反应"。
④ 为什么定时器事件不用登记,按键就要?
| 事件类型 | 谁产生 | 地址从哪来 |
|---|---|---|
| 定时器事件 | 你自己调 osal_start_timerEx(task_id, ...) | 调用时你亲手填了地址,不用登记 |
| 按键事件 | HAL 层检测(按键瞬间你不在场) | 必须提前登记,让系统记住你的地址 |
⑤ 为什么只允许一个任务登记? 协议栈里 ZDO、ZCL、应用层都是任务,都收会重复处理。所以设计成单接收者——SampleSwitch 里应用层 zclSampleSw_Init() 最后初始化,抢先登记成功。
生活类比:物业(OnBoard)收到按键快递(KEY_CHANGE 消息),但只有一个收件箱(RegisteredTaskID)。你提前登记"我的信箱号"(RegisterForKeys),快递才会投给你;不登记,快递员不知道给谁,快递就被退回去了。
第 2 步:写应用回调函数(★你只写这个)
static void zclSampleSw_HandleKeys(byte shift, byte keys) {
if (keys & HAL_KEY_SW_6) { // 按下了独立按键 S1(S4/P0_1)?
// 点亮并闪烁 LED1(原站实验:按键 → 闪烁 LED)
HalLedSet(HAL_LED_1, HAL_LED_MODE_ON);
HalLedBlink(HAL_LED_1, 10, 50, 400); // 闪 10 次:亮 200ms 灭 200ms
}
}回调签名必须是两个参数 (byte shift, byte keys)——shift 是修饰键(一般不用),keys 是按位打包的按键状态,用 keys & HAL_KEY_SW_x 判断哪个键。
生活类比:回调就是你留给前台的电话号码——按键事件发生,HAL 反过来打电话(调用)给你。你只写"接到电话后怎么办"。
第 3 步:事件处理函数收消息(框架已写好,看懂即可)
注册后按键被按下,会产生应用层系统事件 KEY_CHANGE。SampleSwitch 的事件处理函数里已有收信代码:
uint16 zclSampleSw_event_loop(uint8 task_id, uint16 events) {
if (events & SYS_EVENT_MSG) {
uint8 *msg_ptr;
while ((msg_ptr = osal_msg_receive(zclSampleSw_TaskID)) != NULL) {
if (*msg_ptr == KEY_CHANGE) { // 是按键变化消息?
// 消息第 2 个字节带着 keys,交给你的回调
zclSampleSw_HandleKeys(0, *(uint8 *)(msg_ptr + 1));
}
osal_msg_deallocate(msg_ptr); // 用完释放(内存铁律)
}
return (events ^ SYS_EVENT_MSG);
}
return 0;
}不同版本写法略有差异(有的用
switch (MSGpkt->hdr.event) { case KEY_CHANGE: ... }),本质相同:消息头是 KEY_CHANGE → 取出按键状态 → 调回调。
三、启用按键的宏定义(别漏!)
按键驱动默认已启用(HAL_KEY=TRUE 在 hal_board_cfg.h 中)。若你的工程没启用,在 IAR 的 Options → C/C++ Compiler → Preprocessor → Defined symbols 中添加:
HAL_KEY=TRUE可选:想用中断方式检测按键(按下立刻响应、更省电),再加一行:
ISR_KEYINTERRUPT不加
ISR_KEYINTERRUPT就是轮询方式(HAL 每 100ms 查一次)。两种方式的区别和原理,见 5.2 选修。
四、完整实验流程
- 打开工程:SampleSwitch;
- 确认登记:
zclSampleSw_Init()里有RegisterForKeys(zclSampleSw_TaskID);; - 写回调:实现
zclSampleSw_HandleKeys(见上文第 2 步,判断HAL_KEY_SW_6); - 确认宏:
HAL_KEY=TRUE已定义(必要时加ISR_KEYINTERRUPT); - 编译:右键工程 → Rebuild All,确认
0 Error(s); - 下载运行:Download and Debug → Go;
- 测试:按下 S4(P0_1),LED1 点亮并闪烁 10 次。

五、变式挑战(课后)
| 挑战 | 提示 |
|---|---|
| 按一下亮、再按一下灭 | 回调里用 HalLedSet(HAL_LED_1, HAL_LED_MODE_TOGGLE) |
| 按键让红/绿/蓝三色 LED 分别闪烁(原站版) | if(keys & HAL_KEY_SW_6) 里分别调 HalLedBlink(HAL_LED_1, 10, 50, 400) / HAL_LED_2, 1000 / HAL_LED_3, 2000 |
| 用板上其他按键(S3/S5) | 先查工程 hal_key.c 的引脚映射,换对应 HAL_KEY_SW_x |
| 长按 1 秒才响应 | 配合定时器事件计时(3.3 套路) |
| 想搞懂按键"为什么这么工作" | 见 5.2 选修:HAL 按键框架详解 |
小结
| 步骤 | 做什么 | 代码 |
|---|---|---|
| 登记 | 告诉系统"按键消息发给我" | RegisterForKeys(zclSampleSw_TaskID) |
| 写回调 | 判断按了哪个键、干什么 | if (keys & HAL_KEY_SW_6) HalLedBlink(...) |
| 开宏 | 启用按键驱动(可选中断) | HAL_KEY=TRUE(ISR_KEYINTERRUPT) |
一句话总结:按键实验 = "登记(RegisterForKeys)→ 写回调(判断 HAL_KEY_SW_x)→ 开宏(HAL_KEY=TRUE)",框架负责把"按键变化"打包成 KEY_CHANGE 消息送上门,你只写"收到后怎么办"。注意按键编号以工程 hal_key.c 映射为准,别把五向键宏当独立键用!
内容来源:紫甲 IoT《ZigBee 3.0 开发指南》第三部分·5.1 节(原站:按下按键闪烁 LED),本站按 GEC2530 开发板(对照底板原理图)与 SampleSwitch 工程重写;原站链接