StorageHelper Question

Greetings from Singapore! I know I have been a bit quiet but this move has proven to be quite an undertaking. I will be here for the next 2-3 years and hope to continue to be a part of this community (I will need some time to change my hardware from Borons to B-Series to get access to cellular here).

I am using @rickkas7's wonderful StorageHelper library along with (again Rick's) JSONParserGenerator library for my LoRA Particle Gateway to track and manage the LoRA nodes that connect to it. As the number of nodes goes up, the size of the JSON object and the amount of storage goes up as well.

We are running into intermittent issues with the data in the JSON object getting corrupted. I am trying to build a "minimal" code example to replicate the issue but, so far, no luck:

I will continue to work this but, it occurred to me that perhaps I need to take some extra care when saving such a large object (3KB) to FRAM which is connected over i2c.

If anyone has experience with something like this, I have some questions:

  • Has anyone used these libraries for large objects?
  • Should I move to the Boron's Flash memory for faster saves?
  • Should I do something like pausing interrupts or have an atomic block when I store the JSON object into memory? During opening hours, the data in the JSON may be updated every few seconds but I could batch the transfer to storage.

Any advice appreciated. My hope is to get over this technical hurdle and share the project - once it is working as part of our long-running LoRA thread.

Thanks,

Chip

@chipmc Lucky you, Singapore is a really place to be for a few years.

I haven't used the StorageHelper library. I do store data on devices and can perhaps comment from that experience.

Photon - use eeprom for parameter settings (all products) and coin-cell backed retained memory (locker product).

eeprom parameter settings uses a struct (packed to 4 bytes multiple) with a magic number and a checksum. There are 3 key interfaces; a getter, a setter, a magic number has changed handler.

The magic number is required to signal changes to the values of parameters or a change in the struct contents. This is called at startup when recovering parameter data from the eeprom.

The checksum is validated on a get and updated on a set.

The use of retained memory is possibly closer to what you have described. The locker product uses a FSM with each locker having a struct of data. The other use is as a local store for access UIDs / authentication. This supports clients where they cannot have outside (internet) comms - each of these uses has (simple) checksums to ensure the contents has not been compromised.

Last comment on FRAM use. The locker product board includes an I2C FRAM, this is now a no fit because FRAMs became unobtainable. If I was using it, I would use the same approach as before - magic number and checksum. The checksum used could include error recovery, I would write and read back to check that data has been saved without error with a simple checksum.

1 Like

@chipmc, I haven't used @rickkas7's library but I can make some comments nonetheless. You asked:

3KB is not a large object. Rick's FRAM library uses WITH_LOCK() for the wire (aka I2C) object while reading or writing to FRAM and the StorageHelplerRK library uses WITH_LOCK() for the storage instance, in case you have multiple. Locking wire will prevent other uses of the I2C device assuming that if you have other I2C devices, their libraries also use WITH_LOCK() to prevent conflict. Basically, you need all I2C code to check "get" wire mutex before doing anything on the bus. If any I2C device library does not do this and their code is used from another thread (not user thread), then there could be conflict causing the apparent FRAM data corruption.

You could easily do so due to the flexibility of the StorageHelperRK library. Another possiblility is that during opening hours you could use deferred updates (several seconds?) to reduce the number of FRAM writes during that time.

NO!!!! Playing with interrupts will make things worse, not better. Using ATOMIC blocks is basically the same thing. @rickkas7 uses mutex locks to ensure storage objects and bus objects are grabbed exclusively during storage transactions. However, as I mentioned before, this approach relies on other libraries playing nice.

The thing to keep in mind is that if you operate in a single user thread, the only other thread(s) will be DeviceOS one(s). If you have other threads, this is where you need to be vigilant. One thing that could also be occuring is the DeviceOS is preempting the I2C FRAM write sequence during the (larger) data transactions, inducing unwanted delays which WITH_LOCK() would not prevent. @rickkas7 could possibly chime in on that one.

3 Likes

@peekay123 ,

Thank you for your response, very helpful.

I don't use threads and the LoRA Particle gateway is not servicing other sensors on the i2c bus or managing many interrupts so it seems like I am likely OK even with a 3kB object.

The StorageHelper gives one easy way to manage batching writes with the flush command having a minimum time since last write. I could also look for logical places in the code to save.

Thank you again.

Chip

@chipmc, do you service these interrupts as these could "interrupt" the stream of bytes being written to the I2C FRAM. This should not be a problem if the ISR code is short and interrrupts do not occur at a high frequency. I would be lovely if the I2C hardware supported DMA but that is not the case.

@peekay123 ,

I almost universally keep my interrupts short. In this case, simply setting a flag which gets picked up on the next transit of the main loop. Also, for the gateway, interrupts should be infrequent.

void userSwitchISR() {
	userSwitchDectected = true;
}

Still good to keep an eye on.

Thanks,

Chip

1 Like