Volatile Structures and EEPROM

The limitation with volatile is how the FRAM library works. You can’t pass a volatile variable to it, because the FRAM library needs to obtain a pointer to it, which cause the variable to lose its volatile attribute.

However, there’s a more fundamental reason why you can’t do that.

Even though the variable is declared volatile, that only prevents the compiler from making assumptions about the variable, mostly with regards to optimization. It always assumes a side-effect has changed the value and always re-reads it from memory instead of caching it in a register.

What volatile does not do is guarantee atomicity. Say the ISR is incrementing a variable. It’s going from 0x0000ff to 0x000100. Because there is no guarantee of atomicity, the FRAM library could read this as 0x00000000 or some similar invalid value because bytes changed in the middle of a read and you got a half-updated value.

The normal techniques like critical sections and mutexes can’t generally be used from an ISR, but this is one technique that you can use: atomic variables.

This example shows how to atomically increment a variable in the ISR. In loop, it reads and clears the ISR variable, and adds it to another variable. This is where you’d add to your structure you store in FRAM.

#include "Particle.h"

#include <atomic>

SerialLogHandler logHandler;

SYSTEM_THREAD(ENABLED);

const pin_t INT_PIN = D2;

void testISR();

// interruptCounter is an atomic variable, so sets and gets occur atomically
std::atomic<uint32_t> interruptCounter;

// This is the counter we manage from loop
uint32_t counter = 0;

unsigned long lastReport = 0;

void setup() {
    pinMode(D2, INPUT);

    // 
    interruptCounter.store(0, std::memory_order_relaxed);

    attachInterrupt(D2, testISR, FALLING);
}

void loop() {
    // Take the value from and clear it to 0 atomically. Even if
    // the ISR triggers at this exact moment, either the interruptCounter
    // will be updated before reading, or after setting to 0, but never
    // in the middle.
    counter += interruptCounter.fetch_and(0, std::memory_order_relaxed);

    if (millis() - lastReport >= 1000) {
        lastReport = millis();
        Log.info("counter=%lu", counter);
    }

}

void testISR() {
    // This increments the value atomically. Even if the ISR triggers
    // while we're resetting the value from loop, the count will
    // not be lost.
    interruptCounter.fetch_add(1, std::memory_order_relaxed);
}


5 Likes