nRF5 SDK for Thread and Zigbee v1.0.0
Programming principals

Table of Contents

Zigbee stack memory management subsystem

The Zigbee stack does not use dynamic memory allocation during runtime; memory is allocated statically on an application start. The memory management system provides predictable memory usage and minimizes data copying at runtime when possible. The main data structure defined in the memory management system is a memory buffer, which transfers packets and parameters between the user application and stack layers.

According to the standard, packet maximal size is 127 bytes and cannot be fragmented. The same memory buffer can be used for a single packet going down-up or up-down between the user application and stack layers. The application or layer can modify the memory buffer, remove data partly or fully, or add more data if there is free memory space in the buffer.

The Zigbee stack uses a predefined pool of the fixed size memory buffers. A memory buffer consists of a header and a data buffer of 148 bytes. An application or stack function can get a single memory buffer from the pool (allocate the memory buffer) when necessary, or put the buffer back when it is no longer needed (release the memory buffer). If no buffer is available, the memory management system enables the application to wait asynchronously until some buffer is released, and allocates this buffer to a caller ZB_GET_OUT_BUF_DELAYED() , ZB_GET_IN_BUF_DELAYED().

To avoid data copying, when a packet moves up between the Zigbee layers, the data related to the lower layers is removed by modifying the data pointer and data length in the buffer header. When a packet is passing down between the Zigbee layers, the initial data is stored in the middle, leaving space reserved before and after the data. Therefore, if a lower Zigbee layer puts data onto the head or tail of a packet, data copying is generally not required. However, if there is not enough space for a head or tail, the existing data may be moved to the right or to the left.

Logically, the memory buffers are divided into 2 types: in packets and out packets. Therefore, the buffer pool allocates both in and out types of buffers. This approach helps prevent allocating of all the memory buffers for incoming or outgoing packets, only those that may block from sending or receiving data. Such blocking may cause a scenario where it is impossible to send an acknowledgment for the received packet and the packet will be retransmitted by a remote due to the lack of the acknowledgment.

Memory buffers are commonly used for passing parameters to API functions. Space needed for storing the parameters is allocated in the memory buffer at the end of its data buffer. The limitation is that both the caller and called function must be aware of the parameter sizes, making it impossible to pass parameters with a variable length. Special helpers are provided for storing and getting parameters from the memory buffer: ZB_SET_BUF_PARAM() and ZB_GET_BUF_PARAM().

The common practice is to use a so-called buffer reference instead of a buffer pointer. The buffer reference is the number of the memory buffer in the pool. To get the buffer by its reference or the reference for the buffer, the following helpers are defined: ZB_BUF_FROM_REF(ref) and ZB_REF_FROM_BUF(buf). A reference with a value of zero is treated as an invalid value.

The following memory management API calls are used to reserve a space in the memory buffer: ZB_BUF_INITIAL_ALLOC(), ZB_BUF_ALLOC_LEFT(), and ZB_BUF_ALLOC_RIGHT().

Macro Parameters Return value Description
ZB_BUF_FROM_REF(ref) zb_uint8_t ref - memory buffer reference zb_buf_t *buf - memory buffer Get a pointer to the memory buffer using its reference.
ZB_REF_FROM_BUF(buf) zb_buf_t *buf - memory buffer zb_uint8_t ref - buffer reference Get a reference (index) to the memory buffer.
ZB_BUF_BEGIN(buf) zb_buf_t *buf - memory buffer zb_uint8_t *data - data begin Get a pointer to the first allocated data element in the data buffer.
ZB_BUF_LEN(buf) zb_buf_t *buf - memory buffer zb_uint8_t length - data length Get the allocated data size.
ZB_BUF_INITIAL_ALLOC(buf, size, ptr) zb_buf_t *buf - memory buffer
zb_uint8_t size - size to allocate
zb_void_t * ptr - (out) pointer to the allocated data buffer
None Allocate a buffer in the memory buffer. If possible, the buffer is allocated starting with an offset in the data buffer. The existing buffer content is lost.
ZB_BUF_ALLOC_LEFT(buf, size, ptr) zb_buf_t *buf - memory buffer
zb_uint8_t size - size to allocate
zb_void_t * ptr - (out) pointer to the allocated buffer
None Extend the currently allocated buffer to the left. Commonly used for allocating a buffer for a packet header.
ZB_BUF_ALLOC_RIGHT(buf, size, ptr) Zb_buf_t *buf - memory buffer
zb_uint8_t size - size to allocate
zb_void_t * ptr - (out) pointer to the allocated buffer
None Extend the currently allocated buffer to the right. Commonly used for allocating a buffer for a packet tail.
ZB_BUF_CUT_LEFT(buf, size, ptr) zb_buf_t *buf - memory buffer
zb_uint8_t size - size to cut
zb_void_t * ptr - (out) pointer to new buffer beginning
None Cut a buffer beginning. It is normally used for cutting a packet header.
ZB_BUF_CUT_RIGHT(buf, size) zb_buf_t *buf - memory buffer
zb_uint8_t size - size to cut
None Cut a buffer ending. It is usually used for cutting a packet tail.
ZB_GET_BUF_PARAM(buf, type) zb_buf_t *buf - memory buffer
type - requested parameter type
type * ptr - pointer to the parameters Get a pointer to the structured parameters. The parameters are stored at the tail of the data buffer.
ZB_SET_BUF_PARAM_PTR(buf, param, type) zb_buf_t *buf - memory buffer
zb_void_t * param - pointer to the user data
type - parameter type
None Store the structured parameters. The parameters are stored at the tail of the data buffer.
ZB_BUF_REUSE(buf) zb_buf_t *buf - memory buffer None Reuse a buffer, discard data, re-initialize internals of the memory buffer.
ZB_FREE_BUF(buf) zb_buf_t *buf - memory buffer None Release a memory buffer.
ZB_GET_IN_BUF() None zb_buf_t *buf - memory buffer Get IN buffer from the buffer pool. If no buffer is available, return NULL.
ZB_GET_OUT_BUF() None zb_buf_t *buf - memory buffer Get OUT buffer from the buffer pool. If no buffer is available, return NULL.
ZB_GET_IN_BUF_DELAYED(callback) zb_callback_t callback - pointer to a callback function. zb_ret_t RET_OK or error code Get IN buffer from the buffer pool; call the specified callback when the buffer is available.
If the buffer is available, schedule the callback for execution immediately. Otherwise, wait until a buffer is available and execute the callback then.
ZB_GET_OUT_BUF_DELAYED(callback) zb_callback_t callback - pointer to a callback function. zb_ret_t RET_OK or error code Get OUT buffer from the buffer pool; call the specified callback when the buffer is available.
If the buffer is available, schedule the callback for execution immediately. Otherwise, wait until a buffer is available and execute the callback then.
ZB_GET_OUT_BUF_DELAYED2(callback, user_param) zb_callback2_t callback - pointer to a callback function.
zb_uint16_t user data - will be passed to callback.
zb_ret_t RET_OK or error code Get OUT buffer from the buffer pool. The behavior is the same with ZB_GET_OUT_BUF_DELAYED(), but together with the allocated buffer the user parameter is passed to the callback as a second argument.
ZB_BUF_IS_OOM_STATE() None ZB_TRUE or ZB_FALSE Check buffer pool. Return TRUE if out of memory conditions are met.

Scheduler

The framework provides a cooperative multitasking model that is used by the stack as well as any user application. The main component that provides multitasking is the scheduler. There is no "task" abstraction in the scheduler, it uses a "callback" instead. The callback is a base primitive of the scheduler and is a pointer to a function of a predefined type with 1 or 2 input parameters:

Running a task in the scheduler simply executes a callback function. Each callback function may initiate a call of one or more callback functions through the scheduler, meaning the callbacks are scheduled for execution. The scheduled callbacks are stored in the internal scheduler queue. This queue is handled in the scheduler main loop: the callbacks are executed one-by-one in FIFO order. If there is currently no callback to execute, the scheduler may put the device in sleep mode (if configured so).

Callbacks are scheduled for execution with ZB_SCHEDULE_CALLBACK for the callback with one parameter or ZB_SCHEDULE_CALLBACK2 for the callbacks with 2 parameters. A common practice is to specify a buffer reference as a first (or as a single) parameter for the callback. Scheduling a callback does not block the currently running function since it is a fast non-blocking operation. More than one callback can be scheduled successively. If there is no other callback in the queue, the scheduled callback will be executed immediately after the current callback is finished.

Timer functionality uses the scheduler infrastructure and is implemented using alarms. An alarm is a callback that is scheduled for execution with a defined time delay. The time delay is specified while scheduling the alarm and guarantees that the alarm callback function will be called not earlier than specified by the delay parameter. Precise timing is not guaranteed: the alarm callback may be called later than the defined time delay.

There are API primitives provided to work with the alarms. ZB_SCHEDULE_ALARM is used to schedule a callback with a given parameter and a defined timeout. Similar to callbacks, alarms are executed in the main scheduler loop. The second one, ZB_SCHEDULE_ALARM_CANCEL, is used to cancel a previously scheduled alarm.

The following table lists the Scheduler APIs:

Macro Parameters Return value Description
ZB_SCHEDULE_CALLBACK(func, param) zb_callback_t func - function to execute
zb_uint8_t param - callback parameter - usually, but not always a reference to a memory buffer
zb_ret_t RET_OK or error code. Schedule a callback function with one parameter for execution.
ZB_SCHEDULE_CALLBACK2 (func, param, user_param) zb_callback_t func - function to execute
zb_uint8_t param - callback parameter zb_uint16_t user_param- user parameter
zb_ret_t RET_OK or error code. Schedule a callback function with two parameters for execution.
ZB_SCHEDULE_ALARM(func, param, timeout_bi) zb_callback_t func - function to execute
zb_uint8_t param - callback parameter zb_time_t timeout_bi - timeout, in beacon intervals
zb_ret_t RET_OK or error code. Schedule an alarm for execution: a callback with one parameter to be executed after a delay defined by the timeout value.
ZB_SCHEDULE_ALARM_CANCEL( func, param) zb_callback_t func - a scheduled function to cancel
zb_uint8_t param - callback parameter
zb_ret_t RET_OK or error code. Cancel previously scheduled alarm.
ZB_SCHEDULE_ALARM_CANCEL_AND_GET_BUF(func, param, p_param) zb_callback_t func - a scheduled function to cancel
zb_uint8_t param - callback parameter zb_uint8_t p_param - returned parameter
zb_ret_t RET_OK or error code.Cancel previously scheduled alarm and return a parameter defined while scheduling. This API is used to prevent buffer leak.
ZB_SCHEDULE_GET_ALARM_TIME( func, param, timeout_bi) zb_callback_t func - function to execute
zb_uint8_t param - callback parameter zb_time_t timeout_bi - time, left for an execution, in beacon intervals
zb_ret_t RET_OK or error code. Check if an alarm was scheduled and return time left for the callback execution.

The example below shows multitasking usage of callbacks and alarms. A function func1() calls a function func2() as a callback. Then, the func2() schedules an alarm and defines the func1() as a callback function to be executed in 10 seconds, calling each other in an infinite loop.

void func1(zb_uint8_t param)
{
ZB_SCHEDULE_CALLBACK(func2, param);
}
void func2(zb_uint8_t param)
{
ZB_SCHEDULE_ALARM (func1, param, 10 * ZB_TIME_ONE_SECOND));
}
void main()
{
func1(1);
while(1)
{
}
}

Time subsystem

The time subsystem is a part of the framework that is based on a hardware timer and provides an API for handling variables of zb_time_t type. The stack uses a beacon interval as a basic time measurement unit. A type zb_time_t is defined for working with time variables in the framework. The time value is stored in ticks, with each tick equal to one beacon interval (15.36 ms according to the IEEE 802.15.4 specification). zb_time_t type size depends on a platform and has a limited capacity (usually 16 bits), so it may overflow. The provided time API handles time value overflow, with the assumption that the time values will not differ for more than half of the maximum time value.

In the following table, the time APIs are listed.

Macro Parameters Return value Description
ZB_TIMER_GET () None zb_time_t time Get current timer value in beacon intervals
ZB_TIME_SUBTRACT(a, b) zb_time_t a - time value
zb_time_t b - time value to subtract
zb_time_t time - subtraction result Subtract time value: a - b
ZB_TIME_ADD(a, b) zb_time_t a - time value
zb_time_t b - time value
zb_ret_t time -addition result Add time values: a + b
ZB_TIME_GE(a, b) zb_callback_t func - first time value to compare
zb_uint8_t param - second time value to compare
zb_bool_t ZB_TRUE if a ≥ b, ZB_FALSE otherwise Compare time value a and value b, check if a ≥ b
ZB_TIME_ONE_SECOND None zb_time_t time - one second timeout Constant: one second in beacon intervals
ZB_MILLISECONDS_TO_BEACON_INTERVAL(ms) zb_uint16_t ms - number of milliseconds to convert zb_time_t time - ms timeout Convert time from milliseconds to beacon intervals.

Zigbee event handling

Since the full-featured Zigbee stack is implemented, all Zigbee commands and events are processed. In addition, there are tools that allow an application to handle some Zigbee events like a network formation, association status, leave indication and others. Due to its nature, different Zigbee events appear asynchronously and the stack informs the application using application signals. The application may handle or ignore a received signal. All application signals are processed in the application with a predefined callback function zboss_signal_handler(). It is mandatory for each application to implement this function, even if there is no need to process signals. The signal handler function prototype is:

Here, param is a reference to a memory buffer. There are some policies that should be taken into account while implementing the zboss_signal_handler() function:

An application signal is described with the following parameters:

To get the signal description, the application calls zb_get_app_event():

zb_zdo_app_signal_t zb_get_app_event(zb_uint8_t param, zb_zdo_app_event_t **ev_p);

In this example:

The function returns an ID of the application signal.

zboss_signal_handler() implementation example:

/**@brief ZigBee stack event handler.
@param[in] param Reference to ZigBee stack buffer used to pass arguments (signal).
*/
{
zb_zdo_app_signal_hdr_t * p_sg_p = NULL;
zb_zdo_signal_leave_params_t * p_leave_params = NULL;
switch(sig)
{
if (status == RET_OK)
{
NRF_LOG_INFO("Joined network successfully");
}
else
{
NRF_LOG_ERROR("Failed to join network. Status: %d", status);
}
break;
if (status == RET_OK)
{
NRF_LOG_INFO("Network left. Leave type: %d", p_leave_params->leave_type);
/* handle leave parameters leave_params */
}
else
{
NRF_LOG_ERROR("Unable to leave network. Status: %d", status);
}
break;
if (status != RET_OK)
{
NRF_LOG_WARNING("Production config is not present or invalid");
}
break;
default:
/* Unhandled signal. For more information see: zb_zdo_app_signal_type_e and zb_ret_e */
NRF_LOG_INFO("Unhandled signal %d. Status: %d", sig, status);
}
if (param)
{
ZB_FREE_BUF_BY_REF(param);
}
}

BDB commissioning

The stack implements procedures for the following commissioning modes:

An implementation may use commissioning at any time. For example, network steering can be started at any time for the whole node, and finding and binding can be performed at any time on any application endpoint. The commissioning procedure is initiated by triggering a top-level commissioning procedure using bdb_start_top_level_commissioning() function. The commissioning API is seen in the following table:

Function Parameters Return valueDescription
bdb_start_top_level_commissioning(mode_mask) zb_uint8_t mode_mask - mask used to set bits of the bdbCommissioningMode attribute None Initiate BDB commissioning procedure defined by the mode_mask.
zb_bdb_finding_binding_target(endpoint) zb_uint8_t endpoint - endpoint to bind None Set finding and binding target mode on the endpoint.
zb_bdb_finding_binding_initiator(endpoint, user_binding_cb) zb_uint8_t endpoint - endpoint to bind
zb_bdb_comm_binding_callback_t user_binding_cb - user callback function
None Initiate finding and binding on the endpoint. Pass result with user_binding_cb.
typedef zb_bool_t (ZB_CODE * zb_bdb_comm_binding_callback_t)(status, addr, ep, cluster) zb_int16_t status - status (ask user, success or fail)
zb_ieee_addr_t addr - extended address of a device to bind
zb_uint8_t ep - endpoint of a device to bind
zb_uint16_t cluster - cluster id to bind
zb_bool_t - agree or disagree to bind device BDB finding and binding callback template.

API for installing codes security

The stack provides code installation functionality. The API for the install codes based security is described in the table below:

Function Parameters Return value Description
zb_secur_ic_add(address, ic); zb_ieee_addr_t address - long address of the device to add the install code
zb_uint8_t *ic - pointer to the install code buffer
zb_ret_t RET_OK on success Add the device install code with the specified long address. Only for coordinators.
zb_secur_ic_set(zb_uint8_t *ic) zb_uint8_t *ic - pointer to the install code buffer zb_ret_t RET_OK or error code Add the install code for the device.

Debugging

The Zigbee stack provides tracing capabilities for customer support purposes. The stack outputs a HEX encoded binary trace through the NRF_LOG logging module. The binary trace log cannot be decoded with the provided SDK, but it is a very powerful tool for finding stack-level issues by Nordic's support team.

The trace may be enabled or disabled for a particular subsystem, and the level of tracing can be defined. In addition, the stack is able to trace Zigbee input/output traffic (in/out) on the MAC level.

The Zigbee SDK provides only release versions of libraries. The release version does not include stack trace messages except error messages.

The trace mask enables or disables trace messages for a whole subsystem:

Macro Value Description
TRACE_SUBSYSTEM_APP 0x0800 Application
TRACE_SUBSYSTEM_ZCL 0x0100 ZCL subsystem
TRACE_SUBSYSTEM_ZDO 0x0040 ZDO subsystem
TRACE_SUBSYSTEM_NWK 0x0008 NWK subsystem

Tracing is controlled by the trace level (1 - 4) and the trace mask. The trace level defines the importance of the message: 1 is the most important, 4 is the least important. If an application sets the trace level to 4, it means that all trace messages are allowed to print. Setting the trace level to 1 blocks all the messages except those with the highest priority.

Although the trace subsystem is included, tracing is disabled by default. The trace API is used to manage tracing: enable/disable subsystems, set trace level, etc.

Macro Parameters Return value Description
ZB_SET_TRACE_LEVEL(level) zb_uint8_t level - trace level None Set trace level
ZB_SET_TRACE_MASK(mask) zb_uint8_t mask - trace mask None Set trace mask
ZB_SET_TRACE_ON() None None Enable trace
ZB_SET_TRACE_OFF() None None Disable trace
ZB_SET_TRAF_DUMP_ON() None None Enable dump
ZB_SET_TRAF_DUMP_OFF() None None Disable dump

Documentation feedback | Developer Zone | Subscribe | Updated