Reply by Michael Karas June 23, 20182018-06-23
[This followup was posted to comp.arch.embedded and a copy was sent to 
the cited author.]

In article <pftfgp$l3j$1@dont-email.me>, pozzugno@gmail.com says...
> > I need to save some data on a non-volatile memory. They are some > parameters that the user could change infrequently, for example 10 times > per day at a maximum. In the range 100-2kB. > > As usually occurs, the parameters that changes more frequently (10 times > per day) are fewer than parameters that changes very rarely (10-100 > times in the device lifetime). > > How to save those data? After discarding the internal MCU Flash (because > of interrupts block during programming), I'm deciding if it's better a > serial EEPROM or serial Flash. > > First of all, I think SPI is better than I2C. SPI seems much faster: > 10-20MHz against 400kHz-1MHz. At least for reading. Erasing/writing > time is identical between I2C and SPI. > > EEPROM or Flash? I know EEPROM can be written one byte at a time without > erasing an entire block, against Flash that needs a sector erase before > writing even a single byte. > > The firmware would be simpler with EEPROMs, because I don't need to save > the entire sector before erasing and restoring it during programming, > when writing a single byte. With EEPROMs I can write a byte. Stop. > > However I don't think this simple approach can be used in a real > production. Suppose I have 10 bytes to write. What happens if the > writing process is stopped at the middle, maybe after 5 bytes? How to > protect the system against those events? I think one solution is to > have at least two copies of data in the memory and switch to the other > bank after all data is completely written, with an "atomic" write operation. > This means I need to copy&paste an entire block everytime, even for a > single byte change. And this is similar to Flash approach, where I > *need* a sector erase before changing a single byte. > > What about the time? EEPROM write cycle is about 5ms for a 32-bytes > page. For 128 bytes/4 pages, 20 ms. > Flash sector erase time is 18 ms, plus 14us for each byte. The overall > write cycle time is similar between EEPROM and Flash. > > If the data are bigger, for example 1kB, the Flash technology wins. The > sector size in Flash memories are usually bigger than 1kB. So I need to > erase only one time (18m + 14u*1024=32ms). In EEPROM I have 32 32-bytes > pages, so 5m*32=160ms. 5 times more than Flash. > > I'm not considering endurance. EEPROMs are better (1000k write cycles) > than Flash (100k write cycles), but I don't need so much write cycles in > the entire device lifetime. > >
You should consider the use of a FRAM chip. You can get SPI or I2C versions. Endurance almost becomes a non issue. Supports byte by byte write and write speed is pretty much the same as reading - with the serial interface the write speed is pretty much hidden in the interface timing. I use them for NVM storage and it is possible to create a robust parameter and settings system around FRAM. My general concept is to store data in blocks two times with CRCs. The CRCs allow for checking at load time if to use the first or second stored image. If both CRCs bad then initialize to defaults. With the beauty of byte writes I have my driver setup where I keep two copies of the data set in RAM. One matches the stored content and the other copy is where changes are made. At time of the write commit to NVM I only write the bytes that have actually changed, This drastically reduces the amount of time spent storing a data set back to the FRAM when only a few bytes have changed. -- Michael Karas Carousel Design Solutions http://www.carousel-design.com --- This email has been checked for viruses by Avast antivirus software. https://www.avast.com/antivirus
Reply by Richard Damon June 20, 20182018-06-20
On 6/20/18 5:03 AM, pozz wrote:
> Il 20/06/2018 05:14, Richard Damon ha scritto: >> On 6/19/18 11:44 AM, pozz wrote:
>>> >>> Do your application read log entries?&#4294967295; What do you do to avoid reading >>> when the memory chip is busy in writing? >> >> If it is an external flash (or an internal flash that a write blocks >> reading), then when the application asks for the block it will block on >> the Mutex guarding the device. > > In my cooperative kernel, I have two choices: > &#4294967295; - block the entire application waiting for serial memory availability > &#4294967295;&#4294967295;&#4294967295; * for 24LC64, maximum the page-write time, max 5ms > &#4294967295;&#4294967295;&#4294967295; * for a serial Flash, maximum the sectore-erase time that is too > &#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; much > &#4294967295; - convert the code in a state-machine, sigh... :-( > > >> One big reason to use a pre-emption based >> system. Normally, reading of log entries is only done in response to an >> external command > > I usually work on a bare-metal system.
And that is one of the issues YOU need to solve when you drop down to cooperative/bare metal system. What to do if you want to do something but can't at the moment. You need to design in ways to effectively use the wait time, and yes, that often means things like state machines, and that often means that the base level of an operation needs to know when some sub part isn't ready to do its thing. On very small machines, the code isn't that complicated (being limited by the processor's ability), the bare metal approach isn't that bad. As the machine gets bigger, normally because the task has gotten more complicated, then the bare metal, hand crafted cooperative system starts to get heavy, so you 'upgrade' to a pre-emptive micro-kernel. (and if the problem get enormous, maybe you upgrade to a large scale processor running a full embedded os).
Reply by pozz June 20, 20182018-06-20
Il 20/06/2018 05:14, Richard Damon ha scritto:
> On 6/19/18 11:44 AM, pozz wrote: >> Il 17/06/2018 23:05, Richard Damon ha scritto: >>> On 6/16/18 2:12 PM, pozz wrote: >>>> >>>> >>>> Third question, more complex (for me). >>>> Suppose I decided to split non-volatile data in two blocks, for example >>>> calibration data and user settings. >>>> What happens if user settings change when calibration settings are being >>>> written?&#4294967295; I think I should convert your writeTriggered in two updated >>>> flags: calibration_updated and settings_updated. >>>> In idle state I should check both flags and start writing the relative >>>> block. >>>> >>>> >>> >>> I will typically divide my parameters into two groups. One group has the >>> data that the user sets, usage information, and other information that >>> is updated as the user works. This data gets saved shortly after the >>> user makes a setting change or enough time passes that the other >>> information is worth saving. I often also include a user option to reset >>> this to some 'factory default' for if the user totally messes up the >>> settings (this won't reset the usage data, just user settings). There is >>> a second block of factory calibration data. This will never be update by >>> the user (or only by very trusted users) and typically this block >>> doesn't have multiple copies (unless I need a backup for actual flash >>> corruption). To activate the save for this block requires giving the >>> device a special unlock sequence, which allow the adjustment of these >>> parameters and then a specific factory save command. >>> >>>> Again another scenario.&#4294967295; Until now we talked about settings, a structure >>>> filled with parameters that can change when the user wants at any time. >>>> >>>> How to manage a log, a list of events with timestamp and some data? >>>> Suppose one entry takes 8 bytes.&#4294967295; I reserve 4kB of memory for around 500 >>>> entries organized in a FIFO. >>>> >>>> Log isn't so critical as the settings, so I think I could avoid >>>> redundancy in non-volatile memory.&#4294967295; Maybe only the CRC that, when not >>>> valid, clears all the log.&#4294967295; It should be acceptable. >>>> >>>> As usual we can talk about the opportunity to read the full log and put >>>> it in RAM, or read the entries when needed (because the user wants to >>>> read some entries, mostly the more recent). >>>> Reading 10 entries (80 bytes) from 10MHz SPI memory doesn't take too >>>> much (no more than 100usec). But here the problem is that reading can be >>>> needed during writing of settings.&#4294967295; And this is a big problem. >>>> >>>> As usual, simplest solution is to have the full log in RAM... sigh! >>>> >>>> What about writing of one or a few new entries in the log?&#4294967295; A writing >>>> operation (for example, settings) should be in process.&#4294967295; I should >>>> schedule and postpone the log update after writing of settings is >>>> finished. >>>> >>>> Because you have much more experience than me (and you are so kind to >>>> share it with me and other lurkers), could you suggest a smart approach? >>>> >>> >>> For logs, I will define a log information block to store a single log >>> entry, back the number of them I can into a flash sector. The total log >>> then has a number of these sectors reserved for the log, forming a >>> circular list (so writing a new log entry overwrites the oldest log >>> record). I tend to have two sectors of these log entries 'cached', so I >>> can be creating one log entry at the end of one block and one at the >>> beginning of the next block. While I am filling a given log entry, it is >>> marked as 'invalid', and that is cleared when the entry is finished. A >>> given sector is written when it is full, or a sufficient time after a >>> block has been updated to minimize log data loses due to power loss. >>> This write uses the same flash buffer as the parameter flash buffer as I >>> can't be both writing a log sector and a parameter sector at the same >>> moment. >>> >> >> Do your application read log entries?&#4294967295; What do you do to avoid reading >> when the memory chip is busy in writing? > > If it is an external flash (or an internal flash that a write blocks > reading), then when the application asks for the block it will block on > the Mutex guarding the device.
In my cooperative kernel, I have two choices: - block the entire application waiting for serial memory availability * for 24LC64, maximum the page-write time, max 5ms * for a serial Flash, maximum the sectore-erase time that is too much - convert the code in a state-machine, sigh... :-(
> One big reason to use a pre-emption based > system. Normally, reading of log entries is only done in response to an > external command
I usually work on a bare-metal system.
Reply by Richard Damon June 20, 20182018-06-20
On 6/19/18 11:44 AM, pozz wrote:
> Il 17/06/2018 23:05, Richard Damon ha scritto: >> On 6/16/18 2:12 PM, pozz wrote: >>> >>> >>> Third question, more complex (for me). >>> Suppose I decided to split non-volatile data in two blocks, for example >>> calibration data and user settings. >>> What happens if user settings change when calibration settings are being >>> written?&#4294967295; I think I should convert your writeTriggered in two updated >>> flags: calibration_updated and settings_updated. >>> In idle state I should check both flags and start writing the relative >>> block. >>> >>> >> >> I will typically divide my parameters into two groups. One group has the >> data that the user sets, usage information, and other information that >> is updated as the user works. This data gets saved shortly after the >> user makes a setting change or enough time passes that the other >> information is worth saving. I often also include a user option to reset >> this to some 'factory default' for if the user totally messes up the >> settings (this won't reset the usage data, just user settings). There is >> a second block of factory calibration data. This will never be update by >> the user (or only by very trusted users) and typically this block >> doesn't have multiple copies (unless I need a backup for actual flash >> corruption). To activate the save for this block requires giving the >> device a special unlock sequence, which allow the adjustment of these >> parameters and then a specific factory save command. >> >>> Again another scenario.&#4294967295; Until now we talked about settings, a structure >>> filled with parameters that can change when the user wants at any time. >>> >>> How to manage a log, a list of events with timestamp and some data? >>> Suppose one entry takes 8 bytes.&#4294967295; I reserve 4kB of memory for around 500 >>> entries organized in a FIFO. >>> >>> Log isn't so critical as the settings, so I think I could avoid >>> redundancy in non-volatile memory.&#4294967295; Maybe only the CRC that, when not >>> valid, clears all the log.&#4294967295; It should be acceptable. >>> >>> As usual we can talk about the opportunity to read the full log and put >>> it in RAM, or read the entries when needed (because the user wants to >>> read some entries, mostly the more recent). >>> Reading 10 entries (80 bytes) from 10MHz SPI memory doesn't take too >>> much (no more than 100usec). But here the problem is that reading can be >>> needed during writing of settings.&#4294967295; And this is a big problem. >>> >>> As usual, simplest solution is to have the full log in RAM... sigh! >>> >>> What about writing of one or a few new entries in the log?&#4294967295; A writing >>> operation (for example, settings) should be in process.&#4294967295; I should >>> schedule and postpone the log update after writing of settings is >>> finished. >>> >>> Because you have much more experience than me (and you are so kind to >>> share it with me and other lurkers), could you suggest a smart approach? >>> >> >> For logs, I will define a log information block to store a single log >> entry, back the number of them I can into a flash sector. The total log >> then has a number of these sectors reserved for the log, forming a >> circular list (so writing a new log entry overwrites the oldest log >> record). I tend to have two sectors of these log entries 'cached', so I >> can be creating one log entry at the end of one block and one at the >> beginning of the next block. While I am filling a given log entry, it is >> marked as 'invalid', and that is cleared when the entry is finished. A >> given sector is written when it is full, or a sufficient time after a >> block has been updated to minimize log data loses due to power loss. >> This write uses the same flash buffer as the parameter flash buffer as I >> can't be both writing a log sector and a parameter sector at the same >> moment. >> > > Do your application read log entries?&#4294967295; What do you do to avoid reading > when the memory chip is busy in writing?
If it is an external flash (or an internal flash that a write blocks reading), then when the application asks for the block it will block on the Mutex guarding the device. One big reason to use a pre-emption based system. Normally, reading of log entries is only done in response to an external command
Reply by pozz June 19, 20182018-06-19
Il 17/06/2018 23:05, Richard Damon ha scritto:
> On 6/16/18 2:12 PM, pozz wrote: >> >> >> Third question, more complex (for me). >> Suppose I decided to split non-volatile data in two blocks, for example >> calibration data and user settings. >> What happens if user settings change when calibration settings are being >> written?&#4294967295; I think I should convert your writeTriggered in two updated >> flags: calibration_updated and settings_updated. >> In idle state I should check both flags and start writing the relative >> block. >> >> > > I will typically divide my parameters into two groups. One group has the > data that the user sets, usage information, and other information that > is updated as the user works. This data gets saved shortly after the > user makes a setting change or enough time passes that the other > information is worth saving. I often also include a user option to reset > this to some 'factory default' for if the user totally messes up the > settings (this won't reset the usage data, just user settings). There is > a second block of factory calibration data. This will never be update by > the user (or only by very trusted users) and typically this block > doesn't have multiple copies (unless I need a backup for actual flash > corruption). To activate the save for this block requires giving the > device a special unlock sequence, which allow the adjustment of these > parameters and then a specific factory save command. > >> Again another scenario.&#4294967295; Until now we talked about settings, a structure >> filled with parameters that can change when the user wants at any time. >> >> How to manage a log, a list of events with timestamp and some data? >> Suppose one entry takes 8 bytes.&#4294967295; I reserve 4kB of memory for around 500 >> entries organized in a FIFO. >> >> Log isn't so critical as the settings, so I think I could avoid >> redundancy in non-volatile memory.&#4294967295; Maybe only the CRC that, when not >> valid, clears all the log.&#4294967295; It should be acceptable. >> >> As usual we can talk about the opportunity to read the full log and put >> it in RAM, or read the entries when needed (because the user wants to >> read some entries, mostly the more recent). >> Reading 10 entries (80 bytes) from 10MHz SPI memory doesn't take too >> much (no more than 100usec). But here the problem is that reading can be >> needed during writing of settings.&#4294967295; And this is a big problem. >> >> As usual, simplest solution is to have the full log in RAM... sigh! >> >> What about writing of one or a few new entries in the log?&#4294967295; A writing >> operation (for example, settings) should be in process.&#4294967295; I should >> schedule and postpone the log update after writing of settings is finished. >> >> Because you have much more experience than me (and you are so kind to >> share it with me and other lurkers), could you suggest a smart approach? >> > > For logs, I will define a log information block to store a single log > entry, back the number of them I can into a flash sector. The total log > then has a number of these sectors reserved for the log, forming a > circular list (so writing a new log entry overwrites the oldest log > record). I tend to have two sectors of these log entries 'cached', so I > can be creating one log entry at the end of one block and one at the > beginning of the next block. While I am filling a given log entry, it is > marked as 'invalid', and that is cleared when the entry is finished. A > given sector is written when it is full, or a sufficient time after a > block has been updated to minimize log data loses due to power loss. > This write uses the same flash buffer as the parameter flash buffer as I > can't be both writing a log sector and a parameter sector at the same > moment. >
Do your application read log entries? What do you do to avoid reading when the memory chip is busy in writing?
Reply by pozz June 19, 20182018-06-19
Il 17/06/2018 19:27, David Brown ha scritto:
> (You emailed me a copy of this post too.&#4294967295; I guess that was a slip of the > mouse - we all do that from time to time.&#4294967295; Anyway, I'm replying the > newsgroup and ignoring the email version.&#4294967295; That way everyone can see it, > and more people can join in the fun.&#4294967295; It's good to see an interesting > thread here in c.a.e. - it's been a bit idle recently.)
My Thunderbird has a Reply button that sends an email to the sender and not to the groups. I have to right-click and choose "send to group" explicitly. And it sometimes happen I forget.
> On 16/06/18 20:12, pozz wrote: >> >> Il 15/06/2018 16:21, David Brown ha scritto: >> >> &#4294967295;> static enum { idle, writing, checking, erasing } writerState; >> &#4294967295;> >> &#4294967295;> void doWriter(void) { >> &#4294967295;>&#4294967295;&#4294967295;&#4294967295;&#4294967295; switch (writerState) { >> &#4294967295;>&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; case idle : >> &#4294967295;>&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; if (writeTriggered) { >> &#4294967295;>&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; writeTriggered = false; >> &#4294967295;>&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; startWriting(); >> &#4294967295;>&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; writerState = writing; >> &#4294967295;>&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; } >> &#4294967295;>&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; break; >> &#4294967295;>&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; case writing : >> &#4294967295;>&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; bool stillWorking = pollNVMdevice(); >> &#4294967295;>&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; if (stillWorking) return; >> &#4294967295;>&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; startChecking(); >> &#4294967295;>&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; writerState = checking; >> &#4294967295;>&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; break; >> &#4294967295;>&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; case checking : >> &#4294967295;>&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; bool stillWorking = pollNVMdevice(); >> &#4294967295;>&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; if (stillWorking) return; >> &#4294967295;>&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; startErasingNextBlock(); >> &#4294967295;>&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; writerState = erasing; >> &#4294967295;>&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; break; >> &#4294967295;>&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; case erasing : >> &#4294967295;>&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; bool stillWorking = pollNVMdevice(); >> &#4294967295;>&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; if (stillWorking) return; >> &#4294967295;>&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; writerState = idle; >> &#4294967295;>&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; break; >> &#4294967295;>&#4294967295;&#4294967295;&#4294967295;&#4294967295; } >> &#4294967295;> } >> &#4294967295;> >> &#4294967295;> There - half your program is done >> >> I was thinking on this "long-running" task that writes non-volatile >> data to serial memory in the background, during normal execution of >> the&#4294967295; main application. >> >> First question. If I understood well your words, the checking state >> has the goal of comparing data written (by reading them) against >> original data in RAM.&#4294967295; If they differ, we should go back in writing >> state, maybe changing the destination sector (because the write could >> be failed again for physical damage of that sector). > > Yes, basically.&#4294967295; It is up to you how you handle things if the ram copy > can be changed underway.&#4294967295; You might use a second copy in ram for a > check, you might ban changes during the write/check period, or you might > simply run a checksum on the written data and check that it matches. > >> >> Another question is: what happens if saving is triggered again during >> writing?&#4294967295; I think there isn't any big problem.&#4294967295; The writing task can >> be started again from the beginning, even on the same destination sector. >> So during writing and checking states I should check for >> writeTriggered flags again and prematurely stop the writing and start >> again from the beginning. > > Usually the most important thing is that the data stored is a consistent > snapshot of the data structure, rather than the most recent version.&#4294967295; So > you should continue saving what you are doing before triggering a new > write.&#4294967295; But be very careful if you allow writing to the ram copy of the > data while a write is in progress. > >> >> Third question, more complex (for me). >> Suppose I decided to split non-volatile data in two blocks, for >> example calibration data and user settings. >> What happens if user settings change when calibration settings are >> being written?&#4294967295; I think I should convert your writeTriggered in two >> updated flags: calibration_updated and settings_updated. >> In idle state I should check both flags and start writing the relative >> block. >> > > Sure, have as many blocks as you like. > >> >> Again another scenario.&#4294967295; Until now we talked about settings, a >> structure filled with parameters that can change when the user wants >> at any time. >> >> How to manage a log, a list of events with timestamp and some data? >> Suppose one entry takes 8 bytes.&#4294967295; I reserve 4kB of memory for around >> 500 entries organized in a FIFO. >> >> Log isn't so critical as the settings, so I think I could avoid >> redundancy in non-volatile memory.&#4294967295; Maybe only the CRC that, when not >> valid, clears all the log.&#4294967295; It should be acceptable. > > Have part of your NVM chip reserved for the logs.&#4294967295; Log in blocks, with a > crc on each.&#4294967295; Don't bother holding more than two log blocks in ram (one > being stored to NVM, and one for updating at the moment).
Good suggestion. Maybe I missed to explain that the user could want to read the log. If I don't keep *all* the log in RAM, it is possible that the application needs to read the log from the memory chip... this is against our first assumption to read all the data at startup and keep it in RAM to simplify writing without blocking. The function that needs to return one or more entries should read from the memory chip... however it can be busy in writing. One possibility is to block while waiting for the end of writing, but blocking tasks aren't good. Another is to change the function to be asyncronous...
>> As usual we can talk about the opportunity to read the full log and >> put it in RAM, or read the entries when needed (because the user wants >> to read some entries, mostly the more recent). >> Reading 10 entries (80 bytes) from 10MHz SPI memory doesn't take too >> much (no more than 100usec). But here the problem is that reading can >> be needed during writing of settings.&#4294967295; And this is a big problem. >> >> As usual, simplest solution is to have the full log in RAM... sigh! >> >> What about writing of one or a few new entries in the log?&#4294967295; A writing >> operation (for example, settings) should be in process.&#4294967295; I should >> schedule and postpone the log update after writing of settings is >> finished. >> >> Because you have much more experience than me (and you are so kind to >> share it with me and other lurkers), could you suggest a smart approach?
Reply by Richard Damon June 17, 20182018-06-17
On 6/16/18 2:12 PM, pozz wrote:
> > > Third question, more complex (for me). > Suppose I decided to split non-volatile data in two blocks, for example > calibration data and user settings. > What happens if user settings change when calibration settings are being > written?&#4294967295; I think I should convert your writeTriggered in two updated > flags: calibration_updated and settings_updated. > In idle state I should check both flags and start writing the relative > block. > >
I will typically divide my parameters into two groups. One group has the data that the user sets, usage information, and other information that is updated as the user works. This data gets saved shortly after the user makes a setting change or enough time passes that the other information is worth saving. I often also include a user option to reset this to some 'factory default' for if the user totally messes up the settings (this won't reset the usage data, just user settings). There is a second block of factory calibration data. This will never be update by the user (or only by very trusted users) and typically this block doesn't have multiple copies (unless I need a backup for actual flash corruption). To activate the save for this block requires giving the device a special unlock sequence, which allow the adjustment of these parameters and then a specific factory save command.
> Again another scenario.&#4294967295; Until now we talked about settings, a structure > filled with parameters that can change when the user wants at any time. > > How to manage a log, a list of events with timestamp and some data? > Suppose one entry takes 8 bytes.&#4294967295; I reserve 4kB of memory for around 500 > entries organized in a FIFO. > > Log isn't so critical as the settings, so I think I could avoid > redundancy in non-volatile memory.&#4294967295; Maybe only the CRC that, when not > valid, clears all the log.&#4294967295; It should be acceptable. > > As usual we can talk about the opportunity to read the full log and put > it in RAM, or read the entries when needed (because the user wants to > read some entries, mostly the more recent). > Reading 10 entries (80 bytes) from 10MHz SPI memory doesn't take too > much (no more than 100usec). But here the problem is that reading can be > needed during writing of settings.&#4294967295; And this is a big problem. > > As usual, simplest solution is to have the full log in RAM... sigh! > > What about writing of one or a few new entries in the log?&#4294967295; A writing > operation (for example, settings) should be in process.&#4294967295; I should > schedule and postpone the log update after writing of settings is finished. > > Because you have much more experience than me (and you are so kind to > share it with me and other lurkers), could you suggest a smart approach? >
For logs, I will define a log information block to store a single log entry, back the number of them I can into a flash sector. The total log then has a number of these sectors reserved for the log, forming a circular list (so writing a new log entry overwrites the oldest log record). I tend to have two sectors of these log entries 'cached', so I can be creating one log entry at the end of one block and one at the beginning of the next block. While I am filling a given log entry, it is marked as 'invalid', and that is cleared when the entry is finished. A given sector is written when it is full, or a sufficient time after a block has been updated to minimize log data loses due to power loss. This write uses the same flash buffer as the parameter flash buffer as I can't be both writing a log sector and a parameter sector at the same moment.
Reply by David Brown June 17, 20182018-06-17
(You emailed me a copy of this post too.  I guess that was a slip of the 
mouse - we all do that from time to time.  Anyway, I'm replying the 
newsgroup and ignoring the email version.  That way everyone can see it, 
and more people can join in the fun.  It's good to see an interesting 
thread here in c.a.e. - it's been a bit idle recently.)

On 16/06/18 20:12, pozz wrote:
> > Il 15/06/2018 16:21, David Brown ha scritto: > > > static enum { idle, writing, checking, erasing } writerState; > > > > void doWriter(void) { > >&#4294967295;&#4294967295;&#4294967295;&#4294967295; switch (writerState) { > >&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; case idle : > >&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; if (writeTriggered) { > >&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; writeTriggered = false; > >&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; startWriting(); > >&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; writerState = writing; > >&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; } > >&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; break; > >&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; case writing : > >&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; bool stillWorking = pollNVMdevice(); > >&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; if (stillWorking) return; > >&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; startChecking(); > >&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; writerState = checking; > >&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; break; > >&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; case checking : > >&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; bool stillWorking = pollNVMdevice(); > >&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; if (stillWorking) return; > >&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; startErasingNextBlock(); > >&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; writerState = erasing; > >&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; break; > >&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; case erasing : > >&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; bool stillWorking = pollNVMdevice(); > >&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; if (stillWorking) return; > >&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; writerState = idle; > >&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; break; > >&#4294967295;&#4294967295;&#4294967295;&#4294967295; } > > } > > > > There - half your program is done > > I was thinking on this "long-running" task that writes non-volatile data > to serial memory in the background, during normal execution of the&#4294967295; main > application. > > First question. If I understood well your words, the checking state has > the goal of comparing data written (by reading them) against original > data in RAM.&#4294967295; If they differ, we should go back in writing state, maybe > changing the destination sector (because the write could be failed again > for physical damage of that sector).
Yes, basically. It is up to you how you handle things if the ram copy can be changed underway. You might use a second copy in ram for a check, you might ban changes during the write/check period, or you might simply run a checksum on the written data and check that it matches.
> > Another question is: what happens if saving is triggered again during > writing?&#4294967295; I think there isn't any big problem.&#4294967295; The writing task can be > started again from the beginning, even on the same destination sector. > So during writing and checking states I should check for writeTriggered > flags again and prematurely stop the writing and start again from the > beginning.
Usually the most important thing is that the data stored is a consistent snapshot of the data structure, rather than the most recent version. So you should continue saving what you are doing before triggering a new write. But be very careful if you allow writing to the ram copy of the data while a write is in progress.
> > Third question, more complex (for me). > Suppose I decided to split non-volatile data in two blocks, for example > calibration data and user settings. > What happens if user settings change when calibration settings are being > written?&#4294967295; I think I should convert your writeTriggered in two updated > flags: calibration_updated and settings_updated. > In idle state I should check both flags and start writing the relative > block. >
Sure, have as many blocks as you like.
> > Again another scenario.&#4294967295; Until now we talked about settings, a structure > filled with parameters that can change when the user wants at any time. > > How to manage a log, a list of events with timestamp and some data? > Suppose one entry takes 8 bytes.&#4294967295; I reserve 4kB of memory for around 500 > entries organized in a FIFO. > > Log isn't so critical as the settings, so I think I could avoid > redundancy in non-volatile memory.&#4294967295; Maybe only the CRC that, when not > valid, clears all the log.&#4294967295; It should be acceptable.
Have part of your NVM chip reserved for the logs. Log in blocks, with a crc on each. Don't bother holding more than two log blocks in ram (one being stored to NVM, and one for updating at the moment).
> > As usual we can talk about the opportunity to read the full log and put > it in RAM, or read the entries when needed (because the user wants to > read some entries, mostly the more recent). > Reading 10 entries (80 bytes) from 10MHz SPI memory doesn't take too > much (no more than 100usec). But here the problem is that reading can be > needed during writing of settings.&#4294967295; And this is a big problem. > > As usual, simplest solution is to have the full log in RAM... sigh! > > What about writing of one or a few new entries in the log?&#4294967295; A writing > operation (for example, settings) should be in process.&#4294967295; I should > schedule and postpone the log update after writing of settings is finished. > > Because you have much more experience than me (and you are so kind to > share it with me and other lurkers), could you suggest a smart approach? >
Reply by David Brown June 17, 20182018-06-17
On 15/06/18 17:20, pozz wrote:
> Il 15/06/2018 16:21, David Brown ha scritto: >> On 15/06/18 15:10, pozz wrote: >>> Il 15/06/2018 11:25, David Brown ha scritto: >>>> On 15/06/18 09:38, pozz wrote: >>>>> Il 14/06/2018 12:49, David Brown ha scritto: >>>>>> On 14/06/18 12:20, pozz wrote: >>>>>>> I need to save some data on a non-volatile memory. They are some >>>>>>> parameters that the user could change infrequently, for example 10 >>>>>>> times >>>>>>> per day at a maximum.&#4294967295; In the range 100-2kB. >>>>>>> >> <snip> >>>>>> >>>>>> Multiple copies (at least 2) are key, along with timestamps or >>>>>> counters >>>>> >>>>> Two should be sufficient to prevent corruption caused by interruption >>>>> during the writing of a block of data. >>>>> Maybe three (or more) are needed to face memory *physical* corruption >>>>> (maybe for many writings). >>>>> I think I can ignore this event in my actual project. >>>>> >>>> >>>> You use more for wear levelling.&#4294967295; Often your flash chip is /way/ bigger >>>> than you need - perhaps by a factor of 1000 simply because that's what >>>> you have on stock, or that's the cheapest device in the package you >>>> want.&#4294967295; Spread your writes over 1000 blocks instead of 2, and you have >>>> 500 times the endurance.&#4294967295; Use a good checksum and accept that sometimes >>>> a block will be worn out and move onto the next one, and you have >>>> perhaps a million times the endurance (because most blocks last a lot >>>> longer than the guaranteed minimum). >>> >>> So the writing process should be: >>> >>> 1. write the data at block i+1 (where i is the block of the current >>> &#4294967295;&#4294967295; data in RAM) >>> 2. read back the block i+1 and check if checksum is ok >>> 3. if ok, writing process is finished >>> 4. if not, go to block i+2 and start again from 1. >>> >> >> Yes. >> >> Then comes step 5 (for flash with separate erasing) : >> >> 5. If you have written block x, check if block x+1 (modulo the size of >> the device) is erased.&#4294967295; If not, then erase it ready for the next write. >> >> Note that it does not matter if the erase block size is bigger than the >> program block size - if it is, then your "erase block x+1" command will >> cover the next few program blocks. >> >>>>>> and checksums. >>>>> >>>>> This is interesting.&#4294967295; Why do I need a checksum?&#4294967295; My approach is to use >>>>> only a magic number plus a counter... and two memory areas. >>>>> At first startup magic number isn't found on any areas, so the device >>>>> starts with default and write data on Area1 (magic/0, where 0 is the >>>>> counter). >>>> >>>> You use checksums to ensure that you haven't had a power-out or >>>> reset in >>>> the middle of writing, >>> >>> Only for this thing, you can write the counter as the last byte. If the >>> writing is interrupted in the middle, counter hasn't written yet, so the >>> block is not valid (because considered too old or empty). >> >> Nope.&#4294967295; You can't rely on that, unless you are absolutely sure that you >> have a simple, old-fashioned E&#4294967295;PROM device.&#4294967295; Many devices are /not/ like >> that, even if they provide an interface that matches it logically. >> >> A common structure for a modern device is to have 32-byte pages as a >> compromise between density, cost, and flexibility.&#4294967295; (Bigger pages are >> more efficient in device area and cost.)&#4294967295; When you send a command to >> write a byte, the device reads the old 32-byte page into ram, erases the >> old page, updates the ram with the new data, the writes the whole 32 >> byte page back in. >> >> The write process is done by a loop that writes all the data, reads it >> back at a low voltage to see if it has stuck, and writes again as needed >> until the data looks good.&#4294967295; Then it writes again a few times for safety >> - either a fixed number, or a percentage of the number of writes taken. >> >> So it is /entirely/ possible for an interrupted write to give you a >> valid counter, but invalid data.&#4294967295; It is also entirely possible to get >> some bits of the counter as valid while others are still erased (giving >> ones on most devices). >> >> And that is just for simple devices that don't do any fancy wear >> levelling, packing, garbage collection, etc. >> >> >>> >>>> and that the flash has not worn out. >>>> >>>>> >>>>> When the configuration is changed, Area2 is written with magic/1, >>>>> being >>>>> careful to save magic/1 only at the end of area writing. >>>>> >>>>> At startup magics and counters from both area are loaded, and one area >>>>> is chosen (magic should be valid and counter should be the maximum). >>>>> >>>>> I think this approach works, even when the area writing is interrupted >>>>> at the middle. >>>>> >>>>> Why do I need checksum?&#4294967295; The only thing that comes in mind is to >>>>> prevent >>>>> writing errors: for example, I want to write 0x00 but the value really >>>>> written is 0x01, maybe for a noise on the serial bus. >>>>> >>>>> To solve this situation, I need checksum... but also I need to re-read >>>>> and re-calculate the checksum at *every* area writing... and start >>>>> a new >>>>> writing if something was wrong. >>>>> >>>>> Do you have a better strategy? >>>> >>>> You calculate the checksum for a block before writing it, and you check >>>> it when reading it.&#4294967295; Simple. >>> >>> Do you calc the checksum of all the data block in RAM, including padding >>> bytes? >> >> Yes, of course.&#4294967295; The trick is not to have unknown padding bytes.&#4294967295; I make >> a point of /never/ having compiler-generated padding in my structs. >> >> So you have something like this: >> >> #define sizeOfRawBlock 32 >> #define noOfRawBlocks 4 >> #define magicNumber 0x9185be91 >> #define dataStructVersionExpected 0x0001 >> >> typedef union { >> &#4294967295;&#4294967295;&#4294967295;&#4294967295;uint8_t raw8[sizeOfRawBlock * noOfRawBlocks]; >> &#4294967295;&#4294967295;&#4294967295;&#4294967295;uint16_t raw16[sizeOfRawBlock * noOfRawBlocks / 2]; >> &#4294967295;&#4294967295;&#4294967295;&#4294967295;struct { >> &#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; uint32_t magic; >> &#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; uint16_t dataStructVersion; >> &#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; uint16_t crc; >> &#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; uint32_t count; >> >> &#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; // real data >> &#4294967295;&#4294967295;&#4294967295;&#4294967295;} >> } nvmData_t; >> >> static_assert(sizeof(nvmData_t) == (sizeofRawBlock * noOfRawBlocks), >> &#4294967295;&#4294967295;&#4294967295;&#4294967295; "Check size of nvnData!"); > > Why raw8[]? > > I think you can avoid raw16[] too. If you have the function: > > &#4294967295;&#4294967295; uint16_t calc_crc(const void *data, size_t size); > > you can simply call: > > &#4294967295;&#4294967295; nvmData.crc = > &#4294967295;&#4294967295;&#4294967295;&#4294967295; calc_crc( ((unsigned char *)&nvmData) + 8, sizeof(nvmData) ); >
No, you can't necessarily do that. C has rules about what pointers can alias what data in what ways - so the compiler knows the data inside the struct is independent of a pointer to an uint16_t (such as you might use in the crc function), unless they are part of a union. In many cases, the compiler can't actually make use of such information for optimisation - so code messing about with pointers of different types will work. But sometimes it won't, especially with higher optimisation choices and link-time optimisation. It is much better to get in the habit of doing things correctly - if you want to access the data as 16-bit or 32-bit blocks (for checksums, for copying data, for passing in bulk to external memory, etc.) then use a union here. It might not be necessary, depending on the rest of your code (and by C's alias rules, the 8-bit raw is not needed), but I'm showing the principle here.
> >> When you want to store the data, run your crc calculation over the >> raw16[] from index 4 upwards, and set it in the crc field.&#4294967295; Set the >> magic field with a different fixed value per program.&#4294967295; Use the >> dataStructVersion to track versions of the structure of the data - so >> that updated firmware can recognise old data and quietly update it. > > This is interesting.&#4294967295; Suppose you have a new struct version and you are > reading an old version from the memory.&#4294967295; How do you read it?&#4294967295; Do you put > it completely in RAM and after copy it in the final struct making the > changes from one version to the other?&#4294967295; In this case you need a double > space in RAM, for old and new version. >
That depends on the application. I'm not doing /all/ your work for you :-)
> Or you declare a "unsigned char reserved_for_future[1024]" big array in > the struct, so the CRC is calculated over the full area? >
Keeping some extra space for the future is a good idea. How much you use is a matter of taste.
> >> Set the count to a new number each time. >> >> Leave plenty of room to add new fields to your nvm structure. >> >> >>> >>> >>>>>>> This means I need to copy&paste an entire block everytime, even >>>>>>> for a >>>>>>> single byte change.&#4294967295; And this is similar to Flash approach, where I >>>>>>> *need* a sector erase before changing a single byte. >>>>>> >>>>>> Yes. >>>>>> >>>>>> Of course, it is possible to have multiple smaller blocks rather than >>>>>> just one big one. >>>>> >>>>> This is good to reduce writing time of a block of data. >>>>> In my case I have 2kB of data, around 62 32-bytes EEPROM pages, >>>>> that is >>>>> 5*62=300ms.&#4294967295; I don't like to block a task (in a cooperative OS) for so >>>>> long time. >>>> >>>> Your writing should be done in the background.&#4294967295; The background writer >>>> task is blocked - the system is not. >>> >>> Are you thinking of a preemption OS?&#4294967295; I'm using a cooperative OS. >>> >> >> It doesn't matter.&#4294967295; In a pre-emptive OS, your background writer is: > &#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; ^^^^^^^^^^^ > You mean cooperative.
Yes.
> >> >> static enum { idle, writing, checking, erasing } writerState; >> >> void doWriter(void) { >> &#4294967295;&#4294967295;&#4294967295;&#4294967295;switch (writerState) { >> &#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; case idle : >> &#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; if (writeTriggered) { >> &#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; writeTriggered = false; >> &#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; startWriting(); >> &#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; writerState = writing; >> &#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; } >> &#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; break; >> &#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; case writing : >> &#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; bool stillWorking = pollNVMdevice(); >> &#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; if (stillWorking) return; >> &#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; startChecking(); >> &#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; writerState = checking; >> &#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; break; >> &#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; case checking : >> &#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; bool stillWorking = pollNVMdevice(); >> &#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; if (stillWorking) return; >> &#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; startErasingNextBlock(); >> &#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; writerState = erasing; >> &#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; break; >> &#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; case erasing : >> &#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; bool stillWorking = pollNVMdevice(); >> &#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; if (stillWorking) return; >> &#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; writerState = idle; >> &#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295;&#4294967295; break; >> &#4294967295;&#4294967295;&#4294967295;&#4294967295;} >> } >> >> There - half your program is done :-) >> >> Cooperative multitasking does not mean busy waiting for long-running >> tasks.&#4294967295; It means using state machines, interrupt-driven SPI/I&#4294967295;C drivers, >> polling, etc., so that you can mix fast and slow tasks. > > Yes, ok. >
Reply by pozz June 16, 20182018-06-16
Il 16/06/2018 22:41, Richard Damon ha scritto:
 > On 6/16/18 1:38 PM, pozz wrote:
 >> Il 16/06/2018 15:20, Richard Damon ha scritto:
 >>> On 6/16/18 2:26 AM, pozz wrote:
 >>>> Il 16/06/2018 00:25, Richard Damon ha scritto:
 >>>>> On 6/15/18 4:54 PM, pozz wrote:
 >>>>>> Il 15/06/2018 19:11, Richard Damon ha scritto:
 >>>>>>> I typically also do something like this. The data structure is a
 >>>>>>> union
 >>>>>>> of the basic data structure with a preamble that includes (in very
 >>>> fixed
 >>>>>>> locations) a data structure version, checksum/crc, and if a
 >>>>>>> versioning
 >>>>>>> store a timestamp/data generation number. A 'Magic Number' isn't
 >>>>>>> often
 >>>>>>> needed unless it is a removable media as it will either be or 
not the
 >>>>>>> expected data, nothing else could be there (if the unit might have
 >>>>>>> different sorts of programs, then a piece of the data version would
 >>>> be a
 >>>>>>> program ID.)
 >>>>>>>
 >>>>>>> Often I will have TWO copies of the data packet.
 >>>>>>
 >>>>>> TWO copies in EEPROM or in RAM?
 >>>>>
 >>>>> As I described, normally both.
 >>>>
 >>>> So you are so lucky that you have abundant RAM.
 >>>
 >>> If you can't spare the space for the two buffers, then you become 
forced
 >>> to 'lock' the system (or at least the parameter table) for the entire
 >>> time of the flash write, otherwise you end up with corrupted (CRC 
error)
 >>> data blocks.
 >>
 >> Why?  You have a single datablock in RAM that must be transfered to the
 >> serial memory when changes occur.  You start an activity (a task) that
 >> performs the copy in background.
 >> If new changes occur during writing, the task can be restarted without
 >> problems, even on the same memory area.  I hope the changes aren't so
 >> frequent so finally you will have a valid datablock (with correct CRC)
 >> written in memory, without blocking the main application.
 >>
 >> Why do you need an additional datablock to avoid blocking task?
 >>
 >
 > First, often in the parameter block for me are cumulative usage stats,
 > so the parameter block will be automatically saved on the controlled
 > shutdown (like the use flipping the off switch) and maybe automatically
 > after sufficient or significant usage. Yes, I can lose some usage on an
 > unexpected turn off (pull the plug), but some units may even have enough
 > power reserves to save in this situation.
I see, so in your situation the usage stats parameters changes more 
frequently than the writing time of datablock.  So you need to freeze a 
datablock for saving, but allowing the application to continue updating it.

As usuale, different requirements lead to different solutions.


 >>> It also makes writing the format uprev code much easier.
 >>> You comment about how to update 'in place' becomes a REAL question 
which
 >>> needs to be answered, and that answer will generally require detailed
 >>> understanding of how the elements are physically stored and making sure
 >>> that you do things in the right order (which may require limiting the
 >>> optimizations the compiler can do for that code, as you are likely 
to do
 >>> actions the Standard defines as Undefined Behavior).
 >>
 >> Yes, solving the problem of converting an old layout in a new completely
 >> different layout is very complex without an additional structure.  The
 >> in-place conversion would be very difficult and depends on the changes.
 >>
 >> I think you can avoid all this stuff, as I said, changing the datablock
 >> layout with care (adding parameters at the end, without changing the
 >> first/old layout).
 >
 > Yes, you can put off the need with controlled changes, but sometimes you
 > just build up enough cruft, that a re-layout makes sense. Maybe on the
 > really small processors, your tasks are simpler and more stable. I find
 > I want to allow for the possibility that at some point the structure may
 > need to be rearranged, and often I want a unit to be able to be upgraded
 > and preserve its settings.
In my case such big redesign of the memory layout can be managed with a 
reset to default settings.  Instead of implementing a complex conversion 
function.


 >>>>>>> Often organized something like this:
 >>>>>>>
 >>>>>>> struct SystemParameters {
 >>>>>>>          // List of data parameters that need to be saved
 >>>>>>> } parms;
 >>>>>>>
 >>>>>>> union {
 >>>>>>>         uint8_t    raw_data[DATA_SIZE];
 >>>>>>>         struct {
 >>>>>>>             struct StandardHeader header;
 >>>>>>>             struct SystemParameters parms;
 >>>>>>>
 >>>>>>> } flash_parms;
 >>>>>>>
 >>>>>>> /* Check that sizeof(flash_parms) == DATA_SIZE, and that 
DATA_SIZE is
 >>>>>>> a multiple of the flash sector size */
 >>>>>>
 >>>>>> Do you need raw_data[] array only to set a well-known size to
 >>>> flash_parms?
 >>>>>> Do you alloc an entire (or a multiple of) flash sector in RAM?
 >>>>>> Even if
 >>>>>> your params in flash take only 1/5 or 1/2 or 1/10 of a sector?
 >>>>>>
 >>>>>
 >>>>> One purpose of the raw_data array is to fix the size of the flash 
data.
 >>>>
 >>>> Sincerely I can't understand why you need a fixed data size in RAM.
 >>>> Suppose release 1.0 needs 2kB, but I'm so smart to provide a double
 >>>> space for new params in future releases. So I decide to use 4kB blocks
 >>>> *on the serial memory*.  Maybe this big space will never be used 
in all
 >>>> the future releases.
 >>>>
 >>>> Why the hell I should load all 4kB datablock in RAM at startup? 
IMHO it
 >>>> is sufficient to load only 2kB.
 >>>>
 >>>> The only reason I see is to simplify the checksum calculation.
 >>>> CRC must be calculated over the entire fixed-size 4kB block, otherwise
 >>>> how to know the datablock size if it could change from one release to
 >>>> the other?
 >>>> However you can calc the checksum of 4kB block by reading small blocks
 >>>> multiple times (for example, 16 sections of 256 bytes).
 >>>> After CRC validation passed, you can read in RAM *only* 2kB of 
data that
 >>>> the application 1.0 needs.
 >>>>
 >>>> I know, maybe this process is slower, but it happens only at startup.
 >>>> During writing of 4kB datablock, the checksum can be calculated
 >>>> on-the-fly.
 >>>>
 >>> A big reason for having a copy in RAM being an exact copy of the flash
 >>> memory is design isolation. It allows me to define a checksum 
routine to
 >>> compute the value of an arbitrary buffer, and flash routines that
 >>> transfer arbitrary data without it needing full understanding of the
 >>> parameter storage.
 >>
 >> Yes, your earn some cents on implementation of some functions.
 >>
 >>
 >>> As to the use of extra memory, If I anticipate that I am going to
 >>> possibly need a 4k parameter block in the future, but don't have 
the ram
 >>> memory now to read in the 4k block, most assuredly in the future when I
 >>> do need that bigger parameter block, I am not going to have more ram on
 >>> THIS processor. Unless the flash memory is a removable media (which
 >>> presents a totally different set of issues) there is no need to reserve
 >>> the extra space for this processor.
 >>
 >> When you reserve 4k block for future uses, you don't know if it will be
 >> REALLY used in the future.  You are right when you say "no space now, no
 >> space in the future", but are we sure we can know if we have space 
or not?
 >>
 >> I don't know if my approach is correct, I usually don't use heap memory,
 >> so I have only static data and stack.  It's very difficult to calculate
 >> how much space you need for stack to avoid stack overflow in ANY
 >> condition.  So I end up to reserve all the free RAM space to stack,
 >> where the free space is total RAM minus static allocation.
 >
 > I follow roughly similar guidelines. The one exception is that I allow
 > the use of dynamic memory that is allocated during 'startup'. Also, I
 > find it useful to use a lightweight RTOS, so I don't have a single
 > 'system stack', but several task stacks, so I do need to figure out the
 > space requirements and can't just say it gets everything. You do run
 > tests and check how much of the allocated space is used (stack space is
 > prefilled with a funny pattern, and you see how much remains with that
 > pattern.
 >>
 >> So I try being parsimonius when allocate global static variables, mostly
 >> if they are big.
 >> Reserving some space for extra not used memory will sacrify stack space.
 >>
 >> Maybe the stack space is sufficient even with extra padding in RAM, who
 >> knows.  At first I try to do all my best to avoid useless big static
 >> allocation in RAM.
 >>
 >> At the contrary, what happens if you notice that your stack is critical
 >> when padding is in RAM?  Do you prefer to reduce the datablock *padding*
 >> size or to recode some functions to avoid having the padding at all in
 >> the RAM?
 >>
 > When RAM space gets tight, you need to sit down and look at ALL RAM
 > utilization and compare it to your initial estimates. You need to do
 > this sort of estimate at the very beginning of the project so you know
 > it should be feasible to begin with. You need to be able to flag that
 > there is a serious risk in implementation before the project gets too
 > far, and perhaps re-target to a better processor.
 >>
 >> I think there is another approach to keep checksum functions simple and
 >> avoid having padding space in RAM.
 >> The problem here is the space where CRC is calculated.  At startup we
 >> don't know the size of data, because it could belong to an old or new
 >> layout, so we can't calculate CRC on the right area.  We should know the
 >> data layout version before calculating CRC, but version code is *in* the
 >> CRC area.
 >>
 >> We could think to pull the layout code outside the CRC area and protect
 >> it by duplicating.
 >>
 >> typedef struct {
 >>    uint8_t layout_version;
 >>    uint8_t layout_version_check;
 >>    uint16_t crc;
 >>    /* The CRC is calculated starting from the following byte */
 >>    uint16_t counter;
 >>    union {
 >>      nvmdatav1_t datav1;
 >>      nvmdatav2_t datav2;
 >> }
 >>
 >> At startup the layout version is read and checked.  Then CRC is read and
 >> checked against the correct size that depends on the layout version. The
 >> CRC checksum is simple.
 >>
 >> if (datablock.layout_version == datablock.layout_version_check) {
 >>    size_t s = datablock.layout_version == 1 ?
 >>               sizeof(nvmdatav1_t) :
 >>               sizeof(nvmdatav2_t);
 >>    if (datablock.crc ==
 >>          calc_crc(&((const unsigned char *)&datablock)[5]),
 >>               s) {
 >>      if (datablock.counter > prev_counter) {
 >>        /* New valid datablock found */
 >>      }
 >>    }
 >> }
 >>
 >> No padding at all (in RAM and in serial memory), simple functions.
 >>
 >> Of course padding must be taken into account during writing to separate
 >> the blocks in memory for redundancy and wear-leveling.
 >>
 >>
 >
 > There is no problem with the version code being inside the CRCed area.
 > In fact you really want it checked as if something corrupted the version
 > code, you really want the block invalidated. The version should be in
 > the early header (so parm changes can't affect it, one reason I define a
 > separate struct for it that is put in the flash_parms structure. It also
 > says that the flash parameter saving can be a canned routine that
 > defines that structure and uses the parameter definition from the
 > application code.
I suggested to move version code outside CRCed area because you need to 
read version code *before* check CRC, because you need to know the size 
of CRCed area and this depends on the version code.

Before looking inside the CRCed area, I think it would be better to 
check the CRC itsel.


 > I suppose this is one reason I don't want the size to change, at that
 > point the 'canned' code has to be taken out of the can, as it need more
 > application knowledge to know the parameter size. The way I do it, I can
 > have a parms.h that defines the ram parameter structure, and a #define
 > for the size of the block to use in flash for it, and the canned routine
 > does most of the work to find the best saved block and to save the
 > parameters to flash on command. It does need a callback into the
 > application layer with the block to use to provide the application layer
 > up-rev of the parameter data.
If this is your problem, you can save the size of CRCed area, besides 
version code and so on.  The 'canned' code will know the size when the 
application calls the save() function, specifying the size as a parameter.

At startup the 'canned' code reads the size and check the CRC, without 
having to know much more details from the application.