samples: nrf: system_off: add RAM retention example for nRF52
Add an object to retain data across both reboots and entry to SYSTEM_OFF. This only works on nRF52, since nRF51 and nRF53 require different low-level operations to configure RAM retention. Signed-off-by: Peter Bigot <peter.bigot@nordicsemi.no>
This commit is contained in:
parent
42cf4c76ab
commit
bdba2e63f6
8 changed files with 287 additions and 3 deletions
|
@ -4,5 +4,5 @@ cmake_minimum_required(VERSION 3.13.1)
|
|||
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
||||
project(nrf_system_off)
|
||||
|
||||
FILE(GLOB app_sources src/*.c)
|
||||
target_sources(app PRIVATE ${app_sources})
|
||||
target_sources(app PRIVATE src/main.c)
|
||||
target_sources_ifdef(CONFIG_APP_RETENTION app PRIVATE src/retained.c)
|
||||
|
|
14
samples/boards/nrf/system_off/Kconfig
Normal file
14
samples/boards/nrf/system_off/Kconfig
Normal file
|
@ -0,0 +1,14 @@
|
|||
# Copyright (c) 2021 Nordic Semiconductor ASA
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
mainmenu "Nordic SYSTEM_OFF demo"
|
||||
|
||||
config APP_RETENTION
|
||||
bool "Enable state retention in system off"
|
||||
depends on SOC_COMPATIBLE_NRF52X
|
||||
help
|
||||
On some Nordic chips this application supports retaining
|
||||
memory while in system off. Select this to enable the
|
||||
feature.
|
||||
|
||||
source "Kconfig.zephyr"
|
|
@ -22,6 +22,15 @@ disable the deep sleep functionality before the kernel starts, which
|
|||
prevents the board from powering down during initialization of drivers
|
||||
that use unbounded delays to wait for startup.
|
||||
|
||||
RAM Retention
|
||||
=============
|
||||
|
||||
On nRF52 platforms this also can demonstrate RAM retention. By selecting
|
||||
``CONFIG_APP_RETENTION=y`` state related to number of boots, number of times
|
||||
system off was entered, and total uptime since initial power-on are retained
|
||||
in a checksummed data structure. The POWER peripheral is configured to keep
|
||||
the containing RAM section powered while in system-off mode.
|
||||
|
||||
Requirements
|
||||
************
|
||||
|
||||
|
|
|
@ -2,3 +2,5 @@ CONFIG_PM=y
|
|||
# Required to disable default behavior of deep sleep on timeout
|
||||
CONFIG_PM_DEVICE=y
|
||||
CONFIG_GPIO=y
|
||||
# Optional select RAM retention (nRF52 only)
|
||||
#CONFIG_APP_RETENTION=y
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
sample:
|
||||
name: Low Power State Sample for nRF5x
|
||||
common:
|
||||
tags: power
|
||||
tests:
|
||||
sample.boards.nrf.system_off:
|
||||
build_only: true
|
||||
platform_allow: nrf52840dk_nrf52840 nrf52dk_nrf52832 nrf51dk_nrf51422
|
||||
tags: power
|
||||
sample.boards.nrf.system_off.retained:
|
||||
build_only: true
|
||||
platform_allow: nrf52840dk_nrf52840 nrf52dk_nrf52832
|
||||
extra_configs:
|
||||
- CONFIG_APP_RETENTION=y
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include <device.h>
|
||||
#include <init.h>
|
||||
#include <power/power.h>
|
||||
#include "retained.h"
|
||||
#include <hal/nrf_gpio.h>
|
||||
|
||||
#define CONSOLE_LABEL DT_LABEL(DT_CHOSEN(zephyr_console))
|
||||
|
@ -40,6 +41,21 @@ void main(void)
|
|||
|
||||
printk("\n%s system off demo\n", CONFIG_BOARD);
|
||||
|
||||
if (IS_ENABLED(CONFIG_APP_RETENTION)) {
|
||||
bool retained_ok = retained_validate();
|
||||
|
||||
/* Increment for this boot attempt and update. */
|
||||
retained.boots += 1;
|
||||
retained_update();
|
||||
|
||||
printk("Retained data: %s\n", retained_ok ? "valid" : "INVALID");
|
||||
printk("Boot count: %u\n", retained.boots);
|
||||
printk("Off count: %u\n", retained.off_count);
|
||||
printk("Active Ticks: %" PRIu64 "\n", retained.uptime_sum);
|
||||
} else {
|
||||
printk("Retained data not supported\n");
|
||||
}
|
||||
|
||||
/* Configure to generate PORT event (wakeup) on button 1 press. */
|
||||
nrf_gpio_cfg_input(DT_GPIO_PIN(DT_NODELABEL(button0), gpios),
|
||||
NRF_GPIO_PIN_PULLUP);
|
||||
|
@ -64,6 +80,12 @@ void main(void)
|
|||
|
||||
printk("Entering system off; press BUTTON1 to restart\n");
|
||||
|
||||
if (IS_ENABLED(CONFIG_APP_RETENTION)) {
|
||||
/* Update the retained state */
|
||||
retained.off_count += 1;
|
||||
retained_update();
|
||||
}
|
||||
|
||||
/* Above we disabled entry to deep sleep based on duration of
|
||||
* controlled delay. Here we need to override that, then
|
||||
* force entry to deep sleep on any delay.
|
||||
|
|
177
samples/boards/nrf/system_off/src/retained.c
Normal file
177
samples/boards/nrf/system_off/src/retained.c
Normal file
|
@ -0,0 +1,177 @@
|
|||
/*
|
||||
* Copyright (c) 2021 Nordic Semiconductor ASA
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <zephyr.h>
|
||||
#include <devicetree.h>
|
||||
#include <sys/byteorder.h>
|
||||
#include <sys/crc.h>
|
||||
#include <hal/nrf_power.h>
|
||||
#include "retained.h"
|
||||
|
||||
/* nRF52 RAM (really, RAM AHB slaves) are partitioned as:
|
||||
* * Up to 8 blocks of two 4 KiBy byte "small" sections
|
||||
* * A 9th block of with 32 KiBy "large" sections
|
||||
*
|
||||
* At time of writing the maximum number of large sections is 6, all
|
||||
* within the first large block. Theoretically there could be more
|
||||
* sections in the 9th block, and possibly more blocks.
|
||||
*/
|
||||
|
||||
/* Inclusive address of RAM start */
|
||||
#define SRAM_BEGIN (uintptr_t)DT_REG_ADDR(DT_NODELABEL(sram0))
|
||||
|
||||
/* Exclusive address of RAM end */
|
||||
#define SRAM_END (SRAM_BEGIN + (uintptr_t)DT_REG_SIZE(DT_NODELABEL(sram0)))
|
||||
|
||||
/* Size of a controllable RAM section in the small blocks */
|
||||
#define SMALL_SECTION_SIZE 4096
|
||||
|
||||
/* Number of controllable RAM sections in each of the lower blocks */
|
||||
#define SMALL_SECTIONS_PER_BLOCK 2
|
||||
|
||||
/* Span of a small block */
|
||||
#define SMALL_BLOCK_SIZE (SMALL_SECTIONS_PER_BLOCK * SMALL_SECTION_SIZE)
|
||||
|
||||
/* Number of small blocks */
|
||||
#define SMALL_BLOCK_COUNT 8
|
||||
|
||||
/* Span of the SRAM area covered by small sections */
|
||||
#define SMALL_SECTION_SPAN (SMALL_BLOCK_COUNT * SMALL_BLOCK_SIZE)
|
||||
|
||||
/* Inclusive address of the RAM range covered by large sections */
|
||||
#define LARGE_SECTION_BEGIN (SRAM_BEGIN + SMALL_SECTION_SPAN)
|
||||
|
||||
/* Size of a controllable RAM section in large blocks */
|
||||
#define LARGE_SECTION_SIZE 32768
|
||||
|
||||
/* Set or clear RAM retention in SYSTEM_OFF for the provided object.
|
||||
*
|
||||
* @note This only works for nRF52 with the POWER module. The other
|
||||
* Nordic chips use a different low-level API, which is not currently
|
||||
* used by this function.
|
||||
*
|
||||
* @param ptr pointer to the start of the retainable object
|
||||
*
|
||||
* @param len length of the retainable object
|
||||
*
|
||||
* @param enable true to enable retention, false to clear retention
|
||||
*/
|
||||
static int ram_range_retain(const void *ptr,
|
||||
size_t len,
|
||||
bool enable)
|
||||
{
|
||||
uintptr_t addr = (uintptr_t)ptr;
|
||||
uintptr_t addr_end = addr + len;
|
||||
|
||||
/* Error if the provided range is empty or doesn't lie
|
||||
* entirely within the SRAM address space.
|
||||
*/
|
||||
if ((len == 0U)
|
||||
|| (addr < SRAM_BEGIN)
|
||||
|| (addr > (SRAM_END - len))) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Iterate over each section covered by the range, setting the
|
||||
* corresponding RAM OFF retention bit in the parent block.
|
||||
*/
|
||||
do {
|
||||
uintptr_t block_base = SRAM_BEGIN;
|
||||
uint32_t section_size = SMALL_SECTION_SIZE;
|
||||
uint32_t sections_per_block = SMALL_SECTIONS_PER_BLOCK;
|
||||
bool is_large = (addr >= LARGE_SECTION_BEGIN);
|
||||
uint8_t block = 0;
|
||||
|
||||
if (is_large) {
|
||||
block = 8;
|
||||
block_base = LARGE_SECTION_BEGIN;
|
||||
section_size = LARGE_SECTION_SIZE;
|
||||
|
||||
/* RAM[x] supports only 16 sections, each its own bit
|
||||
* for POWER (0..15) and RETENTION (16..31). We don't
|
||||
* know directly how many sections are present, so
|
||||
* assume they all are; the true limit will be
|
||||
* determined by the SRAM size.
|
||||
*/
|
||||
sections_per_block = 16;
|
||||
}
|
||||
|
||||
uint32_t section = (addr - block_base) / section_size;
|
||||
|
||||
if (section >= sections_per_block) {
|
||||
block += section / sections_per_block;
|
||||
section %= sections_per_block;
|
||||
}
|
||||
|
||||
uint32_t section_mask =
|
||||
(POWER_RAM_POWERSET_S0RETENTION_On
|
||||
<< (section + POWER_RAM_POWERSET_S0RETENTION_Pos));
|
||||
|
||||
if (enable) {
|
||||
nrf_power_rampower_mask_on(NRF_POWER, block, section_mask);
|
||||
} else {
|
||||
nrf_power_rampower_mask_off(NRF_POWER, block, section_mask);
|
||||
}
|
||||
|
||||
/* Move to the first address in the next section. */
|
||||
addr += section_size - (addr % section_size);
|
||||
} while (addr < addr_end);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Retained data must be defined in a no-init section to prevent the C
|
||||
* runtime initialization from zeroing it before anybody can see it.
|
||||
*/
|
||||
__noinit struct retained_data retained;
|
||||
|
||||
#define RETAINED_CRC_OFFSET offsetof(struct retained_data, crc)
|
||||
#define RETAINED_CHECKED_SIZE (RETAINED_CRC_OFFSET + sizeof(retained.crc))
|
||||
|
||||
bool retained_validate(void)
|
||||
{
|
||||
/* The residue of a CRC is what you get from the CRC over the
|
||||
* message catenated with its CRC. This is the post-final-xor
|
||||
* residue for CRC-32 (CRC-32/ISO-HDLC) which Zephyr calls
|
||||
* crc32_ieee.
|
||||
*/
|
||||
const uint32_t residue = 0x2144df1c;
|
||||
uint32_t crc = crc32_ieee((const uint8_t *)&retained,
|
||||
RETAINED_CHECKED_SIZE);
|
||||
bool valid = (crc == residue);
|
||||
|
||||
/* If the CRC isn't valid, reset the retained data. */
|
||||
if (!valid) {
|
||||
memset(&retained, 0, sizeof(retained));
|
||||
}
|
||||
|
||||
/* Reset to accrue runtime from this session. */
|
||||
retained.uptime_latest = 0;
|
||||
|
||||
/* Reconfigure to retain the state during system off, regardless of
|
||||
* whether validation succeeded. Although these values can sometimes
|
||||
* be observed to be preserved across System OFF, the product
|
||||
* specification states they are not retained in that situation, and
|
||||
* that can also be observed.
|
||||
*/
|
||||
(void)ram_range_retain(&retained, RETAINED_CHECKED_SIZE, true);
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
void retained_update(void)
|
||||
{
|
||||
uint64_t now = k_uptime_ticks();
|
||||
|
||||
retained.uptime_sum += (now - retained.uptime_latest);
|
||||
retained.uptime_latest = now;
|
||||
|
||||
uint32_t crc = crc32_ieee((const uint8_t *)&retained,
|
||||
RETAINED_CRC_OFFSET);
|
||||
|
||||
retained.crc = sys_cpu_to_le32(crc);
|
||||
}
|
54
samples/boards/nrf/system_off/src/retained.h
Normal file
54
samples/boards/nrf/system_off/src/retained.h
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright (c) 2021 Nordic Semiconductor ASA
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef RETAINED_H_
|
||||
#define RETAINED_H_
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
/* Example of validatable retained data. */
|
||||
struct retained_data {
|
||||
/* The uptime from the current session the last time the
|
||||
* retained data was updated.
|
||||
*/
|
||||
uint64_t uptime_latest;
|
||||
|
||||
/* Cumulative uptime from all previous sessions up through
|
||||
* uptime_latest of this session.
|
||||
*/
|
||||
uint64_t uptime_sum;
|
||||
|
||||
/* Number of times the application has started. */
|
||||
uint32_t boots;
|
||||
|
||||
/* Number of times the application has gone into system off. */
|
||||
uint32_t off_count;
|
||||
|
||||
/* CRC used to validate the retained data. This must be
|
||||
* stored little-endian, and covers everything up to but not
|
||||
* including this field.
|
||||
*/
|
||||
uint32_t crc;
|
||||
};
|
||||
|
||||
/* For simplicity in the sample just allow anybody to see and
|
||||
* manipulate the retained state.
|
||||
*/
|
||||
extern struct retained_data retained;
|
||||
|
||||
/* Check whether the retained data is valid, and if not reset it.
|
||||
*
|
||||
* @return true if and only if the data was valid and reflects state
|
||||
* from previous sessions.
|
||||
*/
|
||||
bool retained_validate(void);
|
||||
|
||||
/* Update any generic retained state and recalculate its checksum so
|
||||
* subsequent boots can verify the retained state.
|
||||
*/
|
||||
void retained_update(void);
|
||||
|
||||
#endif /* RETAINED_H_ */
|
Loading…
Add table
Add a link
Reference in a new issue