Saving interrupt count on QSPI flash

I have written a code that generates interrupt on Double Tap on XIAO BLE Sense. If the number of interrupts is a multiple of 5, my XIAO BLE Sense goes to power off and turns back on upon another interrupt. The only issue was, after power off, the interrupt count resets to 0. Hence, I wanted to use the non- volatile flash. However, the count yet again resets to 0? How do I make sure that the count persists after power off?

#include "LSM6DS3.h"
#include "Wire.h"
#include <ArduinoBLE.h>
#include "QSPIFBlockDevice.h"

QSPIFBlockDevice root(QSPI_FLASH1_IO0, QSPI_FLASH1_IO1, QSPI_FLASH1_IO2, QSPI_FLASH1_IO3, QSPI_FLASH1_SCK, QSPI_FLASH1_CSN, QSPIF_POLARITY_MODE_1, MBED_CONF_QSPIF_QSPI_FREQ);
uint8_t* write_buffer;
uint8_t* read_buffer;

uint64_t sector_size_at_address_0 = 4096; //root.get_erase_size(0)

LSM6DS3 myIMU(I2C_MODE, 0x6A);
#define int1Pin PIN_LSM6DS3TR_C_INT1

uint8_t interruptCount;
uint8_t prevInterruptCount = 0;
char output[] = "";

// BLE service and characteristic UUIDs
const char* deviceServiceUuid = "xxxxx";
const char* deviceServiceCharacteristicUuid = "xxxxx";

BLEService service(deviceServiceUuid); // Custom service UUID
BLEStringCharacteristic characteristic(deviceServiceCharacteristicUuid, BLERead | BLENotify, 100);
String new_value = "";

//------------------------ Functions initialization ------------------------//
void setLedRGB(bool red, bool green, bool blue);
void goToPowerOff();
void setupDoubleTapInterrupt();
void int1ISR();

void setup() {
    Serial.begin(9600);
    pinMode(LEDR, OUTPUT);
    pinMode(LEDG, OUTPUT);
    pinMode(LEDB, OUTPUT);
    setLedRGB(false, true, false); // set green led

    myIMU.settings.gyroEnabled = 0; // Gyro currently not used, disabled to save power 
    if (myIMU.begin() != 0) {
        Serial.println("IMU error");
    } else {
        Serial.println("IMU OK!");
    }

    setupDoubleTapInterrupt(); //interrupt on double tap set up

    NRF_POWER->DCDCEN = 1; // Minimizes power when bluetooth is used

    pinMode(int1Pin, INPUT);
    attachInterrupt(digitalPinToInterrupt(int1Pin), int1ISR, RISING);

    Serial.println("init the spi flash...");
    int ret = root.init();
    if(ret) {
      Serial.print("init error. err code=");
      Serial.println(ret);
    }
    
    root.erase(0, sector_size_at_address_0); 
    write_buffer = (uint8_t*)malloc(sector_size_at_address_0);
    memset(write_buffer, 0x00, sector_size_at_address_0);
    root.program(write_buffer, 0, sector_size_at_address_0);
    free(write_buffer);

    read_buffer = (uint8_t*)malloc(sector_size_at_address_0);
    memset(read_buffer, 0x00, sector_size_at_address_0);
    root.read(read_buffer, 0, sector_size_at_address_0);
    interruptCount = read_buffer[0];
    free(read_buffer);
    
    if (!BLE.begin()) {
    Serial.println("starting BLE failed!");
    while (1);
   }
  
   BLE.setLocalName("XIAO_BLE_Sense");
   BLE.setAdvertisedService(service);
   service.addCharacteristic(characteristic);
  
   BLE.addService(service);
   BLE.advertise();
  
   Serial.println("BLE peripheral advertising...");
}

void loop() {  
  BLEDevice central = BLE.central();
      
  Serial.print("\Iterrupt Counter: ");
  Serial.println(interruptCount);
  if (interruptCount > prevInterruptCount) {
    Serial.println("\Interrupt received!");
    Serial.print("Previous- ");
    Serial.print(prevInterruptCount);
    Serial.print("New- ");
    Serial.println(interruptCount);    
    setLedRGB(true, false, false); // set red for interrupt
    delay(200);
    if (((interruptCount%5)==0)&&(interruptCount>0)) {
      goToPowerOff();
    } 
  }  
  if (!central.connected()){
    setLedRGB(false, true, false); //set led green to indicate power on
  }
  else {
    setLedRGB(false, false, true); // set led to blue to indicate BLE connection
    itoa(interruptCount, output, 10);
    characteristic.writeValue(output);
    delay(100);
  }  
  prevInterruptCount = interruptCount;
  delay(500);
}

// -------------------- Utilities ------------------------- //

void setLedRGB(bool red, bool green, bool blue) {
  if (!blue) { digitalWrite(LEDB, HIGH); } else { digitalWrite(LEDB, LOW); }
  if (!green) { digitalWrite(LEDG, HIGH); } else { digitalWrite(LEDG, LOW); }
  if (!red) { digitalWrite(LEDR, HIGH); } else { digitalWrite(LEDR, LOW); }
}

// -------------------- System ------------------------- //

void goToPowerOff() {
    setLedRGB(false, false, false);

    // Read current interrupt count from flash
    size_t data_size = sizeof(interruptCount);

    // Check if data size fits within erase size
    if (data_size <= sector_size_at_address_0) {
        // Write interrupt count to flash
        write_buffer = (uint8_t*)malloc(sector_size_at_address_0);
        memset(write_buffer, 0x00, sector_size_at_address_0);
        write_buffer[0] = interruptCount;
        root.erase(0, sector_size_at_address_0); // Erase everything, else incorrect response?
        root.program(write_buffer, 0, sector_size_at_address_0);
        free(write_buffer);

        // Read interrupt count from flash
        read_buffer = (uint8_t*)malloc(sector_size_at_address_0);
        memset(read_buffer, 0x00, sector_size_at_address_0);
        root.read(read_buffer, 0, sector_size_at_address_0);
        uint8_t storedCount = read_buffer[0];
        free(read_buffer);
        Serial.print("Interrupt stored at power off- ");
        Serial.println(storedCount);
    } else {
        Serial.println("ERROR: Data size too large for erase sector!");
    }

    Serial.println("Going to System OFF");
    delay(100); // Wait for settings to apply
    NRF_POWER->SYSTEMOFF = 1;
}



// -------------------- Interrupts ------------------------- //

void setupDoubleTapInterrupt() {
  // Double Tap Config
  // High-performance seems to be needed
  //myIMU.writeRegister(LSM6DS3_ACC_GYRO_CTRL1_XL, 0x40); //ODR - 104 Hz, high-performance mode
  myIMU.writeRegister(LSM6DS3_ACC_GYRO_CTRL1_XL, 0x60);
  myIMU.writeRegister(LSM6DS3_ACC_GYRO_TAP_CFG1, 0x8E);// INTERRUPTS_ENABLE, SLOPE_FDS
  myIMU.writeRegister(LSM6DS3_ACC_GYRO_TAP_THS_6D, 0x8C);
  myIMU.writeRegister(LSM6DS3_ACC_GYRO_INT_DUR2, 0x7F); 
  myIMU.writeRegister(LSM6DS3_ACC_GYRO_WAKE_UP_THS, 0x80);
  myIMU.writeRegister(LSM6DS3_ACC_GYRO_MD1_CFG, 0x08);
}

void int1ISR()
{
  interruptCount++;
}

Hi There,
Code looks good, I would add that the Count Variable needs to be declared as “Volatile” for one and you may have to Write a Page at a time with the QSPI as Block device?
HTH
GL :slight_smile: PJ :v:


So I tried this way and it works across resets, From the Wiki…

#include <Preferences.h>

Preferences preferences;

void setup() {
  Serial.begin(115200);
  delay(3000);
  Serial.println();

  // Open Preferences with my-app namespace. Each application module, library, etc
  // has to use a namespace name to prevent key name collisions. We will open storage in
  // RW-mode (second parameter has to be false).
  // Note: Namespace name is limited to 15 chars.
  preferences.begin("my-app", false);

  // Remove all preferences under the opened namespace
  //preferences.clear();

  // Or remove the counter key only
  //preferences.remove("counter");

  // Get the counter value, if the key does not exist, return a default value of 0
  // Note: Key name is limited to 15 chars.
  unsigned int counter = preferences.getUInt("counter", 0);

  // Increase counter by 1
  counter++;

  // Print the counter to Serial Monitor
  Serial.printf("Current counter value: %u\n", counter);

  // Store the counter to the Preferences
  preferences.putUInt("counter", counter);

  // Close the Preferences
  preferences.end();

  // Wait 10 seconds
  Serial.println("Restarting in 10 seconds...");
  delay(10000);

  // Restart ESP
  ESP.restart();
}

void loop() {

}

HTH
GL :slight_smile: PJ:+1:

Thanks for the reply.
I tried compiling the library and unfortunately this error was displayed- #error “FS API not implemented for the target platform”
Looks like Preferences is not compatible with nRF52840/ XIAO BLE Sense…

Hi there,
I see your asking about the Nrf52840 Sense, Not the Xiao ESP32C3 ok yes that LIB doesn’t support it, However , Here are some examples that Seeed claims can be adapted to QSPI from SD:

“There is an example already in the Arduino IDE under named “Adafruit Internal File System on Bluefruit nrf52” => “Internal_ReadWrite” that works. It’s a very simple program that allows you to name a file and some text that you want inside that file and it will write it. Then when you reboot/restart the board you can read back that text from that file.” (the stack exchange)

HTH
GL :slight_smile: PJ

The Adafruit library didn’t compile due to an error on another dependent library. (I faced similar issue in very other solution I saw on the internet.) Except for this one I saw on Seeed Studio’s website
Based on that I wrote this program to store double tap interrupt count on flash and it worked for me. Putting it here so it helps someone else too.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "nrfx_qspi.h"
#include "app_util_platform.h"
#include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.h"
#include "sdk_config.h"
#include "nrf_delay.h"
#include "avr/interrupt.h"

#include "LSM6DS3.h"
#include "Wire.h"
#include <ArduinoBLE.h>

// QSPI Settings
#define QSPI_STD_CMD_WRSR   0x01
#define QSPI_STD_CMD_RSTEN  0x66
#define QSPI_STD_CMD_RST    0x99
#define QSPI_DPM_ENTER      0x0003 // 3 x 256 x 62.5ns = 48ms
#define QSPI_DPM_EXIT       0x0003

LSM6DS3 myIMU(I2C_MODE, 0x6A);
#define int1Pin PIN_LSM6DS3TR_C_INT1

static uint32_t               *QSPI_Status_Ptr = (uint32_t*) 0x40029604;  // Setup for the SEEED XIAO BLE - nRF52840
static nrfx_qspi_config_t     QSPIConfig;
static nrf_qspi_cinstr_conf_t QSPICinstr_cfg;
static const uint32_t         MemToUse = 4 * 1024;  // Alter this to create larger read writes, 4Kb is the size of the Erase
static bool                   Debug_On = true;
static uint16_t               pBuf[MemToUse/2] = {0}; 
//static uint16_t               pBuf[1] = {0}; 
static uint32_t               *BufMem = (uint32_t*) &pBuf;
static bool                   QSPIWait = false;

uint8_t prevInterruptCount = 0; // Interrupt Counter from last loop
char output[] = "";


const char* deviceServiceUuid = "xxxx";
const char* deviceServiceCharacteristicUuid = "xxxx";
char intBuffer[5];

BLEService service(deviceServiceUuid);
BLEStringCharacteristic characteristic(deviceServiceCharacteristicUuid, BLERead | BLENotify, 20);
String new_value = "";

uint32_t Error_Code;
uint32_t TimeTaken;
uint16_t i;
volatile bool countUpdated = false;

// -------------------- Function initialization ------------------------- //
static void QSPI_PrintData(uint16_t *AnAddress, uint32_t AnAmount);
static nrfx_err_t QSPI_IsReady();
static nrfx_err_t QSPI_WaitForReady();
static nrfx_err_t QSPI_Initialise();
static void QSPI_Erase(uint32_t AStartAddress);
static void QSIP_Configure_Memory();

void goToPowerOff();
void setupDoubleTapInterrupt();
void int1ISR();
void setLedRGB(bool red, bool green, bool blue);

// -------------------- Main ------------------------- //
void setup() {
    Serial.begin(115200);
    pinMode(LEDR, OUTPUT);
    pinMode(LEDG, OUTPUT);
    pinMode(LEDB, OUTPUT);
    setLedRGB(false, false, true); // set blue led

    myIMU.settings.gyroEnabled = 0; // Gyro currently not used, disabled to save power 
    if (myIMU.begin() != 0) {
        Serial.println("IMU error");
    } else {
        Serial.println("IMU OK!");
    }
    
    setupDoubleTapInterrupt();

    NRF_POWER->DCDCEN = 1; //saves power during BLE communication
    
    pinMode(int1Pin, INPUT);
    attachInterrupt(digitalPinToInterrupt(int1Pin), int1ISR, RISING);

    delay(3000);

    if (QSPI_Initialise() != NRFX_SUCCESS) {
      if (Debug_On) {
        Serial.println("QSPI Memory failed to start!");
      }
    } else {
      if (Debug_On) {
        Serial.println("QSPI initialised and ready");
      }
    }
    
    if (!BLE.begin()) {
    Serial.println("starting BLE failed!");
    while (1);
   }
  
   BLE.setLocalName("XIAO BLE Sense");
   BLE.setAdvertisedService(service);
   service.addCharacteristic(characteristic);
  
   BLE.addService(service);
   BLE.advertise();
  
   Serial.println("BLE peripheral advertising...");
}

void loop() {  
  BLEDevice central = BLE.central();

  Serial.println("Waiting for interrupt..");

  if (countUpdated) {
    if (Debug_On) {
      Serial.print("Previous count- ");
    }
  
    Error_Code = nrfx_qspi_read(pBuf, MemToUse, 0x0);
    if (Debug_On) {
      QSPI_WaitForReady();
      QSPI_PrintData(&pBuf[0], 1);
    }
    if (Debug_On) {
      Serial.println("QSPI Erasing 4Kb of memory");
    }
    uint16_t temp = pBuf[0]; 
    QSPI_Erase(0); 
    QSPI_WaitForReady();
    Error_Code = nrfx_qspi_read(pBuf, MemToUse, 0x0);
    if (Debug_On) {
      Serial.print("Reading...");
      QSPI_WaitForReady();
      QSPI_PrintData(&pBuf[0], 1);
    }
    temp ++;
    pBuf[0]= temp;
    QSPI_WaitForReady();
    Error_Code = nrfx_qspi_write(pBuf, MemToUse, 0x0);
    if (Debug_On) {
      Serial.println("Writing");
    }
    QSPI_WaitForReady();
    Error_Code = nrfx_qspi_read(pBuf, MemToUse, 0x0);
    if (Debug_On) {
      Serial.print("New interrupt count- ");
      QSPI_WaitForReady();
      QSPI_PrintData(&pBuf[0], 1);
    }
    QSPI_WaitForReady();
    countUpdated = false;
  }   
  if (pBuf[0] > prevInterruptCount) {   
    setLedRGB(true, false, false); // set red for interrupt
    delay(200);
    if (((pBuf[0]%3)==0)&&(pBuf[0]>0)) {
      goToPowerOff();
    } 
  }  
  if (!central.connected()){
    setLedRGB(false, true, false); //set led green to indicate power on
  }
  else {
    setLedRGB(false, false, true); // set led to blue to indicate BLE connection
    itoa(pBuf[0], output, 10);
    characteristic.writeValue(output);
    delay(100);
  }  
  prevInterruptCount = pBuf[0];
  delay(500);
}

// -------------------- Functions to configure flash ------------------------- //

static void QSPI_PrintData(uint16_t *AnAddress, uint32_t AnAmount) { // max AnAmount = page size = 10, here I only use 1
  uint32_t i;
  for (i = 0; i < AnAmount; i++) {
    Serial.print(*(AnAddress + i), DEC);
  }
  Serial.println("");
}

static nrfx_err_t QSPI_IsReady() {
  if (((*QSPI_Status_Ptr & 8) == 8) && (*QSPI_Status_Ptr & 0x01000000) == 0) {
    return NRFX_SUCCESS;  
  } else {
   return NRFX_ERROR_BUSY; 
  }
}

static nrfx_err_t QSPI_WaitForReady() {
  while (QSPI_IsReady() == NRFX_ERROR_BUSY) {
    if (Debug_On) {
      Serial.println("Waiting");
    }  
  }
  return NRFX_SUCCESS;
}

static nrfx_err_t QSPI_Initialise() { // Initialises the QSPI and NRF LOG
  uint32_t Error_Code;

  NRF_LOG_INIT(NULL); // Initialise the NRF Log
  NRF_LOG_DEFAULT_BACKENDS_INIT();
  QSPIConfig.xip_offset = NRFX_QSPI_CONFIG_XIP_OFFSET;                       
  QSPIConfig.pins = { // Setup for the SEEED XIAO BLE - nRF52840                                                     
   .sck_pin     = 21,                                
   .csn_pin     = 25,                                
   .io0_pin     = 20,                                
   .io1_pin     = 24,                                
   .io2_pin     = 22,                                
   .io3_pin     = 23,                                
  };                                                                  
  QSPIConfig.irq_priority = (uint8_t)NRFX_QSPI_CONFIG_IRQ_PRIORITY;           
  QSPIConfig.prot_if = {                                         
    .readoc     = (nrf_qspi_readoc_t)NRF_QSPI_READOC_READ4O,          
    .writeoc    = (nrf_qspi_writeoc_t)NRF_QSPI_WRITEOC_PP4O,
    .addrmode   = (nrf_qspi_addrmode_t)NRFX_QSPI_CONFIG_ADDRMODE,   
    .dpmconfig  = false,                                            
  };                   
  QSPIConfig.phy_if.sck_freq   = (nrf_qspi_frequency_t)NRF_QSPI_FREQ_32MDIV1;                                   
  QSPIConfig.phy_if.spi_mode   = (nrf_qspi_spi_mode_t)NRFX_QSPI_CONFIG_MODE;
  QSPIConfig.phy_if.dpmen      = false;
  QSPIConfig.prot_if.dpmconfig = true;
  NRF_QSPI->DPMDUR = (QSPI_DPM_ENTER << 16) | QSPI_DPM_EXIT; // Found this on the Nordic Q&A pages, Sets the Deep power-down mode timer
  Error_Code = 1;
  while (Error_Code != 0) {
    Error_Code = nrfx_qspi_init(&QSPIConfig, NULL, NULL);
    if (Error_Code != NRFX_SUCCESS) {
      if (Debug_On) {
        Serial.print("(QSPI_Initialise) nrfx_qspi_init returned : ");
        Serial.println(Error_Code);
      }
    } else {
      if (Debug_On) {
        Serial.println("(QSPI_Initialise) nrfx_qspi_init successful");
      }
    }
  }
  QSIP_Configure_Memory();
  if (Debug_On) {
    Serial.println("(QSPI_Initialise) Wait for QSPI to be ready ...");
  }
  NRF_QSPI->TASKS_ACTIVATE = 1;
  QSPI_WaitForReady();
  if (Debug_On) {
    Serial.println("(QSPI_Initialise) QSPI is ready");
  }
  return QSPI_IsReady(); 
}

static void QSPI_Erase(uint32_t AStartAddress) {
  bool       QSPIReady = false;
  bool       AlreadyPrinted = false;

  if (Debug_On) {
    Serial.println("Erasing memory");
  }
  while (!QSPIReady) {
    if (QSPI_IsReady() != NRFX_SUCCESS) {
      if (!AlreadyPrinted) {
        AlreadyPrinted = true;
      }
    } else {
      QSPIReady = true;
    }
  }
  if (nrfx_qspi_erase(NRF_QSPI_ERASE_LEN_4KB, AStartAddress) != NRFX_SUCCESS) {
    if (Debug_On) {
      Serial.print("(QSPI_Initialise_Page) QSPI Address 0x");
      Serial.print(AStartAddress, HEX);
      Serial.println(" failed to erase!");
    }
  }
}

static void QSIP_Configure_Memory() {
  uint8_t  temporary[] = {0x00, 0x02};
  uint32_t Error_Code;
  
  QSPICinstr_cfg = {
    .opcode    = QSPI_STD_CMD_RSTEN,
    .length    = NRF_QSPI_CINSTR_LEN_1B,
    .io2_level = true,
    .io3_level = true,
    .wipwait   = QSPIWait,
    .wren      = true
  };
  QSPI_WaitForReady();
  if (nrfx_qspi_cinstr_xfer(&QSPICinstr_cfg, NULL, NULL) != NRFX_SUCCESS) { // Send reset enable
    if (Debug_On) {
      Serial.println("(QSIP_Configure_Memory) QSPI 'Send reset enable' failed!");
    }
  } else {
    QSPICinstr_cfg.opcode = QSPI_STD_CMD_RST;
    QSPI_WaitForReady();
    if (nrfx_qspi_cinstr_xfer(&QSPICinstr_cfg, NULL, NULL) != NRFX_SUCCESS) { // Send reset command
      if (Debug_On) {
        Serial.println("(QSIP_Configure_Memory) QSPI Reset failed!");
      }
    } else {
      QSPICinstr_cfg.opcode = QSPI_STD_CMD_WRSR;
      QSPICinstr_cfg.length = NRF_QSPI_CINSTR_LEN_3B;
      QSPI_WaitForReady();
      if (nrfx_qspi_cinstr_xfer(&QSPICinstr_cfg, &temporary, NULL) != NRFX_SUCCESS) { // Switch to qspi mode
        if (Debug_On) {
          Serial.println("(QSIP_Configure_Memory) QSPI failed to switch to QSPI mode!");
        }
      } else {
      }
    }
  }
}

// -------------------- System ------------------------- //

void goToPowerOff() {
  setLedRGB(false, false, false);
  
  if (Debug_On) {
    Serial.println("Interrupt count stored in memory:");
    QSPI_PrintData(&pBuf[0], 1);
  }

  delay(1000); // Delay seems important to apply settings before going to System OFF
  Serial.println("Going to System OFF");
  NRF_POWER->SYSTEMOFF = 1;
}



// -------------------- Interrupts ------------------------- //

void setupDoubleTapInterrupt() {
  // Double Tap Config
  // High-performance seems to be needed
  myIMU.writeRegister(LSM6DS3_ACC_GYRO_CTRL1_XL, 0x60); //ODR - 104 Hz, high-performance mode
  myIMU.writeRegister(LSM6DS3_ACC_GYRO_TAP_CFG1, 0x8E);// INTERRUPTS_ENABLE, SLOPE_FDS
  myIMU.writeRegister(LSM6DS3_ACC_GYRO_TAP_THS_6D, 0x8C);
  myIMU.writeRegister(LSM6DS3_ACC_GYRO_INT_DUR2, 0x7F); //0x10 too much wait 
  myIMU.writeRegister(LSM6DS3_ACC_GYRO_WAKE_UP_THS, 0x80);
  myIMU.writeRegister(LSM6DS3_ACC_GYRO_MD1_CFG, 0x08);
}

void int1ISR()
{
  countUpdated = true;
}

// -------------------- Utilities ------------------------- //

void setLedRGB(bool red, bool green, bool blue) {
  if (!blue) { digitalWrite(LEDB, HIGH); } else { digitalWrite(LEDB, LOW); }
  if (!green) { digitalWrite(LEDG, HIGH); } else { digitalWrite(LEDG, LOW); }
  if (!red) { digitalWrite(LEDR, HIGH); } else { digitalWrite(LEDR, LOW); }
}
1 Like

Hi there, Nice work
I’m certain it will help others…
I had same issue with Grove Expansion Board And On Flash chip, I got the LIB SFUD from seeed engineering but there was Zero config info for the Flash Chip or it’s (SPI-2) implementation.
Took a while to get it working , with a couple different LIB’s (2) It’s posted with the successful results.
Nice having an Extra 2048 of Flash…
GL :slight_smile: PJ :+1: