5. User Guides

5.1. The BLE Framework

Figure 40 depicts the general architectural scheme used.

../_images/046.png

Figure 40 BLE Framework architecture

Using a top-down approach, the layers that build-up the BLE Framework functionality can be identified as the following:

  1. The BLE service framework provides BLE services that the application can use “out-of-the-box”, using simple initialization functions and callbacks for the various BLE service events (like the change of an alert level attribute). The functionality of the BLE service framework is built on top of the Dialog BLE API library. The BLE service API header files can be found under <SDK_ROOT_PATH>/sdk/interfaces/ble/services/include. The BLE service callbacks are executed by the application task that uses the BLE service framework.

  2. The Dialog BLE API is a set of functions that can be used to initiate BLE operations or respond to BLE events. The API header files can be found under the path <SDK_ROOT_PATH>/sdk/interfaces/ble/api/include. The API functions can be used to send messages (commands or replies to events) to the BLE manager, either by directly calling the BLE manager command handler or by using queues between the application task and the BLE manager task. The BLE API is called in the context of the application.

  3. The BLE manager provides the interface to the BLE functionality of the chip. Application tasks that are based on BLE functionality use the Dialog BLE API to interface with the BLE manager. The BLE manager is a task that stands between the application and the BLE adapter. It uses the BLE adapter to interface with the BLE Stack. The BLE manager uses a Generic Transport Layer (GTL) interface to communicate with the BLE adapter through a command and event queue. The BLE manager is a separate system task.

  4. The BLE adapter is the system task that provides the interface to the BLE Stack. It runs the BLE Stack internal scheduler, receives the commands or the replies to events from the BLE manager, and passes BLE events to the BLE manager. BLE core functionality is implemented by the BLE adapter task.

  5. The BLE Stack is the software stack that implements both the BLE Host and the Low Energy (LE) controller (Link Layer). The BLE Host includes the Logical Link Control and Adaptation Protocol (L2CAP), the Security Manager Protocol (SMP), the Attribute Protocol (ATT), the Generic Attribute Profile (GATT) and the Generic Access Profile (GAP). The BLE Stack API header files, for the DA1470x, can be found under <SDK_ROOT_PATH>/sdk/interfaces/ble/stack/<device_family>/include. The BLE Stack software is run under the BLE adapter’s task context.

5.1.1. Developing BLE Applications

One of the main goals of the SmartSnippets™ DA1470x SDK is to simplify the development of BLE applications and achieve a fast time to market. The SmartSnippets™ DA1470x SDK separates the application logic from the BLE Stack implementation and provides a clean and powerful API to interact with the BLE capabilities of the device. The BLE API provides an easy way to configure the BLE device, start air operations and set up the attribute database of a GATT server. The BLE service API provides access to predefined Bluetooth SIG profiles with the definition of only a few callback functions.

The Proximity Reporter (pxp_reporter) application is the most typical of the BLE applications that are included in the SmartSnippets™ DA1470x SDK. It is a complete and solid example of a BLE application developed on top of the SmartSnippets™ DA1470x SDK. It uses both the Dialog BLE API and the BLE service framework to implement the functionality of a BLE profile.

However, it may not be the simplest example or the best starting point to become familiar with the development of a BLE application from scratch. Instead, there are BLE projects specifically created to serve as starting points for specific BLE applications such as beacons (ble_adv_demo) or specific roles such as a generic peripheral (ble_peripheral) or central (ble_central) device.

The goal of this section is to introduce the various options and examples that exist in the SmartSnippets™ DA1470x SDK which can be used as building blocks for many applications. After a short introduction on where the API header files can be found, each section describes the functionality they implement along with guidance on how they differ from each other. This information is essential when developing a BLE application from scratch.

5.1.2. The BLE API header files

5.1.2.1. Dialog BLE API

All demos and services API can be found in [Ref_04].

In most projects these API header files are symbolically linked to files located in

<SDK_ROOT_PATH>/sdk/interfaces/ble/api/include.

The API functions are declared across several header files depending on their functionality:

  • Common API (ble_common.h): Functions used for operations, not specific to a specific BLE device software component. For example:

Table 35 API Functions of the common BLE device software component

Function

Description

ble_register_app()

Register the application to the BLE Framework so that it can receive BLE event notifications.

ble_enable()

Enable the BLE module.

ble_reset()

Reset the BLE module.

ble_central_start()

Start the device as a BLE central. This is actually a helper function, since it uses API calls ble_enable() and ble_gap_role_set().

ble_peripheral_start()

Start the device as a BLE peripheral. This is also a helper function that uses ble_enable() and ble_gap_role_set().

ble_get_event()

Get a BLE event from the BLE event queue.

ble_has_event()

Check if there is an event pending at the BLE event queue.

ble_handle_event_default()

Used to define handling of events that are not handled by the added services or the application defined handlers.

ble_read_tx_power()

Read controller TX power

ble_address_to_string()

Convert bd_address to string

ble_address_from_string()

Convert string to bd_address

ble_set_fem_voltage_trim()

Set FEM Voltage GPIO values for a specific channel

  • GAP & L2CAP APIs (ble_gap.h/ble_l2cap.h): Covers a wide range of operations, like

    • Device parameters configuration: device role, MTU size, device name exposed in the GAP service attribute, etc.

    • Air operations: Advertise, scan, connect, respond to connection requests, initiate or respond to connection parameters update, etc.

    • Security operations: Initiate and respond to a pairing or bonding procedure, set the security level, unpair, etc.

Table 36 GAP and L2CAP API functions

Function

Description

BLE device configuration

ble_gap_role_get()

Get the GAP role currently set.

ble_gap_role_set()

Set the device GAP roles (central, peripheral, observer, broadcaster).

ble_gap_mtu_size_get()

Get the MTU size currently set.

ble_gap_mtu_size_set()

Set the MTU size to be used in MTU exchange transactions.

ble_gap_channel_map_get()

Get the currently set channel map of the device (the device has to be configured as central).

ble_gap_channel_map_set()

Set the channel map of the device (device has to be configured as central).

ble_gap_address_get()

Get the currently used BD address of the device.

ble_gap_address_set()

Set the BD address of the device.

ble_gap_device_name_get()

Get the device name used in the respective attribute of GAP service.

ble_gap_device_name_set()

Set the device name used in the respective attribute of GAP service.

ble_gap_appearance_get()

Get the appearance used in the respective attribute of GAP service.

ble_gap_appearance_set()

Set the appearance used in the respective attribute of GAP service.

ble_gap_per_pref_conn_params_get()

Get the peripheral preferred connection parameters used in the respective attribute of GAP service.

ble_gap_per_pref_conn_params_set()

Set the peripheral preferred connection parameters used in the respective attribute of GAP service.

ble_gap_get_io_cap()

Get the I/O capabilities of the device.

ble_gap_set_io_cap()

Set the I/O capabilities of the device (combined with the peer’s I/O capabilities, this will determine which pairing algorithm will be used).

ble_gap_data_length_set()

Set the data length to be used for transmission on new connections.

Advertising

ble_gap_adv_start()

Start advertising.

ble_gap_adv_stop()

Stop advertising.

ble_gap_adv_data_set()

Set the Advertising Data and Scan Response Data used.

ble_gap_adv_ad_struct_set()

Set Advertising Data and Scan Response Data using ::gap_adv_ad_struct_t type.

ble_gap_adv_data_get()

Get currently used Advertising Data and Scan Response Data.

ble_gap_adv_intv_get()

Get the currently set advertising interval.

ble_gap_adv_intv_set()

Set the advertising interval (has to be done prior to ble_gap_adv_start()).

ble_gap_adv_chnl_map_get()

Get the advertising channel map currently set.

ble_gap_adv_chnl_map_set()

Set the advertising channel map (has to be done prior to ble_gap_adv_start()).

ble_gap_adv_mode_get()

Get the discoverability mode used for advertising.

ble_gap_adv_mode_set()

Set the discoverability mode used for advertising (has to be done prior to ble_gap_adv_start()).

ble_gap_adv_set_permutation()

Set the order of the primary advertising channels (Bluetooth Core v5.1 or later)

ble_gap_adv_filt_policy_get()

Get the filtering policy used for advertising.

ble_gap_adv_filt_policy_set()

Set the filtering policy used for advertising.

ble_gap_adv_direct_address_get()

Get the peer address used for directed advertising.

ble_gap_adv_direct_address_set()

Set the peer address used for directed advertising (has to be done prior to ble_gap_adv_start()).

Scanning

ble_gap_scan_start()

Start scanning for devices.

ble_gap_scan_stop()

Stop scanning for devices.

Connection management

ble_gap_scan_params_get()

Get the scan parameters used for future connections.

ble_gap_scan_params_set()

Set the scan parameters used for future connections.

ble_gap_connect()

Initiate a direct connection to a device.

ble_gap_connect_ce()

Initiate a direct connection with an application-defined minimum and maximum connection event length

ble_gap_connect_cancel()

Cancel an initiated connection procedure.

ble_gap_disconnect()

Initiate a disconnection procedure on an established link.

ble_gap_peer_version_get()

Get peer’s version on an established connection.

ble_gap_peer_features_get()

Get peer’s features on an established connection.

ble_gap_conn_rssi_get()

Get the RSSI of a connection.

ble_gap_conn_param_update()

Initiate a connection parameter update or update request procedure (depending on the role set and peer’s supported features).

ble_gap_conn_param_update_reply()

Reply to a connection parameter update request.

ble_gap_data_length_set()

Set the data length used for transmission on a specified connection.

ble_gap_phy_get()

Get the transmitter and receiver PHY (default preferred or for a specified connection).

ble_gap_phy_set()

Set PHY used for RX and TX (default or for a given connection).

Security

ble_gap_pair()

Start pairing.

ble_gap_pair_reply()

Respond to a pairing request.

ble_gap_passkey_reply()

Respond to a passkey request.

ble_gap_numeric_reply()

Respond to a numeric comparison request (LE Secure Connections only).

ble_gap_get_sec_level()

Get link security level.

ble_gap_set_sec_level()

Set link security level.

ble_gap_unpair()

Unpair device (will also remove the related bond data from BLE storage).

ble_gap_address_resolve()

Resolve a BD address using the set of IRKs stored in BLE storage.

Power Control

ble_gap_local_tx_power_get()

Get the current and maximum transmit power levels of the local device, on the ACL connection, for the indicated PHY.

ble_gap_remote_tx_power_get()

Get the TX power level used by the remote device, on the ACL connection, for the indicated PHY.

ble_gap_path_loss_report_params_set()

Set the path loss threshold reporting parameters.

ble_gap_path_loss_report_en()

Enable or disable path loss reporting.

ble_gap_tx_power_report_en()

Enable or disable reporting of TX power level in the local and remote device for the ACL connection.

ble_gap_rf_path_compensation_set()

Indicate the RF path gain or loss between the RF transceiver and the antenna contributed by intermediate components.

Helper functions

ble_gap_get_connected()

Get list of connected devices.

ble_gap_get_bonded()

Get list of bonded devices.

ble_gap_get_devices()

Return list of known devices based on filter.

ble_gap_get_device_by_addr()

Get the device object, given the device address.

ble_gap_get_device_by_conn_idx()

Get device object, given the connection index.

ble_gap_is_bonded()

Get bond state of device (by connection index).

ble_gap_is_addr_bonded()

Get bond state of device (by address).

Expert functions

ble_gap_skip_peripheral_latency()

Controls the peripheral skip latency feature for a connection. Can only be enabled for peripheral (slave) connections.

If enabled, the device ignores the peripheral (slave) latency of connection parameters and listens on all possible connection events.

By doing so, this feature decreases the latency of the data exchange, but may increase the power consumed due to increased listening.

If disabled, the device usually listens only on the connection event that meets the peripheral (slave) latency of connection parameters.

Additionally, it will listen on all possible connection events only when at least one of the below condition is met:

  • data is waiting to be exchanged locally at the Link Layer

  • peer device indicated that data is waiting to be exchanged at the peer device

  • a Link Layer Procedure is currently active

When a connection is established, the peripheral skip latency feature is disabled by default.

  • GATT server API (ble_gatts.h): Set up the attribute database, set attribute values, notify/indicate characteristic values, initiate MTU exchanges, respond to write and read requests, etc.

Table 37 GATT server API

Function

Description

ble_gatts_add_service()

Add a new GATT service to the ATT database. Subsequent calls to ble_gatts_add_include(), ble_gatts_add_characteristic() and ble_gatts_add_descriptor() will add attributes to the service added by this call.

ble_gatts_add_include()

Add an included service declaration to the service added by ble_gatts_add_service().

ble_gatts_add_characteristic()

Add a characteristic declaration to the service added by ble_gatts_add_service().

ble_gatts_add_descriptor()

Add a descriptor declaration to the service added by ble_gatts_add_service().

ble_gatts_register_service()

Add to the ATT database all attributes previously added to the service.

ble_gatts_enable_service()

Enable service in database.

ble_gatts_disable_service()

Disable service in database.

ble_gatts_get_characteristic_prop()

Read current characteristic properties and permissions.

ble_gatts_set_characteristic_prop()

Set characteristic properties and permissions.

ble_gatts_get_value()

Get attribute value.

ble_gatts_set_value()

Set attribute value.

ble_gatts_read_cfm()

Confirmation response to an attribute read request.

ble_gatts_write_cfm()

Confirmation response to an attribute write request.

ble_gatts_prepare_write_cfm()

Confirmation response to an attribute prepare write request.

ble_gatts_send_event()

Send a characteristic value notification or indication.

ble_gatts_service_changed_ind()

Send indication of the Service Changed Characteristic.

ble_gatts_get_num_attr()

Calculate the number of attributes required for a service.

  • GATT client API (ble_gattc.h): Used by a device configured as a GATT client to discover the services, characteristics, etc. of a peer device, read or write its attributes, initiate MTU exchanges, confirm the reception of indications, etc.

Table 38 GATT client API

Function

Description

ble_gattc_browse()

Browse services on a remote GATT server.

ble_gattc_browse_range()

Browse services on remote GATT server in a given range.

ble_gattc_discover_svc()

Discover services on a remote GATT server.

ble_gattc_discover_include()

Discover included services on a remote GATT server.

ble_gattc_discover_char()

Discover characteristics on a remote GATT server.

ble_gattc_discover_desc()

Discover descriptors on a remote GATT server.

ble_gattc_read()

Read a characteristic value or a characteristic descriptor from the remote GATT server, depending on the attribute handle.

ble_gattc_write()

Write a characteristic value or a characteristic descriptor to the remote GATT server, depending on the attribute handle.

ble_gattc_write_no_resp()

Write attribute to remote GATT server without response.

ble_gattc_write_prepare()

Prepare long/reliable write to remote GATT server.

ble_gattc_write_execute()

Execute long/reliable write to remote GATT server.

ble_gattc_indication_cfm()

Send confirmation for received indication.

ble_gattc_get_mtu()

Get current TX MTU of peer.

ble_gattc_exchange_mtu()

Exchange MTU.

Note

Several GAP configuration functions must be called before the attribute database is created, because modifying the device’s configuration can clear the attribute database created up to that point. This is noted in the Doxygen headers of the configuration functions that can have this effect.

5.1.2.2. Dialog BLE service API

The BLE service API header files are in <SDK_ROOT_PATH>/sdk/interfaces/ble/services/include.

The services-specific API, callback function prototypes and definitions are included in each service’s header file. The services implemented are the following:

Table 39 Header files for the BLE services

Header file

Description

bas.h

Battery Service

bcs.h

Body Composition Service

ble_service.h

Services handling routines API

bls.h

Blood Pressure Service

bms.h

Bond Management Service

cts.h

Current Time Service

dis.h

Device Information Service

dlg_debug.h

Dialog Debug Service

dlg_suota.h

Dialog SUOTA Service

hids.h

Human Interface Device Service

hrs.h

Heart Rate Service

ias.h

Immediate Alert Service

lls.h

Link Loss Service

scps.h

Scan Parameter Service

sps.h

Serial Port Service

svc_defines.h

Common definitions for all services

svc_types.h

Common characteristic common

tps.h

Tx Power Service

uds.h

User Data Service

wss.h

Weight Scale Service

All services have an initialization function defined. This function is called with arguments that vary for different services.

The most common argument is a pointer to one or more callback functions that should be called upon a service-specific event. For example, the prototype for the initialization function of the Immediate Alert Service (ias.h) is the following:

Code 35 Initialization code for Immediate Alert Service
ble_service_t *ias_init(ias_alert_level_cb_t alert_level_cb)

Function ias_init() has only one argument. It is a pointer to the callback function that will be called when a peer device has modified the value of the Immediate Alert Level characteristic. This callback function is part of the user application code and should provide the application handling required for the change to the Immediate Alert Level.

The return value from all initialization functions is the created service’s handle which is used to reference the service in the application. To understand how the service interacts with the BLE Framework it is useful to know what this handle represents. The handle is a pointer to a generic structure (ble_service_t) that defines how the service should interact with the framework. Within each service there is an internal service definition (XXX_service_t) as shown in Figure 41. This contains the generic service structure plus a set of handles, one for each GATT characteristic that the service implements. This XXX_service_t structure is populated by XXX_init() for that service. The start_h and end_h handles will contain the start and end handles of the attributes for this service within the overall GATT table provided by the GATT server. So, when a GATT client requests a Service Discovery from the server these represent the start and end handles that the client would use to access service XXX.

../_images/058.png

Figure 41 Structure of a service handle

The set of optional callbacks allow each service to specify if it wants to do some specific handling on a certain event received by the BLE Framework. If the service wants to be informed when another BLE device has connected to this device then it can define its own handle_connected_evt() function and plug it into the connected_evt callback. Each service declares its handle_connected_evt() function as static in xxx.c and by convention in the SmartSnippets™ SDK they all share the same function names in each service.

As each service is initialized and thus added to the BLE services framework with ble_service_add(), its generic services structure is added to a structure of supported services as shown in Figure 42.

../_images/059.png

Figure 42 Structure of supported services

Now that the main internal services structure has been explained, it is easier to follow how the service initialization defines how the service operates.

Within the BLE service framework, the main event handler is ble_service_handle_event() which is shown below.

Code 36 Handle BLE events using BLE service framework
bool ble_service_handle_event(const ble_evt_hdr_t *evt)
{
     switch (evt->evt_code) {
     case BLE_EVT_GAP_CONNECTED:
             connected_evt((const ble_evt_gap_connected_t *) evt);
             return false; // make it "not handled" so app can handle
     case BLE_EVT_GAP_DISCONNECTED:
             disconnected_evt((const ble_evt_gap_disconnected_t *) evt);
             return false; // make it "not handled" so app can handle
     case BLE_EVT_GATTS_READ_REQ:
             return read_req((const ble_evt_gatts_read_req_t *) evt);
     case BLE_EVT_GATTS_WRITE_REQ:
             return write_req((const ble_evt_gatts_write_req_t *) evt);
     case BLE_EVT_GATTS_PREPARE_WRITE_REQ:
             return prepare_write_req((const ble_evt_gatts_prepare_write_req_t *) evt);
     case BLE_EVT_GATTS_EVENT_SENT:
             return event_sent((const ble_evt_gatts_event_sent_t *) evt);
     }

     return false;
}

Each of these sub-handlers inside ble_service_handle_event() search throughout the added services to find one that has defined a behavior for this event. There are two types of events that are handled differently.

5.1.2.3. Connection Orientated Events

The connection and disconnection events are potentially of interest to all registered services, so all services can be informed. The cleanup on shutdown is handled in the same way.

For example a connection event will call the BLE service’s statically defined connected_evt() function (sdk/interfaces/ble/services/src/ble_service.c). This will go through all the services registered to the BLE service framework to check for services that have registered to connection events during the service initialization. For each such service the connected_evt callback will be called.

5.1.2.4. Attribute Orientated Events

These are events that have to do with a given attribute handle. As each attribute is related to a unique service the first step in each of these handlers is to identify which service the attribute belongs to. For example a write request on a specific attribute will call the BLE service’s statically defined write_req() function (sdk/interfaces/ble/services/src/ble_service.c) as shown below. This will first identify which service owns that attribute with find_service_by_handle(). Then if it has a write_req callback defined it executes the callback.

Code 37 Example of code for the Write Request
static bool write_req(const ble_evt_gatts_write_req_t *evt)
{
        ble_service_t *svc = find_service_by_handle(evt->handle);

        if (!svc) {
                return false;
        }

        if (svc->write_req) {
                svc->write_req(svc, evt);
        }

        return true;
}

An example of this flow is the Write No Response procedure that can be applied to the Immediate Alert Level characteristic of the Immediate Alert Service. When a GATT client requests a write to that characteristic it will trigger the write_req() sub-handler under ble_service_handle_event().

The write_req() sub-handler will use find_service_by_handle() to see if any of the added services are registered for that characteristic. It will match it with the Immediate Alert Service (IAS) and as the IAS has registered a Write Request handler the IAS handle_write_req() will be called (sdk/interfaces/ble/services/src/ias.c).

Code 38 Example of code that handle the Write Request and match it with the appropriate instance
static void handle_write_req(ble_service_t *svc, const ble_evt_gatts_write_req_t *evt)
{
        ia_service_t *ias = (ia_service_t *) svc;
        att_error_t err = ATT_ERROR_OK;

        if (evt->length == 1) {
                uint8_t level;

                /*
                * This is write-only characteristic so we don't need to store value written
                * anywhere, can just handle it here and reply.
                */

                level = get_u8(evt->value);

                if (level > 2) {
                        err = ATT_ERROR_APPLICATION_ERROR;
                } else if (ias->cb) {
                        ias->cb(evt->conn_idx, level);
                }
        }

        ble_gatts_write_cfm(evt->conn_idx, evt->handle, err);
}

By calling ias->cb() function, this handler actually calls the application supplied callback function passed as an argument when ias_init() was called by the application. Finally, it sends a Write Confirmation to update the value at the attribute database maintained in the BLE Stack.

This is only an example of the way the BLE service framework translates BLE events to BLE service events. Different events in different services can have different levels of complexity, but most of the times this complexity is contained within the BLE service API. The aim is that the application only needs to call the service’s initialization function and define the appropriate callback functions to handle all service’s events.

In addition, some services define additional service-specific API calls. For instance, the Heart Rate Service implementation defines an API to trigger notifications of the Heart Rate Measurement characteristic, using functions hrs_notify_measurement() and hrs_notify_measurement_all() (the first is used to notify the characteristic to a specified peer, while the second is used to notify the characteristic to all subscribed peers). Some services also define some internal helper functions that are used to manipulate characteristic values, and some services require attribute initial values as arguments of the initialization function.

The BLE service API adds another layer to the general BLE API. Together the BLE adapter, BLE manager, BLE API library and BLE service framework results in the BLE Framework.

The BLE services API provides an off the shelf solution to implement an application using many of the common adopted BLE services. The underlying BLE API and GATT server API can be used to create other adopted services or even custom services using the BLE services as a template.

The following sections will provide an overview of a generic application and then will describe in detail several of the example BLE projects included in the SmartSnippets™ DA1470x SDK:

Table 40 BLE projects included in the SmartSnippets™ DA1470x SDK

ble_peripheral

A project that can be used as a template for developing BLE peripheral applications. The project includes some of the services implemented under the BLE service framework.

ble_suota_client

This application is a SUOTA 1.2 client implementation and allows to update SUOTA-enabled devices over the air, using a simple serial console interface.

ams

Apple Media Service demo application

ancs

This application is a sample implementation for Apple Notification Center Service (ANCS) client. It supports all the features of the Notification Consumer (NC) role provided by this service.

blp_sensor

This application is a sample implementation of the Blood Pressure Sensor as defined in the Blood Pressure Profile Specification.

bms

This application is an implementation of the Bond Management Service, as defined by the Bluetooth Special Interest Group.

cscp_collector

This application is a sample implementation of Cycling Speed And Cadence collector as defined by CSCP specification 1.0. All features are supported (i.e. both mandatory and optional features).

hogp_device

This application is a sample implementation of a HOGP Device.

hogp_host

This application is a sample implementation of the HOGP Host of the HID Over GATT Profile, as defined by the Bluetooth Special Interest Group.

hrp_collector

This application is a sample implementation of Heart Rate collector as defined by HRP specification 1.0. All features are supported (i.e. both mandatory and optional features).

hrp_sensor

This application is a sample implementation of a Heart Rate Sensor of the Heart Rate Profile, as defined by the Bluetooth Special Interest Group.

wsp_weightscale

This application is a sample implementation of Weight Scale role of the Weight Scale Profile specification, as defined by the Bluetooth Special Interest Group.

ble_cli

This application provides a Command Line Interface handling functions and events from the SDK APIs.

ble_external_host

The application purpose is the use of transport layer on DA1470x platform by an external user. For example a different BLE Host stack may be used with BLE Controller on DA146xx targets.

ble_multi_link

This application allows connecting to many devices by writing their addresses to characteristic.

pxp_reporter

This application is a sample implementation of Proximity Reporter of the Proximity Profile, as defined by the Bluetooth Special Interest Group.

ble_central

This application is an example of BLE central role device implementation. It connects to a remote device, searches for services, characteristic and descriptors.

ble_usbhid_device

This application implements a HoGP Device compliant with HoGP specification and can be used with any BLE device supporting HoGP Host (either Boot Host or Report Host), e.g. PC or a smartphone.

ble_usbhid_dongle

This application implements parts of HoGP Report Host role which are necessary to work with HoGP Device implementation of ble_usbhid_device. Its purpose is to act as a bridge between HoGP Device and non-BLE HID host supporting USB HID host. It is a plug & play solution which works only with specific, preconfigured HoGP Device.

5.1.2.5. Configuring the project

In each project the BLE Framework and BSP are configured via a set of custom config files that set the defines and macros used in that project. These files are found in the config directory of each project.

In the case of the ble_adv project these file are config/custom_config_oqspi.h and config/custom_config_ram.h (for OQSPI and RAM build configurations respectively).

Any definitions set in these file will override the default SDK values which are found in the following files:

sdk/bsp/config/bsp_defaults.h

sdk/bsp/config/bsp_defaults_da1470x.h

sdk/bsp/config/bsp_memory_defaults.h

sdk/middleware/config/middleware_defaults.h

sdk/interfaces/ble/config/ble_config.h

sdk/free_rtos/include/FreeRTOSConfig.h

5.1.2.6. BLE application structure

All the BLE application projects in the SmartSnippets™ DA1470x SDK have a similar structure. This involves several FreeRTOS tasks at different priority levels to ensure that the overall system’s real time requirements are met.

The BLE application is implemented in a FreeRTOS task that is created by the system_init() function. system_init() runs at the highest priority at startup and is also responsible for starting the BLE manager and BLE adapter tasks.

The application task has the following flow:

  1. Device initialization and configuration: Start-up BLE, setting device role, device name, appearance and other device parameters.

  2. Attribute database creation (GATT server devices): Creation of services and attributes using the BLE service framework. This must be done after (1) to prevent deletion of the attribute database.

  3. Air operation configuration and initiation: BLE peripheral devices usually end-up advertising and BLE central devices end-up scanning and/or attempting to connect to another device.

  4. The infinite for(;;) loop, which is the application’s event handling loop. The application has been set-up as desired and now it is waiting for BLE events to respond to, like connections or write requests.

    • The BLE adapter (ad_ble_task) must have a higher priority than the application task(s) because it runs the BLE Host stack scheduler and it handles time critical tasks.

    • Most of the BLE applications use the FreeRTOS task notifications mechanism to block. ble_adv_demo is the simplest application and is the only project that does not use this mechanism. Instead, it just blocks on the BLE manager’s event queue.

    • In addition to the BLE-related functionality most projects also use other system modules, like software timers or sensors. In this case, the application usually defines callback functions to be triggered on these system events or interrupts. These callback functions should use task notifications to unblock the application task which can then handle the event or interrupt in the application task’s context (under its for(;;) loop).

Warning

Calling a BLE API function inside a callback function triggered on a timer expiry will execute the BLE API function in the timer’s task context. Calling other functions in the callback functions can also have implications on real time performance or in corrupting the small stack used by the timer task.

5.1.3. BLE Security

The Bluetooth specification defines the security options for device authentication and link encryption. These aspects of security are handled seamlessly by the BLE Framework. The API in Table 41 is able to set-up security, initiate pairing, do a security request or set-up encryption using previously exchanged keys. Most details of the procedures will be handled internally by the BLE Framework and the application will be notified only if intervention is needed or when the procedure is completed. These options will be described in detail in Section 5.1.3.2 and Section 5.1.3.5.

The generation and storage of the security keys and other bonding information is also handled by the BLE Framework. Persistent storage can also be used to enable storage of the security keys and bonding data info in the flash. The BLE Framework can then retrieve this information after a power cycle and use it to restore connections with previously bonded devices. This is described in Section 5.1.5.

5.1.3.1. LE Secure

LE Secure Connections pairing is supported and enabled by default by the SDK using the API described in Section 5.1.3.2, LE Secure Connections pairing will be used if the connected peer supports the feature without the need for the application to specifically request it. If the combination of the devices’ capabilities result in a numeric comparison pairing algorithm (introduced and used for the LE Secure Connections pairing), the application will be notified of a numeric comparison request during pairing by the reception of a BLE_EVT_GAP_NUMERIC_REQUEST event and should respond using ble_gap_numeric_reply() function.

If the application needs to use only LE Legacy Pairing and disable LE Secure Connections support in the SDK, it should define dg_configBLE_SECURE_CONNECTIONS macro to 0 in the application config file.

5.1.3.2. Functions

Table 41 summarizes the API functions that can be used by the application to set-up security features.

Table 41 BLE Security API functions

API call

Description

ble_gap_pair()

Initiate a pairing or bonding procedure with a connected peer. Depending on whether the device is master or slave on the connection, this call will result either in a pairing or a security request respectively.

ble_gap_pair_reply()

Reply to a received BLE_EVT_GAP_PAIR_REQ event. This event will only be received by a peripheral device when the central peer has initiated a pairing procedure, so this function should only be called by a peripheral application and only after a BLE_EVT_GAP_PAIR_REQ event has been received.

ble_gap_passkey_reply()

Reply to a received BLE_EVT_GAP_PASSKEY_REQUEST event. This event will be received if the combination of the devices’ input/output capabilities results in a passkey entry pairing algorithm. The application should use this function to submit the passkey for the pairing procedure to proceed.

ble_gap_numeric_reply()

Reply to a received BLE_EVT_GAP_NUMERIC_REQUEST event. This event will be received if the combination of the devices’ input/output capabilities results in a numeric comparison pairing algorithm. The application should use this function to accept or reject the numeric key for the pairing procedure to proceed. This should be only used if LE Secure Connections are enabled.

ble_gap_set_sec_level()

Set the security level for a connection. If the device is already bonded, the existing Long Term Key (LTK) will be used to set-up encryption. If the device is not bonded, a pairing or a security request will be triggered (depending on whether the device is master or slave on the connection) with the bond flag set to false.

ble_gap_get_sec_level()

Get the security level currently established on a connection.

ble_gap_unpair()

Unpair a previously paired or bonded device. This will also remove security keys and bonding data info currently present in BLE storage.

5.1.3.3. Events

Table 42 describes the BLE events related to security that may be received by the application and the proper API functions to respond to them.

Table 42 BLE Security API events

Event

Argument

Description

BLE_EVT_GAP_PAIR_REQ

ble_evt_gap_pair_req_t

Pairing request received by a connected peer. Member <bond> indicates if the peer has requested a bond (that is, exchange of long term security keys). The application should use ble_gap_pair_reply() to respond to this request.

BLE_EVT_GAP_PAIR_COMPLETED

ble_evt_gap_pair_completed_t

A previously requested pairing procedure has been completed. Member <status> indicates the completion status of the procedure, while members <bond> and <MITM> indicate if a bond was established with the peer and if MITM (Man In The Middle) protection has been enabled on the connected link.

BLE_EVT_GAP_SECURITY_REQUEST

ble_evt_gap_security_request_t

Security request received by a connected peripheral peer. Members <bond> and <MITM> indicate if a bond and MITM protection have been requested by the peer. The application may use ble_gap_pair() to initiate pairing with the peer.

BLE_EVT_GAP_PASSKEY_NOTIFY

ble_evt_gap_passkey_notify_t

A passkey has been generated during a pairing procedure. This event will be received if the application has display capability. Member <passkey> contains the passkey that should be displayed to the user and entered by the peer for the pairing procedure to continue.

BLE_EVT_GAP_PASSKEY_REQUEST

ble_evt_gap_passkey_request_t

A passkey has been requested during a pairing procedure. This event will be received if the application has keyboard capability. The application should use ble_gap_passkey_reply() to respond to this request using the passkey entered by the user.

BLE_EVT_GAP_NUMERIC_REQUEST

ble_evt_gap_numeric_request_t

A numeric comparison has been requested during a pairing procedure. This event will be received if the application has keyboard or Yes/No and display capability. The application should use ble_gap_numeric_reply() to respond to this request using the accept or reject input entered by the user.

BLE_EVT_GAP_SEC_LEVEL_CHANGED

ble_evt_gap_sec_level_changed_t

The security level has been changed on an established link. Member <level> contains the security level that has been reached. This will be received after a pairing or an encryption procedure has been successfully completed.

BLE_EVT_GAP_SET_SEC_LEVEL_FAILED

ble_evt_gap_set_sec_level_failed_t

Setting the security level on an established link using ble_gap_set_sec_level() has failed. Member <status> indicates the reason for the failure. This will be received after an initiated encryption procedure has been unsuccessful. This may indicate that pairing should be requested again for the connected peer (for example, the peer may have lost the previously exchanged security keys).

5.1.3.4. Macros

Table 43 contains the configuration macros related to BLE security.

Table 43 BLE Security API macros

Macro

Default

Description

dg_configBLE_SECURE_CONNECTIONS

1

Set to 1 to use LE Secure Connections pairing if the peer supports the feature or to 0 to always use LE Legacy Pairing.

dg_configBLE_PAIR_INIT_KEY_DIST

GAP_KDIST_ENCKEY | GAP_KDIST_IDKEY | GAP_KDIST_SIGNKEY

Set the security keys requested to be distributed by the pairing initiator during a pairing feature exchange procedure.

dg_configBLE_PAIR_RESP_KEY_DIST

GAP_KDIST_ENCKEY | GAP_KDIST_IDKEY | GAP_KDIST_SIGNKEY

Set the security keys requested to be distributed by the pairing responder during a pairing feature exchange procedure.

5.1.3.5. Message Sequence Charts (MSCs)

5.1.3.6. Central

../_images/047.png

Figure 43 Central Pairing Just Works

../_images/048.png

Figure 44 Central Bonding Just Works

../_images/049.png

Figure 45 Central Bonding Passkey Entry (Central Display)

../_images/050.png

Figure 46 Central Bonding Passkey Entry (Peripheral Display)

../_images/051.png

Figure 47 Central Bonding Numeric Comparison (Secure Connections Only)

5.1.3.7. Peripheral

../_images/052.png

Figure 48 Peripheral Pairing Just Works

../_images/053.png

Figure 49 Peripheral Bonding Just Works

../_images/054.png

Figure 50 Peripheral Bonding Passkey Entry (Peripheral Display)

../_images/055.png

Figure 51 Peripheral Bonding Passkey Entry (Central Display)

../_images/056.png

Figure 52 Peripheral Bonding Numeric Comparison (Secure Connections Only)

5.1.4. BLE Power Control

The Bluetooth specification defines the way by which a device can request a remote device to make a specified change in its TX power level on a given PHY. The response of the peer device contains a value that indicates an acceptable reduction in the power level that allows the local device to further reduce its transmit power level to the minimum level possible. The local and remote devices can also share their current transmit power levels. This way they can also calculate the link path loss between them.

This 5.2 BLE Feature is supported and enabled by default by the SDK using the API described in Section 5.1.4.2

5.1.4.1. RSSI Golden Range

The BLE specification defines that the radio receiver can have an RSSI Golden Range that it prefers the incoming signal to remain within. This functionality is supported and so the local controller adjusts automatically the peer’s TX power to bring RSSI to its preferred value inside this Golden Range.

The local device continuously monitors the RSSI and if it is below the minimum or above maximum acceptable values of the RSSI it uses the Power Control Request procedure to request an increase or decrease the TX power level of the peer device. This procedure is done automatically by the local device. The application can be informed about the changes in the local and the remote TX power levels by using the ble_gap_tx_power_report_en() command. The changes in the TX power level are then reported to the application by the BLE_EVT_GAP_TX_PWR_REPORT event.

The default values of the RSSI Golden Range can be seen in Table 46.

5.1.4.2. Functions

Table 44 summarizes the API functions that can be used by the application to use the LE Power Control feature.

Table 44 BLE Power Control API functions

API call

Description

ble_gap_local_tx_power_get()

Get the current and maximum transmit power levels of the local device, on the ACL connection, for the indicated PHY.

ble_gap_remote_tx_power_get()

Get the TX power level used by the remote device, on the ACL connection for the indicated PHY.

ble_gap_path_loss_report_params_set()

Set the path loss threshold reporting parameters

ble_gap_path_loss_report_en()

Enable or disable path loss reporting.

ble_gap_tx_power_report_en()

Enable or disable reporting of TX power level in the local and remote device for the ACL connection.

ble_gap_rf_path_compensation_set()

Indicate the RF path gain or loss between the RF transceiver and the antenna contributed by intermediate components.

5.1.4.3. Events

Table 45 describes the BLE events related to LE Power Control that may be received by the application.

Table 45 BLE Power Control API events

Event

Argument

Description

BLE_EVT_GAP_LOCAL_TX_PWR

ble_evt_gap_local_tx_pwr_t

Reading of the local transmit power has been completed. Member <phy> indicates the PHY, member <curr_tx_pwr_lvl> indicates the current transmit power level (dBm) while member <max_tx_pwr_lvl> indicates the maximum transmit power level.

BLE_EVT_GAP_TX_PWR_REPORT

ble_evt_gap_tx_pwr_report_t

Reports that the local or remote transmit power has changed or that a ble_gap_remote_tx_power_get() command has been completed. Member <reason> indicates the reason the of event and device (local or remote) , member <phy> indicates the PHY involved (which might not be the current transmit PHY for the device), member <tx_pwr_lvl> indicates the value of TX power level (dBm), member <tx_pwr_lvl_flag> indicates if the TX power level is min or max while the member <delta> indicates the actual change in the TX power level.

BLE_EVT_GAP_PATH_LOSS_THRES

ble_evt_gap_path_loss_thres_t

Reports a path loss threshold crossing. Member <curr_path_loss> indicates the current path loss value while member <zone_enter> indicates which zone path loss has entered.

5.1.4.4. Macros

Table 46 contains the configuration macros related to BLE Power Control.

Table 46 BLE Power Control API macros

Macro

Default

Description

dg_configBLE_GOLDEN_RANGE_LOW

-70

Sets the lower RSSI value (dBm) of the Golden Range.

dg_configBLE_GOLDEN_RANGE_UP

-40

Sets the upper RSSI value (dBm) of the Golden Range.

dg_configBLE_GOLDEN_RANGE_PREF

-55

Sets the preferred RSSI value (dBm) inside the Golden Range (dBm).

dg_configBLE_PCLE_MIN_TX_PWR_IDX

GAP_TX_POWER_MINUS_26_dBm

Sets the Minimum TX Power index used in LE Power Control (dBm). The available TX power levels can be seen in Table 47

dg_configBLE_PCLE_MAX_TX_PWR_IDX

GAP_TX_POWER_MAX

Sets the Maximum TX Power index used in LE Power Control (dBm). The available TX power levels can be seen in Table 47

5.1.4.5. Supported TX Power Levels

Table 47 lists the available TX power levels. The corresponding gap_tx_power_t enumeration is located in the sdk/ble/api/include/ble_gap.h.

Table 47 TX power levels

Tx Power level

Corresponding index

GAP_TX_POWER_MAX

17

GAP_TX_POWER_6_dBm

17

GAP_TX_POWER_5_dBm

16

GAP_TX_POWER_4_5_dBm

15

GAP_TX_POWER_4_dBm

14

GAP_TX_POWER_3_dBm

13

GAP_TX_POWER_2_dBm

12

GAP_TX_POWER_1_5_dBm

11

GAP_TX_POWER_0_dBm

10

GAP_TX_POWER_MINUS_1_dBm

9

GAP_TX_POWER_MINUS_2_dBm

8

GAP_TX_POWER_MINUS_3_dBm

7

GAP_TX_POWER_MINUS_6_dBm

6

GAP_TX_POWER_MINUS_8_dBm

5

GAP_TX_POWER_MINUS_12_dBm

4

GAP_TX_POWER_MINUS_18_dBm

3

GAP_TX_POWER_MINUS_22_dBm

2

GAP_TX_POWER_MINUS_26_dBm

1

GAP_TX_POWER_MINUS_50_dBm

0

GAP_TX_POWER_MIN

0

5.1.5. BLE Storage

BLE Storage is the module that implements storage functionality for information related to connected and bonded peers, like security keys, CCC descriptors configuration and application-defined values. BLE Storage can maintain the list of connected and bonded devices both in RAM and in persistent storage (for example, in the flash). By default, devices are managed in RAM only and persistent storage must be explicitly enabled in the application’s configuration by defining macro CONFIG_BLE_STORAGE. Device data is then stored using Non-Volatile Memory Storage (NVMS) on the generic partition (see Section 4.2 for details).

Two kinds of data are stored:

  • Device pairing data (exchanged keys and related information).

  • Application-defined data managed using the BLE storage API (only the values with the ‘persistent’ flag set are stored in flash, for example CCC descriptor values).

Persistent storage can be enabled by the application by adding the following entries in the application’s custom configuration file:

Code 39 Enable BLE persistent storage
// enable BLE persistent storage
#define CONFIG_BLE_STORAGE

// enable Flash and NVMS adapters with VES (required by BLE persistent storage)
#define dg_configFLASH_ADAPTER                  1
#define dg_configNVMS_ADAPTER                   1
#define dg_configNVMS_VES                       1

The maximum number of bonded devices can be set using the defaultBLE_MAX_BONDED macro (defined to 8 by default). If the application attempts to bond more devices than its allowed, an error will be returned. This error should be handled by the application. It can then either unpair one of the currently bonded devices (using ble_gap_unpair() API function) or perform pairing without bonding.

Technical details on the BLE Storage implementation can be found in the following readme file:

<SDK_ROOT_PATH>/sdk/interfaces/ble/README.md

5.1.7. LE Data Packet Length Extension

For Bluetooth Core versions 4.0 and 4.1 the maximum Packet Data Unit (PDU) size was 27 octets. Bluetooth Core version 4.2 introduced an important enhancement, namely LE Data Packet Length Extension, which allows for the PDU size to be anywhere between 27 and 251 octets. This means that, for example, the L2CAP layer can now fill up to 247 octets of higher layer data packets in every L2CAP PDU compared to 21 octets with previous Bluetooth Core versions. This significant increase (more than 10 times) in the number of octets of user data sent per packet allows devices to transfer data up to 2.5 times faster than with previous versions. This will be of great benefit to applications that might require transferring large amounts of data such as Over-the-Air (OTA) firmware updates or downloading large data logs from sensors.

For the default PDU size to be extended on an established connection, the Data Length Update procedure must be performed. According to this control procedure, the LL_LENGTH_REQ and LL_LENGTH_RSP PDUs must be exchanged by the connected devices so that each is notified of its peer device’s capabilities. Each device uses these PDUs to report its maximum receive data channel and maximum transmit data channel PDU payload length and PDU time. After this update procedure, the PDU size for each direction of the connection’s data path is set by both controllers to the minimum of the values exchanged.

The DA1470x supports the LE Data Length Extension feature, so the values for the Receive and Transmit Data Length are set by default to the maximum allowed, which is 251 octets. The DA1470x controller when configured as a BLE central device will initiate a Data Length Update upon a new connection if the peer device’s controller supports this feature. The BLE Manager will use the values defined by dg_configBLE_DATA_LENGTH_RX_MAX and dg_configBLE_DATA_LENGTH_TX_MAX macros for this initial Data Length Update negotiation.

5.1.7.1. Functions

Table 51 LE Data Length Functions – ble_gap.h

Function

Description

ble_gap_data_length_set()

Set the maximum Transmit data length and time for an existing connection or the preferred Transmit data length for future connections (that is, the Transmit data length to be used in future data length update negotiations). Connection data length change will be signaled using BLE_EVT_GAP_DATA_LENGTH_CHANGED event.

5.1.7.2. Macros

Table 52 LE Data Length Definitions

Macro

Default

Description

dg_configBLE_DATA_LENGTH_RX_MAX

251

Set the maximum Receive Data Channel PDU Payload Length. Unless ble_gap_data_length_set() is used by the application, this will define the Receive data length present in the LE Data Length Update negotiations done by the device.

dg_configBLE_DATA_LENGTH_TX_MAX

251

Set the maximum Transmit Data Channel PDU Payload Length. Unless ble_gap_data_length_set() is used by the application, this will define the Transmit data length present in the LE Data Length Update negotiations done by the device.

5.1.7.3. Events

Table 53 LE Data Length Events – fetched using ble_get_event() - ble_gap.h

Event

Argument

Description

BLE_EVT_GAP_DATA_LENGTH_CHANGED

ble_evt_gap_data_length_changed_t

Data Length changed for specified connection. Members <rx_length>, <rx_time>, <tx_length> and <tx_time> specify the values obtained after an LE Data Length Update negotiation (each direction’s data length is typically set to the minimum of the values reported by the connected devices).

BLE_EVT_GAP_DATA_LENGTH_SET_FAILED

ble_evt_gap_data_length_set_failed_t

Data Length Set operation failed. Member <status> indicates the reason the set operation failed.

5.1.8. 2M PHY

Bluetooth Core versions up to 4.2 supported only the mandatory bit rate of 1 megabit per second (Mb/s), which is referred to as the LE 1M PHY. Bluetooth Core version 5.2, which is implemented in Dialog DA1470x product family, also supports the optional bit rate of 2 Mb/s, which is referred to as the LE 2M PHY. This feature is implemented by the LE controller and it can be configured using the API described in Section 5.1.8.1 and Section 5.1.8.2, although additional configuration is not mandatory; the LE controller will handle PHY update procedures seamlessly and will inform the application of any changes in the PHY configuration regardless of which side initiated the PHY update procedure.

5.1.8.1. Functions

Table 54 LE 2M Functions – ble_gap.h

Function

Description

ble_gap_phy_get()

Get the transmitter and receiver PHY preferences set for an existing connection or for all future connections. The changes in PHY configuration for a given connection will be signaled using BLE_EVT_GAP_PHY_CHANGED event.

ble_gap_phy_set()

Set the transmitter and receiver PHY preferences for an existing connection or for all future connections. Completion of the PHY set operation will be signaled using BLE_EVT_GAP_PHY_SET_COMPLETED event and possible change in PHY configuration for a given connection will be signaled using BLE_EVT_GAP_PHY_CHANGED event.

5.1.8.2. Events

Table 55 LE 2M Events – fetched using ble_get_event() - ble_gap.h

Event

Argument

Description

BLE_EVT_GAP_PHY_CHANGED

ble_evt_gap_phy_changed_t

PHY configuration changed for the specified connection. Members <tx_phy> and <rx_phy> specify the current configuration for the transmitter and received PHY respectively. This event is received only after a change in one or both of the transmitter and receiver PHY configurations.

BLE_EVT_GAP_PHY_SET_COMPLETED

ble_evt_gap_phy_set_completed_t

PHY set operation has completed. This will be received following a ble_gap_phy_set() call. Member <status> indicates the status of the set operation.

5.1.9. NVPARAM fields

Table 56 shows the Non-Volatile memory parameters which can be found in <SDK_ROOT_PATH>/sdk/middleware/adapters/include/platform_nvparam.h

Table 56 NVPARAM fields

Tag

Offset

Length

NVPARAM_BLE_PLATFORM_BD_ADDRESS

0x01

6 bytes array

NVPARAM_BLE_PLATFORM_LPCLK_DRIFT

0x07

16bit value

NVPARAM_BLE_PLATFORM_EXT_WAKEUP_TIME

0x0D

16bit value

NVPARAM_BLE_PLATFORM_OSC_WAKEUP_TIME

0x0E

16bit value

NVPARAM_BLE_PLATFORM_RM_WAKEUP_TIME

0x0F

16bit value

NVPARAM_BLE_PLATFORM_SLEEP_ENABLE

0x11

1 byte

NVPARAM_BLE_PLATFORM_EXT_WAKEUP_ENABLE

0x12

1 byte

NVPARAM_BLE_PLATFORM_BLE_CA_TIMER_DUR

0x40

16bit value

NVPARAM_BLE_PLATFORM_BLE_CRA_TIMER_DUR

0x41

1 byte

NVPARAM_BLE_PLATFORM_BLE_CA_MIN_RSSI

0x42

1 byte

NVPARAM_BLE_PLATFORM_BLE_CA_NB_PKT

0x43

16bit value

NVPARAM_BLE_PLATFORM_BLE_CA_NB_BAD_PKT

0x44

16bit value

NVPARAM_BLE_PLATFORM_IRK

0x80

16 bytes array

5.1.10. Considerations on BLE Task Priorities

The BLE Software in the SDK consists of three modules:

  1. BLE manager: Provides the interface to the BLE functionality of the chip. The application task uses the BLE API to interface with the BLE manager. The BLE manager is a task that stands between the application and the BLE adapter. It uses the BLE adapter to interface with the BLE Stack.

  2. BLE adapter: The system task that provides the interface to the BLE Stack. It runs the BLE Stack internal scheduler, receives the commands or the replies to events from the BLE manager, and passes BLE Stack events to the BLE manager. BLE core functionality is implemented by the BLE adapter task.

  3. BLE Stack: The software stack that implements the Link Layer and the BLE Host stack, specifically the Logical Link Control and Adaptation Protocol (L2CAP), the Security Manager Protocol (SMP), the Attribute Protocol (ATT), the Generic Attribute Profile (GATT) and the Generic Access Profile (GAP).

Note

The BLE Stack software is run under the BLE adapter’s task context, which instantiates and initializes the stack.

The two BLE system tasks have by default a higher priority than application tasks in the SDK.

Note

Application developers should always make sure BLE adapter and BLE manager tasks always have a higher priority than application tasks.

The BLE adapter runs the BLE Stack scheduler, which dispatches all the messages between the BLE Stack’s different layers and calls the appropriate handlers. For example, when an application uses API ble_gatts_send_event() to send a GATT notification, this will result in a propagation of messages between the BLE manager, the BLE adapter and several BLE Stack’s internal layers until it reaches a transmission buffer and, eventually, the air.

5.1.11. BLE tasks timing requirements

In DA1470x the LE controller runs independently from the application. However, the BLE system tasks (BLE adapter, BLE manager) still need to run regularly to make sure BLE data are transmitted and handled in a timely manner. When the application is not making any BLE API calls and no data are exchanged with the peer, the BLE adapter will typically block until it is needed to either retrieve a message from or send a command to the BLE Host.

In some scenarios the BLE manager and the BLE adapter will communicate with messages without notifying the application. For example, upon connection with a peer that uses a resolvable private address, the BLE manager will attempt to resolve it using known devices IRKs. In this case the BLE manager and BLE adapter will have more running slots.

There are also other cases when the BLE framework will require a reply from the application when, for example, a pair request or a write request is received from the peer. Again, in these cases the BLE adapter and BLE manager will have to run more times in a period between two connection events.

5.1.12. Attribute operations

As the Attribute protocol is strict when an attribute request such as a read or a write request is received, the BLE Stack’s GATT layer will switch to a busy state for as long as the request is not completed/handled. In the case of a write request or a read request of an attribute whose value is to be provided by the application, then the application will have to confirm these operations using ble_gatts_read_cfm() or ble_gatts_write_cfm() respectively (after receiving BLE_EVT_GATTS_READ_REQ or BLE_EVT_GATTS_WRITE_REQ). In this case, other GATT operations, such as notification sending, will be queued until this request is confirmed.

5.1.13. BLE Application Examples

5.1.13.1. Advertising Application

The simplest BLE project in the SmartSnippets™ DA1470x SDK is ble_adv demo which is found in the folder <SDK_ROOT_PATH>/projects/dk_apps/demos/ble_adv. The application starts the device as a peripheral, sets the device name and advertising data and starts advertising. Code 40 is an extract from main.c.

Code 40 Set BLE device
// Start BLE module as a peripheral device
ble_peripheral_start();

// Set device name
ble_gap_device_name_set("Dialog ADV Demo", ATT_PERM_READ);

// Set advertising data
ble_gap_adv_data_set(sizeof(adv_data), adv_data, 0, NULL);

// Start advertising
ble_gap_adv_start(GAP_CONN_MODE_UNDIRECTED);

No BLE service is added, and the ones exposed are just GAP and GATT services. The infinite loop that implements the lifetime behavior of the application uses just ble_get_event(true) to block indefinitely on the BLE manager’s event queue. As soon as an event is posted there, the task unblocks and handles it using a switch case. Code 41 is located in main.c.

Code 41 Example of event handle
for (;;) {
             ble_evt_hdr_t *hdr;

             /* notify watchdog on each loop */
             sys_watchdog_notify(wdog_id);

             /* suspend watchdog while blocking on ble_get_event() */
             sys_watchdog_suspend(wdog_id);

             /*
              * Wait for a BLE event - this task will block
              * indefinitely until something is received.
              */
             hdr = ble_get_event(true);

             /* resume watchdog */
             sys_watchdog_notify_and_resume(wdog_id);

             if (!hdr) {
                     continue;
             }

             switch (hdr->evt_code) {
             case BLE_EVT_GAP_CONNECTED:
                     handle_evt_gap_connected((ble_evt_gap_connected_t *) hdr);
                     break;
             case BLE_EVT_GAP_DISCONNECTED:
                     handle_evt_gap_disconnected((ble_evt_gap_disconnected_t *) hdr);
                     break;
             case BLE_EVT_GAP_PAIR_REQ:
             {
                     ble_evt_gap_pair_req_t *evt = (ble_evt_gap_pair_req_t *) hdr;
                     ble_gap_pair_reply(evt->conn_idx, true, evt->bond);
                     break;
             }
             default:
                     ble_handle_event_default(hdr);
                     break;
             }

             // Free event buffer
             OS_FREE(hdr);
     }

Since the BLE service framework is not used, the only events handled by the application are the three events handled by the switch case: connection, disconnection and pair request. This makes sense for this application as its only purpose is to start a connectable advertising, restart it in case of a disconnection and respond to pair requests from devices that require pairing/bonding upon connection.

Running this project will result in an advertising BLE peripheral device exposing GAP and GATT services. GAP service attributes can be read using any standard BLE central device.

The ble_peripheral project is a good starting point for developing BLE peripheral applications. It is found in folder <SDK_ROOT_PATH>/projects/dk_apps/features/ble_peripheral. Unlike other example projects, it does not implement a specific profile, but instead exposes several BLE services via a GATT server.

The application’s initialization is similar to other projects that implement BLE peripheral applications. It uses the BLE service framework to instantiate several BLE services:

  • Battery Service (multiple instance)

  • Current Time Service

  • Device Information Service

  • Scan Parameter Service

  • Dialog Debug Service

  • Custom User Service

In addition to Bluetooth SIG-adopted services, ble_peripheral project instantiates two more services, Dialog Debug Service and a custom user service.

The Dialog Debug Service can be used to interact with the services that the application exposes using a Control Point characteristic to write commands and receive notifications from. A detailed description of the ways to interact with the Dialog Debug Service is included in the readme.md file inside the project’s folder.

The custom user service does not define any specific functionality other than using 128-bit UUIDs for services, characteristics and descriptors. This custom service, referred to as myservice in the project source code, is an example of implementing a custom service using BLE API calls to create its attribute database. No specific functionality is defined when one of these attributes is read or written. More details on how to create and use custom services will be given in Section 5.1.16.

After the attribute database is created, the device will end-up advertising and it will wait for a connection event.

The ble_peripheral project uses the BLE service framework to handle service events, the application also defines handlers for connection, advertising completion and pair request events. The ble_peripheral project stands in terms of its completeness somewhere between the ble_adv demo and an application implementing a full BLE profile like the pxp_reporter.

The services the project will expose can be configured using the file config/ble_peripheral_config.h.

5.1.14. BLE profile projects

In addition to the projects described in the previous sections, there are several application projects that implement BLE profiles. These projects are more complex and provide a full implementation of BLE applications. As such they provide a good reference on how to combine the BLE functionality with several OS mechanisms, GPIO handling and interfacing with external sensors.

The implemented profiles are located under <SDK_ROOT_PATH>/projects/dk_apps/ble_profiles and listed below:

  • Apple Media Service (AMS) - Client role

  • HID over GATT Profile (HOGP) – Device role (hogp_device)

  • HID over GATT Profile (HOGP) – Host role (hogp_host)

  • Heart Rate Profile – Sensor role (hrp_sensor)

  • Heart Rate Profile – Collector role (hrp_collector)

  • Proximity Profile – Reporter role (pxp_reporter) located under <SDK_ROOT_PATH>/projects/dk_apps/demos

  • Weight Scale Profile – Weight Scale role (wsp_weightscale)

  • Apple Notification Center Service (ANCS) - Notification Consumer (NC) role (ancs)

  • Blood Pressure Profile (BLP) – Blood Pressure Sensor role (blp_sensor)

  • Bond Management Service (BMS)

  • Cycling Speed And Cadence collector (CSCP)

5.1.15. Using adopted BLE services

Table 57 summarizes the API header files of the BLE services implemented by the SmartSnippets™ DA1470x SDK. These files can be found under <SDK_ROOT_PATH>/sdk/interfaces/ble/services/include. The developer can use these APIs to add these services to another project.

Table 57 BLE service API header files

File name

Description

ble_service.h

BLE service framework API:

  • Add service to framework

  • Handle event using BLE service framework

  • Elevate permission

  • Get number of attributes in a service

  • Add included services

bls.h

Blood Pressure Service – BLS

bas.h

Battery Service – BAS

bcs.h

Body Composition Service – BCS

bms.h

Bond Management Service – BMS

cts.h

Current Time Service – CTS

dis.h

Device Information Service – DIS

dlg_debug.h

Dialog Debug Service

dlg_suota.h

Dialog SUOTA Service

hids.h

Human Interface Device Service – HID

hrs.h

Heart Rate Service – HRS

ias.h

Immediate Alert Service – IAS

lls.h

Link Loss Service – LLS

scps.h

Scan Parameters Service – ScPS

sps.h

Serial Port Service – SPS

tps.h

Tx Power Service – TPS

uds.h

User Data Service – UDS

wss.h

Weight Scale Service – WSS

5.1.16. Adding a custom service

The following code segments provide an overview of the initialization required to create a new custom service called XXX. It requires the files xxx.c and xxx.h to be created. A good example to base these on is the dlg_mls service in the Multi-Link demo. This provides a single write only characteristic in the service.

Each service needs a structure containing both the generic ble_service_t structure and any callbacks and characteristic handles required by the service. In the example below for service XXX there is one callback and one characteristic defined.

Code 42 Structure definition for XXX service
typedef struct {
     ble_service_t svc;          // Core BLE service structure
     xxx_cb_t cb;                // Callback provided by app to xxx
                                 // service to process an event
     uint16_t xxx_char1_val_h;   // Declare handle for each characteristic
                                 // that can be read or written
} xxx_service_t;

The requirements of the initialization function xxx_init() are illustrated below. They key information here is the comments which are explaining what each line is doing.

Code 43 Initialization function for XXX service
xxx_service_t* xxx_init(callback1){
// Allocate and initialise xxx_service_t structure
// Define any callback functions required by the service, write only in this case
xxx->svc.write_req = <this services write request handler>
// Create primary service UUID with either 16 or 128 bit value
uuid=ble_uuid_from_string() or uuid=ble_uuid_create16()
// add PRIMARY service with X attributes
num_attrs=X
ble_gatts_add_service(&uuid, GATT_SERVICE_PRIMARY, num_attrs)
// Create characteristic 1 for this service and allocate handle for it in GATT table
ble_gatts_add_characteristic(&uuid, GATT property, ATT permissions, size,0, NULL, &xxx->xxx_char1_h)
// Set start_h and pass in null terminated variable length list of all characteristic handles in the service
ble_gatts_register_service(&xxx->svc.start_h, &xxx->xxx_char1_h,0);
// Calculate end handle for service based on number of attributes in service
xxx->svc.end_h= xxx->svc.start_h + num_attrs;
// add the passed in callback function to service structure
xxx->xxx_cb1=callback1;
// add newly created service to ble framework
ble_service_add(&xxx->svc);
// and return handle for the service to the application
return &xxx->svc
}

5.1.17. Extending BLE functionality

The Dialog BLE API can be used to develop BLE applications. The API header files are located in folder <SDK_ROOT_PATH>/sdk/interfaces/ble/api/include. They are documented in [Ref_04] and are summarized in Table 58.

Table 58 Dialog BLE API header files

File name

Description

ble_att.h

Attribute Protocol API: Mostly definitions.

ble_attribdb.h

Helper to manage complex attributes database.

ble_bufops.h

Helpers to put and get data from BLE buffers.

ble_common.h

Common API: Functions used for operations not specific to a certain BLE Host software component

ble_gap.h

GAP API:

  • Device parameters configuration: device role, MTU size, device name exposed in the GAP service attribute, etc.

  • Air operations: Advertise, scan, connect, respond to connection requests, initiate or respond to connection parameters update, etc.

  • Security operations: Initiate and respond to a pairing or bonding procedure, set the security level, unpair, etc.

ble_gatt.h

Common definitions for GATT API

ble_gattc.h

GATT client API:

  • Discover services, characteristics, etc. of a peer device

  • Read or write a peer device’s attributes

  • Initiate MTU exchanges

  • Confirm the reception of indications

ble_gattc_util.h

GATT client utilities

ble_gatts.h

GATT server API:

  • Set up the attribute database

  • Set attribute values

  • Notify/indicate characteristic values

  • Initiate MTU exchanges

  • Respond to write and read requests

ble_l2cap

BLE L2CAP API.

ble_storage.h

BLE persistent storage API.

ble_uuid.h

BLE UUID declarations and helper functions.

5.2. Software Upgrade Over The Air (SUOTA)

5.2.1. Introduction

The SmartSnippets™ DA1470x SDK allows the user to update the software of the device wirelessly, using the Software Upgrade Over The Air (SUOTA) procedure.

When an update procedure is initiated from an Android or iOS device, a new firmware image is first transferred to the Flash memory and then the device reboots to complete the update. The final verification and activation of the new image is performed by the ROM boot loader on the next reboot. For more information see Section 5.3.1.

The SUOTA GATT server runs on the DA1470x device and the GATT client on the Android or iOS device running the SUOTA application.

5.2.2. SUOTA service description

This section gives a brief description of the SUOTA service, responsible for performing software upgrades over BLE. A detailed service characteristic description is given on Table 59.

Table 59 SUOTA service characteristics

Characteris tic

SUOTA Version

(SUOTA_VERS ION definition)

Access

Size

Description

MEM_DEV

since version v1.3

READ

WRITE

4

Using this characteristic the GATT client is able to send commands to the SUOTA service. Some of the most commonly used commands are the following:

  • SPOTAR_IMG_SPI_FLASH( 0x13): Prepare for SUOTA. Image is going to be stored to the FLASH memory.

  • SPOTAR_REBOOT(0xFD): Reboot the device.

  • SPOTAR_IMG_END(0xFE): Client sent the whole image. SUOTA service is allowed to perform CRC calculations and other sanity tests to verify that the image transfer was successful.

GPIO MAP

READ

WRITE

4

Used to specify GPIO map of external FLASH device. Currently not applicable.

MEM_INFO

READ

4

Stores the total number of bytes received until now.

PATCH_LEN

since version v1.3

READ

WRITE

2

Specifies the number of bytes that when received, will trigger a notification back to the GATT client. This is meant to be used for flow control. The exact value is set by the GATT client during SUOTA. The notification is generated from the “STATUS” characteristic

PATCH_DATA

since version v1.3

READ

WRITE

WRITE_NO_RE SP

Exact size is specified by PATCH_DATA CHAR_SIZE

This is the endpoint to which SUOTA image data are sent. The exact size is specified by the “PATCH_DATA CHAR_SIZE” characteristic, and different values (23 – 509) can be used depending on the throughput requirements

STATUS

since version v1.3

READ

NOTIFY

1

This characteristic is used to notify the GATT client of the status of the SUOTA process. Status notifications are sent to indicate error conditions (for example bad command, or CRC) or to allow flow control during SUOTA process.

L2CAP_PSM

since version v1.3

READ

2

This is an optional characteristic that, if exists, indicates that the SUOTA service supports both SUOTA over GATT and SUOTA over L2CAP CoC. The value indicates the dynamic L2CAP channel on which the SUOTA service is listening for connections. The absence of this characteristic indicates that only SUOTA over GATT is supported.

VERSION

since version v1.3

READ

1

Indicates the version of the SUOTA service. The value is retrieved from the “SUOTA_VERSION” definition.

MTU

since version v1.3

READ

2

Stores the current value of the MTU, which is going to be either 23 (default), or a bigger value, if MTU exchange took place. This value can be used by the GATT client to retrieve the MTU size (if such API is not available on its side) and write with optimal rate to the “PATCH_DATA” characteristic.

PATCH_DATA CHAR_SIZE

since version v1.3

READ

2

Specifies the size of the “PATCH_DATA” characteristic.

CCC

READ

WRITE

1

Client Characteristic Configuration Allows the GATT client to enable notifications from the “STATUS” source.

Once the SUOTA service is discovered on the remote device and the GATT client has enabled notifications by writing the CCC characteristic, the SUOTA procedure can be started by issuing the SPOTAR_IMG_SPI_FLASH command. The write command executes successfully only if:

  • No more than one device is currently connected to the SUOTA enabled device

  • The application hosted in the SUOTA enabled device allows the upgrade to take place

  • There is enough memory to allocate the internal working buffers

If any of the above restrictions is violated, then command fails and an error notification is sent back to the GATT client (status SUOTA_SRV_EXIT). After a successful command execution the service is able to receive data either over GATT or L2CAP CoC layer (if the L2CAP_PSM characteristic is available).

The GATT client can use the value of the characteristic MTU to perform ATT write commands to the characteristic PATCH_DATA with optimal size if the GATT Client (mobile phone) has no API to find the optimal packet size. It is also possible for the GATT client to retrieve the size of the PATCH_DATA characteristic by reading the PATCH_DATA_CHAR_SIZE characteristic.

Following this, the GATT client should specify the value of the patch_len variable by writing the PATCH_LEN characteristic. PATCH_LEN specifies the number of bytes that once received, will trigger a notification back to the GATT client. This kind of flow control could be used to avoid flooding the SUOTA enabled device with too much image data. The bigger the value, the better the throughput, since notifications are going to be generated less frequently and therefore the number of missed connection events (where flow has stopped waiting for the notification) is decreased.

For example, if patch_len is set to 500 bytes, notifications will be sent to the GATT client when byte ranges 1-500, 501-1000, 1001 – 1500 etc. are received. Following the Bluetooth low energy specification, the maximum number of bytes that can be written to the PATCH_DATA characteristic with a single ATT write command is the minimum of MTU – 3 and the size of the PATCH_DATA characteristic.

When the whole image has been sent, the GATT client should issue the SPOTAR_IMG_END command to indicate this to the SUOTA service. The service is going to perform some sanity checks to verify that image transfer took place without errors, and then it is going to generate the appropriate status notification (SUOTA_CMP_OK on success, SUOTA_APP_ERROR or SUOTA_CRC_ERR on error).

Finally, the GATT client could issue an SPOTAR_REBOOT command to force a device reboot. This step is optional, but it is highly recommended.

Note

The PATCH_DATA, PATCH_DATA_CHAR_SIZE and PATCH_LEN characteristics are only relevant when SUOTA over GATT is taking place. When L2CAP CoC are used, a connection should be established to the L2CAP_PSM channel via L2CAP CoC and the flow is controlled using L2CAP credits. SUOTA service assigns enough credits to ensure that flow won’t stop during the upgrade. Notifications relevant to the PATCH_LEN characteristic are not sent during image transfer, but all other notifications are still valid.

5.2.3. SUOTA Flow

Figure 54 and Figure 55 illustrate the abstract FW update steps. Figure 56 illustrates in detail the flow diagram of the SUOTA.

The final verification and activation of the new image is performed by the ROM boot loader on the next reboot.

Figure Figure 75 illustrates the booting sequence that shows at which step the acceptance/rejection of a FW image happens.

../_images/112.png

Figure 54 Flash layout after initial programming during production

../_images/113.png

Figure 55 Flash image layout after SUOTA image written in the destination partition

../_images/114.png

Figure 56 Flash image layout after reboot and activation of the new image

5.2.4. Performing SUOTA upgrade using a mobile phone

Note

The following procedure applies when using Android or iOS devices

5.2.4.1. Preparing the SUOTA enabled device

  1. Build the pxp_reporter application using the DA1470x-00-Release_OQSPI_SUOTA configuration.

  2. Erase the Flash memory of DA1470x using the erase_oqspi_jtag script.

  3. Download the pxp_reporter binary to DA1470x using the program_oqspi_jtag script.

Press the B1 (Reset) button on the DK daughter board. PXP Reporter application should start running.

5.2.4.2. Preparing the SUOTA image

A SUOTA image is a binary file with a proper header that can be sent to a target device from an Android or iOS device.

To prepare a SUOTA image using PXP Reporter demo application, perform the following steps:

  1. Import the following two projects into SmartSnippets™ Studio from these locations.

    • python_scripts: <SDK_ROOT_PATH>\utilities

    • pxp_reporter: <SDK_ROOT_PATH>\projects\dk_apps\demos

  2. Build the pxp_reporter application using the DA1470x-00-Release_OQSPI_SUOTA configuration. A new image named pxp_reporter.img is also created under DA1470x-00-Release_OQSPI_SUOTA folder.

5.2.4.3. Perform the SUOTA using the mobile application

  1. Download the Dialog SUOTA application from Google PlayStore or Apple App Store.

  2. Copy pxp_reporter.img to an Android phone or tablet or to an iOS device and placed into the SUOTA folder. The folder is automatically created, if it does not exist, on the device by running the “Dialog Suota” application. On Android it is located at the root directory of the “Internal Storage” drive.

  3. Launch the Dialog SUOTA application on the Android phone and select the DA1470x device you want to update.

../_images/076.png

Figure 57 Device selection

  1. Select Update device.

../_images/077.png

Figure 58 Update device

  1. Select the appropriate image file – this is a list of the files in the SUOTA directory.

../_images/078.png

Figure 59 Image file

Note

The screens shown in Figure 60, Figure 61 have no effect for the DA1470x product.

  1. Touch Send to device on the touchscreen

../_images/079.png

Figure 60 Parameter settings for SPI

../_images/080.png

Figure 61 Parameter settings for I2C

  1. Wait until the process is completed. When the image is uploaded, a dialog box pops up asking for a device reboot. Select OK.

../_images/081.png

Figure 62 Uploading the image file

../_images/082.png

Figure 63 Reboot device

  1. Press Close to return to the main menu.

../_images/083.png

Figure 64 When file upload is finished, press “Close”

5.2.5. Performing SUOTA upgrade using two DA1470x devices

This section describes the procedure for performing SUOTA using two DA1470x devices.

  • One acting as the BLE central. It performs as the SUOTA image transmitter running ble_suota_client application. The SUOTA image will be stored in the NVMS_BIN_PART NVMS partition.

  • One acting as the BLE peripheral. It performs as the SUOTA image receiver, running pxp_reporter application.

Using this setup, it is possible to test both SUOTA methods (over GATT and over L2CAP Connection-Oriented Channels) without using any phone. The image to be transferred is stored in the NVMS_BIN_PART partition in the Flash memory of the BLE central device.

5.2.5.1. Preparing the SUOTA enabled device pxp_reporter

Follow the steps described in Section 5.2.4.1

As soon as program_oqspi_jtag is executed, a new window pops up asking to choose which device is the target; select the appropriate device.

../_images/094.png

Figure 65 Selecting target device

5.2.5.2. Preparing the SUOTA update image

See Section 5.2.4.2

5.2.5.3. Preparing the ble_suota_client device

5.2.5.4. Building the ble_suota_client application

Import the following projects using SmartSnippets™ Studio from the following locations:

  • python_scripts: <SDK_ROOT_PATH>\utilities

  • ble_suota_client: <SDK_ROOT_PATH>\projects\dk_apps\features

and select the following build configuration for the ble_suota_client project:

  • ble_suota_client in DA1470x-00-Release_OQSPI configuration.

To build and install the ble_suota_client, follow the procedure below:

  1. Build the project ble_suota_client by executing DA1470x-00-Release_OQSPI

  2. Erase the flash memory of the device by executing the erase_oqspi_jtag script.

  3. Execute the program_oqspi_jtag script to program the OQSPI Flash memory.

As soon as the script is executed, a new window pops up asking to choose which of the DKs is the target; select the appropriate device.

../_images/087.png

Figure 66 Selecting the target device

5.2.5.5. Storing the SUOTA update image in the ble_suota_client device

Note

The SUOTA update image was created in step Preparing the SUOTA update image

Use cli_programmer to download the binary pxp_reporter.img to the device.

> <SDK_ROOT_PATH>\binaries\cli_programmer.exe <SERIAL_PORT> write_oqspi 0x300000 <SDK_ROOT_PATH>\projects\dk_apps\demos\pxp_reporter\DA1470x-Debug_OQSPI_SUOTA\pxp_reporter.img

5.2.5.6. Performing the software upgrade procedure

When the previous procedure has finished, the two DA1470x devices are ready to communicate. To perform SUOTA on the PXP Reporter device, the following steps should be followed:

  • A serial terminal is needed to connect to the ble_suota_client device and perform the SUOTA. In the example below, “Real Term” is used for this purpose. Configure the serial terminal as follows:

../_images/131.png

Figure 67 Configuring the serial port

  • Connect to the serial port and press the B1 RESET button.

../_images/1301.png

Figure 68 Connecting to the serial port

Information regarding the image stored in the NVMS_BIN_PART partition are displayed during boot

  • In the serial terminal of the ble_suota_client device, give the following command:

    > scan start

../_images/120.png

Figure 69 Scanning for available devices

The ble_suota_client device starts scanning for available devices immediately (Figure 69). In this in example, from the devices listed Figure 69, the pxp_reporter device, is the device with sequence number [01].

  • As soon as the pxp_reporter device is found, the scanning operation can be stopped with the following command:

    > scan stop

  • A connection to the pxp_reporter device can be initiated with the command:

    > connect 1

The first argument of the “connect” command refers to the device index on the scan result list. Once a connection is established, the application automatically queries the remote device for available services and device information. The characteristic values of the Device Information Service (DIS) are read. The following output is printed on the terminal:

../_images/121.png

Figure 70 Connecting to loader device

The presence of “L2CAP PSM” indicates that the remote device supports SUOTA and over-L2CAP COC.

  • To update a device supporting L2CAP COC over L2CAP, issue the update command. To update the same device over GATT, issue the update gatt command. If the remote device does not support L2CAP COC (“L2CAP PSM” is not displayed), both update and update gatt commands begin SUOTA over GATT.

    > update gatt

../_images/122.png

Figure 71 Updating with new image the loader device

After the image transfer has been completed, the remote device disconnects and reboot as shown in Figure 72.

../_images/123.png

Figure 72 Transfer complete

When the pxp_reporter device reboots, execution of the new image begins.

The software upgrade has finished. Now the pxp_reporter device should start advertising as <Dialog PX Reporter>. To verify that, perfom the scan operation again by issuing the scan start command.

> scan start

../_images/108.png

Figure 73 Verifying that loader is running PX Reporter

5.3. Booting

The Boot ROM code is executed by SYSCPU (Cortex-M33) after a POR or HW Reset. As an additional capability it can be executed after wakeup when the RESET_ON_WAKEUP feature is configured.

Four different booting flavors are supported:

  • Boot from cached OQSPI FLASH without any security features, configuration script (CS) in OTP

  • Boot from cached OQSPI FLASH without any security features, configuration script (CS) in FLASH

  • Boot from cached OQSPI FLASH with security features, configuration script (CS) in OTP

  • Boot from UART without FLASH or security features

In case neither Flash nor UART is available, the booter remains in an infinent loop.

The Configuration Script (CS) is a table of up to 256 32-bit entries. System can boot without CS but if it exists it is stored either in Flash or OTP. The booter expects CS to start at address 0x00000000 of flash or at 0x00000C00 of OTP. It contains information related to booter, like Product header location, the development mode flag, entries which can be used by SDK or application software to apply some values to specific registers (address value pair) or to use them as trim values for a subsystem. The address value pair entry in CS is a way to enable the secure features of the booter. For extra protection in secure boot mode CS is stored to OTP in order to be difficult to change the security related entries. For more info about CS valid entries, please refer to the DA1470x datasheet chapter “System Overview”, paragraph “OTP”, section “Configuration Script”.

5.3.1. Boot flow

The boot flow is divided in five separate phases

  • Initialization: The booter takes care of configuring power domains and peripheral clocks. The boot code resets the OTP (One Time Programmable) - and Flash memory in order to be able to access them. It enables the Development Mode by default. In Development Mode the booter enables the debugger and it is also possible to update the devices firmware by UART. Development mode can be deactivated adding the corresponding entry in CS.

  • Run Configuration Script: The booter searches for the CS in the beginning of Flash or address 0x00000C00 of OTP. If CS is found it starts parsing it and applies its directives, otherwise continues to next state.

  • Retrieve application code: This phase depends on Development mode.

    Development mode enabled
    The booter will enable debugger and try to boot form UART. If it fails to retrieve an application from UART it will try to locate a valid product header. If it finds a product header will continue to next phase otherwise will try to boot from UART again. In case there is no valid product header the booter will enter an infinite loop. This loop is active until it boots form UART or a debugger is connected.

    Development mode disabled
    In this case both debugger and booting from UART feature are disabled. The booter tries to locate and parse the Product and image headers from FLASH. If these headers are invalid, HW Reset is performed.

  • Device administration: The booter will first check if there is any pending update. In case of update it will validate the new firmware image and if the validation succeeds the booter will accept the update. Otherwise the booter will reject the update, trigger a HW Reset and boot the old image. In case there is no pending update the booter will validate the active (old) image. If validation fails the booter will trigger again a HW reset. Image validation depends on secure mode. If the image is no secure then the booter checks for ‘Qq’ in the beginning of the image header. If FW image is secure then booter has to do the below additional checks.

    1. Checks if image header contains the security section (0xAA22).

    2. Checks if image header contains the administration section (0xAA44)

    3. Checks if is able to extract public keys, encryption keys, signature verification value and NONCE.

    4. Checks if the public key used for the signature verification is not revoked.

    5. Checks if application encryption key is not revoked.

    6. Checks if signature value calculated using Ed25519 algorithm is the correct one.

    In case any of the above checks fail, then the booter will trigger a HW reset.

  • Load image: This is the final booting phase in which the actual FW image is loaded. This phase is divided in the following steps:

    1. Flash OQSPI controller configuration retrieving information from the Product header.

    2. In case the secure feature is enabled, the decryption key is fetched through DMA to OQSPI controller. OQSPI controller supports image decryption on the fly.

    3. Interrupt Vector Table (IVT) is copied to RAM.

    4. Cache controller is configured to point to the beginning of IVT.

    5. OQSPI FLASH address is remapped to address 0x0.

    6. SW_RESET is triggered.

    If any error occurs during this phase a HW reset will be triggered by the booter.

For more details please refer to Figure 75.

../_images/002.png

Figure 75 BootROM flow

5.3.2. FLASH Layout

The bootROM expects at least three different section in FLASH to be able to boot.

These are shown in : Figure 77.

../_images/005.png

Figure 77 FLASH layout

Configuration Script is an optional region.

Product header starts with “Pp” and contains information about the address of the active and updated FW image, OQSPI controller and the Flash device setup and the CRC code used to verify Product header integrity. There are two Product Headers, Primary and Backup which are identical. The reason is that we want to protect system from a possible Product header corruption. The booter first does a CRC check of the primary header and if is successful it parses it, otherwise does a CRC check of the backup header. In case both headers are corrupted the booter continues as there is no Product Header and behaves as described above in boot flow chapter in paragraph Retrieve application code. If only primary is corrupted, the booter copies backup header to primary header. If active and update FW image addresses are different, the booter will trigger the update procedure. An update image is considered valid when all the checks described in Device Administration phase are successful. An update is considered successful when the booter executes successfully all the boot flow phases described above.

  • Product Header fields:

  1. Flash Programmed identifier “Pp” Product header identifier

  2. Active FW Image Address address in Flash of the active FW Image header

  3. Update FW Image Address address in Flash of the update FW Image header

  4. Flash BURSTCMDA reg value value written in the OQSPIC_BURSTCMDA_REG of the OQSPI controller

  5. Flash BURSTCMDB reg value value written in the OQSPIC_BURSTCMDB_REG of the OQSPI controller

  6. Flash CTRLMODE reg value value written in the OQSPIC_CTRLMODE_REG of the OQSPI controller

  7. 0xAA11 Flash config section identifier

  8. Length of Flash Config Section size of Flash config section

  9. Flash Write Config reg Command sequence commands for OQSPI controller

Image header region starts with “Qq” and is divided into three sections, common, security and administration.

  • Common section fields:

  1. Image identifier (“Qq”) Image header identifier

  2. size the FW image size

  3. CRC CRC value for image integrity check

  4. Timestamp number of seconds passed since epoch (1/1/1970)

  5. IVT offset from the beginning of the Image header to the start of the IVT.

  • Security section fields:

  1. 0xAA22 security section identifier

  2. Length of security section size of security section

  3. Index to ECC key signature key index

  4. Index to Sym. key FW decryption key

  5. NONCE used in FW decryption algorithm

  6. Length of security section size in bytes of the signature value

  7. Signature signature value used for signature verification

  • Administration sections fields:

  1. 0xAA44 administration section identifier

  2. Length of administration section size of administration section

  3. 0xAA55 key revocation section identifier

  4. Length of the key revocation record size of key revocation record

  5. Key Type type of the key(eg ECC, Sym., User data key)

  6. Key Index key index

  7. next key records if are available

For security reasons the key revocation status is stored in a specific area in OTP per key type.

IVT and executable is the section where the IVT and application are stored. In case the boot is secure this section is encrypted.

5.3.3. OTP Layout

The OTP consists of several different segments. None of these segments needed in case of no secure boot. In the below table there is a description of the segments.

Table 60 OTP Layout

Segment

Bytes

Description

OTP Address

1

1024

Configuration Script

~100 registers write operations

0x00000C00

2

256

O/QSPI FW Decryption Keys Area – Payload

write/read protected when secure mode enabled in CS

Secure mode connects those (8 * 256-bits) keys to O/QSPI Controller

0x00000B00

3

256

User Data Encryption Keys – Payload

Write/Read protected when secure mode enabled in CS.

Secure mode connects those (8 * 256-bits) keys to AES engine

0x00000A00

4

32

O/QSPI FW Decryption Keys Area – Index

8 entries for 8 256-bit keys

0x000009E0

5

32

User Data Encryption Keys – Index

8 entries for 8 256-bit keys

0x000009C0

6

256

Signature Keys Area – Payload

0x000008C0

7

32

Signature Keys Area – Index

0x000008A0

8

2208

Customer Application Area (Secondary bootloader, binaries, …)

0x00000000

5.4. Build configurations and startup procedure

This section provides a brief description of the supported project build configurations and the startup procedure of the SmartSnippets™ DA1470x SDK.

5.4.1. Build configurations

SmartSnippets™ DA1470x SDK projects by default support the following two types of build configurations:

Table 61 Build Configurations

Build Configuration

Location of code and read-only data

Cache enabled

Description

RAM

RAM

No

Program is loaded directly to RAM

OQSPI

OQSPI Flash

Yes

Program runs in place from OQSPI Flash

5.4.1.1. OQSPI build configuration

In this mode, the code will be placed in Flash whereas variables will be placed in RAM. It is also possible to move specific functions to RAM using the __RETAINED_CODE macro. This could be useful when a particular function implements time critical functionality. A program built for Flash cached mode is written into OQSPI Flash memory with the CLI programmer tool. Along with the application image, OQSPI Flash shall be programmed with valid Product and Image Headers too. Upon hard reset, the booter reads these headers to set up the external Flash memory and prepare the application image before jumping to it.

After hard reset, ROM is remapped to address 0x0 and the booter starts executing. At first, the booter will read the Product and Image headers (located to Flash) to verify that a valid firmware image is programmed to Flash. It will also program the OQSPI Flash controller according to the contents of the relevant fields in the Product Header to enable OQSPI Auto mode and execution in place (XiP). Before starting executing the programmed firmware image, the booter sets up the cache controller and copies application’s IVT (Interrupt Vector Table) from Flash to RAM. SYS_CTRL_REG[REMAP_INTVECT] is enabled so that the virtual addresses 0 - 0x1FF are mapped to the beginning of RAM, i.e. memory area 0x00000000 0x000001FF is mapped to memory area 0x20000000 0x200001FF. This ensures that the IVT is always located in RAM for quick access. SYS_CTRL_REG[REMAP_ADR0] is set to 2 (Flash is remapped to virtual address 0x0) and a software reset is applied. CPU fetches the location of Reset_Handler from RAM (since SYS_CTRL_REG[REMAP_INTVECT] is enabled), and the Reset_Handler starts executing from Flash.

A program built with the OQSPI build configuration can be executed using the OQSPI_DA1470x debug configuration using the SmartSnippets™ Studio. It is also possible to execute the image by pressing the Reset button on the development kit, after programming the Flash memory.

5.4.1.2. RAM build configuration

In this mode, both code and data are placed in RAM. A program built with the RAM build configuration can be loaded to RAM either directly using the ELF file and J-Link debugger or be first converted to a raw binary and then written to RAM with the CLI programmer tool. RAM mode is used only for debugging purposes as it avoids the step of programming OQSPI Flash. After loading the program, SYS_CTRL_REG[REMAP_ADR0] must be configured so that RAM is mapped to address 0x00000000. After a soft reset is issued, the written program starts execution. The RAM mode does not rely on the boot loader. Therefore, the Product and Image headers in OQSPI Flash do not affect firmware execution.

A program built with the RAM build configuration can be executed using the RAM_DA1470x debug configuration using the SmartSnippets™ Studio. It is also possible to load and execute the image using the CLI programmer tool. In both cases SYS_CTRL_REG[REMAP_ADR0] is configured appropriately by the corresponding tool.

5.4.2. Startup procedure

Startup code is the part of the program that runs after reset and before entering main().

Briefly, it consists of the following steps, as shown in Figure 79 (please consult the startup code within the SmartSnippets™ DA1470x SDK for details):

../_images/startup_procedure_da1470x.png

Figure 79 Startup procedure

  1. Reset_Handler in sdk\bsp\startup\startup_da1470x.S starts execution.

  2. SystemInitPre() in sdk\bsp\startup\system_da1470x.c is called, to do the following:

    • Enable debugger (if the corresponding macro, dg_configENABLE_DEBUGGER, is enabled).

    • Set dividers of the AMBA High Speed Bus and Peripheral Bus.

    • Check IC version compatibility with SW.

    • Check alignment of copy and zero tables.

    • Bring the pad latches, the memory controller, the PDC and the peripherals’ clocks to a well known initial state.

Note

No variable initialization should take place here, since copy and zero tables have not been initialized yet and any modifications to variables will be discarded. For the same reason, functions that initialize or are using initialized variables should not be called from here.

  1. Reset_Handler in sdk\bsp\startup\startup_da1470x.S resumes execution.

    • Copy code and data to RAM according to the .copy.table section.

    • Initialize certain memory areas to zero according to the .zero.table section.

  2. SystemInit() in sdk\bsp\startup\system_da1470.c is called, simply handling the FPU.

  3. _start() is called which in turn calls __libc_init_array(). __libc_init_array() in turn calls da1470x_SystemInit(), with the latter doing the following. In the end, _start() calls main().

    • Initialize the .bss section to zero.

    • Initialize TRNG/DRBG conditionally.

    • Configure interrupt priorities.

    • Configure the QSPI Flash and Cache. This is done to fine-tune the configuration applied by the booter code for the particular Flash model.

    • Read Trim and Calibration Section (TCS) values from One-Time Programmable (OTP) memory.

    • Configure the power domain controller (PDC).

    • Bring power domains to a well known initial state.

    • Activate BOD protection.

5.5. Memory Layout

This section gives a brief description on the available program and data memories.

Information on the memory layout is given in Figure 81 and in Table 63 below.

../_images/da1470x_system_mem.png

Figure 81 System Memory

Table 63 Memory Layout

Memory Device

Start Address | End Address

Size (bytes)

Size (KiB)

Remapped Devices

0x00000000

0x08000000

0x08000000

131072

ROM

0x0F020000

0x0F030000

0x00010000

64

OTP

0x10080000

0x10090000

0x00010000

64

RAM (Code Interface)

0x10000000

0x10050000

0x00050000

320

RAM (System Interface)

0x20000000

0x20180000

0x00180000

1536

OQSPI FLASH (Code Interface, cached)

0x18000000

0x20000000

0x00800000

131072

OQSPI FLASH (System Interface, uncached)

0x38000000

0x40000000

0x00800000

131072

QSPI FLASH (System Interface, cached)

0x28000000

0x30000000

0x00800000

131072

QSPI FLASH (System Interface, uncached)

0x48000000

0x50000000

0x00800000

131072

5.5.1. ROM

The 64KiB ROM is mapped to address 0x0F020000 and is only used for executing the booter code. This is the first piece of code that will be executed after a HW reset, before jumping to the application.

5.5.2. OTP

The 64KiB one-time programmable (OTP) memory is accessible at address 0x10080000 and its main purpose is to store information (Product Header and Configuration Script) that will be used by the booter before jumping to the application.

5.5.3. System RAM

The RAM (up to 1536KiB) is mapped to addresses 0x10000000 (Code Interface) and 0x20000000 (System Interface). Accesses to RAM are never cached. In BLE projects, some memory is used by the CMAC CPU. In SNC projects, some memory is used by the SNC CPU. The system RAM comprises 14 RAM cells (4-256KiB in size), which can be indpendently configured to not retain their content during sleep, to decrease power consumption. It is also possible for a RAM cell to be completely disabled in case it is not needed by the application.

5.5.4. External OQSPI Flash

The external FLASH memory (up to 128MiB) is mapped to addresses 0x18000000 (Code Interface) and 0x38000000 (System Interface). Accesses to the external FLASH memory are only cached when performed through the Code Interface (or the Remapped Region) and are performed transparently thanks to the use of the internal OQSPI FLASH controller. The BLE Host, the SNC and the M33 application code are located here.

5.5.6. External QSPI Flash/PSRAM

The external FLASH memories (up to 128MiB, each) are mapped to addresses 0x28000000 (external Flash System Interface) and 0x48000000 (external PSRAM System Interface). An 8 kB data cache with write-back capabilities, is integrated into one of the two QSPI controllers for increased read/write performance for use with an external PSRAM only.

5.5.7. Remapped Region

Depending on the value of SYS_CTRL_REG[REMAP_ADR0] register field, accesses to the address region [0x0, 0x07FFFFFF] can be mapped to

  • The ROM (0x0F020000)

  • The system RAM (0x10000000)

  • The External OQSPI Flash address range (0x18000000)

When SYS_CTRL_REG[REMAP_INTVECT] is set to 1, the address range [0x0, 0x1FF] is mapped to System RAM [0x10000000, 0x100001FF] address range, allowing to have the interrupt vector table in RAM while executing from FLASH.

When FLASH is remapped to address 0x0 (SYS_CTRL_REG[REMAP_ADR0] is set to 2) and the cache is enabled, the effective FLASH address for an address in the range [0x0, 0x07FFFFFF] used by the CPU or the Debugger depends on the value of CACHE_FLASH_REG:

  • FLASH_REGION_BASE (CACHE_FLASH_REG[31:16]) defines the FLASH region base.

  • FLASH_REGION_OFFSET (CACHE_FLASH_REG[15:4]) defines the offset for each subregion.

  • FLASH_REGION_SIZE (CACHE_FLASH_REG[3:0]) defines the FLASH region size.

The effective FLASH address for a remapped address can be calculated as follows:

(CACHE_FLASH_REG[FLASH_REGION_SIZE] << 16) + (CACHE_FLASH_REG[FLASH_REGION_OFFSET] << 2) + ADDR

This enables an application image compiled to execute from address 0x0, to be placed in different FLASH locations to allow firmware update.

Consider for example the case where CACHE_FLASH_REG has the value 0x18009001:

  • FLASH_REGION_BASE is 0x1800.

  • FLASH_REGION_OFFSET is 0x900. Since this field is in 32-bit words, the actual offset in bytes is 0x2400.

  • FLASH_REGION_SIZE is 0x1. This value corresponds to a flash region size of 0.5MiB (0x80000).

Any CPU or Debugger access to address 0x1000 will be remapped to FLASH address 0x18002400 + 0x1000. Since CACHE_FLASH_REG[FLASH_REGION_BASE] is 0x1 (0.5 MiB), the valid address range that the CPU is allowed to access through the remapped region will be [0x0, 0x7FFFF - 0x2400], which translates to the [0x18002400, 0x1807FFFF] FLASH range. Trying to access an address outside this region will give undefined results. The same restrictions apply also to the code memory range, e.g. the CPU will be able to access only addresses [0x18000000, 0x1807FFFF] using the code memory range, 0x18000000 to 0x187FFFFF. Note though, that the address range [0x18000000, 0x180023FF] will be accessed uncached. However, the debugger can access the whole code memory range, 0x18000000 to 0x187FFFFF, regardless of the CACHE_FLASH_REG configuration.

The CACHE_FLASH_REG is configured to the proper value during booter execution, depending on the product and application FW headers.

Both the CPU and the debugger are able to access the whole FLASH address range without restrictions using the uncached system memory range, 0x38000000 to 0x387FFFFF.

5.6. Non-Volatile Memory Storage (NVMS)

SmartSnippets™ DA1470x SDK defines a software layer for Non-Volatile Memory Storage management. It is essential for the user to have a clear understanding of the requirements for the following elements that could use non-volatile storage:

  • System Parameters that need to be stored in NVM (e.g. device address)

  • Firmware upgrade (dual images)

  • Application specific binaries (e.g. pre-recorded audio messages)

  • Logging (e.g. event logs)

  • Application data (e.g. sensor values, statistics, authentication keys)

For each storage type, a corresponding dedicated region is allocated in the Flash partition table. Each region is identified by a partition ID. When the NVMS Adapter performs read/write accesses from/to storage, it uses the partition ID and an offset. Additional details can be found in the NVMS Adapter The NVMS Adapter.

SmartSnippets™ DA1470x SDK defines the following Flash partitions (in a non-SUOTA build) to manage storage:

  • (PRODUCT_HEADER) Product header

  • (PARTITION_TABLE) Partition table

  • (FW) Firmware Image Region

  • (PARAMS) Parameters Region

  • (BIN) Binaries Region

  • (LOG) Logging of events or values

  • (GENERIC) Generic data Region, Statistics etc.

The exact memory mapping depends on the actual Flash device (i.e. size, sector size) used on the board. This mapping needs to be defined at compile-time in the file <SDK_ROOT_PATH>/bsp/config/8M/partition_table.h (or <SDK_ROOT_PATH>/bsp/config/8M/suota/partition_table.h when SUOTA is used). A default partition table is provided with SmartSnippets™ DA1470x SDK, which fits in the DK OQSPI Flash (8MiB with sectors of 4KiB), the actual definition of which is shown in Code 46:

Code 46 NVMS mapping
PARTITION2( NVMS_PRODUCT_HEADER_PART  , 0 )
PARTITION2( NVMS_PARTITION_TABLE      , PARTITION_FLAG_READ_ONLY )
PARTITION2( NVMS_FIRMWARE_PART        , 0 )
PARTITION2( NVMS_GENERIC_PART         , PARTITION_FLAG_VES )
PARTITION2( NVMS_LOG_PART             , 0 )
PARTITION2( NVMS_BIN_PART             , 0 )
PARTITION2( NVMS_PARAM_PART           , 0 )

When SUOTA is implemented, an additional partition is required so that both the current and the updated image can be stored, as shown in Code 47:

Code 47 NVMS mapping (SUOTA)
PARTITION2( NVMS_PRODUCT_HEADER_PART  , 0 )
PARTITION2( NVMS_PARTITION_TABLE      , PARTITION_FLAG_READ_ONLY )
PARTITION2( NVMS_FW_EXEC_PART         , 0 )
PARTITION2( NVMS_GENERIC_PART         , PARTITION_FLAG_VES )
PARTITION2( NVMS_FW_UPDATE_PART       , 0 )
PARTITION2( NVMS_LOG_PART             , 0 )
PARTITION2( NVMS_BIN_PART             , 0 )
PARTITION2( NVMS_PARAM_PART           , 0 )

5.7. XiP (OQSPI) FLASH Support

This section describes the XiP flash memory support in SmartSnippets™ DA1470x SDK and provides a guideline on how to add support for new flash memories. The XiP flash memory is connected to the OQSPI controller (OQSPIC), which supports both OCTA and QUAD bus flash memories.

5.7.1. API description

The OQSPI flash memory functionality is implemented by the oqspi_automode.{h c} API. The file tree of the API is shown below:

sdk
│
└───memory
|   |   README OQSPI.md
│   │
│   └───include
│       │   oqspi_automode.h
│       │   oqspi_common.h
│       │   oqspi_<manufacturer>_<bus_mode>.h
│       │   oqspi_<manufacturer_prefix><memory_name>.h
│       │
│       src
│       │   oqspi_automode.c

where:

  • <manufacturer>: The name of the manufacturer, e.g. Macronix, Winbond, Adesto, Gigadevice.

  • <bus_mode>: The bus mode of the memory, e.g. quad, octa.

  • <manufacturer_prefix>: A prefix that determines the memory manufacturer, e.g. mx (Macronix), w (Winbond), at (Adesto), gd (Gigadevice).

  • <memory_name>: The OQSPI flash memory name.

The oqspi_common.h contains definitions of common macros and structures, whereas the header files oqspi_<manufacturer>_<bus_mode>.h contain all common code used by a particular group of memories, e.g. the oqspi_macronix_octa.h contains common code for octa bus Macronix flash memories. The oqspi_<manufacturer_prefix><memory_name>.h are the flash memories drivers, which contain all the neccessary settings and callback functions, in order for the API to properly configure the OQSPIC for that memory.

5.7.2. Supported flash memories

The SDK supports the next flash memories:

  • Macronix MX25U6432, 64MBit (QUAD)

  • Winbond W25Q64JWIM, 64MBit (QUAD)

The default XiP flash memory is the Macronix MX25U6432, which is mounted on the ProDK daughterboard.

5.7.2.1. Select OQSPI flash memory

The OQSPI flash memory is selected by defining the macros shown in Table 64.

Table 64 OQSPI flash memory build macros

OQSPI flash memory build macros

Description

dg_configOQSPI_FLASH_HEADER_FILE

This macro must be defined as a string and determines the name of the OQSPI flash driver, for which the application is built for, e.g. “oqspi_mx25u6432.h”, “oqspi_w25q64jwim.h” etc. It must be either one of the oqspi_<flash_memory>.h header files, found in <SDK_ROOT_PATH>/sdk/bsp/memory/include, or a custom header file located in a directory, which is included in the compiler’s search path.

dg_configOQSPI_FLASH_CONFIG

This macro defines the oqspi_flash_config_t structure of the OQSPI flash driver, for which the application is built for, e.g. oqspi_mx25u6432_cfg, oqspi_w25q64jwim_cfg etc

In order to change the OQSPI flash memory of an application, define the aforementioned macros in config/custom_config_xxx.h (see the example Code 48.).

Code 48 Select OQSPI flash memory
   #define dg_configOQSPI_FLASH_HEADER_FILE        "oqspi_w25q64jwim.h"
   #define dg_configOQSPI_FLASH_CONFIG             oqspi_w25q64jwim_cfg

5.7.3. Build configuration options

The SmartSnippets™ DA1470x SDK has two build configuration macros (see Table 65), which determine whether the OQSPI flash memory is pre-configured at compile time or autodetected at runtime, and if the former is selected, whether its JEDEC ID is verified or not.

Table 65 OQSPI flash memory autodetection/verification macros

dg_configOQSPI_FLASH_AUTODETECT

dg_configOQSPI_FLASH_CONFIG_VERIFY

0

0

Default option. The OQSPI flash memory is pre-configured at compile time, yet its JEDEC ID is NOT verified.

0

1

The OQSPI flash memory is pre-configured at compile time, and its JEDEC ID is verified (NOT recommended for production builds).

1

0

The OQSPI flash memory is autodetected at runtime (NOT recommended for production builds).

1

1

Not valid build configuration. When the OQSPI flash is autodetected atruntime the JEDEC ID is verified, hence the dg_configOQSPI_FLASH_CONFIG_VERIFY is meaningless.

5.7.3.1. Pre-configured at compile time

When the XiP flash memory is pre-configured at compile time, only the flash driver of the selected memory is compiled, thus a binary file with smaller build size is generated. For this reason, this option is suitable for production builds.

5.7.3.2. Autodetected at runtime

When the XiP flash memory is autodetected at rutime, the JEDEC ID of the connected flash memory is read and compared with the JEDEC ID of all supported memories. If the JEDEC ID matches, the corresponding flash driver is used in order to configure the OQSPIC properly. If not, the default OQSPI flash driver, determined by the macros shown in Table 64 will be used, and most likely the OQSPIC will not be configured properly.

Since the flash driver is detected at runtime, all OQSPI flash drivers must be included to the binary and this increases its size by a few KBytes. Therefore, this option is NOT recommended for production builds and it is mainly used by the flashing tools (uartboot, cli_programmer, segger_flash_loader etc).

Note

The default pre-configured OQSPI flash memory is the recommended option for production builds, since the autodetect option increases by a few KBytes, both the build size of the binary and the retained RAM usage.

The default values of these macros are defined in sdk/config/bsp_defaults_da1470x.h.

5.7.4. OQSPI flash driver

The OQSPI memory drivers are header files located in sdk/bsp/memory, which contain an instance of the OQSPI flash configuration structure oqspi_flash_config_t, as well as a set of callback and helper functions, neccessary to configure the OQSPIC properly for a particular memory. The oqspi_flash_config_t is defined in oqspi_common.h.

Table 66 oqspi_flash_config_t structure

oqspi_flash_config_t field

Description

jedec.manufacturer_id

The JEDEC manufacturer ID.

jedec.type

The JEDEC device type.

jedec.density

The JEDEC device density.

jedec.density_mask

The JEDEC device density mask, which is used to mask the device density reading, if needed. If not, must be set 0xFF.

size_mbits

The memory size in MBits.

address_size

The address size of the commands, which can be either 24bits or 32bits length.

clk_mode

Pointer to the flash-specific initialization function.

opcode_len

The length of the command opcode, which can be either 1 byte or 2 bytes length. If 2 bytes length is used, the second byte of the opcode equals to the inverted value of the first one.

read_instr_cfg.opcode_bus_mode

The bus mode of the opcode phase of the reading command.

read_instr_cfg.addr_bus_mode

The bus mode of the address phase of the reading command.

read_instr_cfg.extra_byte_bus_mode

The bus mode of the extra byte phase of the reading command. This phase is used in order to determine whether the next reading command will be in continuous mode of operation or not. When the continuous mode of operation is enabled, the opcode is sent only on the first reading and is omitted on all subsequent reading commands.

read_instr_cfg.dummy_bus_mode

The bus mode of the dummy bytes phase of the reading command.

read_instr_cfg.data_bus_mode

The bus mode of the data phase of the reading command.

read_instr_cfg.continuous_mode

Enable/disable the continuous mode of operation. If enabled, the read_instr_cfg.extra_byte_cfg must be enabled as well, and the read_instr_cfg.extra_byte_value must be set according to the datasheet of the memory in order to maintain the continuous mode of operation.

read_instr_cfg.extra_byte_cfg

Enable/disable the extra byte phase.

read_instr_cfg.extra_byte_half_cfg

Enable/disable transmitting only the high nibble of the extra byte, meaning that the output is switched to high-z during the transmission of the low nibble. Not applicable in octa bus mode, where it must always remain disabled. This setting is very rarely enabled in general.

read_instr_cfg.opcode

The opcode of the reading command.

read_instr_cfg.extra_byte_value

The value of the byte sent during the extra byte phase provided that the read_instr_cfg.extra_byte_cfg is enabled. If the continuous mode of operation is enabled, set this field according to the memory. If disabled, is usually set to 0xFF yet many other values could serve the same scope.

read_instr_cfg.cs_high_delay_nsec

The minimum required delay (nsec) that the CS signal has to stay at idle state between two consecutive read commands.

erase_instr_cfg.opcode_bus_mode

The bus mode of the opcode phase of the erasing command.

erase_instr_cfg.addr_bus_mode

The bus mode of the address phase of the erasing command.

erase_instr_cfg.hclk_cycles

The delay in terms of AMBA hclk clock cycles without flash memory reading requests before performing an erase or erase resume command.

erase_instr_cfg.opcode

The opcode of the erasing command.

erase_instr_cfg.cs_high_delay_nsec

The minimum required delay (nsec) that the CS signal has to stay at idle state between a Write Enable, Erase, Erase Suspend or Erase Resume command and the next consecutive command.

read_status_instr_cfg.opcode_bus_mode

The bus mode of the opcode phase of the read status register command.

read_status_instr_cfg.receive_bus_mode

The bus mode of the receive phase of the read status register command.

read_status_instr_cfg.dummy_bus_mode

The bus mode of the dummy bus phase of the read status register command.

read_status_instr_cfg.dummy_value

The value that is transferred on the OQSPI bus during the dummy cycles phase. In fact, the value of the dummy bytes is out of interest. However, there are some octa flash memories (e.g. Macronix) which require an address phase, during which the transmitted data must equal to 0x00. Since the OQSPIC does not support a dedicated address phase for the read status register command, this option allows forcing the dummy bytes value to 0x00 achieving the same result.

read_status_instr_cfg.busy_level

The level where the busy bit (low or high) is considered as busy.

read_status_instr_cfg.busy_pos

The position of the busy bit in the status register.

read_status_instr_cfg.dummy_bytes

The number of the dummy bytes of the read status register command. It should not been confused with the dummy bytes of read command.

read_status_instr_cfg.opcode

The opcode of the read status command.

read_status_instr_cfg.delay_nsec

The minimum delay in nsec between the read status register command and the previous erase command. Usually is not needed, thus is set 0.

write_enable_instr_cfg.opcode_bus_mode

The bus mode of the opcode phase of the write enable command.

write_enable_instr_cfg.opcode

The opcode of write enable command.

page_program_instr_cfg.opcode_bus_mode

The bus mode of the opcode phase of the page program command.

page_program_instr_cfg.addr_bus_mode

The bus mode of the address phase of the page program command.

page_program_instr_cfg.data_bus_mode

The bus mode of the data phase of the page program command.

page_program_instr_cfg.opcode

The opcode of page program command.

suspend_resume_instr_cfg.suspend_bus_mode

The bus mode of the opcode phase of the suspend command.

suspend_resume_instr_cfg.resume_bus_mode

The bus mode of the opcode phase of the resume command.

suspend_resume_instr_cfg.suspend_opcode

The opcode of the suspend command.

suspend_resume_instr_cfg.resume_opcode

The opcode of the resume command.

suspend_resume_instr_cfg.suspend_latency_usec

The minimum required latency in usec to suspend an erase operation. The next consecutive erase resume command cannot be issued before this delay has elapsed.

suspend_resume_instr_cfg.resume_latency_usec

The minimum required latency in usec to resume an erase operation. Once the resume command is issued, the currently suspended erase operation resumes within this time.

suspend_resume_instr_cfg.res_sus_latency_usec

The minimum required latency in usec between an erase resume and the next consequent erase suspend command.

exit_continuous_mode_instr_cfg.opcode_bus_mode

The bus mode of the opcode phase of the exit from continuous mode command.

exit_continuous_mode_instr_cfg.sequence_len

The number of the bytes needed to be shifted out, in order for the flash memory to get out from continuous mode. Once the memory is switched to continuous mode, the value of the extra byte determines whether the next read command will be sent in continuous mode or not, in other words with or without opcode. The extra byte is sent immediately after the address phase, which means that the the number of the bytes must be at least 4 or 5, for 24 bit and 32 bit address size respectively. Any higher amount of bytes work fine as well, yet in will cause a redundant overhead.

exit_continuous_mode_instr_cfg.disable_second_half

Disable the output during the second half of the sequence. Not applicable in octa bus mode.

exit_continuous_mode_instr_cfg.opcode

The opcode of the exit from continuous mode command.

delay.reset_usec

The minimum required delay in usec after a reset sequence.

delay.power_down_usec

The minimum required delay between an enter power down sequence and the moment the memory enters the power down mode.

delay.release_power_down_usec

The minimum required delay between a release power down sequence and the moment the memory exits from power down mode

delay.power_up_usec

The minimum required power up delay.

callback.initialize_cb

Flash memory initialization callback function which is called at system startup.

callback.sys_clk_cfg_cb

This function is called every time the system clock changes in order to re-configure the OQSPIC.

callback.exit_opi_qpi_cb

Callback function which exits the flash memory from OPI/QPI mode for octa/quad flash memories respectively.

callback.get_dummy_bytes_cb

Callback function which returns the number οf the dummy bytes for any given system clock.

callback.is_suspended_cb

Callback function which checks whether the flash erase operation is suspended.

callback.is_busy_cb

Callback function which checks whether the flash memory is busy.

callback.read_status_reg_cb

Callback function to read the status register of the memory.

callback.write_status_reg_cb

Callback function to write the status register of the memory.

5.7.5. Add support for a new OQSPI flash memory

The SDK provides support for a specific set of Octa and Quad bus flash memories. Each of those memories has a dedicated OQSPI flash driver. In order to add support for a new memory, a new driver must be implemented. It is recommended to use as a starting point either the header file of an already existing driver or the template header file oqspi_xxx.h. If the new memory belongs to an already supported group of memories, the macros and the functions from oqspi_<manufacturer>_<bus_mode>.h files can be used in order to eliminate the development effort.

  1. Copy and rename either an existing driver header file or the template header file oqspi_xxx.h. It is recommended to follow the naming pattern oqspi_<manufacturer_prefix><memory_name>.h.

  2. Rename the prefixes of all functions and variables accordingly. Keep in mind, that all drivers reside in the same name-space, therefore the names of all functions and variables must be unique.

  3. Set all JEDEC ID fields (ID, type and density) according to the datasheet.

    • jedec.manufacturer_id

    • jedec.type

    • jedec.density

  4. Set the size of the memory in MBits, the address size and the opcode length:

    • size_mbits

    • address_size

    • opcode_len

  5. Set the opcode and the bus mode of all phases of the following commands (instructions) according to the datasheet:

    • READ command
      • read_instr_cfg.opcode

      • read_instr_cfg.opcode_bus_mode

      • read_instr_cfg.addr_bus_mode

      • read_instr_cfg.extra_byte_bus_mode

      • read_instr_cfg.dummy_bus_mode

      • read_instr_cfg.data_bus_mode

    • ERASE SECTOR command
      • erase_instr_cfg.opcode

      • erase_instr_cfg.opcode_bus_mode

      • erase_instr_cfg.addr_bus_mode

    • READ STATUS command
      • read_status_instr_cfg.opcode

      • read_status_instr_cfg.opcode_bus_mode

      • read_status_instr_cfg.receive_bus_mode

      • read_status_instr_cfg.dummy_bus_mode

    • WRITE ENABLE command
      • write_enable_instr_cfg.opcode

      • write_enable_instr_cfg.opcode_bus_mode

    • PAGE PROGRAM command
      • page_program_instr_cfg.opcode

      • page_program_instr_cfg.opcode_bus_mode

      • page_program_instr_cfg.addr_bus_mode

      • page_program_instr_cfg.data_bus_mode

    • ERASE SUSPEND & ERASE RESUME commands
      • suspend_resume_instr_cfg.suspend_opcode

      • suspend_resume_instr_cfg.resume_opcode

      • suspend_resume_instr_cfg.suspend_bus_mode

      • suspend_resume_instr_cfg.resume_bus_mode

  6. Set the next timing parameters according to the datasheet:

    • read_instr_cfg.cs_idle_delay_nsec

    • erase_instr_cfg.cs_idle_delay_nsec

    • suspend_resume_instr_cfg.suspend_latency_usec

    • suspend_resume_instr_cfg.resume_latency_usec

    • suspend_resume_instr_cfg.res_sus_latency_usec

    • delay.reset_usec

    • delay.power_down_usec

    • delay.release_power_down_usec

    • delay.power_up_usec

The name of the timing parameters vary between different manufacturers, even between different memory variants of the same manufacturers. The next table represents the names of these parameters according to the datasheets of the supported memories, and can be used as a reference point.

manufacturer

Read cs_idle_delay

Erase cs_idle_delay

suspend_latency

resume_latency

res_sus_latency

reset

power_down

release_power_down

power_up

Macronix

tSHSL (read)

tSHSL (erase)

tESL

(*)

tERS

tREADY2 (4KB Sector Erase)

tDP

tRES1

tVSL

Winbond

tSHSL1

tSHSL2

tSUS

(*)

tSUS

tRST

tDP

tRES1

tVSL

Gigadevice

tSHSL

tSHSL

tSUS

(*)

tRS

tRST_E

tDP

tRES1

tVSL

Adesto

tCSH

tCSH

tSUS

(*)/tRES

tSUS

tRST/tSWRST

tDP/tEDPD

tRES1/tRDPD

tVSL/tVCSL

(*) The res_sus_latency is usually not mentioned in the AC characteristics table of the datasheet. Please, read carefully the description of the erase suspend/resume commands for more information.

  1. Set properly the next settings regarding the continuous mode of operation. The continuous mode is also named by some memory manufacturers as performance enhanced mode or burst mode. When this feature is enabled, the opcode is sent only once, during the first read command, and then all subsequent read commands are issued without it. The OQSPI flash memory enters and stays in continuous mode, as long as the value of the extra byte phase maintains a specific pattern, which is memory dependant. In order to get the memory out from continuous mode this pattern must be violated (the value 0xFF is usually used, which works for all supported flash memories).

    • read_instr_cfg.continuous_mode

    • read_instr_cfg.extra_byte_cfg

    • read_instr_cfg.extra_byte_half_cfg

    • read_instr_cfg.extra_byte_value

    • exit_continuous_mode_instr_cfg.opcode_bus_mode

    • exit_continuous_mode_instr_cfg.sequence_len

    • exit_continuous_mode_instr_cfg.disable_second_half

  2. Implement and set the next callback functions. If the new memory belongs to an already supported group of memories consider using the already implemented callback and helper functions from oqspi_<manufacturer>_<bus_mode>.h. Even if the callbacks of the new flash driver must be diffentiated, parts of the aforementioned functions could be used to eliminate the effort.

    • callback.initialize_cb: Make sure that all needed register fields are set properly. Refer to the datasheet for more information. Typical configurations set by this function: Bus Mode, Dummy Bytes, Address Size, Slew Rate, Current Strength etc.

    • callback.sys_clk_cfg_cb: Reconfigure the dummy bytes and any other parameter of the flash memory based on the new system clock frequency.

    • callback.exit_opi_qpi_cb: Issue the right sequence in order to exit the memory from OPI/QPI mode.

    • callback.get_dummy_bytes_cb: Return the dummy bytes for any given system clock. This function is used by initialize_cb() and sys_clk_cfg_cb() in order to set the dummy bytes properly.

    • callback.is_suspended_cb: Implement the right sequence in order to check whether an Erase or Page Program operation is suspended.

    • callback.is_busy_cb: Implement the right sequence in order to check whether the memory is busy or not.

    • callback.read_status_reg_cb: Implement the right sequence in order to read the status register.

    • callback.write_status_reg_cb: Implement the right sequence in order to write the status register.

5.8. QSPI FLASH Support

This section describes the Storage (QSPI) Flash support in SmartSnippets™ DA1470x SDK and provides a guideline on how to add support for new QSPI flash memories. The stotage flash memory is connected to the QSPI controller (QSPIC), which supports QUAD bus flash memories. Below are listed the flash memories supported by the SDK:

  • Adesto: AT25SL128, 128MBit

  • Winbond: W25Q256FW, 256Mbit

All these flash memories have been tested with the SDK release under all build configurations options. The default device is the Adesto AT25SL128, which is mounted on the ProDK motherboard. Another memory can be selected by changing the macros shown in Section 5.8.4.

The Section 5.8.7 describes how to add support for other flash memories, which have the same boot sequence with the aforementioned supported devices.

5.8.1. Build configuration options

The SmartSnippets™ DA1470x SDK supports two discrete build configuration options, in terms of whether the QSPI flash memory is pre-configured at compile time or autodetected at runtime.

Note

The pre-configured QSPI flash configuration is the default and recommended option for production builds, since the autodetect option increases both the build size of the binary, as well as the retained RAM memory usage by a few KBytes.

5.8.2. Pre-configured at compile time

When the QSPI flash memory is pre-configured at compile time, only the corresponding flash driver of the selected memory is compiled, thus a binary file with lower build size is generated. For this reason, this option is suitable for production builds.

5.8.3. Autodetected at runtime

When the QSPI flash memory is autodetected at rutime, the Jedec ID of the connected flash memory is read and in turns compared with the IDs of the memories officially supported by the SmartSnippets™ DA1470x SDK. If the Jedec ID matches, the corresponding flash driver is used in order to configure the QSPI controller properly. If not, the default flash driver, determined by the macros dg_configFLASH_CONFIG and dg_configFLASH_HEADER_FILE, will be used and most likely the QSPI controller will not be configured properly. Since the flash driver is determined at runtime, the flash drivers of all supported memories need to be included to the binary and this is why its size is increased by a few KBytes. This option is NOT recommended for production builds and it is mainly used by the flashing tools, e.g. uartboot and cli_programmer.

5.8.4. Build configuration macros

The Flash subsystem is configured using the macros shown in Table 68, which must be defined in the config/custom_config_xxx.h file of the project:

Table 68 DA1470X QSPI flash memory build configuration macros

qspi_flash_config_t field

Description

dg_configFLASH_AUTODETECT

Set this macro, to enable autodetecting the flash memory at runtime (NOT recommended for production builds). Its default value is 0, i.e. flash is pre-configured at compile.

dg_configFLASH_HEADER_FILE

This macro must be defined as a string that denotes the header file of the specific flash , drivere.g. qspi_at25sl128.h, qspi_w25q256jw.h. This header file must be either one of the qspi_<part_nr>.h header files found in <SDK_ROOT_PATH>/sdk/bsp/memory/include, or a header file under the project’s folder, as long as this path is in the compiler’s include search path (see Section 5.8.7 about adding support for new flash devices).

dg_configFLASH_CONFIG

This macro defines the qspi_flash_config_t structure of the pre-defined flash driver, e.g. flash_at25sl128_config, flash_w25q256jw_config.

The default values of the build configuration macros are defined in sdk/config/bsp_defaults.h.

5.8.5. Code Structure

The QSPI flash access functionality is implemented in qspi_automode.{h c}, whereas all common macros and functions used by all memories are defined in qspi_common.h. Memory specific code is defined in the flash driver header files named as qspi_<flash memory>.h, while code used by all memories of the same manufacturer is located in header files named as qspi_<flash manufacturer>.h.

The code in qspi_automode.c calls memory-specific callback functions and makes use of memory-specific values to properly initialize the selected flash. Each flash driver header file provides an instance of the structure qspi_flash_config_t, which contains all the memory-specific callback function pointers and variables.

5.8.6. The QSPI flash configuration structure

Each driver header file must provide its own instance of qspi_flash_config_t. Please note that this instance must be named with a unique name, like flash_<device name>_config, since all the device header files are included in the qspi_automode.c file. Therefore, there is a single global namespace. Moreover, it is highly recommended the struct instance to be declared as const, so that the compiler can optimize it properly.

The qspi_flash_config_t structure, as shown in Table 69, has the following fields (see <SDK_ROOT_PATH>/sdk/memory/include qspi_common.h for more information):

Table 69 The qspi_flash_config_t structure

qspi_flash_config_t field

Description

initialize

Pointer to the flash-specific initialization function.

is_suspended

Pointer to a flash-specific function that checks if flash is in erase/program suspend state.

sys_clk_cfg

Pointer to a flash-specific function that performs Flash configuration when system clock is changed (e.g. change dummy bytes or QSPIC clock divider).

get_dummy_bytes

Pointer to a flash-specific function that returns the number of dummy bytes currently needed (it may change when the clock changes).

manufacturer_id

The Flash JEDEC vendor ID (Cmd 0x9F, 1st byte). This and the device_type and device_density are needed for flash autodetection, when in Autodetect mode.

device_type

The Flash JEDEC device type (Cmd 0x9F, 2nd byte).

device_density

The Flash JEDEC device density (Cmd 0x9F, 3rd byte).

erase_opcode

The Flash erase opcode.

erase_suspend_opcode

The Flash erase suspend opcode.

erase_resume_opcode

The Flash erase resume opcode.

page_program_opcode

The Flash page program opcode. For PSRAM memories this is the write opcode.

page_qpi_program_opcode

The Flash QPI page program opcode to use.

quad_page_program_address

If true, the address will be transmitted in QUAD mode when writing a page. Otherwise, it will be transmitted in serial mode.

read_erase_progress_opcode

The opcode to use to check if erase is in progress (Usually the Read Status Reg opcode 0x5).

enter_qpi_opcode

The Flash opcode for entering QPI mode.

erase_in_progress_bit

The bit to check when reading the erase progress.

erase_in_progress_bit_high_level

The active state (true: high, false: low) of the bit above.

send_once

If set to 1, the “Performance mode” (or “burst”, or “continuous”; differs per vendor) will be used for read accesses. In this mode, the read opcode is only sent once, and subsequent accesses only transfer the address.

extra_byte

The extra byte to transmit, when in “Performance mode” (send_once is 1), that tells the flash that it should stay in this continuous, performance mode.

address_size

Whether the flash works in 24- or 32-bit addressing mode.

memory_size

Maximum capacity, memory size of selected device in Mbits.

break_seq_size

Whether the break sequence, that puts the flash out of the continuous mode, is one or two bytes long (the break byte is 0xFF).

ucode_wakeup

The QSPIC microcode to use to set up the flash on wakeup. This is automatically used by the QSPI Controller after wakeup, and before CPU starts code execution. This is different, based on whether the flash was active, in deep power down or off while the system was sleeping.

power_down_delay

The maximum time required in usec, after the Power Down command, in order for the flash memory to enter into the deep power down mode.

release_power_down_delay

The maximum time required in usec, after the Release Power Down command, in order for the flash memory to exit from the deep power down mode.

power_up_delay

The maximum time required in usec, to power up the flash memory.

suspend_delay_us

The minimum time required in usec, between an erase/program suspend command and the moment when the memory is ready to accept the next consecutive command.

resume_delay_us

The minimum time required in usec between an erase/program resume command and the moment when the memory is ready to accept the next consecutive command.

reset_delay_us

The minimum time required in usec, between a reset command and the moment when the memory is ready to accept the next consecutive command.

read_cs_idle_delay_ns

The minimum time required in nsec, that the CS signal has to stay in ‘Idle’ state between two consecutive read commands. Also referred as “read CS deselect time”.

erase_cs_idle_delay_ns

The minimum time required in nsec that the CS signal has to stay in ‘Idle’ state between a write enable, erase, erase suspend or erase resume command and the next command. Also referred as “erase/write CS deselect time”.

is_ram

True if device is RAM, false if device is Flash.

qpi_mode

True if the device operates in QPI bus mode. Applicable to PSRAM memories only (QSPIC2).

burst_len

The length of the wrapping burst that the external memory device is capable to implement. Applicable to PSRAM memories only (QSPIC2).

cs_active_time_max_us

The maximum time in usec, that the QSPIC2 CS signal is allowed to stay active. Applicable to PSRAM memories only (QSPIC2).

5.8.7. Adding support for a new QSPI flash memory

The SmartSnippets™ DA1470x SDK driver subsystem currently supports a specific set of QSPI flash memories. It provides, however, the capability to add support for other flash memories as well.

Each flash memory driver must have its own header file that should be named qspi_<flash memory>.h. The programmer can either use the qspi_XXX_template.h, or start with an existing driver file.

Warning

The new flash driver file should be placed inside the project’s path, in a folder that is in the compiler’s include search path (an obvious choice is the config folder, but others can be used as well). This is recommended so that potential SDK upgrades will not interfere with the project-specific flash driver implementation.

Note

As already mentioned before, common code used by all memories of the same manufacturer is located in header files named as qspi_<flash manufacturer>.h, e.g. qspi_macronix.h, qspi_winbond.h etc. If functions implemented in these files are used by a custom flash driver, the corresponding header file must be included.

The following steps are usually needed to create a new flash driver:

  • Copy and rename the template header file, or an existing flash driver file.

  • Rename all functions and variables of the flash driver appropriately. It is important to remember that all drivers reside in the same namespace, thus all function and variable names must be unique.

  • Define the proper JEDEC ID values for the Manufacturer code, the device type and the device density.

  • Set the opcodes for fast read, page program, sector erase, erase suspend, erase resume and read status register commands according to the manufacturer’s datasheet.

  • Rename the qspi_flash_config_t struct to flash_<flash memory name>_config, and initialize all members properly. It is higly recommended to declare this struct as const.

  • Implement the function flash_<flash memory name>_initialize(), if needed, in order to write some special QSPI configuration registers fields, e.g. enable the QUAD mode, set the dummy bytes, set the current strength of the QSPI IOs etc.

  • Implement the function flash_<flash memory name>_sys_clock_cfg(), if needed, in order to re-configure the flash parameters properly, whenever the system (and hence the QSPI) clock changes. This can include modifying the dummy bytes, changing the QSPI clock divider due to max frequency limitation etc.

  • Implement the function is_suspended(), if needed, so that it returns true, if a sector erase or page program command is suspended.

  • If the memory supports continuous mode of operation (also referred as performance mode or burst mode), make sure that the send_once is set to 1, and the extra_byte is set properly, in order to keep the flash memory working in this mode. Refer to the manufacturer datasheet, and more specifically to the fast read command section for more details. The value of the byte, which is sent after the address phase (extra byte phase), determines whether the memory expects the next consecutive read command in continuous mode of operation or not, in other words whether the opcode will be send in the next read command or will be omitted.

  • Set page_program_opcode, erase_opcode, break_seq_size (this should also take into consideration whether the device will be working in Continuous Read mode as well) and address_size.

  • If the address, during write, will be provided in QUAD mode, set quad_page_program_address to true.

Note

The SmartSnippets™ DA1470x SDK supports reading in QUAD I/O mode (where the address and data phases are transferred in QUAD bus mode, whereas the opcode in SINGLE bus mode).

5.8.8. Enabling the SDK tools to support a new QSPI flash memory

In order for the SDK tools to support a new QSPI flash memory the following steps are required:

  • Add support for the new flash memory, as described in paragraph Section 5.8.7.

  • Rebuild the DA1470x-00_Release of the uartboot application with the new QSPI flash driver included. The uartboot is the secondary bootloader used by SDK flash programming tools.

  • Rebuild the DA1470X_Release_static_linux or DA1470X_Release_static_win32 of the cli_programmer application, depending on whether you work in linux or in windows OS.

Note

The SmartSnippets™ Toolbox supports only the officialy supported QSPI flash memories mentioned in the beginning . Therefore, it is not recommended to be used with new flash memories without following first all the aforementioned steps.

5.10. Sensor Node Controller Reference Manual

5.10.1. Introduction

DA1470x chip family is a preferable solution for wearable designs, since it comprises components that can support operations required by such applications. One primary operation is the interface and communication with peripheral devices. For this purpose, the chip features a dedicated HW block named Sensor Node Controller (SNC) performing such operations on the external peripheral devices that are attached to the DA1470x chip. There are two major advantages when utilizing SNC in a DA1470x application:

  • Power is saved when data are fetched from sensors using SNC

  • SNC offloads the System CPU (M33) executing low to medium complexity algorithms for post-processing data purposes.

For instance, SNC can execute algorithms for gesture recognition or sleep quality monitor on data collected by an accelerometer sensor.

SNC block consists of an ARM Cortex M0+ processor and the necessary required HW blocks:

  • AHB & APB interfaces used to directly access the rest of the system (RAM, Register file, Peripheral Interfaces) and a dedicated Watchdog Timer.

  • Two RAM cells, RAM1 and RAM2, dedicated for SNC code and data storage, each having a 32KB size, large enough for supporting complex applications.

  • 2-pin JTAG/SWD interface with which has access on RAM13 cell which can be used as ARM Micro Trace Buffer (MTB).

  • A list of external interrupts including interrupt lines from SYS(M33)/CMAC, HW Timers, GPIO and Serial Interfaces.

It resides in the same power domain with Serial Interfaces and GPADC (PD_SNC), thus the system only needs to power up PD_SNC to use peripheral devices, assisting to SNC autonomous functionality. It supports communication with peripheral devices, using popular serial communication protocols (I2C, SPI, UART). The SNC block operates with a clock up to 32Mhz frequency, which runs independently from system clock, being able to perform post-processing operations on the data acquired from the peripherals with the lowest cost on power performance. SNC is running its firmware autonomously, thus its operations do not require extra processing time in M33 context, saving not only power but also resources management (MIPS) and memory accesses. SNC can communicate with the other two masters (CM33/CMAC) using corresponding interrupt signals and a shared memory space. A 128KB sized RAM cell RAM8 is used to exchange data with the other CPU cores (masters) of the system. With a shared data access mechanism, masters can exchange application or configuration data in an asynchronous way, increasing efficiency and reducing system active time.

5.10.2. Restrictions in SNC programming

SNC can be programmed to use Dialog CoRoutines. Dialog CoRoutines is a single stack OS and this causes the below restrictions.

  • The stack of a co-routine is not maintained when the co-routine blocks and a new co-routine will be in the running state. This means that most probably the variables that are allocated in stack will lose their values. Variables that have to maintain their values after a blocking call should be declared as static.

  • An API call that causes a co-routine to block can be made by the co-routine itself and not from within a calling function inside the co-routine. Due to this restriction, asynchronous (non-blocking) function calls should only be utilized when the SNC uses the adapter middleware.

  • A blocking call cannot be made from within a switch statement.

5.10.3. Usage

The SmartSnippets™ DA1470x SDK supports SNC by providing a framework with all the necessary software modules to develop an SNC application. There are two reference SmartSnippets™ Studio projects, os_app_retarget project for M33 and os_snc_retarget for SNC application development respectively. These projects can be imported in SmartSnippets Environment as shown in the figure below. python_scripts project must also be imported, to enable operation launchers, e.g. program/erase flash.

../_images/snc_applications.png

Figure 93 SNC reference projects

These two projects are used to demonstrate the steps required for the SNC to be utilized in a DA1470x application. In this example SNC wakes up periodically to increase the value of a variable on the shared space and store current timestamp. Then notifies M33 which by turn reads the variable and prints its value along with the timestamp. This example can be used as a template, so users can build on and extend existing projects to meet their own needs, gradually adding necessary code for their application design.

5.10.3.1. SNC programming

The SNC firmware runs over a single stack OS, the Dialog CoRoutines, developed to exploit the SNC’s low power consumption advantages. Among others, Dialog CoRoutines support multiple tasks, timers, mutexes, message queues etc. SNC Framework supports the middleware layer adapters and low-level drivers for the supported peripherals. ARM Cortex-M0+ specific code is provided including assembly modules for low level processor operations, such as startup and sleep mechanism, CPU interrupt handling and SNC initialization process. File structure and compile configuration for an SNC project is similar to M33 project. Macro definitions and preprocessor directives are used to apply SNC-project specific compilation settings, as in M33 projects.

In an SNC project, necessary code should be added for both peripheral initialization, data acquisition and post-processing operations on the acquired data. A peripheral initialization code could consist of peripheral device drivers and dedicated tasks to perform serial interface operations using adapter APIs and a post processing part with code related to algorithm implementation and filters. The SNC specific custom configuration file, custom_config_snc.h, has project specific configuration settings. These settings could be macros to enable adapters and low-level drivers for the peripherals included in the design and application specific settings like heap and stack size.

SNC programming is very similar to M33, a main function is used to initialize the system, and application specific settings are defined in the custom configuration header file. But there are some key differences that user must be aware of.

  • Its firmware runs over a different OS and user must take into consideration Dialog CoRoutines programming guidelines to develop an application.

  • SNC code does not proceed to any system configuration operations. M33 is responsible for this.

  • SNC is responsible for allocating memory in shared data address space, and for defining the corresponding structures for data storage and synchronization between masters. As soon as initialization is finished, the SNC allocates the application’s necessary shared memory and stores its address and initialized data in a predefined structure shared with M33. This structure format is application specific and must be defined in a way so any data type (e.g. acceleration data or location) can be distinguished easily in both SNC and M33.

In a typical application, the SNC creates a task to initialize the peripheral devices and then waits for any event that would trigger a data acquisition process. An SNC triggering event could be a periodic timer expiration, or an interrupt signal driven from a full FIFO in sensor side, or an interrupt from another master. The post-processing algorithms have been initialized and are ready to be fed with the data acquired. Until a triggering event the SNC is in sleep mode, saving power. Upon a triggering event, with the help of the adapters the SNC can read data from the peripherals, and the corresponding tasks are responsible for the next actions. In case M33 is to be notified because either an event has occurred (e.g. wrist movement), or data have been acquired (e.g. GPS location data), the SNC stores this data in shared memory space allocated during its initialization and then sends an SNC2SYS interrupt to the M33. In order for the SNC to modify the shared memory it must first acquire access in memory, using a semaphore-like mechanism, and then to proceed with the shared data modification. In this example, the shared data is a two elements array, with the timestamp and an increasing value variable as elements. After the modification the M33 is notified about the change by calling the corresponding interrupt trigger function. The below code snippet, taken from os_snc_retarget project, shows an SNC task accessing shared data and notifying the M33 afterwards.

Code 59 SNC Modifies shared data and notifies M33
/**
* @brief Template task increases a counter every mainCOUNTER_FREQUENCY_MS ms
*/
static OS_TASK_FUNCTION(prvTemplateTask, pvParameters)
{
        OS_TASK_BEGIN();

        static OS_TICK_TIME xNextWakeTime;
        static uint32_t test_counter = 0;

        /* Initialise xNextWakeTime - this only needs to be done once. */
        xNextWakeTime = OS_GET_TICK_COUNT();

        for ( ;; ) {
                /* Place this task in the blocked state until it is time to run again.
                   The block time is specified in ticks. While in the Blocked state this
                   task will not consume any CPU time. */
                xNextWakeTime += mainCOUNTER_FREQUENCY_MS;
                OS_DELAY_UNTIL(xNextWakeTime);
                test_counter++;

                if (test_counter % (1000 / OS_TICKS_2_MS(mainCOUNTER_FREQUENCY_MS)) == 0) {
                        /* Acquire exclusive access on shared data. */
                        app_semph_take();

                        app_shared_data.buffer[0] = OS_TICKS_2_MS(OS_GET_TICK_COUNT());
                        app_shared_data.buffer[1]++;

                        /* Release exclusive access on shared data. */
                        app_semph_give();

                        /* Send notification to SYSCPU. */
                        snc_set_snc2sys_int();
                }
        }

        OS_TASK_END();
}

In order to compile os_snc_retarget use the build targets DA1470x-00-SNC-Debug or DA1470x-00-SNC-Release to produce the SNC image for a debug- or release-build respectively. After a successful compilation the resulting output header file snc_fw_embed.h is placed in `DA1470x-00-SNC-Release/snc_files (or Debug). This header file contains the SNC firmware with the address of the shared symbols and is ready to be integrated in M33 project.

5.10.3.2. M33 programming

os_app_retarget has a linked folder interface linked to the interface folder of os_snc_retarget. This folder contains the files snc_shared_space.c and snc_shared_space.h, making M33 aware of the share memory structure. Additionally, the os_app_retarget project incorporates the file snc_fw_embed.h containing the SNC firmware in its compiled modules, integrating the SNC firmware in the M33 application image, along with instructions to access the shared memory.

../_images/SNC_usage_files.png

Figure 94 SNC reference projects file structure

After boot, M33 proceeds with the system initialization, configuring power, clock etc. Then it copies the SNC image to the corresponding RAM address and starts SNC firmware execution. It then waits until the SNC has been initialized and configured the shared memory space, before continuing code execution. In the code snippet below M33 registers a callback function to be executed when an interrupt from SNC is received. Then M33 starts SNC, it first disables SNC then copies SNC image to RAM and then resets SNC. After SNC is started, M33 waits until SNC has configured share memory space by checking corresponding status flag. When SNC sets this flag, M33 acquires shared memory address and proceeds to code execution.

Code 60 M33 SNC initialization example
    /* Register callback for SNC2SYS IRQ. */
    snc_register_snc2sys_int(test_snc_cb);

    /* Initialize and start SNC. */
    snc_freeze();
    snc_init();
    snc_start();

    /* Wait for SNC to finish start-up process. */
    while (!snc_is_ready());

    /* Wait for SNC to initialize application shared space. */
    while (!app_shared_space_ctrl_is_ready());

    /* Get the address of application shared data. */
    app_shared_data_ptr = snc_get_shared_space_addr(SNC_SHARED_SPACE_APP(APP_SHARED_SPACE_DATA));

To exchange data from now on, each master can notify other by using corresponding interrupts, the SNC2SYS or SYS2SNC, as soon as they have stored appropriate data on the shared memory space. M33 registered callback function test_snc_cb is executed when SNC triggers an SNC2SYS interrupt. Depending on the shared data information that is read after interrupt is fired, it can either perform a post-processing operation like calculate distance using coordinates from GPS location data or to perform an action, like turn on LCD display after a hand movement. In the below code snippet, a simple example of M33 accessing shared memory data is shown. At first, a task is notified by SNC interrupt callback and after acquiring access in shared memory data with app_semph_take() it reads shared data and prints the values in the output console and finally releases access on shared memory.

Code 61 M33 callback example
/*
* Callback function for SNC2SYS IRQ.
*/
void test_snc_cb(void) {
    OS_TASK_NOTIFY_FROM_ISR(task_h, TASK_SNC_NOTIF, OS_NOTIFY_SET_BITS);
}

/**
* @brief Template task prints a message every time a notification is received from SNC
*/
static OS_TASK_FUNCTION(prvTemplateTask, pvParameters)
{
    OS_TASK_BEGIN();

    uint32_t notif;

    for ( ;; ) {
        OS_TASK_NOTIFY_WAIT(0x0, OS_TASK_NOTIFY_ALL_BITS, &notif, OS_TASK_NOTIFY_FOREVER);

        /* Check whether a notification has been received from SNC. */
        if (notif & TASK_SNC_NOTIF) {
            /* Acquire exclusive access on shared data. */
            app_semph_take();

            printf("@%lu\tSNC->SYSCPU (%lu)\r\n", app_shared_data_ptr->buffer[0],
                    app_shared_data_ptr->buffer[1]);

            /* Release exclusive access on shared data. */
            app_semph_give();
            }
    }

    OS_TASK_END();
}

5.11. The USB Framework

5.11.1. Introduction

This section describes the USB support in the SmartSnippets™ DA1470x SDK and the necessary steps to add USB support to a user application. The USB port can be used as a charging port and/or a USB data interface to a host machine.

5.11.2. SYS_USB

In order to enable the USB port, the user must first add sys_usb to the application, and set the relative configuration macro.

Code 62 Adding sys_usb to a project
#include "sys_usb.h"
.
.
sys_usb_init();
Code 63 Enabling sys_usb
#define dg_configUSE_SYS_USB (1)

The following diagram illustrates the sys_usb initialization procedure, when the device is configured to use both the USB charger and the USB stack.

Please note that any one of the two features can be used independent of each other.

../_images/usb_framework_init_da1470x.png

Figure 95 DA1470x SYS_USB initialization

5.11.3. The USB Charger

For information on how to use the USB charger of the DA1470x please refer to Section 4.1.3.

5.11.4. USB Data Connection

To enable the device’s communication with a connected host via the USB port, the macro dg_configUSE_USB_ENUMERATION must be set.

Code 64 Enabling USB Enumeration
#define dg_configUSE_USB_ENUMERATION (1)

5.11.4.1. The SDK USB Stack

The SmartSnippets™ DA1470x SDK includes a complete USB stack implementation, based on the emUSB stack, version 3.36.1, provided by SEGGER.

To add the USB stack to a user project, link the library object usb_lib_da1470x to the application. The necessary header files are located under <SDK_ROOT_PATH>/sdk/interfaces/usb/include.

In order to configure and use the USB stack, please refer to the SEGGER emUSB user manual, or check the example applications provided with the SDK, under

<SDK_ROOT_PATH>/projects/dk_apps/features/usb_cdc_vmsd

and

<SDK_ROOT_PATH/projects/dk_apps/features/usb_cdc.

5.11.4.2. Using a different USB Stack

A different USB stack than the one provided with the SDK may be used. In that case, please check Section 5.11.4.3 to see which callback functions are necessary to be implemented by the user-provided USB Stack.

5.11.4.3. Callback Functions / Hooks

The application may implement the following hook functions in order to be notified about USB events

Table 71 USB Hook Functions

Function

Description

sys_usb_ext_hook_attach()

This function is called when the device is physically attached to the port. In case the USB Charger is used by the application, then this function is called after the Charger has finished with the port detection procedure.

sys_usb_ext_hook_begin_enumeration()

This function is called when the USB Host has signaled the device that it must begin the enumeration procedure.

sys_usb_ext_hook_detach

This function is called when the device has been physically disconnected from the USB port

In case the application is not using the USB stack provided by the SDK, then it needs to implement the following callback functions.

Table 72 USB callback functions

Function

Description

hw_usb_ep_rx_read_by_driver()

This function will indicate if the USB driver should read the Rx data or if the Rx data is read by something else.

hw_usb_ep_get_rx_buffer()

This function will be called in order to get a buffer to store the data received.

hw_usb_ep_rx_done()

This function will be called when a receive operation has been completed.

hw_usb_ep_tx_done()

This function will be called when a transmit operation has been completed.

hw_usb_ep_nak()

This function will be called when a NAK has been received for an endpoint.

hw_usb_bus_event()

This function will be called to indicate bus events.

hw_usb_bus_frame()

This function will be called with the current USB frame number.

5.11.5. USB Events

5.11.5.1. VBUS Attach

This event signals that the device has been physically attached to a USB port.

  • Upon receiving the interrupt, sys_usb sets the device power mode to ‘Active’, starts using the PLL clock and calls the application sys_usb_ext_hook_attach() hook, if it is implemented.

  • sys_usb then enables the USB pads and programs the USB interrupts.

  • If the application is configured with charger support, the charger subsystem is notified.

  • Afterwards, the USB low level driver is initialized and the device is attached to the USB BUS.

  • Finally, application hook sys_usb_ext_hook_begin_enumeration() is called, if it is implemented.

At this point the USB enumeration must begin.

The following diagram illustrates the USB attach procedure.

../_images/usb_framework_attach_da1470x.png

Figure 97 DA1470x USB VBUS Attach

5.11.5.2. VBUS Detach

This event signals that the device has been physically detached from the USB port.

  • The USB pads are disabled

  • sys_usb restores the power mode that was used before the device was attached to the USB port.

  • If the application is configured with charger support, the charger subsystem is notified.

  • The device is detached from the USB BUS.

  • sys_usb stops using the PLL clock.

  • Finally, application hook sys_usb_ext_hook_detach() is called, if it is implemented.

The following diagram illustrates the USB detach procedure.

../_images/usb_framework_detach_da1470x.png

Figure 99 DA1470x USB VBUS Detach

5.11.5.3. USB Reset / Suspend / Resume

USB Reset

This interrupt is deployed prior to the enumeration procedure in order to ascertain that the device is in the correct state. It may occur at any time in case of device unexpected/improper response or behavior.

  • sys_usb switches the device power mode to ‘Active’.

  • The USB interrupts are programmed accordingly.

  • The USB stack is notified of this event via callback hw_usb_bus_event(), with event UBE_RESET.

Please note that the application is not directly notified by sys_usb, but instead via the USB stack.

USB Suspend

The USB Suspend interrupt indicates that the USB-Host requested the device to enter SUSPEND state. In this state, the USB device must consume no more than 2.5mA on USB bus and should not attempt to perform any data exchange transactions with the USB-Host.

  • If the application is configured with charger support, the charger subsystem is notified.

  • The USB stack is notified of this event via callback hw_usb_bus_event(), with event UBE_SUSPEND.

  • The USB interrupts are programmed accordingly.

  • sys_usb stops using the PLL clock.

Please note that the application is not directly notified by sys_usb, but instead via the USB stack.

USB Resume

The USB Resume interrupt indicates that the USB-Host requested that the device resumed operation fron the SUSPEND state.

  • The USB stack is notified of this event via callback hw_usb_bus_event(), with event UBE_RESUME.

  • The USB interrupts are programmed accordingly.

  • If the application is configured with charger support, the charger subsystem is notified.

Please note that the application is not directly notified by sys_usb, but instead via the USB stack.

The following diagram illustrates the USB interrupt handling procedure.

../_images/usb_framework_irq_handling_da1470x.png

Figure 101 DA1470x USB Interrupt Handling

5.11.6. USB Suspend Modes

In response to a suspend signal from the USB host, the application may switch to an ‘Idle’ power state, so as to decrease its power consumption. Whether the latter will happen or not is controlled by the configuration parameter dg_configUSB_SUSPEND_MODE.

Currently for the DA1470x two suspend modes are supported:

USB_SUSPEND_MODE_NONE indicates that the suspend signal will have no effect on the system’s power state.

USB_SUSPEND_MODE_IDLE indicates that the suspend signal will cause the system’s power state to switch to ‘Idle’. (NOTE: This is the suggested mode in order for the USB-Host to be able to meet the suspended mode power consumption as per the USB specification.)

5.11.7. USB Power Consumption Considerations

In order for a device to be USB-IF certified, among other parameters, it needs to exhibit a low current draw from the USB host, when the host is suspended. The USB framework attempts to keep the power consumption to a minimum when the device is connected to a suspended host. Nonetheless, it is equally essential for the user application as well to go into a power-saving mode when the host is suspended.

5.13. Using SEGGER Open Flashloader

5.13.2. Toolset preparation

  1. Import the project segger_flash_loader located at sdk10/sdk/bsp/system/loaders into workspace.

  2. Build the project using both DA1470x-00_OQFLASH_Release and DA1470x-00_QFLASH_Release configurations.

  3. Make sure that the corresponding segger_flash_loader.elf files have been built under the path:

    OFLASH:  sdk10/binaries/da1470x/oflash/segger_flash_loader.elf

    QFLASH:  sdk10/binaries/da1470x/qflash/segger_flash_loader.elf

  4. Make sure that an appropriate J-Link version is installed and selected in SmartSnippets™ Studio.

  5. Make sure that the valid XML tags and attributes have been added to then JLinkDevices.xml so that the SEGGER tools can use the segger_flash_loader.elf properly. The XML tags and attributes can be found under:

    sdk10/config/segger/DA1470x/JLinkDevices.xml

Note

Only steps 1-2 are necessary for the user to setup the SEGGER flash loader. Steps 3-5 are only necessary to validate that everything has been configured properly.

For easier maintenance the SDK integrates the JLinkDevices.xml using the J-Link command

-JLinkDevicesXMLPath <path to JLinkDevices.xml>

according to SEGGER’s suggestion

This command allows a SmartSnippets™ launcher to flash a device by making use of segger_flash_loader.elf functionality.

The OQFLASH: OQSPI_DA1470x_segger_flasher launcher can be used, in order to flash and debug an application built for a non-volatile memory.

5.13.4. Project preparation

In order to build an application capable to be flashed with Segger Flash Loader it is necessary to add the following entry in the custom_config_qspi.h file of the project.

Code 67 Produce SEGGER loader compatible images
#define  dg_configUSE_SEGGER_FLASH_LOADER       ( 1 )

Keep in mind that the linker script is only applicable for the default memory of each device. If it is necessary to build an application for another memory, the linker script must be modified so that the proper product headers etc are included in the binary file.