This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Losing NOINIT RAM contents on a soft reset, nRF52832, SDK 14.0.0, gcc

I have an in memory log, built using a custom log backend, which I write to using the NRF_LOG_*() macros. 

My implementation of app_error_fault_handler() will do some NRF_LOG_ERROR() logging, then call NVIC_SystemReset().

It's my hope that I'll be able to use this to continue logging past the point of an assertion in my code, handled using APP_ERROR_CHECK(). But I'm seeing that on a soft reset, I'm losing the whole in memory log and my pointer into it is reset to the start. Both the in memory log and the pointer into it are in a NOINIT section.

// RAM log.
uint8_t m_ram_log[RAM_LOG_SIZE] __attribute__ ((section(".noinit")));

// The RAM log write index is in .noinit and is only reset to zero when we reset after flashing.
uint32_t m_write_index __attribute__ ((section(".noinit")));

Here's the relevant piece of my linker script:

MEMORY
{
  /* Application flash:
   */
  FLASH (rx) : ORIGIN = 0x23000, LENGTH = 0x2F000

  /* Application RAM from "Device" sheet, NOT including NOINIT below:
   */
  RAM (rwx) :  ORIGIN = 0x20002790, LENGTH = 0xB470

  /** Location of non initialized RAM.
   * In-RAM log:                                                         8,192 B (8 kB)
   * In-RAM log write index (pointer):                                       4 B
   * Future version of buttonless DFU sharing bonds with the bootloader:   128 B
   * Accelerometer resting state:                                           24 B
   * Device ID (9, aligned):                                                12 B
   * Total:                                                              8,360 B (0x20A8)
   *
   * Since we're not at all short of RAM, let's just round this up to 9 kB (0x2400).
   * Warning: Put this same size in the bootloader linker script, so that it doesn't init this memory.
   */
  NOINIT (rwx) :  ORIGIN = 0x2000DC00, LENGTH = 0x2400

  /** Location of bootloader settings in the last flash page. */
  /* BOOTLOADER_SETTINGS (rw) : ORIGIN = 0x0007F000, LENGTH = 0x1000 */ /* end: 0x0007F000, length: 4 kB */

  /** Location in UICR where bootloader start address is stored. */
  /* UICR_BOOTLOADER (r) : ORIGIN = 0x10001014, LENGTH = 0x04 */
}

I see from section 18.8 of the nRF52832 Product Specification that "The RAM is never reset, but depending on reset source, RAM content may be corrupted." Are the reset sources that is referring to the ones *other* than "CPU lockup" and "Soft reset", which are the ones without an "x" next to them in the table?

Should this work, if I've done it right?

[Edit]

Here's the SECTION from the linker script that I should have added earlier, as it was when I posted.

  .noinit(NOLOAD) :
  {

  } > NOINIT
  

And here it is as it is now that I've seen the comment below.

  .noinit (NOLOAD) :
  {
    . = ALIGN(4);
    __noinit_start__ = .;
    *(.noinit*)
    __noinit_end__ = .;
  } > NOINIT
  

And here's a test I wrote to assert whether RAM retention is working.

static int8_t retained_ram_test_int __attribute__ ((section(".noinit")));

#define START_FROM_TEST 0x1

void test_retained_ram(void)
{
    uint32_t err_code = 0;

	NRF_LOG_DEBUG("Retained RAM test. Best run with gdb server, gdb client and rtt in separate shells.");

	uint32_t gp_reg = NRF_POWER->GPREGRET;
	bool started_from_test = (gp_reg == START_FROM_TEST);
	NRF_POWER->GPREGRET = 0x00000000UL;

	if (started_from_test)
	{
		NRF_LOG_DEBUG("After sys off. Retained RAM value: %d.\n", retained_ram_test_int);
		NRF_LOG_DEBUG("Done. Did we see a value > 0?");

		if (retained_ram_test_int)
		{
			NRF_LOG_DEBUG("Value != 0.");
			retained_ram_test_int = 0;
		}

		for(;;);
	}

    // Timers.
	err_code = app_timer_init();
	APP_ERROR_CHECK(err_code);

	// Scheduler.
	APP_SCHED_INIT(sizeof(ble_bts_t), APP_SCHED_QUEUE_SIZE);

	// GPIO tasks and events.
	err_code = nrf_drv_gpiote_init();

	// Start RTC1. We need this because we don't yet have the soft device enabled at this point.
    start_clock();

	NRF_LOG_DEBUG("Before sys off. Writing to value in retained RAM.");

	// Retain RAM.
	retain_ram();

	retained_ram_test_int = 1;

	// Wake on motion.
	acc_init();
	acc_program(ACC_STATE_PHASE_2);
	acc_store_resting_state();
	acc_program(ACC_STATE_PHASE_1);
	acc_clear_interrupt(NULL, 0);
	nrf_gpio_cfg_sense_input(PIN_DEF_ACC_INT1, NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_SENSE_HIGH);

	NRF_LOG_DEBUG("Going into sys off. Shake me to wake me, then continue in gdb.");
	nrf_delay_ms(100);
	NRF_POWER->GPREGRET = START_FROM_TEST;
	NRF_POWER->SYSTEMOFF = 1;

	for(;;);
}

It's not working (not before the change to the SECTION and not after). Here's the log output (the "shaking" is because I have an accelerometer interrupt doing a GPIO wake of the Nordic).

------
D test Retained RAM test. Best run with gdb server, gdb client and rtt in separate shells.
D test Before sys off. Writing to value in retained RAM.
D test Going into sys off. Shake me to wake me, then continue in gdb.

------
D test Retained RAM test. Best run with gdb server, gdb client and rtt in separate shells.
D test Before sys off. Writing to value in retained RAM.
D test Going into sys off. Shake me to wake me, then continue in gdb.

Here's the *.map file, showing the source files with .noinit variables in the .noinit section (good) but the .noinit section not where it should be in memory (probably bad). I put it at ORIGIN = 0x2000DC00. Instead it's ended up at 0x00000000200028fc.

.noinit         0x00000000200028fc     0x1eee load address 0x000000000004db1c
 .noinit        0x00000000200028fc     0x1ee4 _build/app/log_backend_ram.c.o
                0x00000000200028fc                m_ram_log
                0x00000000200047dc                m_write_index
 .noinit        0x00000000200047e0        0x9 _build/app/main.c.o
                0x00000000200047e0                m_device_id
 .noinit        0x00000000200047e9        0x1 _build/app/tests.c.o
                0x00000000200047ec                . = ALIGN (0x4)
                

I can't set a breakpoint in the ResetHandler. That's implemented in /components/toolchain/gcc/gcc_startup_nrf52.S and gdb doesn't like me setting a breakpoint there:

(gdb) break gcc_startup_nrf52.S:229
No source file named gcc_startup_nrf52.S.
Make breakpoint pending on future shared library load? (y or [n]) n

[Edit 2]

And here's the function I use to keep the power on to the RAM. I call this pretty early in main(), before I've enabled the soft device.

// Retain all RAM while we're in system off mode.
void retain_ram(void)
{
	// We have Nordic nRF52832 variant AAB0 with 64 kB of RAM and 512 kB of flash.
	// 0x41414141: AAAA
	// 0x41414142: AAAB
	// 0x41414241: AABA
	// 0x41414242: AABB
	// 0x41414230: AAB0
	// 0x41414530: AAE0
	// 0xFFFFFFFF: Unspecified
	// NRF_LOG_DEBUG("Variant: 0x%08X", NRF_FICR->INFO.VARIANT); // 0x41414230: AAB0
	// NRF_LOG_DEBUG("RAM: 0x%02X", NRF_FICR->INFO.RAM); // 0x40: 64 kB
	// NRF_LOG_DEBUG("FLASH: 0x%03X", NRF_FICR->INFO.FLASH); // 0x200: 512 kB

	// Variants QFAA-B00, QFAB-B00, CIAA-B00 have an errata on RAM retention.
	// See: http://infocenter.nordicsemi.com/topic/com.nordic.infocenter.nrf52832.Rev1.errata/anomaly_832_108.html
	*(volatile uint32_t *)0x40000EE4 = (*(volatile uint32_t *)0x10000258 & 0x0000004F);

	// The 64 kB of RAM is broken into 16 sections of 4 kB each. There are two sections to each block. Each index to NRF_POWER->RAM[] is a block, with a bit in the register for each of the two sections in that block.

	// Note that the <SDK>/examples/peripheral/ram_retention application is well out of date for this and still using the deprecated RAMON register. Don't copy it. There's also a soft device function, sd_power_ramon_set(), for setting this but we must do this *before* the soft device is enabled for it to have any effect.
	// On the nRF52 this uses 20 nA per 4kB RAM section, for 320 nA total. We could use less if we know we're not using the full RAM, but at the nA level, we don't care.
	NRF_POWER->RAM[0].POWER = RAM_MASK;
    NRF_POWER->RAM[0].POWERSET = RAM_MASK;
	NRF_POWER->RAM[1].POWER = RAM_MASK;
    NRF_POWER->RAM[1].POWERSET = RAM_MASK;
	NRF_POWER->RAM[2].POWER = RAM_MASK;
    NRF_POWER->RAM[2].POWERSET = RAM_MASK;
	NRF_POWER->RAM[3].POWER = RAM_MASK;
    NRF_POWER->RAM[3].POWERSET = RAM_MASK;
	NRF_POWER->RAM[4].POWER = RAM_MASK;
    NRF_POWER->RAM[4].POWERSET = RAM_MASK;
	NRF_POWER->RAM[5].POWER = RAM_MASK;
    NRF_POWER->RAM[5].POWERSET = RAM_MASK;
	NRF_POWER->RAM[6].POWER = RAM_MASK;
    NRF_POWER->RAM[6].POWERSET = RAM_MASK;
	NRF_POWER->RAM[7].POWER = RAM_MASK;
    NRF_POWER->RAM[7].POWERSET = RAM_MASK;
}

// Mask for setting both the POWER and POWERSET values in the NRF_POWER->RAM[] register. 0x00030003 is a bit mask in which bits 0, 1, 16 and 17 are on. See the Product Specification p 88.
#define RAM_MASK (0x00030003)

  • Not sure if this will help, but try keeping power applied to the particular RAM area you are using, which may be lost on a NVIC_SystemReset(). Also you might want to move the buffer so it aligns with a specific RAM location rather than overlapping two areas. Here's some code I use for this:

       // Set RAM power modes in system off
       NRF_POWER->RAM[7].POWERSET = (POWER_RAM_POWER_S0POWER_On     << POWER_RAM_POWER_S0POWER_Pos)      |
                                    (POWER_RAM_POWER_S1POWER_On     << POWER_RAM_POWER_S1POWER_Pos)      |
                                    (POWER_RAM_POWER_S0RETENTION_On << POWER_RAM_POWER_S0RETENTION_Pos)  |
                                    (POWER_RAM_POWER_S1RETENTION_On << POWER_RAM_POWER_S1RETENTION_Pos);
    

  • Thanks. I've got the power on to the RAM, but I haven't aligned my buffer to the RAM areas. Will try that.

  • what's it being reset to, 0? Where do you initialise the index pointer (or is the default for that 0 too)? You need to step through the early init code, the stuff called long before main, to see what it's doing with your noinit area and why. Just because you put it in section .noinit doesn't mean much unless your linker file puts that somewhere useful and your crt file doesn't overwrite it. 

    Put a breakpoint on the very first instruction after reset, the reset handler, and look at the contents of memory then. If it's correct, then your init code is overwriting it. 

  • I tested this on my setup, and can confirm that NVIC_SystemReset() does not cause power to be removed from SRAM on the nRF52832; pity, maybe it is indeed the linker issue RK mentions. The sections and actual locations will be in the link map, of course. I do not see any RAM corruption with or without the above POWERSET setting.

  • In addition to adding the extra segment to MEMORY, I believe you also need to add a SECTION directive and instruct the linker to direct your .noinit variables into NOINIT MEMORY area.

    eg. Within the SECTION area of your linker script, add the following.  As described in the comment for you existing linker script, ensure that your bootloader and any other program loader which runs before your app has a similar defined area which is not touched.

    .noinit (NOLOAD) :
    {
    . = ALIGN(4);
    __noinit_start__ = .;
    *(.noinit*)
    __noinit_end__ = .;
    } > NOINIT

    You can verify that your m_ram_log is in the correct memory location after linking by examining the map file or using nm on the linked binary to show symbol locations.

Related