4. Middleware

4.1. System

4.1.1. Power and Clock Management

4.1.1.1. Introduction

This section illustrates :

  • The basic steps of the system when it goes to sleep and when it wakes up.

  • The supported power modes.

  • The basic system calls needed by the application to initialize the clock and the power management.

4.1.1.2. Going to sleep and waking up steps

The basic walk through is shown in Figure 11.

../_images/sleep_and_wake_up.png

Figure 11 Going to sleep and waking up steps

Note

In case of different paths, the color of the arrow matches with the color of the comment.

4.1.1.3. Supported power modes

The supported power modes are depicted in Table 16.

Table 16 Supported power modes

Power mode

Description

active

  • ARM M33 goes to WFI state.

  • OS tick period remains unmodified.

idle

  • ARM M33 goes to WFI state.

  • OS tick period is extended based on the earliest wake up time.

extended sleep

  • ARM M33 goes to WFI state.

  • OS tick period is extended based on the earliest wake up time.

  • The Power Domain Controller allows the system to go to sleep, if possible. This is achieved by checking the pending activity on the rest of the masters (ARM M0+ and Sensor Node Controller).

  • If the Power Domain Controller decides that the device can go to sleep, then the HW FSM takes over and moves to “SLEEP” state.

  • RAM is retained, RTC is on (XTAL32K is used as sleep clock), all power domains may go off.

  • The system could be configured to wake up by RTC alarm, a GPIO trigger, MAC timer or any other timer.

deep sleep

  • ARM M33 goes to WFI state.

  • OS tick period is extended based on the earliest wake up time.

  • The Power Domain Controller allows the system to go to sleep, if possible. This is achieved by checking the pending activity on the rest of the masters (ARM M0+ and Sensor Node Controller).

  • If the Power Domain Controller decides that the device can go to sleep, then the HW FSM takes over and moves to “SLEEP” state.

  • RAM is not retained, RTC is on (XTAL32K is used as sleep clock), all power domains are off.

  • The system could be configured to wake up by RTC alarm or a GPIO trigger.

hibernation

  • ARM M33 goes to WFI state.

  • OS tick period is extended based on the earliest wake up time.

  • The Power Domain Controller allows the system to go to sleep, if possible. This is achieved by checking the pending activity on the rest of the masters (ARM M0+ and Sensor Node Controller).

  • If the Power Domain Controller decides that the device can go to sleep, then the HW FSM takes over and moves to “SLEEP” state.

  • RAM is not retained, RTC is off, all power domains are off.

  • The system could be configured to wake up by a GPIO trigger.

4.1.1.4. Initializing clock and power management

The flow for an application to initialize the clock and the power management is shown in Figure 13.

../_images/sys_init.png

Figure 13 Power and clock manager initialization

4.1.2. Watchdog Service

4.1.2.7. Description

The system watchdog service (sys_watchdog) has been designed to monitor system tasks and avoid system freezes. The interaction of this service with other parts of the system is shown in Figure 15:

../_images/043.png

Figure 15 Watchdog overview

Effectively, sys_watchdog is a layer on top of the watchdog low-level driver that allows multiple tasks to share the underlying hardware watchdog timer. The watchdog service can be used to trigger a full system reset. This will allow system to recover from a catastrophic failure in one or more tasks.

4.1.2.8. Concept

A task that should be monitored has to first register itself with this service to receive a unique handle (id). The task must then is periodically notify sys_watchdog using this id, to signal that the task working properly. In case of any error during registration, the invalid id -1 is returned.

The hardware watchdog is essentially a countdown timer, which is powered by the always on domain (PD_AON) and it will be automatically enabled as soon as the system powers up. It is decremented by 1 every ~10 ms (assuming default source clock the RC32K) and it is set to max value at reset. This results to a maximum watchdog time-out of 84 seconds. If the RCX is used as a source clock, the time-out time is even longer, depending on the RCX frequency.

If WATCHDOG_CTRL_REG[NMI_RST] = 0, the watchdog timer will generate an NMI to the Cortex M33 when the watchdog timer reaches 0 and a HW reset when the counter becomes less or equal to -16. The NMI handler must write any value > -16 to the WATCHDOG_REG to prevent the generation of a WDOG reset within 16x10 = 160 ms. If WATCHDOG_CTRL_REG[NMI_RST] = 1, the watchdog timer generates a WDOG reset if the timer becomes less than or equal to 0.

To prevent this, the watchdog timer must be reset to its starting value before it expires. This starting value can be configured in the application custom configuration files via the numerical macro dg_configWDOG_RESET_VALUE. Its default value is the maximum 0xFF, which corresponds to approximately 2.6 seconds (assuming default source clock the RC32K). The maximum number of tasks that can be monitored is defined by the configuration macro dg_configWDOG_MAX_TASKS_CNT. Its default value is 6 (the absolute maximum is 32).

If during one watchdog period all monitored tasks notify sys_watchdog, the hardware watchdog will be updated via the hw_watchdog_set_pos_val() LLD API function; in this case, no platform reset will be triggered for this watchdog period. However, a platform reset will be triggered if at least one task does not notify sys_watchdog in time. There are two ways for a task to notify sys_watchdog.

Each task is responsible for periodically notifying sys_watchdog that it is still running using sys_watchdog_notify(). This must be done before the watchdog timer expires. Occasionally a registered task may want to temporarily exclude itself from being monitored if it expects to be blocked for a long time waiting for an event. This is done using the sys_watchdog_suspend() API function. This function suspends monitoring of specific tasks in sys_watchdog, as there is no need to monitor a task that is blocked waiting for an event that might take too long to occur (i.e. it would lead to the task failing to notify the watchdog service, thus resulting in a system reset). When the task is unblocked, the sys_watchdog_resume() API function should be called to restore task monitoring by the watchdog service. From that moment on the task shall notify the watchdog service as usual.

Finally, the sys_watchdog_set_latency() API function is intended to be used in cases where a task would require a watchdog period greater than the configured watchdog timer reset value. Using this API allows a task to delay notification of sys_watchdog for a given number of watchdog periods, without triggering a system reset. The effect of calling the API function is one-off, thus it must be set every time increased latency is required.

4.1.2.9. Examples

To register the task with sys_watchdog use the following code snippet

Code 11 Notify sys_watchdog of the task
/* register pxp task to be monitored by watchdog */
wdog_id = sys_watchdog_register(false);

To notify sys_watchdog use sys_watchdog_notify(). If the task is going to suspend for an event then temporarily exclude the current task from being monitored using sys_watchdog_suspend(). Once the task has received an event it can resume its watchdog operation with sys_watchdog_resume(). This flow is shown below :

Code 12 Using sys_watchdog while suspending task for an event
/*
 * notify watchdog on each loop since there's no other trigger for this - monitoring
 * will be suspended while blocking on OS_TASK_NOTIFY_WAIT()
 */
sys_watchdog_notify(wdog_id);
/*
 * Wait on any of the event group bits, then clear them all
 */
sys_watchdog_suspend(wdog_id);
ret = OS_TASK_NOTIFY_WAIT(0, OS_TASK_NOTIFY_ALL_BITS, &notif, OS_TASK_NOTIFY_FOREVER);
/*
 * Blocks forever waiting for the task notification. Therefore, the return value must
 * always be OS_OK
 */
OS_ASSERT(ret == OS_OK);
sys_watchdog_resume(wdog_id);

4.1.2.10. API

Table 20 Configuration functions for sys_watchdog

Function

Description

void sys_watchdog_init (void)

Initialize sys_watchdog service.

This should be called before using the sys_watchdog service, preferably at application startup.

int8_t sys_watchdog_register(bool notify_trigger)

Register current task with the sys_watchdog service. Returned identifier shall be used in all other sys_watchdog API calls from current task. Once registered, the task shall notify sys_watchdog periodically using sys_watchdog_notify() to prevent watchdog expiration. It is up to each task how this is done, but a task can request that it will be triggered periodically using the task notification capability, to notify sys_watchdog back as a response.

void sys_watchdog_unregister(int8_t id)

Unregister task from the sys_watchdog service.

void sys_watchdog_configure_idle_id(int8_t id)

Inform sys_watchdog about the wdog id of the IDLE task.

void sys_watchdog_suspend(int8_t id)

Suspend task being monitoring by the sys_watchdog service.

A monitor-suspended task is not unregistered entirely, but it is ignored by the watchdog service until its monitoring is resumed. It is faster than unregistering and registering the task again.

void sys_watchdog_resume(int8_t id)

Resume monitoring of a task by the sys_watchdog service.

It should be called as soon as the reason that sys_watchdog_suspend() was called is removed.

void sys_watchdog_notify(int8_t id)

Notify sys_watchdog module about task.

A registered task shall call this API function periodically to notify sys_watchdog that it is alive. This should be done frequently enough to fit into the watchdog timer interval set by dg_configWDOG_RESET_VALUE.

void sys_watchdog_notify_and_resume(int8_t id)

Notify sys_watchdog module about task and resume its monitoring.

This function combines the functionality of sys_watchdog_notify() and sys_watchdog_resume().

void sys_watchdog_set_latency(uint8_t id, uint8_t latency)

Set watchdog latency for a task.

This allows a task to miss a given number of notification periods to sys_watchdog without triggering a system reset. Once set, the task is allowed to not notify sys_watchdog for “latency” consecutive watchdog timer intervals as defined by dg_configWDOG_RESET_VALUE. This option can be used to facilitate the operation of code that is expected to remain blocked for long periods of time (i.e. computation). This value is set once and does not reload automatically, thus it shall be set every time increased latency is required.

4.1.3. The Charger

4.1.3.1. Introduction

This section describes the DA1469x charger software implementation and its integration in the SmartSnippets™ DA1469x SDK. It describes also the System’s charging operation and how to use System’s charging service.

USB Connection Terminology

  • Attach: A downstream device is considered to be attached to an upstream port when there is a physical cable between the two.

  • Connect: A downstream device is considered to be connected to an upstream port when it is attached to the upstream port, and when the downstream device has pulled either the D+ or D- data line high through a 1.5 kΩ resistor, in order to enter Low-Speed, Full-Speed or High-Speed signalling.

  • Enumerate: The initial data exchange between a PD and the host to identify the device type.

Port description

Port description can be retrieved from [Ref_03].

4.1.3.2. Software architecture

The charger’s code is executed by the Cortex M33. It needs to support the charging procedure of Li-On rechargeable batteries.

../_images/124.png

Figure 16 Charger’s layers

Charger’s layers are depicted in Figure 16. In more detail:

  • Hardware which consists of:
    • HW FSM (including the main FSM and JEITA FSM)

    • Data Contact Detection (DCD) and Port Detection

    • VBUS Detection

  • Low Level Driver (LLD), an abstraction layer consisting of services accessing the HW resources.

  • Middleware a secondary abstraction layer which “glues” the LLD services and forwards the registered events to the application.

4.1.3.3. Low Level Driver

The conceptual entity organisation of the charger’s LLD is described in Figure 17.

../_images/125.png

Figure 17 Entity associations of the charger’s LLD

The upper layer programs/sets up the HW FSM by defining:

  1. A charging profile

  2. The fine tuning settings (if there is a need to overwrite the default values)

  3. The IRQ callback functions for retrieving state/transition and error events out of the HW FSM

../_images/126.png

Figure 19 Flowchart of the charger’s LLD

The corresponding procedures taking place during HW FSM configuration are shown in Figure 19, while Figure 20 depicts the actual states of the HW FSM.

../_images/127.png

Figure 20 Charger’s HW FSM

4.1.3.4. Middleware

The basic steps the middleware goes through are shown in Figure 21.

../_images/128.png

Figure 21 Middleware high level flowchart

The steps are the following:

  1. Detect that a USB cable has been physically connected.

  2. Start the port detection procedure in order to determine the current budget provided by the host side.

  3. Program the HW FSM according to the battery specification.

  4. Send notifications to the application.

  5. Detect that the USB cable has been physically disconnected.

4.1.3.5. System charging operation

The internals of the charger’s middleware layer and how the notifications are forwarded to the application are depicted in more detail in Figure 22.

../_images/129.png

Figure 22 System charger operation

The next image Figure 25 shows the flow chart of the USB port detection. The type of the port influences the maximum current budget the charger can draw.

../_images/usb_port_detection.png

Figure 25 USB charging port detection

4.1.3.6. Using the system charging service

The charging system service is a daemon that:

  • Detects the VBUS presence.

  • Runs the port detection algorithm.

  • Programs the HW FSM according to the application charging profile taking into account the port detection result.

  • Notifies the application for the events the latter is interested in.

Note

The charging daemon depends on the Operating System services.

4.1.3.7. Configuring the system charging service

The following need to be configured for employing the charging service:

  1. Enable the charging service configuration option

In the custom header file (for example, custom_config_qspi.h) the next entry needs to be enabled.

#define dg_configUSE_SYS_CHARGER                1
  1. In the source code USB data pins must be configured accordingly:

/* USB data pin configuration */
hw_gpio_set_pin_function(HW_GPIO_PORT_0, HW_GPIO_PIN_14, HW_GPIO_MODE_INPUT, HW_GPIO_FUNC_USB);
hw_gpio_set_pin_function(HW_GPIO_PORT_0, HW_GPIO_PIN_15, HW_GPIO_MODE_INPUT, HW_GPIO_FUNC_USB);

This usually takes place in the void periph_init() function.

  1. If the battery temperature needs to be monitored, the NTC pins must be configured accordingly:

/* Pin configuration for the NTC */
hw_gpio_set_pin_function(HW_GPIO_PORT_0, HW_GPIO_PIN_7, HW_GPIO_MODE_INPUT, HW_GPIO_FUNC_GPIO);
hw_gpio_set_pin_function(HW_GPIO_PORT_1, HW_GPIO_PIN_0, HW_GPIO_MODE_OUTPUT, HW_GPIO_FUNC_GPIO);

hw_gpio_pad_latch_enable(HW_GPIO_PORT_0, HW_GPIO_PIN_7);
hw_gpio_pad_latch_enable(HW_GPIO_PORT_1, HW_GPIO_PIN_0);

hw_gpio_set_active(HW_GPIO_PORT_1, HW_GPIO_PIN_0);

This usually takes place in the void periph_init() function.

  1. Add a custom charging profile, for example:

static const sys_charger_configuration_t sys_charger_conf = {
      {
            .ctrl_flags = HW_CHARGER_CTRL_ENABLE_DIE_TEMP_PROTECTION                        |
                           HW_CHARGER_CTRL_RESUME_FROM_DIE_PROTECTION_STATE                  |
                           HW_CHARGER_CTRL_ENABLE_BAT_TEMP_PROTECTION                        |
                           HW_CHARGER_CTRL_ENABLE_JEITA_SUPPORT                              |
                           HW_CHARGER_CTRL_RESUME_FROM_ERROR_STATE                           |
                           HW_CHARGER_CTRL_HALT_CHARGE_TIMERS_ON_TEMP_PROTECTION_STATES,

            /* Tbat monitoring settings */
            .tbat_monitor_mode = HW_CHARGER_TBAT_MONITOR_MODE_PERIODIC_FSM_ON,

            /* IRQ settings */
            .irq_ok_mask = HW_CHARGER_FSM_IRQ_OK_ALL ^
                           (HW_CHARGER_FSM_IRQ_OK_MASK(TBAT_STATUS_UPDATE)  |
                            HW_CHARGER_FSM_IRQ_OK_MASK(CV_TO_CC)            |
                            HW_CHARGER_FSM_IRQ_OK_MASK(CC_TO_CV)),
            .irq_nok_mask = HW_CHARGER_FSM_IRQ_NOK_ALL,

            /* Voltage settings */
            .ovp_level = HW_CHARGER_V_LEVEL_4900,
            .replenish_v_level = HW_CHARGER_V_LEVEL_4000,
            .precharged_v_thr = HW_CHARGER_V_LEVEL_3000,
            .cv_level = HW_CHARGER_V_LEVEL_4200,

            /* Current settings */
            .eoc_i_thr = HW_CHARGER_I_EOC_PERCENT_LEVEL_10,
            .precharge_cc_level = HW_CHARGER_I_PRECHARGE_LEVEL_10,
            .cc_level = HW_CHARGER_I_LEVEL_120,

            /* Default JEITA voltage settings */
            .jeita_ovp_cool_level = HW_CHARGER_V_LEVEL_4480,
            .jeita_ovp_warm_level = HW_CHARGER_V_LEVEL_4460,
            .jeita_replenish_v_cool_level = HW_CHARGER_V_LEVEL_4020,
            .jeita_replenish_v_warm_level = HW_CHARGER_V_LEVEL_4000,
            .jeita_precharged_v_cool_thr = HW_CHARGER_V_LEVEL_3150,
            .jeita_precharged_v_warm_thr = HW_CHARGER_V_LEVEL_3100,
            .jeita_cv_cool_level = HW_CHARGER_V_LEVEL_4200,
            .jeita_cv_warm_level = HW_CHARGER_V_LEVEL_4220,

            /* Default JEITA current settings */
            .jeita_precharge_cc_cool_level = HW_CHARGER_I_PRECHARGE_LEVEL_1_5,
            .jeita_precharge_cc_warm_level = HW_CHARGER_I_PRECHARGE_LEVEL_1_0,
            .jeita_cc_cool_level = HW_CHARGER_I_LEVEL_30,
            .jeita_cc_warm_level = HW_CHARGER_I_LEVEL_25,

            /* Default Tbat limits */
            .bat_temp_cold_limit = HW_CHARGER_BAT_TEMP_LIMIT_0,
            .bat_temp_cool_limit = HW_CHARGER_BAT_TEMP_LIMIT_10,
            .bat_temp_warm_limit = HW_CHARGER_BAT_TEMP_LIMIT_35,
            .bat_temp_hot_limit =  HW_CHARGER_BAT_TEMP_LIMIT_45,

            /* Tdie settings */
            .die_temp_limit = HW_CHARGER_DIE_TEMP_LIMIT_90,

            /* Default charging timeout settings */
            .max_precharge_timeout = 0x708,         /*  30min */
            .max_cc_charge_timeout = 0x1C20,        /* 120min */
            .max_cv_charge_timeout = 0x1C20,        /* 120min */
            .max_total_charge_timeout = 0x3F48      /* 270min */
      }
   };

The corresponding fields of a charging profile are documented in [Ref_04]. Variable sys_charger_conf can be declared on a separate file to ease project segregation and is an optional step. If this approach is to be followed, then the compiler’s include-directories could need an update.

  1. Call sys_usb_init(), sys_charger_init() inside system_init() task. For example:

static void system_init( void *pvParameters )
{
       /* … */
       /* Initialize USB service */
       sys_usb_init();
       /* Initialize charging service */
       sys_charger_init(&sys_charger_conf);
       /* … */
       /* Initialize BLE Manager */
       ble_mgr_init();
       /* … */
}

4.1.3.8. Notifications

The notification hook functions can be found in <SDK_ROOT_PATH>/sdk/bsp/system/sys_man/include/sys_charger_v2.h and are documented in [Ref_04].

4.1.3.10. Supported battery types

Table 21 Battery types

Battery types

Description

LiCoO2

Lithium cobalt oxide

LiMn2O4

Lithium ion manganese oxide

LiFePO4

Lithium iron phosphate

LiNiCoAlO2

Lithium Nickel Cobalt Aluminum Oxide

4.1.4. ADC Service

4.1.4.1. Description

The system ADC service (sys_adc) has been designed to monitor periodically the on-chip temperature using Sensor Node Controller (SNC) and notify the application in case the temperature exceeds the specified limits.

The interaction of this service with other parts of the system is shown in Figure 26:

../_images/sys_adc.png

Figure 26 ADC service overview

4.1.4.2. Concept

The sys_adc registers an SNC ucode with a custom callback and also configures and starts the RTC. The registered ucode executes periodically on an RTC-to-PDC event to monitor the temperature. Upon every RTC event, whose period is defined through RTC_PDC_EVENT_PERIOD_REG register, and if the current temperature is less than the lower or greater than the upper limit, the ucode notifies CM33.

In order for an application to utilize the sys_adc to monitor the on-chip temperature, a GPADC service must be configured using the sys_adc_config(). The function takes as input parameters the desirable temperature offset in order for the lower and upper limits to be determined accordingly, the period of the RTC-to-PDC event and a custom callback, which is called upon SNC ucode notification. Using the API the application is capable of enabling/disabling the monitoring, setting different temperature offset and getting the value for which CM33 was notified.

An example of using sys_adc service is located in the projects/dk_apps/demos/snc_gpadc_demo folder.

4.1.4.3. System use

The SmartSnippets™ DA1469X SDK enables and internally uses the service by default, in all the configurations requiring temperature monitoring, namely:

  • BLE applications (for triggering RF calibration)

  • Applications using RCX (for triggering clock calibration)

When sys_adc is used by the system (cases above) the period of RTC-to-PDC event is configured using dg_configRTC_PDC_EVENT_PERIOD and set by default to 1 second.

The application can still make use of sys_adc (in parallel to the system use), with the limitation that the period of RTC-to-PDC event is fixed to dg_configRTC_PDC_EVENT_PERIOD.

Code 13 RTC-to-PDC event period
 #define dg_configRTC_PDC_EVENT_PERIOD   (100)   // in 10ms

Note

The period for the RF calibration SNC ucode is inextricably linked to the above macro. For details on how to set the specific value see the SmartSnippets™ DA1469X SDK Documentation.

4.1.5. Audio Manager

4.1.5.1. Introduction

Audio manager is a middleware component for configuring the APU (Audio Processing Unit). It provides an API for easily setting up audio paths.

4.1.5.2. Software architecture

../_images/audio_framework_architecture_da1469x.png

Figure 30 Audio Framework Architecture

As shown in the Audio Framework Architecture in Figure 30, the Audio Manager:

  • Provides an api for configuring, starting and stopping an audio path

  • Verifies that the requested audio path is supported

  • Interfaces with audio Low Level Drivers(LLD) and DMA

  • Does not configure GPIOS, this should be done by the application.

Utilities marked with yellow are used complementary in audio unit.

4.1.5.3. Audio Data Paths

The supported audio data paths are depicted in more detail in Figure 31.

../_images/apu_data_paths_da1469x.png

Figure 31 Audio Manager’s data paths

The supported combinations of input and output interfaces are depicted in Table 23

Table 23 DA1469X Overview of input and output interfaces used in data paths

Input

Output

PCM

PCM *

PCM

PDM

PCM

Memory

PDM

PCM

PDM

Memory

Memory

PCM

Memory

PDM

Memory

Memory

* APU supports only one PCM interface (hardware limitation), therefore PCM sample rate is the same for input and for output. APU SRC is not used (pass-through).

For data paths where memory is involved:

  • The DMA engine is used

  • If input and output sample rates are the same then the APU SRC engine is not used

  • For data paths that memory is used as input and output only sample rate conversions are applicable when audio manager is used. Memory input and output with the same sample rate are not applicable.

4.1.5.4. Configuring Audio Data Paths

In order to use the Audio Manager for configuring an audio path, the following steps are required:

  1. Modify the application header file (for example, custom_config_qspi.h) as follows:

    #define dg_configSYS_AUDIO_MGR      (1)
    #define dg_configUSE_HW_APU_SRC     (1)
    

    In case PDM is used it must be added:

    #define dg_configUSE_HW_PDM         (1)
    

    In case PCM is used it must be added:

    #define dg_configUSE_HW_PCM         (1)
    
  2. Configure the interface pins (PCM, PDM):

  • PDM has the following configurable pins:

    PDM_CLK_PIN : drives the clk signal

    PDM_DATA_PIN : drives the input/output data signal

    where x_x_PIN is defined as HW_GPIO_PORT_x GPIO_PIN_x

    Pin configuration of PDM is depicted in Table 24

Table 24 DA1469X PDM pin configuration

Mode

Data Direction

Pin configuration

Master

INPUT

hw_gpio_configure_pin(PDM_CLK_PIN, HW_GPIO_MODE_OUTPUT, HW_GPIO_FUNC_PDM_CLK, false);
hw_gpio_configure_pin(PDM_DATA_PIN, HW_GPIO_MODE_INPUT, HW_GPIO_FUNC_PDM_DATA, false);

OUTPUT

hw_gpio_configure_pin(PDM_CLK_PIN, HW_GPIO_MODE_OUTPUT, HW_GPIO_FUNC_PDM_CLK, false);
hw_gpio_configure_pin(PDM_DATA_PIN, HW_GPIO_MODE_OUTPUT, HW_GPIO_FUNC_PDM_DATA, false);

SLAVE

INPUT

hw_gpio_configure_pin(PDM_CLK_PIN, HW_GPIO_MODE_INPUT, HW_GPIO_FUNC_PDM_CLK, false);
hw_gpio_configure_pin(PDM_DATA_PIN, HW_GPIO_MODE_INPUT, HW_GPIO_FUNC_PDM_DATA, false);

OUTPUT

hw_gpio_configure_pin(PDM_CLK_PIN, HW_GPIO_MODE_INPUT, HW_GPIO_FUNC_PDM_CLK, false);
hw_gpio_configure_pin(PDM_DATA_PIN, HW_GPIO_MODE_OUTPUT, HW_GPIO_FUNC_PDM_DATA, false);
  • PCM has the following configurable pins:

    PCM_CLK_PIN : drives the clk signal

    PCM_FSC_PIN : drives the FSC signal

    PCM_DI_PIN : drives the input data

    PCM_DO_PIN : drives the output data

    where x_x_PIN is defined as HW_GPIO_PORT_x GPIO_PIN_x

    Pin configuration of PCM is depicted in Table 25

Table 25 DA1469X PCM pin configuration

Mode

Data Direction

Pin configuration

MASTER

INPUT

hw_gpio_configure_pin(PCM_CLK_PIN, HW_GPIO_MODE_OUTPUT, HW_GPIO_FUNC_PCM_CLK, false);
hw_gpio_configure_pin(PCM_FSC_PIN, HW_GPIO_MODE_OUTPUT, HW_GPIO_FUNC_PCM_FSC, false);
hw_gpio_configure_pin(PCM_DI_PIN, HW_GPIO_MODE_INPUT, HW_GPIO_FUNC_PCM_DI, false);

OUTPUT

hw_gpio_configure_pin(PCM_CLK_PIN, HW_GPIO_MODE_OUTPUT, HW_GPIO_FUNC_PCM_CLK, false);
hw_gpio_configure_pin(PCM_FSC_PIN, HW_GPIO_MODE_OUTPUT, HW_GPIO_FUNC_PCM_FSC, false);
hw_gpio_configure_pin(PCM_DO_PIN, HW_GPIO_MODE_OUTPUT, HW_GPIO_FUNC_PCM_DO, false);

SLAVE

INPUT

hw_gpio_configure_pin(PCM_CLK_PIN, HW_GPIO_MODE_INPUT, HW_GPIO_FUNC_PCM_CLK, false);
hw_gpio_configure_pin(PCM_FSC_PIN, HW_GPIO_MODE_INPUT, HW_GPIO_FUNC_PCM_FSC, false);
hw_gpio_configure_pin(PCM_DI_PIN, HW_GPIO_MODE_INPUT, HW_GPIO_FUNC_PCM_DI, false);

OUTPUT

hw_gpio_configure_pin(PCM_CLK_PIN, HW_GPIO_MODE_INPUT, HW_GPIO_FUNC_PCM_CLK, false);
hw_gpio_configure_pin(PCM_FSC_PIN, HW_GPIO_MODE_INPUT, HW_GPIO_FUNC_PCM_FSC, false);
hw_gpio_configure_pin(PCM_DO_PIN, HW_GPIO_MODE_OUTPUT, HW_GPIO_FUNC_PCM_DO, false);
  1. Initialize the audio HW resources according to the examples below:

  1. PDM to I2S

#include "sys_audio_mgr.h"

typedef struct {
        void *audio_dev;            //resource
        sys_audio_device_t dev_in;  //input resource
        sys_audio_device_t dev_out; //output resource
} context_demo_apu_t;

context_demo_apu_t context_demo_apu;

void audio_task()
{
        /* 1. Initialize default parameters for PDM input */
        sys_audio_mgr_default_pdm_data_init(&context_demo_apu.dev_in, false, MODE_MASTER);

        context_demo_apu.dev_in.pdm_param.clk_frequency = 2000000; // PDM frequency in Hz

        /* 2. Initialize default parameters for I2S output */
        sys_audio_mgr_default_pcm_data_init(&context_demo_apu.dev_out, 2, MODE_MASTER, I2S_MODE);

        context_demo_apu.dev_out.pcm_param.sample_rate = 48000; // Set I2S sample rate in Hz
        context_demo_apu.dev_out.pcm_param.bits_depth = 32;     // Set I2S bit depth

        /* 3. Open audio interfaces of apu for the required path */
        sys_audio_mgr_open(&context_demo_apu.dev_in, &context_demo_apu.dev_out);

        /* 4. Enable resources of the required path */
        sys_audio_mgr_start(context_demo_apu.audio_dev);

        /* 5. Stop resources */
        sys_audio_mgr_stop(context_demo_apu.audio_dev);
        sys_audio_mgr_close(context_demo_apu.audio_dev);
}
  1. I2S to Memory

#include "sys_audio_mgr.h"

typedef struct {
        void *audio_dev;            //resource
        sys_audio_device_t dev_in;  //input resource
        sys_audio_device_t dev_out; //output resource
} context_demo_apu_t;

context_demo_apu_t context_demo_apu;

void audio_task()
{
    uint32_t size = 1024 * 64 * 3;

    /* 1. Initialize default parameters for I2S input */
    sys_audio_mgr_default_pcm_data_init(&context_demo_apu.dev_in, 2, MODE_MASTER, I2S_MODE);

    context_demo_apu.dev_in.pcm_param.sample_rate = 16000; // Set I2S sample rate in Hz
    context_demo_apu.dev_in.pcm_param.bits_depth = 16;     // Set I2S bit depth

    /* 2. Initialize default parameters for memory output */
    sys_audio_mgr_default_memory_data_init(&context_demo_apu.dev_out, true, size,
                                                        1024 * 64,
                                                        audio_buffer_ready_cb,
                                                        &context_demo_apu);

    context_demo_apu.dev_out.memory_param.sample_rate = 8000; //Set memory sample rate in Hz
    context_demo_apu.dev_out.memory_param.bits_depth = 16;    //Set memory bit depth

    // Channels 0, 2, 4 or 6 must be used for SRC output
    context_demo_apu.dev_out.memory_param.dma_channel[0] = HW_DMA_CHANNEL_2;
    context_demo_apu.dev_out.memory_param.dma_channel[1] = HW_DMA_CHANNEL_0;

    for (i = 0 ; i < (context_demo_apu.dev_out.memory_param.stereo ? 2 : 1); i++) {

            if (context_demo_apu.dev_out.memory_param.dma_channel[i] != HW_DMA_CHANNEL_INVALID) {

                    if (size > OS_GET_FREE_HEAP_SIZE()) {
                            context_demo_apu.dev_out.memory_param.buff_addr[i] = 0;
                            return;
                    }
                    context_demo_apu.dev_out.memory_param.buff_addr[i] = (uint32_t)OS_MALLOC(size);
            }
    }

    /* 3. Open audio interfaces of apu for the required path */
    sys_audio_mgr_open(&context_demo_apu.dev_in, &context_demo_apu.dev_out);

    /* 4. Enable resources of the required path */
    sys_audio_mgr_start(context_demo_apu.audio_dev);

    /* 5. Stop resources */
    sys_audio_mgr_stop(context_demo_apu.audio_dev);


    /* 6. Close data path */
    sys_audio_mgr_close(context_demo_apu.audio_dev);

    for (i = 0 ; i < (context_demo_apu.dev_out.memory_param.stereo ? 2 : 1); i++) {

        if (context_demo_apu.dev_out.memory_param.buff_addr[i]) {
                    OS_FREE((void *)context_demo_apu.dev_out.memory_param.buff_addr[i]);
            }
    }
}
Notes :
  • Set DIVN or DIV1 in application layer for PCM clock. Default value is DIVN.

  • In case of employing memory as part of the data path, then there are limitations with respect to the size of the memory that can be allocated. These limitations derive from:
    • The total available heap memory size (configTOTAL_HEAP_SIZE)

    • The PCM sample rate or PDM frequency, PCM bit depth and the number of audio channels.

  • In case of using I2S, the number of channels must be 1 or 2 for one or two audio channels, respectively.

An example audio application is available in projects/dk_apps/demos/apu_demo folder.

4.1.9. Logging

In the SDK the supported method of logging is to use the standard C API printf() in the application code. There are four mutually exclusive configuration options that will override printf() in the file sdk/bsp/startup/config.c. These config options shall be set in the custom header files of your project, e.g config/custom_config_ram.h.

Warning

As printf() takes a variable length list of arguments and supports many formatting options the execution time and memory usage are unbounded. This can easily break an embedded system.

The recommendation is to be very careful with printf() .

Ideally use printf() in application tasks as they are lowest priority and tend to have larger stacks.

If it needs to log inside a high-priority RTOS task such as a timer callback then do not pass any variables to be parsed. Just print a short string with no formatting to avoid blowing the small 100 byte stack for the timer task and corrupting other variables.

Possible configuration options are as follows:

  1. CONFIG_RETARGET

    In this mode the logging data is redirected to a UART (ProDK and USB-Kit use UART2 by default) with an overridden version of the low level API _write(). With this configuration the printf() statements appear on the host PC on the lower numbered COM port that is enumerated for the USB cable (COMx for both Pro DK and USB-Kit on Windows, /dev/ttyUSB0 for Pro DK and /dev/ttyACM0 for USB-Kit on Linux).

  2. dg_configSYSTEMVIEW

    In this mode the logging data is redirected to SEGGER’s SystemView tool running on the Host PC.

Note

SystemView only supports integer arguments, i.e. %d.

  1. CONFIG_RTT

In this mode the logging data is redirected to a Segger Real Time Transfer (RTT) link which uses JTAG to communicate the data to the Segger RTT tools running on the Host PC. Using RTT keeps the time taken for printf() to a minimum and allows printing debug information, while the application is performing time-critical, real-time tasks. To view logging data in the host pc J-Link RTT Client Segger’s application shall be used. To set and use RTT the follow steps must be done:

  • SEGGER_RTT.c

  • SEGGER_RTT.h

  • Add RTT Files to Your Project

    • Copy SEGGER_RTT.c, SEGGER_RTT.h, and optionally SEGGER_RTT_printf.c to a folder in your project (e.g., src/segger_rtt/).

    • In e² studio:

      • Right-click your project → Properties → C/C++ General → Paths and Symbols

      • Under Includes, add the path to the RTT header files.

  • Initialize RTT in your Code

4.2. Adapters

4.2.1. Overview

The adapter scheme provides a transparent way for an application to access shared resources. The shared resources can be anything e.g data buffers, hardware resources etc see Figure 37. Hence, it can be considered as a thin layer which offloads the application from doing the resource management by its own.

../_images/029.png

Figure 37 Adapter overview

The adapters use OS features such as semaphores, mutexes or events to synchronize resource-acquisition and resource-release requests. Depending on the adapter, these requests can be issued by different originators. That is, different tasks and / or different masters e.g M33 and Sensor Node Controller. As a result, the main goal of the adapter is to provide a “thread-safe” access to the underlying shared resource see Figure 38.

../_images/030.png

Figure 38 Adapter resource management

Note

It is recommended to access the harware resources via the adapter layer.

The adapter header files can be found under <SDK_ROOT_PATH>/sdk/middleware/adapters/include. Table 35

Table 35 Overview of adapter header files

Filename

Description

ad_crypto.h

ECC and AES/HASH device access API

ad_flash.h

Flash adapter API.

ad_gpadc.h

General Perpose Analog-Digital Converter adapter API.

ad_haptic.h

Haptic (LRA/ERM) adapter API.

ad_i2c.h

I2C device access API.

ad_iso7816.h

ISO7816 device access API.

ad_lcdc.h

LCD device access API.

ad_nvms.h

Non-Volatile Memory Storage adapter API.

ad_nvms_direct.h

Non-Volatile Memory Storage direct-access adapter API.

ad_nvms_ves.h

Non-Volatile Memory Virtual EEPROM Storage adapter API.

ad_nvparam.h

Non-Volatile Parameters adapter API.

ad_nvparam_defs.h

Non-Volatile Parameters adapter definitions.

ad_pmu.h

Power Manager adapter API.

ad_sdadc.h

Sigma Delta Analog-Digital Converter adapter API.

ad_snc.h

Sensor Node Controller Adapter API

ad_spi.h

SPI adapter API.

ad_template.h

Template for a new adapter.

ad_uart.h

UART adapter API.

Important

Crypto API Update

The cryptographic APIs have been updated. Deprecated and unused code has been removed, and the new ad_crypto adapter has been added.

4.2.2. Extending adapters for I/O-configured devices

Apart from the adapters essential to the system control (power management, non-volatile memory access, crypto device Access), the adapter concept has been extended for the following blocks:

  • All the serial interfaces (UART, I2C, SPI).

  • All the analog to digital converters GPADC or SDADC.

  • The LCD controller.

May the user implement a new adapter, the basic entities and relations of the adapter concept are explained in template source code in the adapter section.

Table 37 Adapter terminology

Terminology

Description

Adapter

A middleware abstraction layer for using a controller. It provides the following services:

  • Arbitration in accessing the controller (between multiple masters, multiple tasks).

  • Configuration of the controller.

  • Block sleep while the controller is in use.

Controller

Top level view of a peripheral (e.g SPI, I2C), including the complete configuration needed for being fully functional (IOs, DMAs, Driver configuration)

Driver (LLD)

The Low Level Driver associated with the controller.

Note

Depending on the peripheral there are configuration macros in low level drivers to enable or disable slave, DMA support or other features.

4.2.3. The UART adapter example

The UART adapter is an intermediate layer between the UART LLD and a user application. It allows the user to utilize the UART interface without implementing an Ad-Hoc resource management.

Features:

  • Provides a direction-specific resource management in order for the UART interface to be accessed in a “thread safe” manner by:

    • Different masters.

    • Different tasks.

    • Simultaneous read / write initiators.

  • Provides blocking and non-blocking read / write API.

Using the UART Adapter

  1. Configure the UART adapter

    To enable the UART adapter, the relevant macros need to enable the software controlling the UART interface. Configuration options affecting the UART interface define also the adapter’s behavior. These macros must be defined in the project’s custom header file, e.g config/custom_config_ram.h.

    Table 38 uart config

    Configuration macro

    Default value

    Enable/Disable

    dg_configUART_ADAPTER

    0 (disabled)

    UART adapter

    dg_configUSE_HW_UART

    1 (enabled)

    UART driver

    dg_configUART_DMA_SUPPORT

    0

    DMA support

    dg_configUART_SOFTWARE_FIFO

    0

    Software FIFO support

    dg_configUART_RX_CIRCULAR_DMA

    0

    DMA circular buffer for the RX channel

  2. Create a UART controller configuration ad_uart_controller_conf_t.

This is an example of a UART controller configuration taken from the file sdk/middleware/adapters/src/sys_platform_devices_internal.c:

Code 15 Example of a UART I/O and LLD configuration
const ad_uart_io_conf_t sys_platform_console_io_conf = {
    /* Rx UART2 */
    .rx = {
            .port = SER1_RX_PORT,
            .pin = SER1_RX_PIN,
            /* On */
            {
                    .mode = SER1_RX_MODE,
                    .function = SER1_RX_FUNC,
                    .high = true,
            },
            /* Off */
            {
                    .mode = SER1_RX_MODE,
                    .function = SER1_RX_FUNC,
                    .high = true,
            },
    },
    /* Tx UART2 */
    .tx = {
            .port = SER1_TX_PORT,
            .pin = SER1_TX_PIN,
            /* On */
            {
                    .mode = SER1_TX_MODE,
                    .function = SER1_TX_FUNC,
                    .high = true,
            },
            /* Off */
            {
                    .mode = SER1_TX_MODE,
                    .function = SER1_TX_FUNC,
                    .high = true,
            },
    },
    /* RTSN */
    .rtsn = {
            .port = SER1_RTS_PORT,
            .pin = SER1_RTS_PIN,
            /* On */
            {
                    .mode = SER1_RTS_MODE,
                    .function = SER1_RTS_FUNC,
                    .high = true,
            },
            /* Off */
            {
                    .mode = SER1_RTS_MODE,
                    .function = SER1_RTS_FUNC,
                    .high = true,
            },
    },
    /* CTSN */
    .ctsn = {
            .port = SER1_CTS_PORT,
            .pin = SER1_CTS_PIN,
            /* On */
            {
                    .mode = SER1_CTS_MODE,
                    .function = SER1_CTS_FUNC,
                    .high = true,
            },
            /* Off */
            {
                    .mode = SER1_CTS_MODE,
                    .function = SER1_CTS_FUNC,
                    .high = true,
            },
    },
    /* Voltage Level */
    .voltage_level = HW_GPIO_POWER_V33,
};
Code 16 Example of the platform SER1 UART configuration
const ad_uart_driver_conf_t sys_platform_console_uart_driver_conf = {
     {

             .baud_rate = HW_UART_BAUDRATE_115200,
             .data = HW_UART_DATABITS_8,
             .parity = HW_UART_PARITY_NONE,
             .stop = HW_UART_STOPBITS_1,
             .auto_flow_control = 1,
             .use_fifo = 1,
             .use_dma = 1,
             .tx_dma_channel = HW_DMA_CHANNEL_3,
             .rx_dma_channel = HW_DMA_CHANNEL_2,
             .tx_fifo_tr_lvl = 0,
             .rx_fifo_tr_lvl = 0,
    }
 };

 const ad_uart_controller_conf_t sys_platform_console_controller_conf = {
     .id = SER1_UART, /* The UART interface dedicated to the platofrm */
     .io = &sys_platform_console_io_conf,
     .drv = &sys_platform_console_uart_driver_conf,
 };

User code

  1. Initialize UART controller pins (optional)

    The user is allowed to configure the I/O part of the controller before powering up the external connected devices (e.g sensors).

Code 17 Initialize UART controller pins
/**
 * \brief Initialize controller pins to on / off io configuration
 *
 * This function should be called for setting pins to the correct level before external
 * devices are powered up (e.g on system init). It does not need to be called before every
 * ad_uart_open() call.
 *
 * \param [in] id         controller instance
 * \param [in] io         controller io configuration
 * \param [in] state      on/off io configuration
 *
 * \return 0: success, <0: error code
 */
 int ad_uart_io_config (HW_UART_ID id, const ad_uart_io_conf_t *io, AD_IO_CONF_STATE state);

Note

The API function ad_xxx_io_config should be called after adapter’s mutex initialization from pm_system_init().

  1. Open the UART controller

    Before starting any read or write transaction the UART controller must be instantiated using Code 18.

Code 18 Open the UART controller
/**
 * \brief Open UART controller
 *
 * This function:
 * - Acquires the resources needed for using the controller
 * - Configures the controller interface IOs
 * - Initializes the drivers associated with the controller
 *
 *
 * \param [in] ad_uart_ctrl_conf  controller configuration
 *
 * \return >0: handle that should be used in subsequent API calls, NULL: error
 *
 * \note The function will block until it acquires all controller resources
 */
ad_uart_handle_t ad_uart_open(const ad_uart_controller_conf_t *ad_uart_ctrl_conf);
  1. Write/Read to/from the UART controller

    Write and read API comes into two flavours, synchronous (blocking) and asynchronous (non-blocking):

  • Synchronous

    Synchronous API suspends the calling task until the transaction is completed.

Code 19 Write function (Blocking)
/**
 * \brief Perform a blocking write transaction
 *
 * This function performs a synchronous write only transaction
 *
 * \param [in] handle handle returned from  ad_uart_open()
 * \param [in] wbuf   buffer containing the data to be sent to the device
 * \param [in] wlen   size of data to be sent to the device
 *
 * \sa ad_uart_open()
 *
 * \return 0 on success, <0: error
 *
 */
int ad_uart_write(ad_uart_handle_t handle, const char *wbuf, size_t wlen);
Code 20 Read function (Blocking)
/**
 * \brief Perform a blocking read transaction
 *
 * This function performs a synchronous read only transaction
 *
 * \param [in]  handle handle returned from ad_uart_open()
 * \param [out] rbuf   buffer for incoming data
 * \param [in]  rlen   number of bytes to read
 * \param [in]  timeout timeout time in ticks to wait for data
 *
 * \sa ad_uart_open()
 *
 * \note  If timeout is OS_EVENT_FOREVER, exactly \p rlen bytes must be received.
 *        If timeout is specified, function can exit after timeout with less bytes
 *        than requested.
 *
 * \return Number of transferred bytes on success, <0: error
 *
 */
int ad_uart_read(ad_uart_handle_t handle, char *rbuf, size_t rlen, OS_TICK_TIME timeout);
  • Asynchronous

Asynchronous API does not suspend the calling task. The task may continue with other operations. It will be notified via the registered callback function.

Code 21 Write function (Non-blocking)
/**
 * \brief Perform a non blocking write transaction
 *
 * This function performs an asynchronous write only transaction
 * Caller task should retry until function returns no error
 * Callback will be called when transaction is completed
 *
 * \param [in] handle handle returned from ad_uart_open()
 * \param [in] wbuf   buffer containing the data to be sent to the device
 * \param [in] wlen   size of data to be sent to the device
 * \param [in] cb     callback to call after transaction is over (from ISR context)
 * \param [in] user_data user data passed to cb callback
 *
 * \sa ad_uart_open()
 *
 * \warning Do not call this function consecutively without guaranteeing that the previous
 *          async transaction has been completed.
 *
 * \warning After the callback is called, it is not guaranteed that the scheduler will give
 *          control to the task waiting for this transaction to complete. This is important to
 *          consider if more than one tasks are using this API.
 *
 * \return 0 on success, <0: error
 *
 */
int ad_uart_write_async(ad_uart_handle_t handle, const char *wbuf, size_t wlen,
                        ad_uart_user_cb cb, void *user_data);
Code 22 Read function (Non-blocking)
/**
 * \brief Perform a non blocking read transaction
 *
 * This function performs an asynchronous read only transaction
 * Caller task should retry until function returns no error
 * Callback will be called when transaction is completed
 *
 * \param [in]  handle    handle returned from ad_uart_open()
 * \param [out] rbuf      buffer for incoming data
 * \param [in]  rlen      number of bytes to read
 * \param [in]  cb        callback to call after transaction is over (from ISR context)
 * \param [in]  user_data user data passed to cb callback
 *
 * \sa ad_uart_open()
 *
 * \warning Do not call this function consecutively without guaranteeing that the previous
 *          async transaction has been completed.
 *
 * \warning After the callback is called, it is not guaranteed that the scheduler will give
 *          control to the task waiting for this transaction to complete. This is important to
 *          consider if more than one tasks are using this API.
 *
 * \return 0 on success, <0: error
 *
 */
int ad_uart_read_async(ad_uart_handle_t handle, char *rbuf, size_t rlen, ad_uart_user_cb cb,
                       void *user_data);

See also

For more detailed information about how to enable asynchronous APIs please refer to Section 4.2.4.

  1. Closing the UART device.

    After completing the transactions and the UART controller instance is not needed anymore for additional tasks, it should be closed by using Code 23.

Code 23 Close UART controller
/**
 * \brief Close UART controller
 *
 * This function:
 * - Aborts ongoing transactions
 * - De-initializes the drivers associated with the controller
 * - Resets controller interface IOs (as specified in ad_uart_open())
 * - Releases the controller resources
 *
 * \param [in] handle handle returned from ad_uart_open()
 * \param [in] force force close even if controller is busy
 *
 * \sa ad_uart_open()
 *
 * \return 0: success, <0: error code
 */
int ad_uart_close(ad_uart_handle_t handle, bool force);

Example of a UART synchronous access:

Code 24 Example of a UART synchronous access
static ad_uart_handle_t uart_handle;
static char wbuf[5] = "Test";
static char rbuf[5];

uart_handle = ad_uart_open(&ad_uart_controller_conf);                /* Open the UART controller */
ad_uart_write(uart_handle, wbuf, sizeof(wbuf));                      /* Write synchronously some data to UART controller */
ad_uart_read(uart_handle, rbuf, sizeof(rbuf), OS_EVENT_FOREVER);     /* Read synchronously the data from UART device */
ad_uart_close(uart_handle);                                          /* Close the UART controller */

4.2.4. Adapter’s configuration macros

This section describes available configuration macros in the adapter level that enable or disable synchronous (blocking) and asynchronous (non-blocking) API for read/write operations and resource locking. YYY can be any of the serial interface, or ADC available adapters. The macros are all enabled by default.

Enable synchronous transactions

The following macros are used in order to activate synchronous transactions, and can be found in each adapter’s header:

Code 25 synchronous transactions macro
    /**
     * \def CONFIG_YYY_USE_SYNC_TRANSACTIONS
     *
     * \brief Controls whether YYY synchronous transaction API will be used
     *
     */
    #ifndef CONFIG_YYY_USE_SYNC_TRANSACTIONS
    #define CONFIG_YYY_USE_SYNC_TRANSACTIONS       (1)
    #endif

Enable asynchronous transactions

The following macros are used in order to activate asynchronous transactions, and can be found in each adapter’s header:

Code 26 asynchronous transactions macro
    /**
     * \def CONFIG_YYY_USE_SYNC_TRANSACTIONS
     *
     * \brief Controls whether YYY asynchronous transaction API will be used
     *
     */
    #ifndef CONFIG_YYY_USE_ASYNC_TRANSACTIONS
    #define CONFIG_YYY_USE_ASYNC_TRANSACTIONS      (1)
    #endif

Enable resource locking

The following macros are used in order to enable/disable resource locking. A simple scenario to use a peripheral via its adapter may allow to disable locking, producing a decreased size firmware.

Code 27 resource locking macro
/**
 * \def CONFIG_AD_YYY_LOCKING
 *
 * \brief Controls whether YYY adapter resource locking will be enabled
 *
 */
#ifndef CONFIG_AD_YYY_LOCKING
#define CONFIG_AD_YYY_LOCKING                     (1)
#endif

Warning

If resource locking is disabled, all such internal handling is disabled, thus becoming the application’s responsibility to handle concurrent accesses and controlling the resource management.

4.2.6. The NVMS Adapter

4.2.6.1. Overview

The DA1469x SDK includes a Non-Volatile Memory Storage (NVMS) Adapter providing non-volatile memory storage access capabilities to the application (including cached mode). The Adapter provides two main functions:

  • Non-Volatile Memory Storage to external Flash devices over standard SPI and Quad SPI performing Write / Read / Erase operations

  • Virtual EEPROM (VES) emulation with the following functionalities

    • Wear-levelling

    • Garbage Collection

    • Power failure protection

    The VES partition should be used for data that is frequently written to flash. The wear levelling and garbage collection allow the driver to maximize the number of write cycles from software given the limitations of the number of erase & write cycles of the actual flash device.

An overview of NVMS Adapter is shown in Figure 41.

../_images/nvms_layers.png

Figure 41 NVMS Overview

In theory NVMS is interface agnostic, as it is independent from the interface type being used (Standard SPI, I2C or QuadSPI) for data storage. However, the QSPI interface is a special interface from the NVMS perspective. As well as performing eXecution in Place (XiP), the QSPI Controller hardware block is also handling specific Flash-aware actions when Flash devices Read/Write/Erase operations are executed. As shown in Figure 43 when DA1469x is in cached mode, special mechanisms from the QSPI block are invoked.

The main consequence of this approach is that all Flash memory models used when utilizing NVMS over QSPI in cached mode must support Erase suspend/resume.

The rest of this section gives an overview on:

  • Common NVMS interface for all usage scenarios

  • Mechanisms implemented specifically for NVMS over QSPI in cached mode usage scenario

  • Virtual EEPROM (VES) emulation

  • Flash Memory Map in various usage scenarios.

4.2.6.2. Interface

To enable the NVMS adapter, both dg_configNVMS_ADAPTER and dg_configFLASH_ADAPTER macros must be defined and set to 1 in the project’s custom header file, e.g config/custom_config_qspi.h. From this point and on, the adapter services become available.

Code 32 Enabling NVMS and Flash Adapters
#define dg_configNVMS_ADAPTER         (1)

#define dg_configFLASH_ADAPTER        (1)

NVMS Adapter exposes functions: ad_nvms_init(), ad_nvms_open(), ad_nvms_write() and ad_nvms_read(), ad_nvms_erase(). Function ad_nvms_init() must be called once at platform start to perform all necessary initialization routines, including discovering underlying storage partitions. The Application must open one of the partitions before any read or write activity can be performed. If several partitions are stored on one physical device (i.e. SPI Flash), opening one partition will limit read and writes to this partition only, making all addressing relative to beginning of that partition and not the whole flash. After opening, each partition is accessed in same way, but the exact way that data is stored depends on partition type. Only in the Virtual EEPROM (VES) partition do all reads and writes use virtual addresses that are independent of actual flash location.

Code 33 Usage of NVMS
nvms_t ad_nvms_open(nvms_partition_id_t id);

for (;;) {

/* addr is any address in partition address space
 * buf can be any address in memory including QSPI mapped flash */

ad_nvms_read(part, addr, buf, sizeof(buf));

ad_nvms_write(part, addr, buf, sizeof(buf));

}

Function ad_nvms_open() can be called many times since it does not allocate any resources.

Function ad_nvms_read() can be called with any address within the partition. If address is outside the partition boundaries ad_nvms_read() will return 0. If the address is inside the partition but the size would exceed the partition boundary only the data from within the partition will be accessed.

Function ad_nvms_write() can be called with any address inside partition address space.

How read or write actually work depends on the accessing method. In the current version of the platform, two accessing methods are supported: (a) Direct Access and (b) VES.

Direct Access

Direct access driver uses the relative address from the beginning of the partition but apart from this there is no address translation. Writes are performed exactly at requested addresses. If write would not change data (same data written) it will not be performed at all. If write can be performed without erasing it will be executed. If write can’t be performed without an erase, then the erase is also initiated. Currently the direct driver does not support caching so writing small pieces of data on an already used sector will trigger many erase operations. If a small write is required for some reason, then ad_nvms_erase() should be called explicitly before each write for efficiency reasons. Power failure during write or erase will result in data loss including data that was not touched by the last write. For power fail safe operation the VES partition should be used.

VES

For an application to use VES there are two prerequisites:

  • The Partition ID should only be NVMS_GENERIC_PART.

  • dg_configNVMS_VES variable should be defined.

VES driver provides access to the partition with power failure protection. It uses virtual addressing. The address space available for the user application is smaller than physical flash space occupied by the partition, but user can read and write to this address space without worrying that data will be lost. If power fails during a write, the specific data being written can be lost but other data will not be affected.

../_images/032.png

Figure 42 Virtual/Physical Addressing with and without VES

When VES is used:

  • The application configures NVMS with the VES section information. The VES section is represented by a virtual address range that is mapped to a physical address range.

  • NVMS prevents the application from performing “raw” writes to the allocated VES section

4.2.6.3. NVMS partition table

Code 34 shows the full list of the NVMS IDs and it can be found in

<SDK_ROOT_PATH>/sdk/middleware/adapters/include/partition_def.h

Code 34 NVMS Partition IDs
/**
* \brief NVMS Partition IDs
*/
typedef enum {
NVMS_FIRMWARE_PART              = 1,
NVMS_PARAM_PART                 = 2,
NVMS_BIN_PART                   = 3,
NVMS_LOG_PART                   = 4,
NVMS_GENERIC_PART               = 5,
NVMS_PLATFORM_PARAMS_PART       = 15,
NVMS_PARTITION_TABLE            = 16,
NVMS_FW_EXEC_PART               = 17,
NVMS_FW_UPDATE_PART             = 18,
NVMS_PRODUCT_HEADER_PART        = 19,
NVMS_IMAGE_HEADER_PART          = 20,
} nvms_partition_id_t;

Code 35 shows the format for the data of the NVMS.

Code 35 Partition entry
/**
 * \brief Partition entry.
 */
typedef struct partition_entry_t {
uint8_t magic;          /**< Partition magic number 0xEA */
uint8_t type;           /**< Partition ID */
uint8_t valid;          /**< Valid marker 0xFF */
uint8_t flags;          /**< */
uint16_t start_sector;  /**< Partition start sector */
uint16_t sector_count;  /**< Number of sectors in partition */
uint8_t reserved2[8];   /**< Reserved for future use */
} partition_entry_t;
Table 44 Description of the Partition entry

Magic code

(1 byte)

Partition ID

(1 byte)

Valid flag

(1 byte)

Flags

(1 byte)

Start sector of partition

(2 bytes)

Sector count of partition

(2 bytes)

Reserve

(8 bytes)

0xEA

0xFF means it’s an invalid partition entry. See nvms_partition_id_t.

0xFF means it’s a valid partition entry.

#define PARTITION_FLAG_READ_ONLY     (1 << 0)

#define PARTITION_FLAG_VES           (1 << 1)

  • 0 A normal writable / readable direct partition.

  • 1 A read-only partition.

  • 2 A VES partition.

Start sector in flash of this partition.

Sector count of this partition.

For future use.

4.2.6.4. NVMS over QSPI in cached mode

../_images/033.png

Figure 43 NVMS Adapter NVMS over QSPI and Virtual EEPROM emulation in Cached mode

When executing in place (XiP) from Flash in cached mode, the Flash device is used to store both firmware images and data.

Preemptive RTOS scheduling remains operational while programming the Flash.

PROGRAM and ERASE are the 2 critical Flash operations triggered by NVMS that need to be considered, as they can’t be performed at the same time as the READ operations triggered by the cache controller when it fetches cache lines from the FLASH to the cache RAM. To handle this conflict a specific mechanism is needed.

This mechanism is disabled when DA1469x is not in cached mode.

4.2.6.5. Slice PROGRAM operation

When writing a buffer to the Flash, the NVMS Adapter will slice the buffer into several smaller buffers and will issue several uninterruptible PROGRAM QSPI requests. Note that one parameter determining the size of the slice in bytes is stored in the flash itself. This parameter is determined by:

  1. The interrupt latency time in microseconds that the application authorizes.

  2. The flash model and the time taken to perform a program.

In general, the first byte to be programmed takes longer than the subsequent bytes so a trade-off is possible and the exact value is left to the application developer. However, a default value of 16 bytes is currently used.

4.2.6.6. Suspend/Resume ERASE Operation

Instead of slicing ERASE, the DA1469x SDK leverages from Smartbond™ QSPI Controller SUSPEND/ERASE in auto-mode capability. It is assumed here that all selected Flash models support suspending ERASE.

../_images/034.png

Figure 44 Suspend/Resume ERASE Operation

When requested to erase a sector by NVMS Adapter (staying in auto-mode), the QSPIC will automatically suspend the Erasing operation when a “read” from the Cache controller is triggered due to a miss hit. Two parameters to be stored in the flash itself are SW configurable:

  • ERASE/RESUME Hold: Refers to QSPIC_ERASECMDB_REG[QSPIC_ERSRES_HLD]

  • RESUME/SUSPEND delay: Refers to QSPIC_ERASECMDB_REG[QSPIC_RESSUS_DLY]

As QSPIC is not firing any interrupt on Erase completion, NVMS adapter must poll QSPIC_ERASECTRL_CMD.

A detailed analysis of the Suspend/Resume ERASE Operation is shown in Figure 44. The overall process begins when the Application/OS issues an “Erase Command” Request. The NVMS Adapter receives the Request and issues an “Erase Sector” Request to the QSPIC (Set QSPIC_ERASECTRL_REC [QSPIC_ERASE_EN]). This request changes the QSPIC state (Figure 44, Reference Point 1), which checks whether the Flash Memory is idle for a certain number of Clock Cycles before initiating the ERASE process (QSPIC_ERASECMDB_REC [QSPIC_ERSRES_HLD]). Provided that the process can be initiated, QSPIC issues an “Erase Sector” Request to the Flash Memory and erasing begins. In the meantime, NVMS adapter also switches state, by writing CHECK_ERASE_REG and ERASECTRL_REQ bits (Figure 44, Reference Point 2). The Erase process taking place in the Flash Memory, is able to be suspended by the QSPIC via an “Erase Suspend” Request in two cases (i) if a Cache Miss Notification arrives in the Cache control, leading to a “Fetch cache – Read” Request towards the QSPIC and (ii) if a Read Request is issued by the Application/OS directly to the QSPIC. The consequent “Erase Suspend” Request initiates the READ process shown in Figure 44, where QSPIC reads data from the Flash Memory and issues a “Read Done” Response to (i) Cache Control or (ii) Application/OS respectively. Then, the QSPIC state switches again, intending to resume Flash erase if memory is idle for a certain number of Clock Cycles (QSPIC_ERASECMDB_REG [QSPIC_ERSRES_HLD]) as shown in Figure 44, Reference Point 3. Provided that the Flash erase can be resumed, QSPIC issues an “Erase Resume” Request towards the Flash Memory and changes state once more. In its current state (Figure 44, Reference Point 4) QPSIC is programmed to delay any new suspension of the newly-resumed Erase for a pre-defined number of Clock Cycles (QSPIC_ERASECMDB_REG [QSPIC_RESSUS_DLY]). In the meantime, Flash Memory concludes the erasing process and notifies the QSPIC with an “Erase Complete” Response. QSPIC then issues an “Erase Complete” Response to the NVMS adapter, changing its state thus clearing CHECK_ERASE_REG and ERASECTRL_REQ bits (Figure 44, Reference Point 5). Finally, NVMS Adapter issues the conclusive “Erase Complete” Response to the Application/OS.