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 9.

../_images/sleep_and_wake_up_70x.png

Figure 9 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 11.

Table 11 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 10.

../_images/sys_init.png

Figure 10 Power and clock manager initialization

4.1.2. Watchdog Service

4.1.2.1. 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 11:

../_images/043.png

Figure 11 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.2. 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.3. Examples

To register the task with sys_watchdog use the following code snippet

Code 8 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 9 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.4. API

Table 12 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 DA1470x charger software implementation and its integration in the SmartSnippets™ DA1470x 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 12 Charger’s layers

Charger’s layers are depicted in Figure 12. 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 14.

../_images/130.png

Figure 14 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 15 Flowchart of the charger’s LLD

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

../_images/127.png

Figure 16 Charger’s HW FSM

4.1.3.4. Middleware

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

../_images/128.png

Figure 17 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 19 and Figure 20, depending on the method of charger port detection employed.

../_images/sys_charger_operating_cycle_sw_det.png

Figure 19 System charger operation (SW Port Detection)

../_images/sys_charger_operating_cycle_hw_det.png

Figure 20 System charger operation (HW Port Detection)

The port detection algorithm can be executed either in hardware or software. The default setting is for port detection to be executed in hardware, however port detection type (HW/SW) can be configured via the following configuration option.

#define dg_configUSE_HW_PORT_DETECTION            (0)

The next image Figure 21 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 21 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_oqspi.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_2, HW_GPIO_PIN_10, HW_GPIO_MODE_INPUT, HW_GPIO_FUNC_USB);
hw_gpio_set_pin_function(HW_GPIO_PORT_2, HW_GPIO_PIN_11, 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_29, HW_GPIO_MODE_INPUT, HW_GPIO_FUNC_GPIO);
hw_gpio_set_pin_function(HW_GPIO_PORT_0, HW_GPIO_PIN_11, HW_GPIO_MODE_OUTPUT, HW_GPIO_FUNC_GPIO);

hw_gpio_pad_latch_enable(HW_GPIO_PORT_0, HW_GPIO_PIN_29);
hw_gpio_pad_latch_enable(HW_GPIO_PORT_0, HW_GPIO_PIN_11);

hw_gpio_set_active(HW_GPIO_PORT_0, HW_GPIO_PIN_11);

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_cooler_level = HW_CHARGER_V_LEVEL_4480,
                .jeita_ovp_cool_level = HW_CHARGER_V_LEVEL_4480,
                .jeita_ovp_warm_level = HW_CHARGER_V_LEVEL_4460,
                .jeita_ovp_warmer_level = HW_CHARGER_V_LEVEL_4460,
                .jeita_replenish_v_cooler_level = HW_CHARGER_V_LEVEL_4020,
                .jeita_replenish_v_cool_level = HW_CHARGER_V_LEVEL_4020,
                .jeita_replenish_v_warm_level = HW_CHARGER_V_LEVEL_4000,
                .jeita_replenish_v_warmer_level = HW_CHARGER_V_LEVEL_4000,
                .jeita_precharged_v_cooler_thr = HW_CHARGER_V_LEVEL_3150,
                .jeita_precharged_v_cool_thr = HW_CHARGER_V_LEVEL_3150,
                .jeita_precharged_v_warm_thr = HW_CHARGER_V_LEVEL_3100,
                .jeita_precharged_v_warmer_thr = HW_CHARGER_V_LEVEL_3100,
                .jeita_cv_cooler_level = HW_CHARGER_V_LEVEL_4200,
                .jeita_cv_cool_level = HW_CHARGER_V_LEVEL_4200,
                .jeita_cv_warm_level = HW_CHARGER_V_LEVEL_4220,
                .jeita_cv_warmer_level = HW_CHARGER_V_LEVEL_4220,

                /* Default JEITA current settings */
                .jeita_precharge_cc_cooler_level = HW_CHARGER_I_PRECHARGE_LEVEL_1_5,
                .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_precharge_cc_warmer_level = HW_CHARGER_I_PRECHARGE_LEVEL_1_0,
                .jeita_cc_cooler_level = HW_CHARGER_I_LEVEL_40,
                .jeita_cc_cool_level = HW_CHARGER_I_LEVEL_30,
                .jeita_cc_warm_level = HW_CHARGER_I_LEVEL_25,
                .jeita_cc_warmer_level = HW_CHARGER_I_LEVEL_25,

                /* Default Tbat limits */
                .bat_temp_cold_limit = HW_CHARGER_BAT_TEMP_LIMIT_0,
                .bat_temp_cooler_limit = HW_CHARGER_BAT_TEMP_LIMIT_5,
                .bat_temp_cool_limit = HW_CHARGER_BAT_TEMP_LIMIT_10,
                .bat_temp_warm_limit = HW_CHARGER_BAT_TEMP_LIMIT_35,
                .bat_temp_warmer_limit = HW_CHARGER_BAT_TEMP_LIMIT_40,
                .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.9. Charger Oscillation

If the voltage difference between VBUS and VBAT is low enough to be close to the hardware VBUS comparator limits, it is possible that the Charger FSM may enter an erroneous loop, where it rapidly oscillates between the PRECHARGE and CC states.

The VBUS voltage is never low enough for the oscillation to occur under normal operating conditions, but a faulty charger or charging cable may trigger this oscillation.

In order to detect this event, an oscillation detection mechanism has been implemented in the charger service. When an FSM state transition from CC to PRECHARGE occurs, the charger service will keep a count of all state machine transitions for a small period of time. If the number of transitions exceeds a preconfigured threshold, then charging is halted, and the application is notified of the event by means of a callback function. If charging needs to be resumed, the application must then take corrective action.

The oscillation detection mechanism can be configured using the following parameters (default values in parenthesis).

/* Enable the oscillation detection mechanism */
#define dg_configSYS_CHARGER_OSC_CHECK_EN                       (1)

/* Duration of the oscillation detection mechanism, in milliseconds */
#define dg_configSYS_CHARGER_OSC_CHECK_TIMER_INTERVAL_MS        (10)

/* The FSM transitions threshold */
#define dg_configSYS_CHARGER_VBUS_IRQ_CNT_THRESH                (40)

The callback function used to indicate oscillation detection to the application is

void sys_charger_ext_hook_oscillation_detected(void);

4.1.3.10. Supported battery types

Table 14 Battery types

Battery types

Description

NiMH

Nickel–metal hydride

LiCoO2

Lithium cobalt oxide

LiMn2O4

Lithium ion manganese oxide

LiFePO4

Lithium iron phosphate

NMC

Lithium Nickel Manganese Cobalt 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, detect a temperature change, share temperature information with CMAC and start RCHS calibration when temperature exceeds specified limits.

The interaction of this service with other parts of the system is shown in Figure 23 and Figure 24:

../_images/sys_adc_70x_1.png

Figure 23 System ADC service overview (with timer trigger)

../_images/sys_adc_70x_2.png

Figure 24 System ADC service overview (on sleep exit)

4.1.4.2. Concept

The sys_adc creates the Sys_adc task and a periodic software OS timer. There are two possible events that will change the state of the Sys_adc task from blocked to running, either a timer timeout (Figure 23) or a trigger when system exits sleep (Figure 24). The latter will execute only if the time interval between the upcoming and last temperature measurement exceeds a specified time threshold. On both events, the task gets notified to measure and store the current temperature through the GPADC temperature channel, as well as to identify if the temperature delta from the previous measurement exceeds certain limits. In such a case, the RC clocks calibration task, which executes the RCHS calibration routine, is notified. If the application requires BLE operations, the current temperature measurement is stored in a shared space variable. This is essential for CMAC and eventually the RF driver, in order to perform the RF calibration.

4.1.4.3. System use

The SmartSnippets™ DA1470X SDK enables and internally uses the service by default. However, if the application wants to disable the service (though it is not recommended) it should set the dg_configENABLE_RCHS_CALIBRATION and dg_configRF_ENABLE_RECALIBRATION configuration options to 0 in the custom header file (for example, custom_config_oqspi.h).

#define dg_configENABLE_RCHS_CALIBRATION   (0)
#define dg_configRF_ENABLE_RECALIBRATION   (0)

Note

The maximum timer period is defined internally by the SDK to 1sec. In case RF calibration is enabled (i.e. dg_configRF_ENABLE_RECALIBRATION configuration option is set to 1) the timer period is the minimum between the time defined in dg_configRF_CALIB_TEMP_POLL_INTV configuration option and the internally defined time of 1sec.

4.1.6. Audio Manager

4.1.6.1. Introduction

The Audio Manager is a middleware component for configuring the Audio Unit. It provides an API to setup and use the assorted audio paths available in this HW block.

4.1.6.2. Software architecture

../_images/audio_framework_architecture_da1470x.png

Figure 27 Audio Framework Architecture

As shown in the Audio Framework Architecture in Figure 27, 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

Note: The yellow marked LLDs are not audio h/w blocks and need to be configured.

4.1.6.3. Audio Data Paths

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

../_images/apu_data_paths_da1470x.png

Figure 28 Audio Manager’s data paths

In DA1470x, up to 4 data paths are supported at the same time.

Examples:

Valid parallel paths:

Case 1

Input

SRCX

Output

MEMORY

No

PCM

SDADC

SRC1

PDM

PCM

No

MEMORY

MEMORY

SRC2

MEMORY

Case 2

Input

SRCX

Output

PCM

No

MEMORY

SDADC

No

MEMORY

PCM

SRC1

MEMORY

PDM

SRC2

MEMORY

The supported combinations of input and output interfaces for each data path are depicted in Table 18

Table 18 DA1470X Overview of input and output interfaces used in data paths

Input

SRCX

Output

PCM

No

PCM

PCM

Yes

PDM

PCM

Yes

MEMORY

PCM

No

MEMORY

PDM

Yes

PCM

PDM

Yes

MEMORY

MEMORY

Yes

PCM

MEMORY

No

PCM

MEMORY

Yes

PDM

MEMORY

Yes

MEMORY

SDADC

Yes

PCM

SDADC

Yes

PDM

SDADC

Yes

MEMORY

SDADC

No

MEMORY

The Audio Unit supports only one PCM interface, limited from the fact that both input and output PCM sample rates should be equal. ​Choosing a PCM audio path, the data is processed by handling the PCM’s own interrupts, rather than the SRC interrupt handling.

For data paths where memory is involved:

  • The DMA engine is used

  • If the input and output sample rates are equal, this renders the SRC engine obsolete, hence it is bypassed.

  • In data paths where memory is used as both input and output only sample rate conversions are applicable when the Audio Manager is used. A data path with memory as input and output, both having equal sample rates, is obsolete.

For data paths using SDADC the sole supported bit resolution is the one delivered by the SDADC filter, that being 16-bits.

In cases of simultaneous data path implementation, the supported parallel data paths follow the rules below:

  • PDM device may only be used once, either as input or as an output device for each data path, as PDM device has only one data pin that can be configured either as input or as output.

  • With the exception of MEMORY, each device-type can be used multiple times as input with all inputs sharing the same configuration and only once as output (if supported).

4.1.6.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_oqspi.h) as follows:

    #define dg_configSYS_AUDIO_MGR      (1)
    #define dg_configUSE_HW_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)
    

    In case SDADC is used it must be added:

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

  • PDM has the following configurable pins:

    PDM_CLK_PIN : drives the clock 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 19

Table 19 DA1470X 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 20

Table 20 DA1470X 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);
  • SDADC has the following configurable pins:

    PGA_INP : drives the Programmable Gain Amplifier (PGA) positive input. This pin should be defined as HW_GPIO_PORT_1 GPIO_PIN_5, binding to the corresponding pin.

    PGA_INM : drives the PGA negative input. This pin should be defined as HW_GPIO_PORT_1 GPIO_PIN_6, binding to the corresponding pin.

    Pin configuration of SDADC is depicted in Table 21

Table 21 SDADC pin configuration

Data Direction

Pin configuration

INPUT

hw_gpio_configure_pin(PGA_INP, HW_GPIO_MODE_INPUT, HW_GPIO_FUNC_ADC, false);
hw_gpio_configure_pin(PGA_INM, HW_GPIO_MODE_INPUT, HW_GPIO_FUNC_ADC, false);
  1. Initialize the HW resources (see examples below):

  1. One Data Path: PDM to I2S

 #include "sys_audio_mgr.h"

 typedef struct {
         void *audio_dev;            //resource
         sys_audio_path_t paths_cfg; // audio paths configurations
 } context_audio_demo_t;

 context_audio_demo_t context_audio_demo = {
     .audio_dev = 0,
     .paths_cfg = {
             .audio_path[0] = {
                     .dev_in = (sys_audio_device_t*)&dev_1_in,
                     .dev_out = (sys_audio_device_t*)&dev_1_out,
             },
     },
 };

/* 1. Initialize parameters for PDM input */
 static  sys_audio_device_t dev_1_in = {
         /* Select interface as input of path 1 */
         .device_type = AUDIO_PDM,
         /* Initialize interfaces for input of path 1*/
         .pdm_param = {
                 .mode = MODE_MASTER,
                 .clk_frequency = 2000000, // Set PDM frequency in Hz
                 .in_delay = HW_PDM_DI_NO_DELAY,
                 .swap_channel = 0,
         },
 };

/* 2. Initialize parameters for I2S output */
 static sys_audio_device_t dev_1_out = {
         /* Select interface as output of path 1 */
         .device_type = AUDIO_PCM,
         /* Initialize interfaces for output of path 1*/
         .pcm_param = {
                 .bit_depth = 32, // Set I2S bit depth
                 .channel_delay = 0,
                 .clk_generation = HW_PCM_CLK_GEN_FRACTIONAL,
                 .clock = HW_PCM_CLK_DIVN,
                 .cycle_per_bit = HW_PCM_ONE_CYCLE_PER_BIT,
                 .format = I2S_MODE,
                 .fsc_delay = HW_PCM_FSC_STARTS_SYNCH_TO_MSB_BIT,
                 .fsc_length = 2,
                 .inverted_clk_polarity = HW_PCM_CLK_POLARITY_INVERTED,
                 .inverted_fsc_polarity = HW_PCM_FSC_POLARITY_NORMAL,
                 .mode = MODE_MASTER,
                 .output_mode = HW_PCM_DO_OUTPUT_PUSH_PULL,
                 .sample_rate = 48000, // Set I2S sample rate in Hz
                 .total_channel_num = 2,
         },
 };

 void audio_task()
 {
         /* 3. Open audio interfaces of audio for the required path */
         uint8_t idx_1 = sys_audio_mgr_open_path(dev_1_in, dev_1_out, SRC2); // Open path PDM to I2S

         /* 4. Enable resources of the required path. */
         sys_audio_mgr_start(idx_1); // idx_1 indicates return index of 'sys_audio_mgr_open_path'

         /* 5. Stop resources */
         sys_audio_mgr_stop(idx_1); // idx_1 indicates return index of 'sys_audio_mgr_open_path'

         /* 6. Close path */
         sys_audio_mgr_close_path(idx_1); // idx_1 indicates return index of 'sys_audio_mgr_open_path'
 }
  1. Two parallel Data Paths:
    • PDM to Memory

    • SDADC to I2S

 #include "sys_audio_mgr.h"

 typedef struct {
         void *audio_dev;            //resource
         sys_audio_path_t paths_cfg; // audio paths configurations
 } context_audio_demo_t;

 context_audio_demo_t context_audio_demo = {
     .audio_dev = 0,
     .paths_cfg = {
             .audio_path[0] = {
                     .dev_in = (sys_audio_device_t*)&dev_1_in,
                     .dev_out = (sys_audio_device_t*)&dev_1_out,
             },
             .audio_path[1] = {
                     .dev_in = (sys_audio_device_t*)&dev_2_in,
                     .dev_out = (sys_audio_device_t*)&dev_2_out,
             },
     },
 };

/* 1. Initialize parameters for PDM input */
 static  sys_audio_device_t dev_1_in = {
         .device_type = AUDIO_PDM,
         .pdm_param = {
                 .mode = MODE_MASTER,
                 .clk_frequency = 2000000, // Set PDM frequency in Hz
                 .in_delay = HW_PDM_DI_NO_DELAY,
                 .swap_channel = 0,
         },
 };

 /* 2. Initialize parameters for memory output */
 static sys_audio_device_t dev_1_out = {
         .device_type = AUDIO_MEMORY,
         .memory_param = {
                 .app_ud = 0,
                 .bit_depth = 16, // Set memory's bit depth
                 .buff_addr[0] = 0,
                 .buff_addr[1] = 0,
                 .cb_buffer_len = 1024,
                 .cb = audio_buffer_ready_cb_1,
                 .dma_channel[0] = HW_DMA_CHANNEL_2,
                 .dma_channel[1] = HW_DMA_CHANNEL_0,
                 .sample_rate = 8000, // Set memory's sample rate in Hz
                 .stereo = true,
                 .total_buffer_len = 1024 * 5,
                 .circular = false,
         },
 };

 /* 3. Initialize parameters for SDADC input of audio data path 2*/
 static sys_audio_device_t dev_2_in = {
         .device_type = AUDIO_SDADC,
         .sdadc_param = {
                 .pga_gain = HW_SDADC_PGA_GAIN_18dB,
                 .pga_mode = HW_SDADC_PGA_MODE_DIFF,
         }
 };

 /* 4. Initialize parameters for memory output of audio data path 2*/
 static sys_audio_device_t dev_2_out = {    static sys_audio_device_t dev_1_out = {
         .device_type = AUDIO_MEMORY,
         .memory_param = {
                 .app_ud = 0,
                 .bit_depth = 16, // Set memory's bit depth
                 .buff_addr[0] = 0,
                 .buff_addr[1] = 0,
                 .cb_buffer_len = 1024,
                 .cb = audio_buffer_ready_cb_2,
                 .dma_channel[0] = HW_DMA_CHANNEL_4,
                 .dma_channel[1] = HW_DMA_CHANNEL_INVALID,
                 .sample_rate = 8000, // Set memory's sample rate in Hz
                 .stereo = true,
                 .total_buffer_len = 1024 * 5,
                 .circular = false,
         },
 };


 void audio_task()
 {
         for (uint8_t i = 0 ; i < (dev_1_out.memory_param.stereo ? 2 : 1); i++) {

                 if (dev_1_out.memory_param.dma_channel[i] != HW_DMA_CHANNEL_INVALID) {
                         if (size > OS_GET_FREE_HEAP_SIZE()) {
                                 dev_1_out.memory_param.buff_addr[i] = 0;
                                 return;
                         }
                         dev_1_out.memory_param.buff_addr[i] = (uint32_t)OS_MALLOC(size);

                         if (!dev_1_out.memory_param.buff_addr[i]) {
                                 ASSERT_ERROR(0);
                                 return;
                         }
                 }
         }

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

                  if (dev_2_out.memory_param.dma_channel[i] != HW_DMA_CHANNEL_INVALID) {
                          if (size > OS_GET_FREE_HEAP_SIZE()) {
                                  dev_2_out.memory_param.buff_addr[i] = 0;
                                  return;
                          }
                          dev_2_out.memory_param.buff_addr[i] = (uint32_t)OS_MALLOC(size);

                          if (!dev_2_out.memory_param.buff_addr[i]) {
                                  ASSERT_ERROR(0);
                                  return;
                          }
                  }
          }
         /* 5. Open audio interfaces of audio for the required path */
         uint8_t idx_1 = sys_audio_mgr_open_path(dev_1_in,  dev_1_out, SRC2); // Open path 1 - PDM to Memory
         uint8_t idx_2 = sys_audio_mgr_open_path(dev_2_in,  dev_2_out, NULL); // Open path 2 - SDADC to I2S

         /* 6. Enable resources of the required paths. */
         sys_audio_mgr_start(idx_1); // idx_1 indicates return index of 'sys_audio_mgr_open_path' for path 1
         sys_audio_mgr_start(idx_2); // idx_2 indicates return index of 'sys_audio_mgr_open_path' for path 2

         /* 7. Stop resources */
         sys_audio_mgr_stop(idx_1); // idx_1 indicates return index of 'sys_audio_mgr_open_path' for path 1
         sys_audio_mgr_stop(idx_2); // idx_2 indicates return index of 'sys_audio_mgr_open_path' for path 2

         for (uint8_t i = 0 ; i < (dev_1_out.memory_param.stereo ? 2 : 1); i++) {
             if (dev_1_out.memory_param.buff_addr[i]) {
                         OS_FREE((void *)dev_1_out.memory_param.buff_addr[i]);
                 }
         }

         for (uint8_t i = 0 ; i < (dev_2_out.memory_param.stereo ? 2 : 1); i++) {
             if (dev_2_out.memory_param.buff_addr[i]) {
                         OS_FREE((void *)dev_2_out.memory_param.buff_addr[i]);
                 }
         }

         /* 8. Close paths */
         sys_audio_mgr_close_path(idx_1); // idx_1 indicates return index of 'sys_audio_mgr_open_path' for path 1
         sys_audio_mgr_close_path(idx_2); // idx_2 indicates return index of 'sys_audio_mgr_open_path' for path 2
 }
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/audio_demo folder.

4.1.7. Background Flash Operations

4.1.7.1. Introduction

The Background Flash Operations API provides a transparent mechanism, which enables any RTOS application to write and erase blocks in XiP flash memory while the application is executed. When an application needs to write/erase the XiP flash memory, it actually has to perform two actions, which cannot be performed simultaneously: (a) to fetch cache lines from XiP flash to cache memory, when cache misses occur due to the read accesses triggered by the instruction cache controller (eXecute in Place), and (b) the write/erase flash operations requested by the application to update the contents of the flash memory. The first action has always higher priority, therefore the flash operations are exclusively executed by the Idle Task.

4.1.7.2. Build Options and Limitations

The Background Flash Operations are enabled by default for all RTOS applications unless explicitily disabled at application level by defining the dg_configUSE_SYS_BACKGROUND_FLASH_OPS to 0. If disabled, the ad_flash adapter instead of registering write and erase flash operation to be executed when the system becomes available, i.e. in the idle task, it will execute them immediately calling the oqspi_automode_write_flash_page() and oqspi_automode_erase_flash_sector(), for write and erase operations respectively. This might cause issues to critical functions, such as BLE events timeout, thus it is user’s responsibility to prevent malfunctions due to timing issues, racing conditions etc.

Warning

The Background Flash Operations cannot be used in bare metal projects.

4.1.7.3. Overview

A high level overview of Background Flash Operations API is depicted in Figure 29.

../_images/background_flash_operations_overview.png

Figure 29 Background Flash Operations Overview

The Background Flash Operations are initialized at application’s startup by system_init task. After that, any task can request an erase or write flash operation calling the corresponding functions of the ad_nvms or ad_flash. Each request registers a new Background Flash operation, which is added in a FIFO list. The Idle Task is responsible for performing the flash operations, thus any other task is executed with higher priority. This prevents critical functions from being blocked by long flash operations. While an operation is being performed, the global interrupts are disabled to prevent potential cache misses in case an interrupt is fired. However, the API polls continuously the corresponding NVIC flags to detect whether an IRQ has been fired. If so, the current operation is suspended, the IRQ is served and afterwards the operation is resumed and completed. As long as there are pending operations, the system is not allowed to go to sleep.

4.1.8. 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:

  1. Download the latest J-Link version from SEGGER official web site (https://www.segger.com/downloads/jlink/#J-LinkSoftwareAndDocumentationPack).

  2. Accept the terms and install J-Link in your host machine.

  3. In DIALOG’S eclipse-based IDE SmartSnippets Studio go to Windows->Preferences->MCU->Workspace SEGGER J-Link path.

  4. Set the path of the installed J-Link version (e.g C:/Program Files (x86)/SEGGER/JLink).

  5. In your host machine launch the J-Link RTT Client application.

  6. Back to eclipse, add the following preprocessor definition: #define CONFIG_RTT in the custom header files of your project, e.g. config/custom_config_ram.h.

  7. Add the RTT folder (e.g sdk/middleware/segger_tools) as Linked Folder in the project.

  8. Add the following include paths in compiler settings (go to Properties for freertos_retarget->Settings->Cross ARM Compiler->Includes):

    “${workspace_loc:/${ProjName}/sdk/segger_tools/SEGGER}”

    “${workspace_loc:/${ProjName}/sdk/segger_tools/Config}”

    “${workspace_loc:/${ProjName}/sdk/segger_tools/OS}”

  9. Execute your preferred build configuration (e.g “DA1470x-00-Debug_RAM”).

  10. Go to Run->Debug Configurations and launch the debugger.

  11. Execute your application through debugger.

  12. The logging data will be printed in the console window of the jLink RTT client application.

  1. CONFIG_NO_PRINT

In this mode nothing is logged and in fact the printf() function is overridden by an empty stub function.

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 30. 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 30 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 31.

../_images/030.png

Figure 31 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 23

Table 23 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_i2c.h

I2C device access API.

ad_i3c.h

I3C 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_spi.h

SPI adapter API.

ad_uart.h

UART adapter API.

4.2.2. Extending adapters depending on IO configuration

The adapter concept has been extended for the following blocks:

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

  • The analog to digital converter GPADC.

  • The LCD controller.

Table 24 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

The Low Level Driver associated with the controller.

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. dg_configUART_ADAPTER and dg_configUSE_HW_UART definition

    To enable the UART adapter, both dg_configUART_ADAPTER and dg_configUSE_HW_UART 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 11 Enabling UART Adapter
#define dg_configUART_ADAPTER        (1)

#define dg_configUSE_HW_UART         (1)
  1. Create a UART controller configuration ad_uart_controller_conf_t.

Code 12 The UART controller configuration
/**
 * \brief UART controller configuration
 *
 * Configuration of UART controller
 *
 */
 typedef struct {
           const HW_UART_ID id;                    /** Pointer to the UART id */
           const ad_uart_io_conf_t *io;            /** Pointer to the pin configuration */
           const ad_uart_driver_conf_t *drv;       /** Pointer to the low level driver configuration */
 } ad_uart_controller_conf_t;
Code 13 The UART controller parameter configuration
/**
 * \brief UART I/O configuration
 *
 * UART I/O configuration
 */
typedef struct {
        ad_io_conf_t rx;                /** Rx pin configuration */
        ad_io_conf_t tx;                /** Tx pin configuration */
        ad_io_conf_t rtsn;              /** RTS pin configuration */
        ad_io_conf_t ctsn;              /** CTS pin configuration */
        HW_GPIO_POWER voltage_level;    /** Pin voltage level */
 } ad_uart_io_conf_t;

This is an example of a UART controller configuration:

Code 14 Example of a UART controller 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,
};

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,
     .io = &sys_platform_console_io_conf,
     .drv = &sys_platform_console_uart_driver_conf,
};

User code

  1. Open the UART controller

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

Code 15 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. Initialize UART controller pins

    Called by the application for configuring the controller interface before powering up the external connected devices (e.g sensors)

Code 16 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. 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 17 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 18 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);

Note

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

  • 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 19 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 20 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 21.

Code 21 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 22 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 adapter level for serial interfaces and GPADC used to enable synchronous (blocking) and asynchronous (non-blocking) APIs for read/write operations and resource locking. These macros are used to optimize adapters code.

Note

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

Enable synchronous transactions

The following macros are used in order to enable synchronous transaction APIs.

Table 25 Adapter’s configuration macros for blocking operations

Adapter

Configuration macros for blocking operations

UART

CONFIG_UART_USE_SYNC_TRANSACTIONS

I2C

CONFIG_I2C_USE_SYNC_TRANSACTIONS

I3C

CONFIG_I3C_USE_SYNC_TRANSACTIONS

SPI

CONFIG_SPI_USE_SYNC_TRANSACTIONS

GPADC

CONFIG_GPADC_USE_SYNC_TRANSACTIONS

Note

These macros are defined to 1 in the respective header file. So blocking operations are enabled by default.

Warning

Synchronous transactions API is not supported when Dialog CoRoutines is used.

Code 23 UART synchronous transactions macro derived from respective ad_uart.h file
/**
 * \def CONFIG_UART_USE_SYNC_TRANSACTIONS
 *
 * \brief Controls whether UART synchronous transaction API will be used
 *
 * UART synchronous transaction API maintains state in retention RAM for every UART bus declared.
 * If the API is not to be used, setting this macro to 0 will save retention RAM.
 */
#ifndef CONFIG_UART_USE_SYNC_TRANSACTIONS
#define CONFIG_UART_USE_SYNC_TRANSACTIONS       (1)
#endif

Enable asynchronous transactions

The following macros are used in order to enable asynchronous transaction APIs.

Table 26 Adapter’s configuration macros for non-blocking operations

Adapter

Configuration macros for non-blocking operations

UART

CONFIG_UART_USE_ASYNC_TRANSACTIONS

I2C

CONFIG_I2C_USE_ASYNC_TRANSACTIONS

I3C

CONFIG_I3C_USE_ASYNC_TRANSACTIONS

SPI

CONFIG_SPI_USE_ASYNC_TRANSACTIONS

GPADC

CONFIG_GPADC_USE_ASYNC_TRANSACTIONS

Note

These macros are defined to 1 in the respective header file. So non-blocking operations are enabled by default.

Code 24 UART asynchronous transactions macro derived from respective ad_uart.h file
/**
 * \def CONFIG_UART_USE_ASYNC_TRANSACTIONS
 *
 * \brief Controls whether UART asynchronous transaction API will be used
 *
 */
#ifndef CONFIG_UART_USE_ASYNC_TRANSACTIONS
#define CONFIG_UART_USE_ASYNC_TRANSACTIONS      (1)
#endif

Enable resource locking

The following macros are used in order to enable resource locking. By default, the adapter internally handles concurrent accesses by different masters and tasks.

Table 28 Adapter’s configuration macros for resource locking

Adapter

Configuration macros for resource locking

UART

CONFIG_AD_UART_LOCKING

I2C

CONFIG_AD_I2C_LOCKING

I3C

CONFIG_AD_I3C_LOCKING

SPI

CONFIG_AD_SPI_LOCKING

GPADC

CONFIG_AD_GPADC_LOCKING

Code 26 UART resource locking macro derived from respective ad_uart.c file
/**
 * \def CONFIG_AD_UART_LOCKING
 *
 * \brief Controls whether UART adapter resource locking will be enabled
 *
 * By default, the UART adapter internally handles concurrent accesses to a UART controller by different masters
 * and tasks. If resource locking is disabled by setting this macro to 0, all such internal handling
 * is disabled, thus becoming the application's responsibility to handle concurrent accesses, using
 * the busy status register (BSR) driver and controlling the resource management.
 */
#ifndef CONFIG_AD_UART_LOCKING
# define CONFIG_AD_UART_LOCKING                 ( 1 )
#endif /* CONFIG_AD_UART_LOCKING */

Note

These macros are defined to 1 in the respective source file. So resource locking is enabled by default.

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. Introduction

This section provides information regarding the Non Volatile Memory Storage (NVMS) Adapter, which is responsible for accessing data from several external memory resources. The NVMS Adapter is designed to provide NVMS access capabilities in order to simplify the process of reading, erasing and writing external non volatile flash memories. It enables users to access the external flash memories connected to OQSPIC, QSPIC and QSPIC2 without having to deal with the low level drivers.

4.2.6.2. Architecture overview

The software architecture for accessing the external flash memories in SmartSnippets™ DA1470x SDK follows the multilayer approach depicted in Figure 36. The NVMS Adapter is the highest layer of this architecture.

../_images/Architecture_overview_NVMS_Adapter.png

Figure 36 Architecture overview of NVMS Adapter

This architecture consists of the layers shown in Table 30

Table 30 External Flash Memories API Layers

Layer

Description

ad_nvms

Thread safe API for handling NVMS partition.

ad_flash

Thread safe API for accessing the external flash memories.

(o)qspi_automode

API for accessing the external flash/PSRAM memories.

hw_(o)qspi

Low level drivers for handling the OQSPIC, QSPIC and QSPIC2.

4.2.6.3. API Description

4.2.6.4. Addressing schemes

There are three types of addressing schemes used by the aforementioned APIs, the relative, the virtual and the physical address. The relative address is the address of the data with respect to the start address of a partition. It is used exclusively by the NVMS Adapter (ad_nvms) to provide a more convenient user interface, i.e. tο prevent the user from dealing with virtual/physical addresses. The physical address is needed by the flash memory controllers (OQSPIC, QSPIC, QSPIC2) to access the data and is exclusively used by the Low Level Drivers (hw_oqspi, hw_qspi).

The virtual address is an addressing schema, where the accessible areas of the external memory controllers are placed one after the other starting from 0x0000000. The maximum accessible area for each controller is 0x8000000, which allows to access memories up to 128MB. However, the NVMS Adapter is also limited by the size of the external flash memories. The Table 31 depicts the NVMS Virtual Address Map of the DA1470X.

Table 31 NVMS Virtual Address Map

Memory controller

Flash memory

Virtual Base Address

Virtual End Address

OQSPIC

External XiP flash memory.

0x00000000

0x07FFFFFF

QSPIC

External storage flash memory.

0x08000000

0x15FFFFFF

QSPIC2

External storage flash2 memory.

0x16000000

0x1DFFFFFF

Note

The QSPIC2 is mainly used to access a PSRAM memory due to the data cache contoller (dCache). In this case, the NVMS Adapter is out of scope, since the PSRAM can be accessed transparently by simply reading from or writing to the corresponding addresses, provided that the QSPIC2 and the dCache have been properly configured at system startup. Nevertheless, connecting a secondary external storage flash memory to QSPIC2 is possible as well, so the NVMS Adapter could be involved in order to access the partitions that reside on it.

All API layers between the NVMS Adapter and the Low Level Drivers make use of the virtual address.This enables them to handle all external flash memories as a unified memory space. The ad_nvms translates the Relative Address to Virtual Address, while the (o)qspi_automode the Virtual Address to Physical Address (see Figure 36).

Table 32 Addressing schemes

Addressing schemes

API

Description

Relative address

ad_nvms

The address of the data with respect to the start address of a partition.

Virtual address

ad_flash, oqspi_automode, qspi_automode

The address of the unified memory space, where the accessible areas of the external flash memories are placed one after the other staring from 0x00.

Physical address

hw_oqspi, hw_qspi

The physical address used by the flash memory controllers (OQSPIC, QSPIC, QSPIC2) to access the data of the external flash memories.

4.2.6.5. Operation modes

The NVMS Adapter supports two modes of operation, the Direct Access mode and the Virtual EEPROM mode.

Direct Access Mode

When the Direct Access Mode is used, the data are written to the requested physical address. The API enables accessing the flash memories in such a way to prevent all redundant erase and write cycles, in order to increase the lifetime of the memories. More specifically, it follows the next steps:

  • Checks whether the data in the destination address are equal to the data in the source address. If so, both erase and write operations are omitted.

  • If not, it checks whether the source data can be written to the destination address without erasing the respective sectors. This is possible, if none of the cleared bits of the destination address need to be set (0s –> 1s). Otherwise, the erase operation is mandatory. Based on this criterion, the erase operation is performed or omitted.

  • The source data are written to the destination address.

Direct Access Mode does not provide power failure protection. If a write operation requires a sector erase operation before, and a power failure takes place during this erase cycle all data contained in this sector will be lost. Hence, if a power safe operation is needed, the Virtual EEPROM Mode must be used.

Virtual EEPROM Mode

The Virtual EEPROM (VES) Mode provides a more sophisticated way to access a flash memory by incorporating the following mechanisms:

  • Wear leveling

Wear leveling mechanism allows the flash memory to evenly distribute the write and erase cycles among all memory blocks. It prevents the premature wear-out of overused blocks, so all blocks can be used to the maximum. Wear leveling extends the life span and improves the reliability and durability of the storage device. However, it comes with the penalty that the available memory size becomes a fraction of the corresponding physical memory block.

This is accomplished by translating the user specified address to different physical addresses every time the contents of a specific location is modified. Thus, another virtual addressing mechanism is involved that associates the address provided by the user with a physical address, which changes every time these data are updated. This virtual addressing mechanism should not be confused with the one mentioned in Table 31, since it concerns exclusively the internal handling of a VES partition.

The Figure 37 depicts the structure of a partition in VES mode. Every partition consists of several sectors depending on its size. In VES mode the flash sectors are divided into a number of containers, and each container holds the data for a range of virtual EEPROM addresses. Every container has a header, which keeps its status (whether it is valid/dirty or not) and a 14 bits index, which is used to translate the relative address to virtual. An optional CRC code field is also contained. The CAT maps all data entries onto the corresponding containers and sectors. The CAT is initialized at system startup by extracting all needed information from containers’ index. The smaller the container size is, the more erase cycles per sector are needed.

../_images/VES_Storage_Model.png

Figure 37 VES Storage Model

  • Garbage Collection

Garbage collection is another method of improving the functional life and write performance of a flash memory. The memory cells of a flash memory are made up of sectors, which are comprised of several pages. Although individual pages can be updated with a write operation, a whole sector is the smaller block of data that can be erased by an erase operation. This means that small data updates waste erase cycles for the unused pages in a sector.

During garbage collection, all of the pages being written to a sector are moved to a new sector and the unchanged pages in the previous sector are erased. This completely frees up the previous sector for use in wear leveling.

  • Power failure protection

VES Mode provides access to the partition with power failure protection. If power fails during a write operation, the specific data being written can be lost but other data will not be affected. This is achieved by the wear leveling mechanism described above.

4.2.6.6. Build Configuration

To enable the NVMS adapter both macros depicted in Code 31 must be defined and set.

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

On top of that, if even one partition uses Virtual EEPROM mode, the dg_configNVMS_VES must be defined and set as well.

4.2.6.7. NVMS Partition Table

The Code 32 shows the default NVMS partition table for an application with SUOTA support.

Code 32 NVMS Partition Table
#define NVMS_PRODUCT_HEADER_PART_START  (OQSPI_MEM1_VIRTUAL_BASE_ADDR + 0x00000000)
#define NVMS_PRODUCT_HEADER_PART_SIZE   (0x00002000)    /* Enough to hold primary and backup Product Headers. */

#define NVMS_PARTITION_TABLE_START      (OQSPI_MEM1_VIRTUAL_BASE_ADDR + 0x00002000)
#define NVMS_PARTITION_TABLE_SIZE       (0x00001000)    /* Recommended location, follows the Product Headers. */

#define NVMS_FW_EXEC_PART_START         (OQSPI_MEM1_VIRTUAL_BASE_ADDR + 0x00003000)
#define NVMS_FW_EXEC_PART_SIZE          (0x0037D000)    /* Image firmware max size ~ 3.5MB */

/* +----------------3.5MB---------------------+ */

#define NVMS_GENERIC_PART_START         (OQSPI_MEM1_VIRTUAL_BASE_ADDR + 0x00380000)
#define NVMS_GENERIC_PART_SIZE          (0x00080000)

/* +------------------4MB---------------------+ */

#define NVMS_FW_UPDATE_PART_START       (OQSPI_MEM1_VIRTUAL_BASE_ADDR + 0x00400000)
#define NVMS_FW_UPDATE_PART_SIZE        (0x00380000)

/* +----------------7.5MB---------------------+ */

#define NVMS_LOG_PART_START             (OQSPI_MEM1_VIRTUAL_BASE_ADDR + 0x00780000)
#define NVMS_LOG_PART_SIZE              (0x00040000)

#define NVMS_BIN_PART_START             (OQSPI_MEM1_VIRTUAL_BASE_ADDR + 0x007C0000)
#define NVMS_BIN_PART_SIZE              (0x0003F000)

#define NVMS_PARAM_PART_START           (OQSPI_MEM1_VIRTUAL_BASE_ADDR + 0x007FF000)
#define NVMS_PARAM_PART_SIZE            (0x00001000)    /* Recommended location, last sector of the flash device. */

/* +------------------8MB---------------------+ */

PARTITION2( NVMS_PRODUCT_HEADER_PART  , 0 )                             /* Mandatory partition - Do not relocate - Do not resize */
PARTITION2( NVMS_PARTITION_TABLE      , PARTITION_FLAG_READ_ONLY )      /* Mandatory partition - Relocate or resize at your own risk! */
PARTITION2( NVMS_FW_EXEC_PART         , 0 )                             /* Mandatory partition - Do not relocate */
PARTITION2( NVMS_GENERIC_PART         , PARTITION_FLAG_VES )            /* Optional - Suggestive position, size and flags */

PARTITION2( NVMS_FW_UPDATE_PART       , 0 )                             /* Mandatory partition - Do not relocate */
PARTITION2( NVMS_LOG_PART             , 0 )                             /* Optional - Suggestive position, size and flags */
PARTITION2( NVMS_BIN_PART             , 0 )                             /* Optional - Suggestive position, size and flags */
PARTITION2( NVMS_PARAM_PART           , 0 )                             /* Mandatory partition for NVMS parameter feature - Place at the last flash sector */

For each partition, the macro PARTITION2(id, flags) with a unique id and the appropriate flags must be instantiated. The id can take one of the values depicted in Code 33, whereas the flags one of the values of the Table 33.

Code 33 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;
Table 33 NVMS partition flags

NVMS partition flags

Description

0

Direct mode partition with read/write access

PARTITION_FLAG_READ_ONLY

Direct mode partition with read only access

PARTITION_FLAG_VES

Virtual EEPROM mode partition

Along with the definition of PARTITION2(id, flags), the corresponding id_START and id_SIZE must be defined to determine the virtual start address and the size of the partition respectively.

4.2.6.8. Code Example

The Table 34 lists the essential functions of the NVMS Adapter to accessan external flash memory:

Table 34 NVMS Adapter essential functions

NVMS Adapter essential functions

Description

ad_nvms_init()

Initialize NVMS adapter

ad_nvms_open()

Open partition to perform a read/write access

ad_nvms_read()

Read partition data

ad_nvms_write()

Writes data to partition

ad_nvms_erase_region()

Erases partition region

The ad_nvms_init() is called at application startup to perform all necessary initialization actions, including discovering the underlying memory partitions. Actually, it is called by pm_system_init(), which initilalizes the system after a power up cycle. Before accessing a partition the ad_nvms_open() must be called to get its handler. In turns, the ad_nvms_read(), ad_nvms_write() and ad_nvms_erase_region() are used to perform read, write and erase operations respectively. As already mentioned, the NVMS adapter uses the relative addressing scheme. Thus, the aforementioned functions are limited to access a single partition. If another partition needs to be accessed, the aforementioned sequence must be repeated. The Code 34 shows a typical example of using the NVMS Adapter:

Code 34 Usage of NVMS Adapter
nvms_t partition = ad_nvms_open(NVMS_GENERIC_PART);

for (;;) {

   ad_nvms_read(partition, addr, buf, sizeof(buf));
   ad_nvms_write(partition, addr, buf, sizeof(buf));
}

4.2.6.9. eXecute in Place and NVMS Adapter co-existence

When the firmware is eXecuted in Place (XiP) from the flash memory, the memory contains the FW image partition(s), as well as other partitions with application data, which need to be updated at runtime. Moreover, in order for the code execution to be accelerated, the instruction cache controller is enabled.

There are two actions that need to be served regarding the XiP flash memory: (a) The read accesses triggered by the instruction cache controller (iCache) to fetch cache lines from XiP flash to cache memory, whenever cache misses occur, and (b) the erase and program flash operations to update the application’s data partitions. These actions cannot be performed simultaneously due to limitations of the flash memory itself. To handle the co-existence of them effectively, theSmartSnippets™ DA1470x SDK applies the following principles:

  • Slices the program operations to small chunks of data.

  • Makes use of the erase suspend/resume mechanism of the flash memory.

The Figure 38 represents how executing in place co-exists with the NVMS Adapter.

../_images/Xip_cached_vs_NVMS_Adapter.png

Figure 38 XiP in cached mode and NVMS Adapter co-existence

Note

Preemptive RTOS scheduling remains operational during erase and program operations.

4.2.6.10. Slice program operations

When writing a data block to flash memory, the NVMS Adapter requests multiple uninterruptible program operations by slicing the block into smaller chuncks of data. During these program operations the global interrupts are disabled to prevent cache misses, which would trigger the cache controller fetching cache lines from memory. In order to avoid serving critical events with significant delay the size of the chunks must be small, so that the global interrupts remain disabled for a short time. This time should not exceed the maximum interrupt latency the application can tolerate. Thus, the chunk size is determined by the maximum acceptable latency and the time it takes to perform a program operation of specific amount of data, which depends on the memory’s specifications. The default chunk size is 128 bytes, however, the user can re-configure it by re-defining the dg_configOQSPI_FLASH_MAX_WRITE_SIZE.

4.2.6.11. Erase suspend/resume operation

As already mentioned in another section, the smaller block of data that can be erased by an erase operation is a flash sector (4KBytes), which takes too long. For this reason, slicing the erase operations in chunks is not realistic, because it would violate the maximum acceptable latency of the application. Therefore, the SmartSnippets™ DA1470x SDK leverages from the erase suspend/resume mechanism depicted in Figure 39.

../_images/Erase_suspend_and_resume_operation.png

Figure 39 Erase suspend/resume operation

When the application requests to erase a flash section, the NVMS Adapter translates it to the corresponding amount of erase sector requests depending on the size of the section. In turns, the OQSPIC sends the erase sector command to the flash memory and the erase operation starts. While the memory is being erased, an interrupt is fired and the eXecute in Place request to the instruction cache controller is triggered. If a cache miss occurs, the cache controller fetches the cache lines from the memory causing a read operation. Since the erase operation is ongoing, the OQSPIC sends an erase suspend command in order to first serve the read operation. The erase operation is suspeded and the read access is performed. When completed, the OQSPIC sends an erase resume command and the erase operation is resumed and completed.