1

tl;dr: In the code below, why does the Serial.println(value); statement change the behaviour of the code, and how can I make the code work properly without it ?

I need to save some values on a microcontroller that doesn't have any eeprom (an Arduino 33 BLE sense)

I have found a way to do that by declaring a const variable to reserve a region in the flash memory, and write to it using the microcontroller's NVMC (Non Volatile Memory Controller). This solution works fine but there is some weirdness I'd like to get rid of:

To read the values, I use the following function:

float getFloat(float *ptr) {
  float value = *ptr;
  Serial.println(value);
  return value;
}

This works, but whenever I try to simplify this by

  • removing the Serial.println statement
  • not using the function at all and doing what it does inline: something.floatvalue = *ptr

...the code behaves as if the saved values were always 0 (A memory dump shows this is not the case)

I suspect the compiler is optimising stuff in a way I don't understand (I'm not too familiar with c++). How can I avoid that ?

Here ny current code:

#include <Arduino.h>
#include <stdbool.h>

// https://infocenter.nordicsemi.com/pdf/nRF52840_PS_v1.2.pdf
// nFR52 NVMC registers
#define NVMC_BASE (0x4001E000U)
#define NVMC_READY (NVMC_BASE + 0x400U)
#define NVMC_READYNEXT (NVMC_BASE + 0x408U)
#define NVMC_CONFIG (NVMC_BASE + 0x504U)
#define NVMC_ERASEPAGE (NVMC_BASE + 0x508U)
#define NVMC_ERASEALL (NVMC_BASE + 0x50CU)
#define NVMC_ERASEUICR (NVMC_BASE + 0x514U)
#define NVMC_ERASEPAGEPARTIAL (NVMC_BASE + 0X518U)
#define NVMC_ERASEPAGEPARTIALCFG (NVMC_BASE + 0X51CU)
#define NVMC_ICACHECNF (NVMC_BASE + 0x540U)
#define NVMC_IHIT (NVMC_BASE + 0x548U)
#define NVMC_IMISS (NMVC_BASE + 0x54cU)
// nFR52 MVMC values
#define MVMC_READ_MODE 0x00
#define MVMC_WRITE_MODE 0x01
#define MVMC_ERASE_MODE 0x02

typedef struct flash_mem {
  float val_1;
  float val_2;
  // We want to fill a whole page of memory.
  // A page is 4096 bytes, each float takes 4 bytes.
  char filler[4096 - (4+4) ];
} flash_mem_t;

// This will reserve a space in flash memory for the values we need to save.
const flash_mem_t _values __attribute__((section("FLASH"), aligned(0x1000))) = {};
// A regular, in-memory instance of our values for easy manipulations.
// we'll use the load() and save() functions to move data between values and _values
flash_mem_t values;

void setup() {
  // Initialize serial
  Serial.begin(115200);
  while (!Serial.ready()) { }
  delay(500);

  // Load and show saved values
  load();
  Serial.println(values.val_1);
  Serial.println(values.val_2);

  // Update and save values
  values.val_1 += 1;
  values.val_2 += 0.642;
  save();
}

// Arduino expects this
void loop() {}

void load () {
  // TODO: find a way to do this for all values in our struct automatically
  values.val_1 = getFloat((float *)&_values.val_1);
  values.val_2 = getFloat((float *)&_values.val_2);
}

void writeValues () {
  // TODO: find a way to do this for all values in our struct automatically
  *(float *)(&_values.val_1) = values.val_1;
  *(float *)(&_values.val_2) = values.val_2;
}

bool save() {
  // NVMC can only write on "deleted" bytes, so we delete the page _values sits on 
  deletePage((void *)&_values);

  // make sure NVMC is ready
  if (*(uint32_t *)NVMC_READY == false) return false;

  // write values to flash
  *(uint32_t *)NVMC_CONFIG = MVMC_WRITE_MODE;
  writeValues();
  while(*(uint32_t *)NVMC_READY == false) delayMicroseconds(50);
  *(uint32_t *)NVMC_CONFIG = MVMC_READ_MODE;

  return true;
}

bool deletePage(void *pageStart) {
  if (*(uint32_t *)NVMC_READY == false) return false;
  
  *(uint32_t *)NVMC_CONFIG = MVMC_ERASE_MODE;
  *(uint32_t *)NVMC_ERASEPAGE = (uint32_t)pageStart;
  while (*(uint32_t *)NVMC_READY == false) delay(85);
  *(uint32_t *)NVMC_CONFIG = MVMC_READ_MODE;

  return true;
}

float getFloat(float *ptr) {
  float value = *ptr;
  // This is the weird part: everything works properly whem the following line is here,
  // but value is always 0 when it is commented out
  Serial.println(value);
  return value;
}

I didn't tag this question with the "arduino" tag because I feel like it's not really an arduino problem. If you disagree, I'll be happy to update the tags.

Thanks for any help!

bastien girschig
  • 663
  • 6
  • 26
  • 6
    `_values` is declared `const`, the compiler is allowed to assume its value never changes, and optimize accordingly. See if `const volatile flash_mem_t _values...` helps. – Igor Tandetnik May 02 '21 at 18:16
  • Typically, such inexplicable behavior is the result of unintentionally overwriting memory, e.g. by exceeding array boundaries and such. These errors can be hard to find because they can disappear (like in your example) when unrelated changes are made, because the memory layout changes and the overwritten memory is now irrelevant. If you have a debugger that supports break points when memory locations are written they can be used to find the offender. – Peter - Reinstate Monica May 02 '21 at 18:17
  • 4
    Note also that `*(float *)(&_values.val_1) = values.val_1;` exhibits undefined behavior, by way of modifying a `const` object. You might or might not get away with it on your particular compiler. – Igor Tandetnik May 02 '21 at 18:19
  • 5
    Why is `_values` declared `const` in the first place if it shouldn't actually be? – Quentin May 02 '21 at 18:19
  • @IgorTandetnik. Thanks! that looks exactly like what I need. I didn't know about that keyword. Unfortunately, it doesn't work (in fact, with the volatile keyword, the values are no longer saved, even when the Serial.println statement – bastien girschig May 02 '21 at 21:11
  • @Peter-ReinstateMonica Thanks for the suggestion. I've had these kinds of issues, but I think I solved them by making sure my const is filling a whole page of memory (by padding out the remaining space with a dummy variable). Unfortunately I can't connect a debugger: this is running on a microcontroller, not on my computer – bastien girschig May 02 '21 at 21:14
  • @IgorTandetnik When you say "get away with it" do you mean "get it to compile" or "make it do what you want" ? I know your'e not supposed to be modifying a const, but from what I understood, it's a trick to get the compiler to assign some space in the flash memory for that value. Do you know of a more conventional way of doing that ? – bastien girschig May 02 '21 at 21:19
  • @Quentin This feels very weird for me as well... I use this to "trick" the compiler into assigning some space in the flash memory of the microcontroller. This way, I know I can modify the values there without breaking my program. If you know a better way of doing that, I'd be super grateful. – bastien girschig May 02 '21 at 21:22
  • Well, I don't know embedded programming so I can't help you there, but declaring a `const` object to modify it later goes directly against the language rules so it doesn't look like a viable route. It could have been that your compiler offers that possibility as an extension, but it clearly does not since the optimizer "breaks" the code. – Quentin May 02 '21 at 22:24

1 Answers1

1

Usually you can use volatile to inform the compiler that the variable can change (for example, in an interrupt), so it wouldn't optimize it away. See Why is volatile needed in C?.

Andrey Belykh
  • 2,578
  • 4
  • 32
  • 46
  • I tried that but it doesn't work (not only is is not fixing the issue, it's also breaking stuff that was working...). Thanks anyways! I'll keep the current implementation for now (it's ugly but it works...) – bastien girschig May 31 '21 at 14:45