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)

Parents Reply Children
  • of course you can set a breakpoint in the reset handler, set it by name or set it by address. Or use a better debugger than remote gdb, Segger Ozone is great at this stuff. Either way gdb, if pointed at a proper executable with debugging info can set a breakpoint on any line of any file. 

    Do you even have NOINIT defined in your memory map file? Are you getting link warnings? 

    And NOLOAD only deals with how a loader puts stuff into flash, it's really got nothing much to do with how the early runtime then copies stuff around into RAM or zeroes it out every time you reset. This is why you need to look at the early init code and see what it does with the various sections. 

Related