nRF5 SDK for Thread and Zigbee v4.1.0
Programming principles

Table of Contents

The nRF5 SDK for Thread and Zigbee provides the following services for the Zigbee application developer:


Application structure

The SDK provides header files and libraries for C programming language. This means that applications must be implemented using C or C++.

The typical application source must:

Zigbee type definitions

The stack uses a set of platform-independent type definitions such as:

All ZBOSS type names start with the prefix "zb_".

Zigbee stack header files

The application must include the header file zboss_api.h, and do so before any other ZBOSS header files. This file contains most of the required routines to start the application.

Zigbee stack multitasking (scheduler)

The stack uses a cooperative multitasking model. The main component that provides multitasking capabilities is the scheduler.

Callbacks

There is no "task" abstraction in the scheduler; it uses "callback" instead. The callback has to match one of the following types:

Running a task in the scheduler simply executes a callback function.

Each callback function can schedule one or more callback functions for execution through the scheduler. 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 the FIFO order. If there is currently no callback to execute, the scheduler can put the device into sleep mode (if so configured).

Callbacks are scheduled for execution with:

A common practice is to specify a buffer ID as the first (or as the single) parameter for the callback. Scheduling a callback does not block the currently running function, as it is a fast non-blocking operation. More than one callback can be scheduled in succession. If there is no other callback in the queue, the scheduled callback is executed immediately after the current callback is finished.

Timer alarms

The timer functionality uses the scheduler infrastructure and is implemented with alarms. An alarm is a callback that is scheduled for execution with a defined time delay. The time delay is specified during the scheduling of the alarm and guarantees that the alarm callback function is called not earlier than specified by the delay parameter.

Note
Precise timing is not guaranteed: the alarm callback can be called later than the defined time delay.

There are API primitives provided to work with the alarms:

Scheduler API

The following table lists the Scheduler API macros:

Macro Parameters Return value Description
ZB_SCHEDULE_APP_CALLBACK(func, param) zb_callback_t func – function to execute.
zb_uint8_t param – callback parameter – usually a buffer ID.
RET_OK or RET_OVERFLOW. Schedule a callback function with one parameter for execution.
ZB_SCHEDULE_APP_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.
RET_OK or RET_OVERFLOW. Schedule a callback function with two parameters for execution.
ZB_SCHEDULE_APP_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.
RET_OK or RET_OVERFLOW. Schedule an alarm for execution in the main scheduler loop: a callback with one parameter to be executed after a delay defined by the timeout value.
ZB_SCHEDULE_APP_ALARM_CANCEL(func, param) zb_callback_t func – scheduled function to cancel.
zb_uint8_t param – callback parameter.
RET_OK or RET_OVERFLOW. Cancel previously scheduled alarm.
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.
RET_OK or RET_NOT_FOUND. Check whether an alarm was scheduled and return time left for the callback execution.
Scheduler API calls
Note
Except for the scheduler API, the Zigbee stack API is not thread-safe. This means that calls to the Zigbee API must be invoked through scheduler callbacks or from the same thread that runs the scheduler loop.

The following examples demonstrate the usage of Zigbee multitasking with two functions executing each other in an infinite loop. The first function executes immediately. The second function executes after a 10-second delay.

Zigbee stack memory management subsystem

The Zigbee stack uses static memory allocation for its internal data structures (the memory is not allocated dynamically at runtime). The memory management system provides predictable memory usage and minimizes data copying at runtime when possible. The internal stack data structures are usually invisible to the application.

The main data structure visible for the application is the memory buffer, which transfers packets and parameters between the application and stack layers.

The stack uses a predefined pool of memory buffers of a fixed size and has a number of default presets to define the size of the memory buffer pool. You can configure the pool by using one of the following header files:

A memory buffer consists of a header and a data buffer. The memory buffer size equals 128 bytes + buffer tail with a set of parameters. This is enough to hold a single Zigbee packet or an APS fragment.

The application or the stack function can allocate or release a single memory buffer from the pool on demand. Memory buffer allocation is a blocking operation: ZBOSS kernel calls the user’s callback when the buffer becomes available.

The application must use zb_buf_get_out_delayed() or zb_buf_get_in_delayed() calls to allocate a new buffer. When a buffer becomes available, the delayed buffer allocation makes a callback call through the scheduler.

Logically, the memory buffers are divided into two types:

This separation prevents allocating all memory buffers to only one type of buffer. For example, if all buffers are only TX buffers and all are in use waiting for an ACK packet, no ACK packet will be received because no RX buffer is available. To prevent this situation, the stack stops allocating TX buffers when half of the buffer pool is allocated to TX buffers. The same rule applies to allocating RX buffers.

Most of the stack functions require some parameters and some data (usually Zigbee packet or its fragment). Both parameters and data are passed to the API in a single memory buffer. Call parameters are stored in the tail of the buffer, while the data section is stored at the head of the buffer. Both the caller and the called functions must be aware of the parameter types.

For setting parameters and getting them from the buffer, ZB_BUF_GET_PARAM() is used.

The following buffer operations are available in a Zigbee application:

To reserve a space in the memory buffer, use the following methods:

The following table lists the available memory management API macros.

Macro Parameters Return value Description
zb_buf_begin(bufid) zb_bufid_t bufid – memory buffer ID. void *data – data begin. Get a pointer to the first allocated data element in the data buffer.
zb_buf_len(bufid) zb_bufid_t bufid – memory buffer ID. zb_uint_t length – data length. Get the allocated data size.
zb_buf_initial_alloc(bufid, size)zb_bufid_t bufid – memory buffer ID.
zb_uint8_t size – size to allocate.
void *data – data begin. 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(bufid, size) zb_bufid_t bufid – memory buffer ID.
zb_uint8_t size – size to allocate.
void *data – data begin. Extend the currently allocated buffer to the left. Commonly used for allocating a buffer for a packet header.
zb_buf_alloc_right(bufid, size) zb_bufid_t bufid – memory buffer ID.
zb_uint8_t size – size to allocate.
void *data – data begin. Extend the currently allocated buffer to the right. Commonly used for allocating a buffer for a packet tail.
zb_buf_cut_left(bufid, size) zb_bufid_t bufid – memory buffer ID.
zb_uint8_t size – size to cut.
void *data – data begin. Cut a buffer beginning. This macro is normally used for cutting a packet header.
zb_buf_cut_right(bufid, size) zb_bufid_t bufid – memory buffer ID.
zb_uint8_t size – size to cut.
None. Cut a buffer ending. This macro is usually used for cutting a packet tail.
ZB_BUF_GET_PARAM(bufid, type) zb_bufid_t bufid – memory buffer ID.
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_buf_reuse(bufid) zb_bufid_t bufid – memory buffer ID. None. Get the specified IN or OUT buffer, clear all the data, and prepare the buffer for further usage. The buffer does not change its type: if it was an IN buffer, it remains an IN buffer after the procedure.
zb_buf_free(bufid) zb_bufid_t bufid – memory buffer ID. None. Release a memory buffer.
zb_buf_get() None. zb_bufid_t bufid – memory buffer ID Get an IN buffer from the buffer pool. If no buffer is available, return NULL. If possible, use the zb_buf_get_in_delayed(callback) instead.
zb_buf_get_in_delayed(callback) zb_callback_t callback – pointer to a callback function. RET_OK or error code. Get an 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 then execute the callback.
zb_buf_get_out_delayed(callback) zb_callback_t callback – pointer to a callback function. RET_OK or error code. Get an 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 then execute the callback.
zb_buf_get_out_delayed_ext(callback, arg, max_size) zb_callback2_t callback – pointer to a callback function.
zb_uint16_t arg – to be passed to callback.
zb_uint_t max_size – required maximum buffer payload size
RET_OK or error code Get an OUT buffer from the buffer pool. The behavior is the same as with zb_buf_get_out_delayed(), but a user parameter is passed to the callback as the second argument alongside the allocated buffer. Call this macro to use extended buffers.
zb_buf_get_in_delayed_ext(callback, arg, max_size) zb_callback_t callback – pointer to a callback function.
zb_uint16_t arg – to be passed to callback.
zb_uint_t max_size – required maximum buffer payload size
RET_OK or error code. Get an 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 then execute the callback. Call this macro to use extended buffers.
Memory management API calls

Time subsystem

The time subsystem is based on the hardware timer. It uses the special type zb_time_t, defined in the stack API. The time value is stored in ticks. Each tick is equal to one "beacon interval" (15.36 ms). The size of the zb_time_t type depends on the platform. The overflow is handled by the Time API.

The Time API provides the ability to:

The following table lists the available time API macros.

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_TRUE if ab, ZB_FALSE otherwise. Compare time value a and value b, check if ab.
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 – timeout in ms. Convert time from milliseconds to beacon intervals.
Time API calls

Debugging

The Zigbee stack provides API for tracing capabilities for customer support purposes. The stack outputs a hex-encoded binary trace through the Logger module. The binary trace log cannot be decoded with the provided SDK, but the Nordic support team can use it to find stack-level issues.

You can enable or disable tracing for a particular subsystem, and define the level of tracing. In addition, the stack is able to trace Zigbee input and output traffic (in/out) on the MAC level.

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

Macro Value Description
TRACE_SUBSYSTEM_APP 0x0800 Application.
TRACE_SUBSYSTEM_ZDO 0x0040 ZDO subsystem.
TRACE_SUBSYSTEM_ZCL 0x0010 APS subsystem.
TRACE_SUBSYSTEM_NWK 0x0008 NWK subsystem.
Trace masks

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

To use the trace libraries, you must link your project with them. They are located at <InstallFolder>\external\zboss\lib\debug.

It is also recommended to set the following values in the sdk_config.h file if you want to gather the traces:

Configuration option Value for nRF52840 Value for nRF52833*
NRF_LOG_DEFAULT_LEVEL 4 4
NRF_LOG_BUFSIZE 65536 32768
NRF_LOG_MSGPOOL_ELEMENT_COUNT 32 32
NRF_LOG_MSGPOOL_ELEMENT_SIZE 40 40
NRF_LOG_BACKEND_RTT_TX_RETRY_CNT 100 100
SEGGER_RTT_CONFIG_BUFFER_SIZE_UP 32768 512
Note
The nRF52833 can use larger buffers for the nRF Logger. However, for multiprotocol applications, setting larger buffers might not be possible, because of memory size constraints. For this reason, use the values from the table for your multiprotocol applications.

The provided tracing subsystem is disabled by default. Use the following tracing API to manage tracing.

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.
Trace API calls

Power optimization

By default, the Zigbee examples are power optimized. This means that the CPU enters the low power mode in the time of inactivity. The Sleepy End devices use the signal feature to know when to sleep (see Power saving for ZED), while the other devices enter the low power mode whenever the stack decides it has nothing more to do. The weak function void zb_osif_go_idle(void) is defined to check if the logger has anything to process. The CPU goes idle if nothing is left to process.

If some other checks are to be performed (or the behavior must be changed in any way), you can redefine this function in the application. Check the example in the Light Bulb device (see Zigbee Light Control example).

Powering down RAM sections

To reduce the current consumption of Zigbee examples even more, you can power down sections of the RAM memory that are not used by the application. This solution is implemented in Zigbee light switch example and BLE UART and Zigbee Color Dimmer Light Switch Example. With the sleepy behavior enabled at the device, a part of the RAM memory is powered off when using these examples.

To implement the solution, use zigbee_power_down_unused_ram() function from Zigbee application components. In linker scripter, set only a part of RAM as available for use. The remaining (unused) sections of RAM are powered off by the application at startup.

When extending optimized examples, building may fail if the application requires more RAM than it is initially allowed to use. In such case, increase the size of RAM in linker script and make sure that the application powers off only unused RAM. The application can print which sections of RAM are powered off. Set logging level to debug to see the logs.

Example
Assuming that Zigbee light switch example built for the nRF52840 device uses up to 8 kB of RAM, using this power saving method can amount to the current consumption reduction of 1.45 uA.

See the device's Product Specification for details about potential power savings and the device's memory layout.


End device keep alive

All end devices (including RxOnWhenIdle=TRUE) that have received an End Device Timeout Response Command with a status SUCCESS can periodically send keep alive messages to their router parent to ensure they remain in the router’s neighbor table.

You can configure the end device timeout period with zb_set_ed_timeout. The default timeout that is set in NWK_ED_DEVICE_TIMEOUT_DEFAULT equals 256 minutes. If the parent does not receive any keep alive messages from the child during this period, it deletes the child from the neighbor table.

The period for sending the keep alive to the router parent must be determined by the manufacturer of the device and is not specified by the standard. It is recommended to set this period in such a way that the end device sends by default 3 keep alive messages during the End Device Timeout period. The application can use zb_set_keepalive_timeout to configure how often the keep alive message is sent.

The keep alive method that the end device uses depends on the support of the router parent. It can be MAC data poll keep alive (which is the most common option), or End Device Timeout Request keep alive.

If the router parent supports the MAC data poll keep alive, the keep alive configuration may conflict with the Long Poll configuration. This happens when the application changes the Long Poll interval using the Poll Control Cluster API or using zb_zdo_pim_set_long_poll_interval. It is recommended to set the Long Poll interval to a value smaller than ((End Device Timeout period) / 3) to avoid unexpected device aging.

If the router parent supports the End Device Timeout Request keep alive, then the device sends an End Device Timeout Request command as a unicast and waits for an End Device Timeout Response from its parent, according to the configured Keep Alive Timeout.


Data polling mechanism

The polling intervals can be configured by the Poll control ZCL cluster commands or by zb_zdo_pim_set_long_poll_interval.

There are two standard polling presets:

The device always uses the long poll preset, unless there is an ongoing communication that requires a higher packet exchange rate.

Turbo poll

The turbo poll is a ZBOSS extension that allows to temporary increase the poll rate when ZED is awaiting an answer from the parent. This is useful for actions when you know either the exact number of packets to be received or the time period when these packets are expected to be received.

The extension implements an adaptive algorithm that lowers the poll interval to the configured value and raises it gradually up to the long poll interval when the parent is not responding. The turbo poll can be enabled and disabled by zb_zdo_pim_permit_turbo_poll.

When the turbo poll is enabled, it starts automatically for the specified number of packets when one of the following conditions is met:

In each of these cases, the turbo poll uses the adaptive algorithm until the expected number of packets is received.

The application can start the turbo poll for the specified number of packets by calling zb_zdo_pim_start_turbo_poll_packets.

The turbo poll can also be forced for a certain period of time by calling zb_zdo_pim_start_turbo_poll_continuous, and stopped by calling zb_zdo_pim_turbo_poll_continuous_leave when the application expects multiple packets during this period. In this case, the turbo poll uses the adaptive algorithm until the timeout expiration. For instance, the application may enable the turbo poll during the initial configuration of Sleepy End Device to speed up the communication process.


Production configuration

Zigbee stack includes a production configuration block feature. This block is useful for per-device customization and is supposed to be written at device production time. The size of the production block must not exceed 128 bytes.

The production configuration block:

The block is loaded at the very start of zboss_main_loop_iteration(), so it rewrites all the corresponding data from NVRAM.

The production configuration block has a system part and an optional application-specific part:

Data that can be placed into the system part of the production configuration block includes:

Moreover, the data specific to your application can be placed in the application part of the production configuration block.

Note
- The size of the application block of production configuration is limited to 74 bytes for first version of production config. It may change across versions because newer versions of the production configuration may include additional data. To see latest supported version, see Nordic Semiconductor nRF Util GitHub repository.
- After the production configuration is applied, do not call functions that would overwrite this data (zb_set_long_address() and so on).
- An alternative way to change the long address for the final product is to modify the zb_osif_get_ieee_eui64(ieee_addr) function. When used during development, the function uses a random EUI64 address from the FICR registry. Modify it to use your own OUI.

You can generate the production configuration HEX files with the nRF Util tool. For details, see nRF Util documentation about HEX files for Zigbee.

Apart from the system part parameters, the production configuration includes the application configuration app_data made of three bytes: 0x01, 0xAB, 0xCD.


Documentation feedback | Developer Zone | Subscribe | Updated