一些常用的功能由实用程序处理,开发人员可以根据应用程序对其进行调整。
1 AGI模块
AGI模块与Association Plus模块一起工作,为处理多个Z-Wave设备之间的关联的设置和使用提供通用API。AGI模块与“CC_AssociationGroupInfo.h”接口,用于外部访问AGI表。
图6,AGI的行为图
AGI模块包含一个配置部分和一个API,用于为特定关联组提取目标设备。
1.1 AGI配置
AGI模块有一个名为AGI_Init()的构造函数,需要在配置AGI组之前调用它。 构造函数调用重置模块参数。
void AGI_Init(void);
组1 (Lifeline)的配置是在调用CC_AGI_LifeLineGroupSetup函数来设置profile和命令类组时完成的。 此功能还用于配置组1(端点Lifeline)的每个端点。 对于根设备配置,端点参数必须设置为ENDPOINT_ROOT (0):
void CC_AGI_LifeLineGroupSetup(
cc_group_t const * const pCmdGrpList,
uint8_t listSize,
uint8_t endpoint);
组2的配置。 端点X的2..N是通过调用AGI_ResourceGroupSetup函数完成的。 该函数设置一个类型为AGI_GROUP的列表,包括profile、命令类组和组名。 对于根设备配置,端点参数必须设置为ENDPOINT_ROOT (0)。
1.1.1 示例1:如何为Wall Controller设置AGI
图7,Wall Controller,通过关联组1(lifeline)发送Command Class Scene和Device Reset Locally,通过关联组2发送Command Class Basic
应用程序使用config_app.h的定义来配置agiTableLifeLine[]和agiTableRootDeviceGroups[]
CMD_CLASS_GRP agiTableLifeLine[] =
{
{COMMAND_CLASS_CENTRAL_SCENE, CENTRAL_SCENE_NOTIFICATION},
{COMMAND_CLASS_DEVICE_RESET_LOCALLY, DEVICE_RESET_LOCALLY_NOTIFICATION}
};
AGI_GROUP agiTableRootDeviceGroups[] =
{
{
ASSOCIATION_GROUP_INFO_REPORT_PROFILE_CONTROL,
ASSOCIATION_GROUP_INFO_REPORT_PROFILE_CONTROL_KEY01,
{COMMAND_CLASS_BASIC, BASIC_SET}, "Button 1"
}
};
void AppStateManager(EVENT_APP event)
{
switch (currentState) {
case STATE_APP_STARTUP:
if (EVENT_APP_INIT == event) {
AGI_Init();
CC_AGI_LifeLineGroupSetup(
agiTableLifeLine,
sizeof_array(agiTableLifeLine),
ENDPOINT_ROOT);
AGI_ResourceGroupSetup(
agiTableRootDeviceGroups,
sizeof_array (agiTableRootDeviceGroups),
ENDPOINT_ROOT);
:
表2,WallController的配置
1.1.2 示例2:如何使用2个按钮扩展WallController
在本例中,每个按钮代表一个端点。 将根设备关联组2移动到端点1。 当前示例中没有显示到端点的根映射,更多信息请参见9.2.1章节。
图8,扩展两个端点和删除根组2
CMD_CLASS_GRP agiTableLifeLine[] =
{
{COMMAND_CLASS_CENTRAL_SCENE, CENTRAL_SCENE_NOTIFICATION},
{COMMAND_CLASS_DEVICE_RESET_LOCALLY, DEVICE_RESET_LOCALLY_NOTIFICATION}
};
AGI_GROUP agiTableEndpoint1Groups[] =
{
ASSOCIATION_GROUP_INFO_REPORT_PROFILE_CONTROL,
ASSOCIATION_GROUP_INFO_REPORT_PROFILE_CONTROL_KEY01,
{COMMAND_CLASS_BASIC, BASIC_SET}, "Button 1"
};
AGI_GROUP agiTableEndpoint2Groups[] =
{
ASSOCIATION_GROUP_INFO_REPORT_PROFILE_CONTROL,
ASSOCIATION_GROUP_INFO_REPORT_PROFILE_CONTROL_KEY02,
{COMMAND_CLASS_BASIC, BASIC_SET}, "Button 2"
};
void AppStateManager(EVENT_APP event)
{
switch (currentState) {
case STATE_APP_STARTUP:
if (EVENT_APP_INIT == event) {
AGI_Init();
/*Root device configuration*/
CC_AGI_LifeLineGroupSetup(
agiTableLifeLine,
sizeof_array(agiTableLifeLine),
ENDPOINT_ROOT);
/*Endpoint configuration*/
CC_AGI_LifeLineGroupSetup(
agiTableLifeLine,
sizeof_array(agiTableLifeLine),
ENDPOINT_1);
AGI_ResourceGroupSetup(
agiTableEndpoint1Groups,
sizeof_array(agiTableEndpoint1Groups),
ENDPOINT_1);
CC_AGI_LifeLineGroupSetup(
agiTableLifeLine,
sizeof_array(agiTableLifeLine),
ENDPOINT_2);
AGI_ResourceGroupSetup(
agiTableEndpoint2Groups,
sizeof_array(agiTableEndpoint2Groups),
ENDPOINT_2);
:
表3,向WallController添加端点
1.2 使用AGI
AGI模块处理从Association模块读取目标节点列表。 配置完AGI模块后,应用程序不再访问AGI模块。 命令类在为主动请求的事件作业调用ReqNodeList()函数时访问AGI模块。
TRANSMIT_OPTIONS_TYPE_EX * ReqNodeList(
AGI_PROFILE const * const pProfile,
CMD_CLASS_GRP* pCurrentCmdGrp,
uint8_t sourceEndpoint);
2 Association模块
Association模块是一个处理所有关联的小型数据库。Association和Multi-Channel AssociationsCommand Class模块与Association模块接口,用于添加或删除关联。 数据库存储在NVM中。AGI模块处理从数据库中读取节点列表。
图9,Association行为图
2.1 初始化
Association模块的初始化是在SetDefaultConfiguration和LoadConfiguration函数中完成的,并且依赖于对端点的支持。
关联的数量根据以下定义计算:
组中的关联数: MAX_ASSOCIATION_IN_GROUP
关联组个数: MAX_ASSOCIATION_GROUPS
单个端点数量: NUMBER_OF_INDIVIDUAL_ENDPOINTS
聚合端点数量: NUMBER_OF_AGGREGATED_ENDPOINTS
终端的总数是设备中单个终端和聚合终端的总和: NUMBER_OF_ENDPOINTS
定义在config_app.h文件中配置,并在association_plus.h中使用。
当没有实现端点时,应用程序必须调用AssociationInit()函数。 参数forceClearMem用于清除关联的NVM数据,pFS是指向NVM中应用文件系统的指针。
void AssociationInit(bool forceClearMem, nvm3_Handle_t* pFS);
当应用程序实现端点时,应用程序必须调用AssociationInitEndpointSupport()函数。
void AssociationInitEndpointSupport(
bool forceClearMem,
ASSOCIATION_ROOT_GROUP_MAPPING* pMapping,
uint8_t nbrGrp,
nvm3_Handle_t* pFS);
该函数接受以下参数:
—“forceClearMem”用于清除关联的NVM数据。
—“pMapping”用于向后兼容非多通道设备。 根组映射用于配置根设备以发布代表端点的关联组。
—“nbrGrp” 为pMapping列表中的组数。
—“pFS”是NVM中指向应用文件系统的指针。
2.1.1 例3:如何使用组映射
在本例中,WallController根设备有2个关联组,并将它们映射到它的端点; 参见9.1.1.2中的前一个示例。 目标是从[[Root Device, group 2]映射到[endpoint 1, group 2],从[Root Device, group 3]映射到[endpoint 2, group 2]。 更多信息可以在[6]中找到。
图10, Wall Controller的Root Device Group Mapping
AGI_GROUP agiTableRootDeviceGroups[] =
{
{
ASSOCIATION_GROUP_INFO_REPORT_PROFILE_CONTROL,
ASSOCIATION_GROUP_INFO_REPORT_PROFILE_CONTROL_KEY01,
{COMMAND_CLASS_BASIC, BASIC_SET}, "Button 1"
},
{
ASSOCIATION_GROUP_INFO_REPORT_PROFILE_CONTROL,
ASSOCIATION_GROUP_INFO_REPORT_PROFILE_CONTROL_KEY02,
{COMMAND_CLASS_BASIC, BASIC_SET}, "Button 2"
}
};
/*root grp, endpoint, endpoint group*/
ASSOCIATION_ROOT_GROUP_MAPPING rootGroupMapping[] = {
{ASS_GRP_ID_2, ENDPOINT_1, ASS_GRP_ID_2},
{ASS_GRP_ID_3, ENDPOINT_2, ASS_GRP_ID_2}
};
void AppStateManager(EVENT_APP event)
{
switch (currentState) {
case STATE_APP_STARTUP:
if (EVENT_APP_INIT == event) {
AGI_Init();
/*Root device configuration*/
AGI_ResourceGroupSetup(
agiTableRootDeviceGroups,
sizeof_array(agiTableRootDeviceGroups),
ENDPOINT_ROOT);
:
}
void SetDefaultConfiguration(void)
{
:
AssociationInitEndpointSupport(TRUE,
rootGroupMapping,
sizeof_array(rootGroupMapping));
:
}
bool LoadConfiguration(void)
{
:
AssociationInitEndpointSupport(FALSE,
rootGroupMapping,
sizeof_array(rootGroupMapping));
:
}
表4,AGI Root 组2 和3 (被映射到Endpoint 1 和2)设置
3 固件更新接口模块“ota_util”
Ota_util模块处理OTA固件更新。 它与Firmware Update Meta Data CC连接接口,并向应用程序交付一个接口来管理该过程。
Ota_util API:
BYTE
OtaInit(
BOOL (CODE *pOtaStart)(WORD fwId, WORD CRC),
VOID_CALLBACKFUNC(pOtaExtWrite)( BYTE *pData, BYTE dataLen),
VOID_CALLBACKFUNC(pOtaFinish)(BYTE val));
输入参数pOtaStart和pOtaFinish用于通知应用程序固件更新的状态,并让应用程序能够控制固件更新的开始。 有可能不调用OtaInit,并且进程在没有使用txOption标准参数的应用程序的情况下运行。 输入参数pOtaExtWrite用于更新主机固件。
[3]给出了如何接口到“ota_util”模块的详细描述
4 外围设备驱动程序
有几个外设驱动程序可用(EMDRV和EMLIB),它们必须在访问所讨论的硬件设备之前链接到应用程序。EMDRV存在于较低层次的EMLIB之上。 源代码也可用于定制。
Z-Wave 700 SDK是事件驱动的,需要事件驱动设计来访问硬件设备。 已经实现了处理gpio的调用(参见9.4.1节)。 更多细节请参考下面的链接。
软件文档(例如,EMDRV和EMLIB)
https://siliconlabs.github.io/Gecko_SDK_Doc/efr32fg13/html/index.html
搜索技术资源(应用笔记)
https://www.silabs.com/support/resources.ct-application-notes.ct-examplecode.p-microcontrollers_32-bit-mcus?
32位外设示例(EMLIB)
https://github.com/SiliconLabs/peripheral_examples
4.1 GPIO
对于带有ZGM130S射频板(BRD4202A)和按钮和LED EXP板(BRD8029A)的无线启动套件主板(BRD4001A),文件板提供了对按钮和led的高级支持(注意:板目前不使用EFR32 FlexGecko板支持包)。
按钮和led到实际GPIO端口和引脚的映射在两个头文件中定义:与board.h一起包含的extension_board_4001a.h和radio_board_zgm130 .h。
应该使用ApplicationInit()中的Board_Init()对单板进行初始化。
ZW_APPLICATION_STATUS ApplicationInit(EResetReason_t eResetReason)
{
:
Board_Init();
:
}
要订阅来自特定按钮的按钮事件,只需从应用程序线程调用Board_EnableButton()。
void Board_EnableButton(button_id_t btn);
然后应用程序将接收类型为BUTTON_EVENT的事件(<btn_id>_DOWN, <btn_id>_UP, <btn_id>_SHORT_PRESS,<btn_id>_HOLD, <btn_id>_LONG_PRESS)。
led由Board_SetLed()函数控制,该函数简单地以LED_ON或LED_OFF值作为参数:
void Board_SetLed(led_id_t led, led_state_t state);
4.1.1 串口API中的GPIO端口使用
在EFR32ZG14和ZGM130S的串口应用中,GPIO端口的使用情况分别如下:
|
|
4.2 UART驱动
UART驱动程序在收集到足够的相关数据后使用UART RX中断来唤醒应用程序。 具体操作如下:
1.定义一个新的应用事件,例如在enumEApplicationEvent中定义EAPPLICATIONEVENT_SERIALDATARX。
2. 在静态const EventDistributorEventHandler g_aEventHandlerTable中安装事件处理程序(这是一个回调函数)。
Trigger the event from the ISR
Void USART0_RX_IRQHandler() {
… Collect data …
If(enough_data) {
xTaskNotifyFromISR(g_AppTaskHandle,
1<< EAPPLICATIONEVENT_SERIALDATARX,
eSetBits,
NULL);
}
}
3. 启用UART RX IRQ(有关更多信息,请阅读标准EMLIB文档,如[21])。
事件处理程序在应用程序线程上下文中调用,这确保了应用程序中的线程安全。
4.2.1 UART通信设置
UARTAPI应用程序的UART接口使用以下设置:
参数 | 值 |
Baud | rate 115200 bits/s |
Parity | No |
Data | bits 8 |
Stop | bits 1 |
表5,串口APIUART (RS-232) 设置
每个字节的最低有效位(LSB)必须首先在物理线路上传输
4.3 ADC驱动
Z-Wave应用框架包含一个API,用于使用ZGM130S的模拟到数字转换器(ADC)测量电源电压,应用程序可以使用这个API来获得当前的电源电压水平。 典型的用例是在电池操作的设备中获取电池状态。
该API由以下函数组成:
函数 | 描述: |
void ZAF_ADC_Enable(void) | 初始化并启用ADC |
void ZAF_ADC_Disable(void) | 禁用ADC |
uint32_t ZAF_ADC_Measure_VSupply(void) | 返回以毫伏为单位的电源电压 |
API函数原型定义在下面的头文件中,这些头文件包含了很多来自应用程序的内容:
#include"ZAF_adc.h"
典型应用示例:
ZAF_ADC_Enable(); //启用ADC
VBATT_mV =ZAF_ADC_Measure_VSupply (); //读取电池电压
ZAF_ADC_Disable(); //所有完成-禁用ADC
进一步的使用示例可以在认证的Z-Wave应用程序DoorLockKeyPad和SensorPIR中找到。
5 事件分发
事件分发程序的目标是接收几个FIFO消息队列上的事件,每当有消息可用时就唤醒应用程序任务,并调用与每个队列关联的事件处理程序。
5.1 事件循环
Z-Wave应用程序任务基本上是一个调用EventDistributorDistribute()的无休止的事件循环
ApplicationTask(SApplicationHandles* pAppHandles)
{
/* Task initialization */
:
/* Endless event loop */
for (;;)
{
EventDistributorDistribute(&g_EventDistributor, 10, 0);
}
}
应用程序任务的事件到达多个队列。 如果没有任何队列需要读取消息,应用程序任务将休眠若干毫秒(在上面的示例中为10毫秒)。 每当消息到达队列时,应用程序任务将唤醒并将消息分派给配置的函数之一。
类型为sevendistributor的第一个参数包含事件分发程序的配置数据。
uint32_t EventDistributorDistribute(const SEventDistributor* pThis,
uint32_t iEventWait,
uint32_t NotificationClearMask);
与EventDistributorDistribute()一起使用的sevendistributor变量必须首先用EventDistributorConfig()初始化:
EEventDistributorStatus EventDistributorConfig(
SEventDistributor* pThis,
uint8_t iEventHandlerTableSize,
const EventDistributorEventHandler* pEventHandlerTable,
void(*pNoEvent)(void) );
pEventHandlerTable是EventDistributorEventHandler的数组,它只是一个指向事件处理程序函数的函数指针。
typedef void (*EventDistributorEventHandler)(void);
注意:参数pNoEvent,如果非空,将在EventDistributorDistribute()每次唤醒并发现没有消息要处理时被调用。
每个消息队列被分配一个通知位号。pEventHandlerTable中的事件处理函数的顺序必须与这些位数字对应; 例如,当消息到达通知位为2的队列时,将调用pEventHandlerTable中数组索引2的事件处理函数。
位号可以通过EApplicationEvent枚举方便地定义:
typedef enum
{
EAPPLICATIONEVENT_TIMER = 0,
EAPPLICATIONEVENT_ZWRX,
EAPPLICATIONEVENT_ZWCOMMANDSTATUS,
EAPPLICATIONEVENT_APP
} EApplicationEvent;
与EventDistributorConfig()一起使用的事件处理程序表可以这样定义:
static const EventDistributorEventHandler m_aEventHandlerTable[] =
{
AppTimerNotificationHandler,
EventHandlerZwRx,
EventHandlerZwCommandStatus,
EventHandlerApp
};
框架提供了AppTimerNotificationHandler()的实现,其余的函数必须由应用程序开发人员实现。
5.2 事件队列
接收Z-Wave包和Z-Wave命令结果的消息队列的创建(和通知位分配)在ApplicationInit()中使用ZW_UserTask_ApplicationRegisterTask()指定。 应用程序计时器事件不会被放在队列中,而是通过通知接口发送给应用程序,因此必须通过调用AppTimerInit()来分配一个通知位号:
ZW_APPLICATION_STATUS ApplicationInit(EResetReason_t eResetReason)
{
:
AppTimerInit(EAPPLICATIONEVENT_TIMER, NULL);
:
ZW_UserTask_ApplicationRegisterTask(ApplicationTask,
EAPPLICATIONEVENT_ZWRX,
EAPPLICATIONEVENT_ZWCOMMANDSTATUS,
&ProtocolConfig);
:
}
应用程序消息队列必须使用FreeRTOS函数xQueueCreateStatic()创建。 首先,必须为队列定义一个存储空间(在本例中,我们为队列上的5个应用程序事件分配一个空间)。 创建队列之后,必须通过QueueNotifyingInit()为它分配应用程序通知位。 最后,必须将ZAF_EventHelper模块配置为使用应用程序队列。
static EVENT_APP eventQueueStorage[5];
static StaticQueue_t m_AppEventQueueObject;
static QueueHandle_t m_AppEventQueueHandle;
static SQueueNotifying m_AppEventNotifyingQueue;
static TaskHandle_t m_AppTaskHandle;
void
ApplicationTask(SApplicationHandles* pAppHandles)
{
m_AppTaskHandle = xTaskGetCurrentTaskHandle();
:
m_AppEventQueueHandle = xQueueCreateStatic(sizeof_array(eventQueueStorage),
sizeof(eventQueueStorage[0]),
(uint8_t*)eventQueueStorage,
&m_AppEventQueueObject);
QueueNotifyingInit(&m_AppEventNotifyingQueue,
m_AppEventQueueHandle,
m_AppTaskHandle,
EAPPLICATIONEVENT_APP);
ZAF_EventHelperInit(&m_AppEventNotifyingQueue);
:
}
ZAF_EventHelper模块提供了一种将事件放置到应用程序事件队列的简单方法:
File zaf_event_helper.h:
void ZAF_EventHelperInit(SQueueNotifying * pQueueNotifyingHandle);
bool ZAF_EventHelperEventEnqueue(const uint8_t event);
bool ZAF_EventHelperEventEnqueueFromISR(const uint8_t event);
5.3 事件处理
调用EventDistributorConfig()时使用的EventDistributorEventHandler表中的每个事件处理程序都是在其消息队列上有一个或多个消息可用时简单调用的。 事件处理程序必须接收和处理来自队列的所有可用消息。 例如,应用程序事件处理程序通常会被实现如下,其中所有事件都被简单地转发到应用程序状态管理器函数:
static void EventHandlerApp(void)
{
uint8_t event;
while (xQueueReceive(m_AppEventQueue, &event, 0) == pdTRUE)
{
AppStateManager((EVENT_APP)event);
}
}
5.4 工作事件队列
应用程序通常需要临时排队事件,这些事件应该在活动作业完成后首先处理。 因此,ZAF_JobHelper模块提供了自己的作业事件队列,该队列不由事件分发程序处理。 应用程序必须根据需要显式地将作业队列上的事件加入和退出队列。 首先,应用程序必须调用ZAF_JobHelperInit()来初始化作业事件队列以保存JOB_QUEUE_BUFFER_SIZE事件。 然后可以使用ZAF_JobHelperJobEnqueue()和ZAF_JobHelperJobDequeue()在队列上放置和获取事件。
File zaf_job_helper.h:
#define JOB_QUEUE_BUFFER_SIZE 3
void ZAF_JobHelperInit(void);
bool ZAF_JobHelperJobEnqueue(uint8_t event);
bool ZAF_JobHelperJobDequeue(uint8_t * pEvent);
5.5 简单的事件处理
在最简单的形式中,仅使用应用程序事件队列,例如,在用户按下按钮时执行单个命令。 文件板使用ZAF_EventHelper模块将按钮事件发送到应用程序队列,这将导致EventHandlerApp()函数使用按钮事件激活AppStateManager()。
1. 在学习模式下,用户按下key01,将状态更改为STATE_APP_STARTUP:
void AppStateManager(EVENT_APP event)
{
:
switch(currentState) {
:
case STATE_APP_LEARN_MODE:
:
if ((BTN_EVENT_SHORT_PRESS(APP_BUTTON_LEARN_RESET) == (BUTTON_EVENT)event ||
(EVENT_SYSTEM_LEARNMODE_STOP == (EVENT_SYSTEM)event)) {
:
ChangeState(STATE_APP_STARTUP);
:
}
:
}
}
2. 当学习进程完成时,EventHandlerZwCommandStatus将被状态EZWAVECOMMANDSTATUS_LEARN_MODE_STATUS的协议触发,这将触发LearnCompleted(),从那里ZAF_EventHelperEventEnqueue((EVENT_APP)EVENT_SYSTEM_LEARNMODE_FINISHED)被调用:
static void LearnCompleted(void)
{
..:
ZAF_EventHelperEventEnqueue((EVENT_APP) EVENT_SYSTEM_LEARNMODE_FINISHED);
Transport_OnLearnCompleted(bNodeID);
}
3.在STATE_APP_STARTUP状态中,执行所需的功能,并将状态更改回空闲状态STATE_APP_IDLE。
:
case STATE_APP_STARTUP:
if (EVENT_APP_INIT == event) {
:
ChangeState(STATE_APP_IDLE);
}
:
5.6 多事件作业处理
在使用ZAF_JobHelper模块执行期间,可以加入更多的作业队列。
下面的示例演示了如何使用作业队列将按钮按下扩展为多个操作(发送一个基本设置,然后发送一个通知),这些操作将在STATE_APP_TRANSMIT_DATA状态中执行。
case STATE_APP_IDLE:
:
if ((BTN_EVENT_DOWN(PIR_EVENT_BTN) == (BUTTON_EVENT)event) ||
(BTN_EVENT_HOLD(PIR_EVENT_BTN) == (BUTTON_EVENT)event))
{
:
ChangeState(STATE_APP_TRANSMIT_DATA);
if (false == ZAF_EventHelperEventEnqueue(EVENT_APP_NEXT_EVENT_JOB))
{
DPRINT("\r\n** EVENT_APP_NEXT_EVENT_JOB fail\r\n");
}
/*Add event's on job-queue*/
ZAF_JobHelperJobEnqueue(EVENT_APP_BASIC_START_JOB);
ZAF_JobHelperJobEnqueue(EVENT_APP_NOTIFICATION_START_JOB);
ZAF_JobHelperJobEnqueue(EVENT_APP_START_TIMER_EVENTJOB_STOP);
}
在STATE_APP_TRANSMIT_DATA状态中(如下所示),事件EVENT_APP_NEXT_EVENT_JOB通过从作业事件队列获取第一个作业事件来启动作业。
每个发送函数都提供了一个指向回调函数ZCB_JobStatus()的指针,该函数将在传输完成后由框架调用。ZCB_JobStatus()将简单地将EVENT_APP_NEXT_EVENT_JOB事件放置在应用程序事件队列上,以触发对下一个作业事件的处理。
void ZCB_JobStatus(TRANSMISSION_RESULT * pTransmissionResult)
{
if (TRANSMISSION_RESULT_FINISHED == pTransmissionResult->isFinished)
{
ZAF_EventHelperEventEnqueue(EVENT_APP_NEXT_EVENT_JOB);
}
}
6 电源管理
Z-Wave芯片提供多种低功耗模式(EM)。 每个能源模式管理CPU和它的各种外设是否可用。 能量模式范围从EM0主动到EM4关闭。EM0主动模式提供最多的特性,使CPU、Radio和外围设备具有最高的时钟频率。EM4关闭模式提供最低的电源状态,允许芯片在唤醒条件下返回到EM0 激活模式。
为了实现最长的电池寿命,在电池供电的设备上使用Z-Wave应用程序至关重要,它可以尽可能地减少在高能量模式下的时间。
Z-Wave协议线程拥有的Z-Wave电源管理器控制RTOS在空闲时的能量模式。 它使用能量锁的概念,代码区域可以锁定芯片,使其不进入特定的能量模式。 电源管理器跟踪所有电源锁,并确保芯片不会在任何时候进入低于当前要求的电源模式。
要从应用程序与电源管理器交互,请使用ZAF_PM_Wrapper.h中的电源管理API。 要使用它,首先用API_IF_init()初始化接口,然后用ZAF_PM_Register()注册一个电源锁。
void API_IF_init(SApplicationHandles* pAppHandles);
void ZAF_PM_Register(SPowerLock_t* handle, pm_type_t type);
使用电源锁类型pm_type_t来告诉电源管理器电源锁是否应该“保护”需要访问无线电的代码(PM_TYPE_RADIO:不要进入EM2/EM3/EM4)或只是一些外围设备(PM_TYPE_PERIPHERAL:不要进入EM3/EM4)。
在电源锁被注册后,ZAF_PM_StayAwake()和ZAF_PM_Cancel()可以用来包装需要访问无线电或外围设备的代码:
void ZAF_PM_StayAwake(SPowerLock_t* handle, unsigned int msec);
void ZAF_PM_Cancel(SPowerLock_t* handle);
如果ZAF_PM_StayAwake()的msec参数不为零,电源锁将在指定的时间之后自动取消(在这种情况下,不需要调用ZAP_PM_Cancel())。
static SPowerLock_t m_RadioPowerLock;
static void
ApplicationTask(SApplicationHandles* pAppHandles)
{
API_IF_init(pAppHandles);
ZAF_PM_Register(&m_RadioPowerLock, PM_TYPE_RADIO);
:
}
void SomeFunction(void)
{
ZAF_PM_StayAwake(&m_RadioPowerLock, 0);
:
/* Do something where the radio module is required */
:
ZAF_PM_Cancel(&m_RadioPowerLock);
:
}
从协议到框架的几个回调支持在进入睡眠模式之前执行特定的代码。 最多有三个回调。 详细信息请参考ZAF_PM_Wrapper.h中的函数ZAF_PM_SetPowerDownCallback()。
应用程序DoorLockKeyPad(监听睡眠终端设备)和SensorPIR(报告睡眠终端设备)都使用电源锁。SensorPIR在空闲时一直到EM4,但由于侦听睡眠终端设备必须每250毫秒或1000毫秒唤醒一次,DoorLockKeyPad应用程序只能在EM2中睡眠,以实现快速唤醒时间。
7 应用程序计时器
AppTimer模块为软件计时器提供了一个应用程序接口。
File: AppTimer.h
void AppTimerInit(uint8_t iTaskNotificationBitNumber, TaskHandle_t ReceiverTask);
void AppTimerSetReceiverTask(TaskHandle_t ReceiverTask);
bool AppTimerRegister(SSwTimer* pTimer, bool bAutoReload, void(*pCallback)(SSwTimer*
pTimer) );
void AppTimerNotificationHandler(void);
所有软件计时器本质上都是运行在高优先级FreeRTOS计时器任务中的FreeRTOS计时器。 任何计时器回调通常都在计时器任务的上下文中执行。 使用AppTimer模块,您可以确保计时器回调函数在应用程序任务的上下文中执行。
注意:如果定时器超时,例如电池供电的设备在能量模式EM4 shutdown下深度睡眠,设备将被唤醒,但定时器回调函数不会被执行,因为设备将进行完全启动初始化。
#include <AppTimer.h>
static SSwTimer myAppTimer;
ZW_APPLICATION_STATUS ApplicationInit(EResetReason_t eResetReason)
{
AppTimerInit(EAPPLICATIONEVENT_TIMER, NULL);
}
关于EAPPLICATIONEVENT_TIMER通知位和框架函数AppTimerNotificationHandler()的描述,请参见9.5.1节。
在ApplicationTask()函数中,你需要向AppTimer模块注册应用任务的句柄,并注册所有的swtimers(最多可以注册8个应用计时器):
void ApplicationTask(SApplicationHandles* pAppHandles)
{
m_AppTaskHandle = xTaskGetCurrentTaskHandle();
AppTimerSetReceiverTask(m_AppTaskHandle);
AppTimerRegister(&myAppTimer, false, ZCB_MyAppTimerCallback);
}
超时回调可以像这样实现:
void ZCB_MyAppTimerCallback(SSwTimer *pTimer)
{
UNUSED(pTimer);
ZAF_EventHelperEventEnqueue(EVENT_APP_MY_TIMER_TIMEOUT);
}
要启动和停止应用程序计时器,只需使用SwTimer模块中的函数(所有超时值以毫秒为单位):
File: SwTimer.h
ESwTimerStatus TimerStart(SSwTimer* pTimer, uint32_t iTimeout);
ESwTimerStatus TimerStartFromISR(SSwTimer* pTimer, uint32_t iTimeout);
ESwTimerStatus TimerRestart(SSwTimer* pTimer);
ESwTimerStatus TimerRestartFromISR(SSwTimer* pTimer);
ESwTimerStatus TimerStop(SSwTimer* pTimer);
ESwTimerStatus TimerStopFromISR(SSwTimer* pTimer);
bool TimerIsActive(SSwTimer* pTimer);
当一个或多个应用程序计时器过期时,将调用框架函数AppTimerNotificationHandler(),该函数依次为每个过期的计时器调用计时器回调函数。
如果自动加载计时器(即自动重启)配置了一个非常小的超时值,那么在调用AppTimerNotificationHandler()之前,它可能会过期多次。 在这种情况下,超时事件将不会排队; AppTimerNotificationHandler()将只被调用一次。
在中断服务程序(ISR)中使用计时器时要小心。 由于TimerStartISR使用xTimerChangePeriodFromISR (freeRTOS)https://www.freertos.org/FreeRTOS-timers-xTimerChangePeriodFromISR.html,因此不建议使用TimerStartISR从ISR启动多个计时器。 最后,只使用ISR中专用的ISR函数。