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 bufferzb_uint8_t size - size to allocatezb_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 bufferzb_uint8_t size - size to allocatezb_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 bufferzb_uint8_t size - size to allocatezb_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 bufferzb_uint8_t size - size to cutzb_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 bufferzb_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 buffertype - 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 bufferzb_void_t * param - pointer to the user datatype - 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. |
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:
typedef void (* zb_callback_t)(zb_uint8_t param);
typedef void (* zb_callback2_t)(zb_uint8_t param, zb_uint16_t cb_param);
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 executezb_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 executezb_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 executezb_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 cancelzb_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 cancelzb_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 executezb_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.
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 valuezb_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 valuezb_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 comparezb_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. |
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()
:
In this example:
The function returns an ID of the application signal.
zboss_signal_handler() implementation example:
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 value | Description |
---|---|---|---|
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 bindzb_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 bindzb_uint8_t ep - endpoint of a device to bindzb_uint16_t cluster - cluster id to bind | zb_bool_t - agree or disagree to bind device | BDB finding and binding callback template. |
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 codezb_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. |
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 |