8. DA14531 Hibernate Mode

The DA14531 supports a number of lower power modes; sleep, extended sleep, deep sleep and hibernate. Hibernate mode provides the lowest power operation as no internal clocks are running and the RAM is not retained (although there is an option to retain it if required).

The DA14531 contains a Clockless Wakeup Controller which allows the device to be woken from hibernate mode by a change of state on a GPIO. The following section details how to use the driver provided within SDK6 to put the DA14531 into Hibernate mode and how to handle wakeup events.

See the DA14531 Datasheet for a complete description of the Clockless Wakeup Controller and all of the available sleep modes.

Note

You must have created a modified version of the empty_peripheral_template example project, as described in the Initial Project chapter, before proceeding!

8.1. Hardware Setup

When operating in Hibernate mode the DA14531 can only be woken by a change of state on pins P0_1, P0_2, P0_3, P0_4 or P0_5. Pins P0_1, P0_3 and P0_4 are used to connect to the external SPI flash in which our application is stored. Pin P0_2 is used as the Serial Debug Clock signal (SWCLK) and therefore we will use P0_5 to wake the device. However, P0_5 is also used as the UART transmit signal (which is used by our application to output serial debug information) and so we will move the UART TX to P0_6. To do this remove the URX, UTX and RxTx jumpers on J1 and connect a jumper wire from pin 17 on J1 to pin 27 on J2 (which is labelled as P26 on the silkscreen). Finally, the jumper connecting pins 2 and 3 on J19 should be removed and, a wire should be connected from pin 26 on J2 to pin 2 on J19 (this will connect P0_5 to button SW2). This hardware setup is summarized in the following diagram.

_images/da14531_pro_tx.svg

Fig. 10 Hibernate mode hardware setup

8.2. Shipping Mode

Hibernate mode is frequently used to implement shipping mode in products where the battery is permanently connected to the DA14531 (i.e. products that do not contain an on/off switch that physically disconnects the battery from the DA14531).

There are a number of ways to implement a shipping mode with the DA14531. In this example, once factory programming and testing of the DA14531 has been completed, we want it to enter shipping (hibernate) mode. One way to achieve this is to reset the device using a hardware or power-on reset. The application firmware can then detect the cause of the reset and put the device into hibernate mode. When pin P0_5 is then pulled low (by pressing SW2) the device will wake from shipping mode and operate as normal (start advertising etc.).

The following section describes how to modify the empty_peripheral_template example project to implement such a low power shipping mode.

Note

To achieve the lowest possible power consumption, RAM will not be retained and the application will be programmed into external SPI flash.

8.2.1. UART Transmit Output

We will use serial debug output to confirm the example is working. However, as described above, we will use P0_6 instead of P0_5 as the UART output and therefore must change the UART2_TX_PIN definition found in the user_periph_setup.h file to the following:

#define UART2_TX_PIN            GPIO_PIN_6

8.2.2. SPI Flash Power Down

The application firmware that implements shipping mode is stored in external SPI flash and loaded into internal RAM by the ROM based bootloader. Once this process is complete we must put the flash into power down mode to achieve the lowest possible system current consumption. To do this, add the following function to the user_empty_peripheral_template.c before the user_app_on_init function:

static void flash_power_down(void)
{
    /* Default SPI configuration */
    static const spi_cfg_t spi_cfg = {
        .spi_ms = SPI_MS_MODE_MASTER,
        .spi_cp = SPI_CP_MODE_0,
        .spi_speed = SPI_SPEED_MODE_4MHz,
        .spi_wsz = SPI_MODE_8BIT,
        .spi_cs = SPI_CS_0,
        .cs_pad.port = SPI_EN_PORT,
        .cs_pad.pin = SPI_EN_PIN,
        .spi_capture = SPI_MASTER_EDGE_CAPTURE,
    };

    /* Initialize interface to SPI flash */
    spi_initialize(&spi_cfg);

    /* Disable HW RST on P0_0 so it can be used as SPI MOSI */
    GPIO_Disable_HW_Reset();
    spi_flash_power_down();

    /* Re-enable HW reset input (must be disabled if/when further operations on
       external flash are performed) - must set as input first! */
    GPIO_ConfigurePin(SPI_DO_PORT, SPI_DO_PIN, INPUT_PULLDOWN, PID_GPIO, false);
    GPIO_Enable_HW_Reset();
}

The following include files must also be added to the user_empty_peripheral_template.c file:

#include "spi.h"
#include "spi_flash.h"
#include "user_periph_setup.h"
#include "arch_hibernation.h"

8.2.3. Entering Ship Mode

Now add the following enter_ship_mode function to the start of the user_empty_peripheral_template.c file, before the user_on_init function:

static void enter_ship_mode(void)
{
    arch_printf("\n\r%s", __FUNCTION__);

    /* Ensure all debug output is complete before entering ship mode */
    arch_printf_process();
    while(GetBits32(UART2_USR_REG, UART_BUSY) != 0);

    /* Put device into hibernation mode with no RAM retained (cold boot on wakeup)
       function DOES NOT RETURN */
    arch_set_hibernation(0x20, /* P0_5 */
                         PD_SYS_DOWN_RAM_OFF,
                         PD_SYS_DOWN_RAM_OFF,
                         PD_SYS_DOWN_RAM_OFF,
                         REMAP_ADDR0_TO_ROM,
                         false);
}

8.2.4. SW2 GPIO Configuration

The GPIO (P0_5) that we connected to SW2 must be configured as an input with the internal pull-up enabled (pressing SW2 will connect P0_5 to ground). To do this, we must first add the following definitions to the user_periph_setup.h file:

#define SW2_PORT                    GPIO_PORT_0
#define SW2_PIN                     GPIO_PIN_5

Next we will reserve P0_5 by adding the following to the GPIO_reservations function that can be found in the user_periph_setup.c file:

RESERVE_GPIO(SW2, SW2_PORT, SW2_PIN, PID_GPIO);

Finally, we will configure P0_5 to act as an input with pull-up enabled by adding the following to the set_pad_functions function that can also be found in the user_periph_setup.c file:

GPIO_ConfigurePin(SW2_PORT, SW2_PIN, INPUT_PULLUP, PID_GPIO, false);

8.2.5. Detecting Reset Status

We are not retaining the contents of RAM and so the DA14531 will perform a full boot cycle following a reset (hardware or power-on) or a wake from hibernate mode. Because of this we need to be able to detect why the device booted when the application starts running (if due to a reset we can enter hibernate mode, if not then it must be a wake event and we can start advertising).

The reason for booting can be determined using the reset status provided by the SDK. To do this we must first add a variable in which the reset status can be stored to the user_empty_peripheral_template.c file as follows:

static uint16_t reset_status __SECTION_ZERO("retention_mem_area0");

Next we must implement a callback that will be called by the SDK when the device boots and will store the reset status in the variable we declared above. To do this add the following function to the user_empty_peripheral_template.c file:

void reset_indication(uint16_t reset_stat)
{
    reset_status = reset_stat;
}

8.2.6. Putting it all Together

When the application firmware starts executing, we will first put the flash into power down mode to ensure system level current consumption is at a minimum. We will then read the reset_status variable to determine if a reset or wake from hibernate has occured. To do this add the following code to the user_app_on_init function in the file user_empty_peripheral_template.c, just before the default_app_on_init function call:

arch_printf("\n\rreset_status: 0x%04x", reset_status);

/* Boot from flash complete so put it into lowest power mode */
flash_power_down();

if ((reset_status & PORESET_STAT) || (reset_status & HWRESET_STAT)) {
    /* Following function does not return */
    enter_ship_mode();
}

8.2.7. Testing

When it comes to testing the ship mode example it first has to be built and then programmed into flash using the procedure detail in the Flash Programming section of the Getting Started with SDK6 Tutorial.

Once the flash has been programmed configure your Terminal Emulator as follows:

  • Baud Rate: 115200

  • Data: 8 bits

  • Parity: None

  • Stop Bits: 1

  • Flow Control: None

Now when the reset button (SW1) on the PRO development kit board is pressed you should see the following displayed by your Terminal Emulator:

_images/hib_enter_ship.png

Fig. 11 Serial debug output on entering ship mode

The DA14531 is now in ship (or hibernate) mode and is consuming very little current. Pressing SW2 on the PRO development kit will wake the device and cause it to start advertising. When this happens you will see the following displayed by your Terminal Emulator:

_images/hib_wake.png

Fig. 12 Serial debug output on exiting ship mode

Pressing the reset button (SW1) again will return the device to ship (hibernate) mode.

Note

You can also confirm the example is working by monitoring the current consumption of the DA14531 using the Power Profiler found in the SmartSnippets™ Toolbox.

8.3. Retaining Data

It is possible to retain application data while the device is in hibernate mode. To do this, the RAM must be retained and the data to be stored must be placed in the correct memory section (so that it is not initialized when the device wakes).

To do this, add a variable to the user_empty_peripheral_template.c file that will be used to store the data we want to retain while the device is in hibernate mode:

static uint32_t retained_data __SECTION("retention_mem_area_uninit");

Now add the following to the start of the user_app_on_init function so we can observe that the data is retained correctly:

arch_printf("\n\rretained_data: 0x%08x", retained_data);

Next we need to set the value of the retained_data variable when the device is reset, but not when it wakes from hibernate mode. To do this, we initialize the variable inside the if statement contained within the user_app_on_init function as follows:

if ((reset_status & PORESET_STAT) || (reset_status & HWRESET_STAT)) {
    retained_data = 0x1BADCAFE;
    /* Following function does not return */
    enter_ship_mode();
}

We need to ensure the contents of RAM are retained while the device is in hibernation mode. To do this, we change the parameters passed to the arch_set_hibernation function (that is called from the enter_ship_mode function) as follows:

arch_set_hibernation(0x20, /* P0_5 */
                     PD_SYS_DOWN_RAM_ON,
                     PD_SYS_DOWN_RAM_ON,
                     PD_SYS_DOWN_RAM_ON,
                     REMAP_ADDR0_TO_RAM1,
                     false);

Because we are retaining RAM, the ROM based bootloader no longer needs to load the application when the devices wakes from hibernation mode. This means we now only need to put the flash into sleep mode when the device is reset, and not when it wakes from hibernate mode. To implement this change simply move the call to the flash_power_down function that is found within user_on_init so that it is inside the if statement.

Once all of these changes have been implemented your user_on_init and enter_ship_mode functions should be as follows:

void user_on_init(void)
{
    arch_printf("\n\r%s", __FUNCTION__);
    arch_printf("\n\rreset_status: 0x%04x", reset_status);
    arch_printf("\n\rretained_data: 0x%08x", retained_data);

    if ((reset_status & PORESET_STAT) || (reset_status & HWRESET_STAT)) {
        retained_data = 0x1BADCAFE;
        flash_power_down();

        /* Following function does not return */
        enter_ship_mode();
    }
    default_app_on_init();
}

static void enter_ship_mode(void)
{
    arch_printf("\n\r%s", __FUNCTION__);

    /* Ensure all debug output is complete before entering ship mode */
    arch_printf_process();
    while(GetBits32(UART2_USR_REG, UART_BUSY) != 0);

    /* Put device into hibernation mode with RAM retained
       function DOES NOT RETURN */
    arch_set_hibernation(0x20, /* P0_5 */
                        PD_SYS_DOWN_RAM_ON,
                        PD_SYS_DOWN_RAM_ON,
                        PD_SYS_DOWN_RAM_ON,
                        REMAP_ADDR0_TO_RAM1,
                        false);
}

To test these changes the application must be compiled and then programmed into external flash memory. When the device is reset the value of the retained_data variable will be zero and will then be set to 0x1BADCAFE, just before the device enters hibernation mode. When the device is then woken, by pressed SW2, the value of the retained_data variable will still be 0x1BADCAFE, proving it has been retained. This functionality can be observed by monitoring the serial debug output generated when the device is reset and when it wakes from hibernation mode, see below:

_images/hib_enter_ship_ret.png

Fig. 13 Serial debug output on entering ship mode with data retained

_images/hib_wake_ret.png

Fig. 14 Serial debug output on exiting ship mode with data retained