Z-Stack协议栈分析-LED模块

前言

TI的Zigbee协议(Z-Stack)是典型的事件驱动系统,整个系统分为多个任务,各自任务内部采用状态机非阻塞似的完成任务,
完成后,主动释放MCU控制权;下层的任务或异步事件产生的事件,将其封装成事件消息+数据,然后发送到事件处理函数,
在事件处理函数中,也是采用状态机的形式分发消息并处理。

这就是所谓的协作式调度器,它的好处,是避免了传统RTOS的重型线程调度和资源保护机制,从而可以做到无锁资源保护机制
和极度轻量化,节省RAM/ROM

关于这点,后面会详细的讨论,这里重点讨论HAL层的LED机制

注:对于HAL层的实现机制,会另有专门的文章来讨论这个话题

使用LED

Zigbee提供的接口函数如下所示

LED Interface
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96

注:这里我修改了原Zigbee接口注释,让读者更容易理解

/***************************************************************************************************
* @fn HalLedInit
*
* @brief Initialize LED Service
*
* @param init - pointer to void that contains the initialized value
*
* @return None
***************************************************************************************************/

void HalLedInit( void );

/***************************************************************************************************
* @fn HalLedSet
*
* @brief Tun ON/OFF/TOGGLE given LEDs
*
* @param led - bit mask value of leds to be turned ON/OFF/TOGGLE
* which can be OR of the following values
* - \ref HAL_LED_1
* - \ref HAL_LED_2
* - \ref HAL_LED_3
* - \ref HAL_LED_4
* - \ref HAL_LED_ALL
*
* mode - BLINK, FLASH, TOGGLE, ON, OFF mode
* which can be one of the following values
* - \ref HAL_LED_MODE_OFF
* - \ref HAL_LED_MODE_ON
* - \ref HAL_LED_MODE_BLINK
* - \ref HAL_LED_MODE_FLASH
* - \ref HAL_LED_MODE_TOGGLE
*
* @return None
***************************************************************************************************/

uint8 HalLedSet( uint8 led, uint8 mode );


/***************************************************************************************************
* @fn HalLedBlink
*
* @brief Blink the leds
*
* @param leds - bit mask value of leds to be blinked
* which can be OR of the following values
* - \ref HAL_LED_1
* - \ref HAL_LED_2
* - \ref HAL_LED_3
* - \ref HAL_LED_4
* - \ref HAL_LED_ALL
* numBlinks - number of blinks
* percent - the percentage in each period where the led
* will be on
* period - length of each cycle in milliseconds
*
* @return None
***************************************************************************************************/

void HalLedBlink( uint8 leds, uint8 cnt, uint8 duty, uint16 time );


/***************************************************************************************************
* @fn HalLedEnterSleep
*
* @brief Store current LEDs state before sleep
*
* @param none
*
* @return none
***************************************************************************************************/

void HalLedEnterSleep( void );


/***************************************************************************************************
* @fn HalLedExitSleep
*
* @brief Restore current LEDs state after sleep
*
* @param none
*
* @return none
***************************************************************************************************/

void HalLedExitSleep( void );


/***************************************************************************************************
* @fn HalGetLedState
*
* @brief Get LEDs on/off current state.
*
* @param none
*
* @return led state
***************************************************************************************************/

uint8 HalLedGetState ( void );

Zigbee中,LED是非阻塞式的,你调用函数后,LED会在后台自己完成对应动作(比如闪烁50次)。这种统一后台管理设备的方式,对系统资源占用极小,很值得我们学习。

API很简单,基本流程是:

1:调用HalLedInit初始化LED
2:在用户代码中,调用HalLedSet或者HalLedBlink打开,关闭,闪烁LED
3:根据需要,调用休眠相关函数HalLedEnterSleepHalLedExitSleep,或者调用HalLedGetState获取LED整体状态

代码过于简单,例程留给各位来完成

初始化

实际上,Zigbee在系统初始化的时候,已经帮我们完成LED硬件初始化了,在hal_drivers.c文件中,我们可以看到下面的代码

1
2
3
4
5

/* LED */
#if (defined HAL_LED) && (HAL_LED == TRUE)
HalLedInit();
#endif

从这里我们看到,只要我们定义了HAL_LED宏,且设置为TRUE条件时,HalLedInit函数便会生效。问题是:HAL_LED在哪定义的呢?
这个要看开发平台的配置,不同平台,可能支持的LED不一样或者LED需要留给用户来初始化,所以需要平台配置文件hal_board_cfg.h
,在这个文件里,可以看到下面代码

1
2
3
4
5
6
7
/* Set to TRUE enable LED usage, FALSE disable it */
#ifndef HAL_LED
#define HAL_LED TRUE
#endif
#if (!defined BLINK_LEDS) && (HAL_LED == TRUE)
#define BLINK_LEDS
#endif

可以看到默认情况下,会启用HAL_LED TRUEBLINK_LEDS宏,如果您希望默认关闭LED,则将

1
#define HAL_LED TRUE

改成

1
#define HAL_LED FALSE

即可

平台配置

说到这里,有人该问了,我的硬件平台和TI官方LED接口不一样,在哪里修改呢?

上面讲的全是面向APP上层用户的接口,对于LED驱动的编写者,需要实现LED接口的硬件驱动机制和适配平台。

TI的Z-Stack在将LED的驱动层,再次分层,用宏屏蔽最低层的硬件操作,比如下面这种

1
2
3
4
5
/* 1 - Green */
#define LED1_BV BV(0)
#define LED1_SBIT P1_0
#define LED1_DDR P1DIR
#define LED1_POLARITY ACTIVE_HIGH

来封装硬件平台接口,一般情况下,我们主要的移植工作也就是修改宏实现的硬件接口,从而适配我们的平台,
这部分代码,在hal_board_cfg.h文件的LED section中,具体请打开这个文件,细细品味

有了硬件接口(HAL),我们便可以操作这些接口来完成特定的动作,比如打开/关闭/闪烁
这部分操作是通用的,在hal_led.c中实现,比如完成HalLedOnOff函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
/***************************************************************************************************
* @fn HalLedOnOff
*
* @brief Turns specified LED ON or OFF
*
* @param leds - LED bit mask
* mode - LED_ON,LED_OFF,
*
* @return none
***************************************************************************************************/

void HalLedOnOff (uint8 leds, uint8 mode)
{

if (leds & HAL_LED_1)
{
if (mode == HAL_LED_MODE_ON)
{
HAL_TURN_ON_LED1();
}
else
{
HAL_TURN_OFF_LED1();
}
}

if (leds & HAL_LED_2)
{
if (mode == HAL_LED_MODE_ON)
{
HAL_TURN_ON_LED2();
}
else
{
HAL_TURN_OFF_LED2();
}
}

if (leds & HAL_LED_3)
{
if (mode == HAL_LED_MODE_ON)
{
HAL_TURN_ON_LED3();
}
else
{
HAL_TURN_OFF_LED3();
}
}

if (leds & HAL_LED_4)
{
if (mode == HAL_LED_MODE_ON)
{
HAL_TURN_ON_LED4();
}
else
{
HAL_TURN_OFF_LED4();
}
}

/* Remember current state */
if (mode)
{
HalLedState |= leds;
}
else
{
HalLedState &= (leds ^ 0xFF);
}
}
#endif /* HAL_LED */

总结一下,硬件驱动分为<逻辑驱动>和<硬件操作>,逻辑操作调用硬件屏蔽宏和RTOS的资源来完成HAL LED接口要求的特定动作。

再次强调下,一般移植时,只需修改hal_board_cfg.h中LED部分接口即可

具体实现

从上面来看,LED的架构设计的非常好

  • 非阻塞式实现
  • 抽象了硬件层
  • 非常灵活,能很方面的移植到其它平台

下面我们来研究一下LED模块的实现原理,上面已经谈了抽象层的实现,这里重点讨论下LED设备管理机制

首先,用一个数据结构表示单个LED

1
2
3
4
5
6
7
8
9

/* LED control structure */
typedef struct {
uint8 mode; // LED模式,比如ON/OFF/BLINK等
uint8 todo; // 剩余的次数
uint8 onPct; // 占空比
uint16 time; // 闪烁周期(S)
uint32 next; // 下次动作时间
} HalLedControl_t;

我们知道,数据结构的制定和应用需求有密切相关,在前文中,我们看到,对单个LED的操作模式有

*   ON     打开LED
*   OFF    关闭LED
*   BLINK  快闪LED
*   FLLASH 闪烁LED
*   TOGGLE 反转LED

具体分析这些行为,可以知道他们的行为由下面几个基本元素组成

1:当前LED状态
2:下一个动作触发时间
3:下一个动作
4:剩余动作次数

这也是状态机的典型机制,比如我们希望LED以20Hz的频率闪烁5次,细分步骤

1:记录LED当前状态
2:关闭LED
3:计时25MS
4:打开LED
5:延迟25MS
6:计数值增1,判断是否到5次
   到?  进入(7)状态
   不到?进入(2)状态
7:将LED状态恢复到闪烁前

其它状态,也可以类似分析

这里的主要难点是:

1:怎么实现无阻塞延时?
2:怎么管理多个LED?
3:多个LED的状态延时怎么处理?

Z-Stack的解决方法如下:

Q1:怎么实现无阻塞延时?

A1:LED单次状态切换函数(比如ON/OFF等),直接操作底层,比如HalLedOnOff这种

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void HalLedOnOff (uint8 leds, uint8 mode)
{

if (leds & HAL_LED_1)
{
if (mode == HAL_LED_MODE_ON)
{
HAL_TURN_ON_LED1();
}
else
{
HAL_TURN_OFF_LED1();
}
}
...

对于复杂的,涉及到延时的函数,比如 按固定占空比闪烁50 ,则纳入时间管理系统,将多个LED发生的事件时间进行排序,
确定最近的时间,然后设置定时器,当定时器时间到达的时候,会往HAL这个任务发送一个HAL_LED_BLINK_EVENT事件消息,事件
处理函数Hal_ProcessEvent函数会调用LED函数更新LED管理表,并适时删除已完成的LED事件

这部分比较复杂,具体代码可以参考
1:hal\common\hal_drivers.c中的Hal_ProcessEvent
2:hao\target\CC2530EB\HalLedUpdate
两个函数

Q2:怎么管理多个LED?

1
2
3
4
5
typedef struct
{
HalLedControl_t HalLedControlTable[HAL_LED_DEFAULT_MAX_LEDS];
uint8 sleepActive;
} HalLedStatus_t;

系统中,使用上述结构体,每个LED占一个数组单元,便于统一管理;sleepActive用于记录整体休眠状态,至于休眠模式的实现,
主要使用HalSleepLedState内部变量来保存休眠前的各LED状态,便于睡醒后恢复,这部分较简单,不再详述

Q3:多个LED的状态延时怎么处理?

在Q1中,已经对这个问题进行了描述,需要注意的是:因为使用的是软件定时器和事件轮询机制,无法做的非常精确。
如果您希望精确延时,请用硬件定时器触发中断,并在中断中调用HalLedUpdate函数

后记:

如果您有任何疑问,欢迎讨论,About页中有我联系方式