Wirepas SDK
How to Develop Application with SDK

The Single-MCU feature allows an application to be executed on the same chip as the Wirepas Connectivity stack. Wirepas provides an SDK containing multiple applications examples. Each application describes a different aspect and can be used as a starting point.

This document will focus on a practical approach of writing an application. This document will highlight some crucial point like the minimal steps to follow in the initialization of an application and will give some practical guidance to correctly write an application.

This page contains following sections:

Pre-requirements

In order to develop the software with SDK, you need to have a license for Wirepas Mesh. Then, you have access to the firmware image according to your architecture. If you don't have a license, contact Wirepas sales.

This page contains following sections:

Debugging

In order to debug the devices with JTAG debugger, you need to use unprotected bootloader. Whereas normal licensed Wirepas Mesh has JTAG debugging capabilities prevented, this one has JTAG debugging active. It is intended to be used during development phase to speed up application software development. If you don't yet have an access to unlocked bootloader, contact Wirepas sales.

Note
It is forbidden to install devices with unprotected bootloader in public areas!

Installing firmware image

If you have licensed Wirepas Mesh, you have access to the firmware image. In order to link that with the SDK, you have to unzip the firmware delivery to image/ folder.

Create new application

In this chapter we will create a new application named new_app.

It describes the initial steps to start writing a new application.

This page contains following sections:

Copy of an application example

The provided SDK contains several example applications. They can be used as a starting point (Here, custom_app is used as an reference).

To quickly start the development of a new application you can copy an already existing application and use it as a template. Any application from source/ folder can be used as a starting point.

To start developing the new application, copy folder custom_app to a new folder named new_app in source/ directory.

Change default network

address and channel

To form a network, all nodes must share the same network address and network channel.

These information can come from multiple sources like NFC, provisioned in persistent memory during production,... In order to ease the setup, build system allows to set those settings at build time of an application from the application config.mk file.

In custom_app application example, this information can be seen in file config.mk In the new application folder created, you can modified these variables default_network_address and default_network_channel to arbitrary values that feat your needs. For example:

# Define default network settings
default_network_address ?= 0x67EB4A
default_network_channel ?= 12

These variables are then automatically assigned to constants NETWORK_ADDRESS and NETWORK_CHANNEL and accessible from the code.

Additionally, you can define network keys the same way. Those keys are 16 bytes long and must be kept secret. It is very important to set them to protect your network.

default_network_cipher_key ?= 0x??,..,0x?? // Must be 16 bytes long
default_network_authen_key ?= 0x??,..,0x?? // Must be 16 bytes long

Those settings are used with configureNodeFromBuildParameters utility function.

Change of app_area_id

It is mandatory to have unique app_area_id in order to update specific application independently on other applications. Thus, modify file config.mk following by using new arbitrary area id specific for this application:

# Define a specific application area_id
app_specific_area_id=0x8054AA

Configuration of a node

The App_Init() function is the entry point for the application. It is called by the stack after each boot.

The Wirepas Connectivity stack is in the stopped state during this call. All the API calls that require the stack to be in the stopped state, like configuring node settings, must be done in this function.

The code below shows the minimal steps for an application to configure a node and start the stack.

void App_init(const app_global_functions_t * functions)
{
// Basic configuration of the node with a unique node address
NETWORK_ADDRESS,
NETWORK_CHANNEL) != APP_RES_OK)
{
// Could not configure the node. It should not happen
// except if one of the config value is invalid
return;
}
...
lib_state->startStack();
}

A newly flashed device starts with its role set to APP_LIB_SETTINGS_ROLE_AUTOROLE_LE by default.

To be able to join a network, the application must set at least a unique node address, a common network address and a common network channel. Consequently, it is important to check if a node setting (role, node address, etc.) is already set before updating it from application initialization code. Otherwise it would break the Remote API, as the remotely updated value would be overwritten in App_Init().

This is the role of the configureNode() function. It sets the node address, network address and network channel, but ONLY if these settings are missing from the node. This is the case on first boot after flashing but not after any reboots after that, unless the settings are explicitly cleared by the application.

Note that the lib_state->stopStack() function will cause a reboot of the device and the App_Init() function will be called again.

Once the node is correctly configured, the stack must be started.

This initialization is just an example and can be something different, depending on the use case. For example, the application can wait for configuration via another interface (UART, SPI, NFC, ...). This is the case with the dualmcu_app application, for example.

Note
Application must call lib_state->startStack(). It can be at the end of App_Init() function or in a deferred context but it must be called! Without this call, the node will not be part of any network.
Remote API built-in feature of the stack (described in [7]) allows the change of a setting remotely. This change can happen any time from the application point of view and will generate a reboot and a new call to App_Init() after the update.
Some node settings must be the same across all the nodes in the network. More general information about node configuration can be found in [4].

Build application

Building is done in root of the SDK file structure. It is done by calling the makefile. There can be many options for building but necessary ones are in application makefile. For example by using pca10040 as a target board, following build command can be issued:

make app_name=new_app target_board=pca10040

For detailed information on build process, see here.

Note
Many times the application must be built on custom hardware, not yet having board definition. See documentation here.

Test application

To practically test the application, a minimum of two boards is needed. One of them must be configured as a sink Low Energy or a sink Low Latency and the other as a node (i.e. something else than sink). In a first step, the sink can be connected to a PC running the Wirepas Terminal.

The application runs on the board configured as a node. Even if it is technically possible to run the application on a sink, it implies that the board has another network connection (WiFi, Ethernet,...) and everything is managed by the same MCU. In this basic configuration, it is assumed that it is not the case.

To program the sink, application dualmcu_app can be flashed and then configured by using Wirepas Terminal.

Flashing device

Now when application has been compiled, next step is to program that to the device. The generated binary is located in following path (based on the makefile options used):

<code>build/[target_name]/[app_name]/final_image_[app_name].hex

For example:

build/pca10040/new_app/final_image_new_app.hex

To flash the image to the device, see here.

Using OTAP

For more information about OTAP, see [1].

OTAP images can be found in following paths (based on the makefile options used):

To update application image only:

<code>build/[target_name]/[app_name]/[app_name].otap

To update both application and stack images:

<code>build/[target_name]/[app_name]/[app_name]_wpc_stack.otap

For example:

build/pca10040/new_app/new_app.otap
build/pca10040/new_app/new_app_wpc_stack.otap

Define custom board definition

Many times application requires custom board definition when application is intended to be executed on board not defined in existing board definitions. Albeit it is totally possible to discard the board definition altogether and hard-code all board-specific definitions (such as GPIO pin numbers) directly in application source code, it is still recommended to define board properly. This allows many benefits, such as:

  • Easily compile other applications to board
  • To port the application to multiple boards, such as new variants of the product.

To implement new board, check out the documentation of board definitions and modify existing board template (according to processor architecture).

Application startup

The Wirepas Mesh Single-MCU SDK low-level initialization code sets up the application environment to run C code. The low-level setup is outside the scope of this document, but once the setup is done, the application initialization function App_init() will be run::

void App_init(const app_global_functions_t * functions)
{
...
}
Note
This entry point function must be implemented in every application!

functions parameter is a global list of function pointers for the application. Normally this is not needed at all and is mainly needed for backwards compatibility of the applications.

The stack is not yet running when App_init() is called. Depending on the stored settings and stack state, the stack may start right after returning from App_init().

Adding new source files

For better code readability and organization, the application can be split in to multiple source files. Adding a new source file is as simple as declaring it in application specific source files in makefile, for example:

# You can add more sources here if needed
SRCS += new_source.c
INCLUDES +=

The file named new_source.c is created alongside app.c in this example.

By default, the <app_folder>/include folder is added to the list of paths to check for header files. Any additional folders can be added to the INCLUDES variable in the application makefile.

Recommendations

This chapter contains various recommendations and best practices to use with application development.

This page contains following sections:

Optimization of throughput

The throughput of a Wirepas Connectivity network is expressed in packet per seconds. To optimize this throughput, it is important to fill the packet to the maximum available PDU size when possible.

It is even more important when operating in time-slotted mode. The network will handle the same number of packets independently of its payload size.

Free resources

All hardware resources that are not used by the Wirepas Connectivity stack can be used freely by the application.

Note
All the hardware that is not used by the stack is left in its initial boot state. It must be configured by the application as needed.

For example, unused GPIOs must be properly configured by the application, to avoid unnecessary power consumption due to pull-up or pull-down resistors.

Power consumption

The Wirepas Connectivity stack will try to enter the deepest possible sleep state of the platform, to optimize power consumption.

But as the application may require staying in a higher power state (to keep a peripheral clock enabled for example), the application can ask the stack to prevent entering the deep sleep state.

Please see the lib_system->disableDeepSleep() function in the System library.

How to store data in persistent memory

It is often necessary to store data from an application to persistent memory in order to still have access to it across reboots of the node. The different methods to achieve it and their pros and cons are described in this section.

This page contains following sections:

Using storage library

Wirepas Mesh Stack uses reserved areas for its own usage in internal flash. To avoid reserving too much flash, this area is kept as small as possible. However, a small amount of this area is reserved for the application.

This application area can be accessed through the storage library handle.

  • Pros
    1. Really easy to use as no flash driver needed
    2. Stack is using wear leveling for this area (but area shouldn't be write too often < 1/30 minutes)
  • Cons
    1. Very limited in size. Maximum size can be asked with lib_stoarge->getPersistentMaxSize()
    2. It cannot be pre-flashed before first execution
    3. Application must manage validity of the content (with magic number for example)

Using platform specific storage

Most of the platforms supported by Wirepas have their own specific persistent area to store persistent data.

  • On Nrf52 it is called UICR area (limited to 128 bytes)
  • On EFR32 it is called User Data (limited to 2 kB) As the Wirepas Stack doesn't use it, it can freely be used by the application to store its own persistent data.

To use it, users must refer to platform specific reference manual. For NRF52 users, Wirepas developed a wrapper to use it. You can find it persistent.h here.

  • Pros
    1. Relatively easy to use depending on the platform
    2. Can be pre-flashed on the production line and later accessed by application (easy solution for initial provisioning)
  • Cons
    1. Limited in size (especially for NRF52)
    2. Application must manage validity of the content (with magic number for example).
      Note
      It is done with the Wirepas wrapper for NRF52.

Using a dedicated area in flash

The internal flash is partitioned in multiple areas. Some of these areas are used for Wirepas usage like the bootloader or the firmware that cannot be moved. But the remaining part of flash can be freely partitioned by the application. Each area has a dedicated area ID and a given size. More information about area ids and its usage can be found in [2]

The steps to realize are:

  1. Defining a new partitioning in the Ini file by adding a new area with flag user (i.e. flags = 0x00000014)
  2. Access it through the memory area library handle from your application.
  3. Application can also directly access the memory area without using the library, but it requires the application to know the absolute address of the area (not needed with the memory library)

An example can be found from the provisioning joining node with the code to access the memory area in storage_memarea.c and example of modified ini file in the scratchpad_ini folder

  • Pros
    1. Reserved persistent area for application can be quite large as long as enough room is reserved for receiving new scratchpads
    2. Content of persistent data is kept unchanged when doing an OTAP of the application
    3. Content of the persistent data area can be OTAP independently of the application. It is particularly handy if application needs a big configuration file to operate that can evolve during product life. Like a local schedule for a luminar to operate in an autonomous way.
    4. Can be pre-flashed on the production line and later accessed by application (easy solution for initial provisioning)
  • Cons
    1. Flash partitioning must be modified and must be done carefully. Adding it just after the app may reduce the possibility of the application to expand later. Or defining a too big area may reduce the maximum scratchpad size the node can receive.
    2. Application must manage validity of the content (with magic number for example).

Related Material

[1] https://developer.wirepas.com/support/solutions/articles/77000496639

[2] https://developer.wirepas.com/support/solutions/articles/77000496582

[4] https://github.com/wirepas/wm-sdk/blob/master/source/reference_apps/dualmcu_app/api/DualMcuAPI.md Manual

[7] https://developer.wirepas.com/support/solutions/articles/77000407101

configureNode
__STATIC_INLINE app_res_e configureNode(app_addr_t my_addr, app_lib_settings_net_addr_t my_network_addr, app_lib_settings_net_channel_t my_network_ch, const uint8_t *my_authentication_key_p, const uint8_t *my_encryption_key_p)
Helper function to initially setup a node if not already configured. This configuration can be modifi...
Definition: node_configuration.h:63
APP_RES_OK
@ APP_RES_OK
Definition: wms_app.h:204
app_global_functions_t
List of global functions, passed to App_entrypoint()
Definition: wms_app.h:157
getUniqueAddress
__STATIC_INLINE app_addr_t getUniqueAddress()
Helper function to generate a unique unicast address.
Definition: node_configuration.h:139