Advanced Users can post their questions and comments here for the not so Newbie crowd.

Moderator: eriksl

User avatar
By davydnorris
#81601 This seems to be something that people need to do a lot, and in response to @eriksl posting up that he needed to change his own routines, I've decided to release my module.

This code implements volatile and persistent storage for the ESP8266. The persistent storage reads and writes key/value pairs and uses a journal file system kind of approach - when you write data with the same key, the old key is wiped (using the trick that in NAND flash you can successfully overwrite a memory location with zeros but not ones), and the newer value is written to the end of storage. The code maintains a round robin of flash sectors, and when a flash sector is full, the code moves on to the next one. When it moves onto the last available free flash sector, it goes back to the oldest used sector, moves any active records to the new one, and then erases it so that there's always at least one free sector waiting.

The volatile storage is much simpler and just reads and writes the entire user area in RTC memory at once.

The calling code is responsible for any caching of data read from either system. For volatile memory I just have a structure that looks like this:

Code: Select alltypedef struct VolatileInfo {
   /* ... */
   WiFiInfoV_t wifi;
   TimerInfoV_t timers;
} VolatileInfo_t;

static VolatileInfo_t volatile_info;
static bool volatile_retrieved = false;


and I add the various subsystem's volatile info into it as needed, and then just use pointers to that to play with the data.

For persistent info I set up a set of pointers to the data and retrieve as needed:
Code: Select alltypedef struct PersistentInfo {
   /* ... */
   WiFiInfoP_t *wifi;
   TimerInfoP_t *timers;
   /* ... */
} PersistentInfo_t;

static PersistentInfo_t persistent_info = {NULL, NULL, /* ... */};


So my code to get the data looks like this - note that in the persistent storage section, I am also setting up system defaults if the data doesn't exist in the store, and then I only write the record if it gets changed from the system default:

Code: Select allvoid * ICACHE_FLASH_ATTR sysinfo_get(uint32_t id) {
   if (STORE(id) == STORAGE_VOLATILE) {
      if (volatile_retrieved == false) {
         uint8_t data[512];
         int32_t len = store_volatile_read(data);
         INFO("SYSINFO", "read volatile store,  len = %d", len);
         if (len > 0) {
            os_memcpy(&volatile_info, data, len);
         } else {
            os_memset(&volatile_info, 0, sizeof(VolatileInfo_t));
         }
         volatile_retrieved = true;
      }

      switch (SUBSYSTEM(id)) {
      case SS_WIFI:
         return &volatile_info.wifi;
         break;
      /* ... */
      }
   }

   switch (SUBSYSTEM(id)) {
   case SS_WIFI:
      if (persistent_info.wifi == NULL) {
         persistent_info.wifi = os_zalloc(sizeof(WiFiInfoP_t));
         int32_t size = store_persist_exists(id);
         if (size == 0 || store_persist_read(id, persistent_info.wifi) != size) {
            os_memcpy(persistent_info.wifi, &static_wifi_list, sizeof(WiFiInfoP_t));
         }
      }
      return persistent_info.wifi;
      break;
      /* ... */
   }
}

void ICACHE_FLASH_ATTR sysinfo_write_if_dirty(uint32_t id) {
   if (STORE(id) == STORAGE_VOLATILE) {
      INFO("SYSINFO", "write volatile store,  len = %d", store_volatile_write(&volatile_info, sizeof(VolatileInfo_t)));
   } else {
      uint32_t len = 0;
      switch (SUBSYSTEM(id)) {
      case SS_WIFI:
         len = sizeof(WiFiInfoP_t);
         if (os_memcmp(&persistent_info.wifi, &static_wifi_list, len) != 0) {
            INFO("SYSINFO", "write wifi info, len = %d", store_persist_write(id, persistent_info.wifi, len));
         }
         break;
         /* ... */
      }
   }
}


I've also included code for doing 32bit CRC checks - that's the only other dependency. Replace if you have your own already - mine is as fast as you can get without using static table lookups (I built it for size and OK speed)

As always comments and improvement suggestions are always appreciated!
You do not have the required permissions to view the files attached to this post.
User avatar
By eriksl
#81713 Nice, interesting concept. Doesn't use any (precious) DRAM at all.

For my project I already have 4k spare for caching, so I might as well use it for writing the sector when necessary. The config needs very seldomly to be re-read into cached so I am quite happy with it, almost all reads hit the cache in DRAM.

I have been thinking of an alternative, it could be done without reading the config in DRAM at all, using direct flash reads. This has two consequences one would take care of
- only read chunks of 4 bytes at a time, aligned on 4 byte boundaries
- the config sector(s) need to be in the mapped range

The first one is quite easy to overcome, the second one is nastier, if you're using e.g. rboot, you'd need to have a separate config sector for each slot, this may not be acceptable.

For writing you'd still have to write the whole sector (even if you not erase it). I am not completely sure, but I am afraid the "partial sector write" the SDK offers, is no more than a read-modify-write cycle, for which the SDK would still, temporarily, allocate 4k. I think the flash chips need to always have a complete sector rewritten.