Author Topic: [Atmega 0-series] EEPROM workings  (Read 3472 times)

0 Members and 1 Guest are viewing this topic.

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 17940
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
[Atmega 0-series] EEPROM workings
« on: July 03, 2021, 11:18:42 am »
I'm looking at how I would go about saving data to the EEPROM of an ATmega1608.

The text talks a lot about instructions that are I guess assembler instructions. I'm rather confused about how I address the memory space and where I stick the data using C. There is a command register but the only data register is for the UPDI to access fuses and the address register is for the address of the location last updated.

So how do I actually put what I want where I want it. If I read correctly piecing together the commands explanation and the explanation of "Page Buffer Load" together by writing to the EEPROM address I actually implicitly tell the buffer than I am writing to that page and where in the page and I can do that several times to fill different parts of the buffer before giving the command to write that buffer to the EEPROM.
 

Offline T3sl4co1l

  • Super Contributor
  • ***
  • Posts: 22231
  • Country: us
  • Expert, Analog Electronics, PCB Layout, EMC
    • Seven Transistor Labs
Re: [Atmega 0-series] EEPROM workings
« Reply #1 on: July 03, 2021, 11:35:53 am »
#include <avr/eeprom.h>

That's all you need to know (technically speaking -- read the header).  The eeprom_read and write functions do it all for you. :-+

Tim
Seven Transistor Labs, LLC
Electronic design, from concept to prototype.
Bringing a project to life?  Send me a message!
 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 17940
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: [Atmega 0-series] EEPROM workings
« Reply #2 on: July 03, 2021, 11:37:09 am »
Is that for the atmega 0-series as well? looking at the AVR-LIBC site I never see the newer parts mentioned.
 

Offline T3sl4co1l

  • Super Contributor
  • ***
  • Posts: 22231
  • Country: us
  • Expert, Analog Electronics, PCB Layout, EMC
    • Seven Transistor Labs
Re: [Atmega 0-series] EEPROM workings
« Reply #3 on: July 03, 2021, 11:54:39 am »
If you have the IO header for the device, you ought to have everything else as well.

Oh yeah, 0-series is the ones that are like XMEGA but mainline (and newer? I forget).  EEPROM is memory mapped so you can read it like any other memory (read_eeprom_X can potentially be optimized down to an ordinary load).  At a glance, I don't see a flag to toggle this; for some reason XMEGA has this option, to enable the EEPROM memory mapping window or not (possibly because security, Idunno?), or do it the old fashioned way (via NVM registers).

Writing should be something like, write to any addresses within a page, in the EEPROM space, then hit the write bit (possibly through a macro because CCP?) and wait.

If libc isn't supported and current, YMMV with whatever toolchain/libs you're using.

Tim
Seven Transistor Labs, LLC
Electronic design, from concept to prototype.
Bringing a project to life?  Send me a message!
 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 17940
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: [Atmega 0-series] EEPROM workings
« Reply #4 on: July 03, 2021, 11:59:24 am »
Yea it's the Xmega rehashed and the silly sods in marketing thought it would be a good idea to reuse the name of the most popular AVR series and so made it utterly impossible to find any information on google as for all the google-foo you can use an ATMEGA is still an ATMEGA and adding 0-series to the terms does not really do anything.

My guess is that they will push the old AVR's out as soon as they can and these do have a few sweet features. I'm only really using these right now as I can't get anything else and they will more than do for my application so not much point in splurging on an ARM.
 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 17940
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: [Atmega 0-series] EEPROM workings
« Reply #5 on: July 03, 2021, 02:02:43 pm »
As there is now an Arduino board that uses a 0-series mega I guess that the library was updated to include these chips.
 

Offline cv007

  • Frequent Contributor
  • **
  • Posts: 850
Re: [Atmega 0-series] EEPROM workings
« Reply #6 on: July 03, 2021, 05:29:49 pm »
I think you have to be using a somewhat up to date libatmega1608.a which is from the microchip 'packs'. You can then use the eeprom.h header as before. At some point they corrected the problem they had in the eeprom code where it was using an 'old' xmega nvm command to do the eeprom erase/write.

There is a way to simplify the reading.writing of eeprom since they went through the trouble of memory mapping it, but requires a little work.

You can create a header file like so-

#pragma once //eeprom01.h
#include <avr/io.h>

__attribute((always_inline)) inline
void eememcpy(uint16_t src, uint16_t eemem, uint8_t size){
    volatile uint8_t* s = (volatile uint8_t*)src;
    volatile uint8_t* d = (volatile uint8_t*)eemem;
    while( size-- ){
        *(volatile uint8_t*)0x1400; //ee read, blocks if busy
        *d++ = *s++;
        _PROTECTED_WRITE_SPM( NVMCTRL.CTRLA, 3 );
    }
}

#define EEWRITE(eemem,val) \
    { typeof(eemem) v__ = (typeof(eemem))val; eememcpy( (uint16_t)&v__, (uint16_t)&eemem, sizeof(eemem) ); }

#define EEMEM __attribute__((section(".eeprom")))
#define USERROW __attribute__((section(".userrow")))


Then modify the linker script avrxmega3.xn to give the eeprom section a vma address that matches the eeprom memory mapped address, and keep the lma address 0 based for the hex file, userrow the same (is just 1 more eeprom page)-


    .eeprom 0x1400 : {

        KEEP(*(.eeprom*))
        PROVIDE( __eeprom_end = . ) ;

    } AT>eeprom

    .userrow 0x1300 : {

        KEEP(*(.userrow*))
        PROVIDE( __userrow_end = . ) ;

    } AT>userrow



Now you can use eeprom-

#include "eeprom01.h"

EEMEM uint16_t a = 0x1234;
EEMEM struct { uint8_t pinbm; PORT_t* port; } eedata;
USERROW uint32_t bootcount;

int main(){
    EEWRITE( bootcount, boutcount+1 ); //do not use boutcount++ as that will write to page buffer, even though would be harmless in this case
    EEWRITE( a, a+1 );
    //since can read eeprom without special functions, the above writes 0x1235 to a

    EEWRITE( eedata.port, &PORTC  );
    EEWRITE( eedata.pinbm, 1<<2  );
    eedata.port->DIRSET = eedata.pinbm;

    while(1){}
}


This is not really tested much, so the EEWRITE macro could have odd use cases that may cause an error, but the underlying eememcpy should be usable in any case (and requires work to use, which EEWRITE normally does for you). The cost of having the code in a header is it may get inefficient when used often, but the regular eeprom library requires setup to its calls so may not be that much better. Also, this is not as efficient as possible because you can write a series of bytes to the page buffer before an nvm erase/write command, but this code eliminates having to deal with crossing page boundaries. I would normally balk at that, but the newer avrDx series can only write a byte at a time so would be the same as the Dx (they have no page boundaries, but means the nvm has to treat each eeprom byte write as a separate operation).





 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 17940
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: [Atmega 0-series] EEPROM workings
« Reply #7 on: July 03, 2021, 05:52:22 pm »
No idea how i modify the linker script, if it's all that I may as well just write my own code. I will be developing in mplabx or atmel, sorry..... microchip..... actually microsoft! studio so I should have the pack. From what I see the code provided just writes to address locations, nothing fancy like variables in EEPROM being handled so it feels like not too mich work to write my own code if required.
 

Offline cv007

  • Frequent Contributor
  • **
  • Posts: 850
Re: [Atmega 0-series] EEPROM workings
« Reply #8 on: July 03, 2021, 06:11:50 pm »
Quote
if it's all that I may as well just write my own code
Well yeah, that's always the idea. There is nothing complicated going on, and a read through the datasheet nvmctrl chapter would have pointed that out.

Quote
No idea how i modify the linker script,
Its a text file, with the name specified. I didn't specify its location as finding a file on your pc is not difficult.

I put the info on github, you can use it, or write your own code, or use the original eeprom.h and eeprom library.
https://github.com/cv007/Avr01_EEprom
 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 17940
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: [Atmega 0-series] EEPROM workings
« Reply #9 on: July 03, 2021, 06:50:52 pm »
Quote

if it's all that I may as well just write my own code
Well yeah, that's always the idea. There is nothing complicated going on, and a read through the datasheet nvmctrl chapter would have pointed that out.




Which I did as I explained in the first post where I explained how I pieced together two separate pieces of text to understand the full process.
 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 17940
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: [Atmega 0-series] EEPROM workings
« Reply #10 on: July 03, 2021, 07:09:25 pm »
What is bugging me is what happens if I want to send the contents of a variable larger than 1 byte? If I send a 16 bit value to an address does the upper byte get written (I think it does) but at what address? which byte goes where?
 

Offline cv007

  • Frequent Contributor
  • **
  • Posts: 850
Re: [Atmega 0-series] EEPROM workings
« Reply #11 on: July 03, 2021, 07:26:48 pm »
Quote
The text talks a lot about instructions that are I guess assembler instructions. I'm rather confused about how I address the memory space and where I stick the data using C. There is a command register but the only data register is for the UPDI to access fuses and the address register is for the address of the location last updated.
There is no assembler instructions mentioned or needed, and updi access to fuses is not involved.

The very basics using only the c language-

//check if eeprom busy
*(volatile uint8_t*)0x1400 ; //reads eeprom 0x1400, an eeprom read blocks if eeprom busy, so this can replace a busy wait loop
//write to page buffer, using address of eeprom (address listed in chapter 7)
*(volatile uint8_t*)0x1400 = 0; //write 0 to page buffer[0], ADDR is set to 0x1400
*(volatile uint8_t*)0x1401 = 1; //write 1 to page buffer[1], ADDR is set to 0x1401
//write page buffer to eeprom, using ADDR set previously to determine where to write (which page in eeprom in this case)
*(volatile uint8_t*)0x34 = 0x9D; //CCP = 0x9D, nvmctrl.ctrla is ccp/spm protected
*(volatile uint8_t*)0x1000 = 3; //nvmctrl.ctrla = 3, command ERWP erases and write, for eeprom only bytes written in page buffer are updated
//cpu can continue on eeprom writes
//page buffer is cleared when done


Quote
What is bugging me is what happens if I want to send the contents of a variable larger than 1 byte? If I send a 16 bit value to an address does the upper byte get written (I think it does) but at what address? which byte goes where?
No different than you writing to ram. Do you worry about byte order when writing to a uint16_t in ram? The above simple example could be changed to *(volatile uint16_t*)0x1400 = 0x0100 with the same results. The only thing you cannot do is cross a page boundary without doing a write command, which is why my eeprom01.h header writes a byte at a time so you do not ever have to deal with page boundaries. The macro in the header also treats the var as a series of bytes so the uint16_t write for example writes the first byte to the page buffer, does the nvm write, then writes the second byte to the page buffer and another nvm write command.
« Last Edit: July 04, 2021, 10:53:35 am by cv007 »
 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 17940
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: [Atmega 0-series] EEPROM workings
« Reply #12 on: July 04, 2021, 08:48:40 am »
Quote
No different than you writing to ram. Do you worry about byte order when writing to a uint16_t in ram? The above simple example could be changed to *(volatile uint16_t*)0x1400 = 0x0100 with the same results. The only thing you cannot do is cross a page boundary without doing a write command, which is why my eeprom01.h header writes a byte at a time so you do not ever have to deal with page boundaries. The macro in the header also treats the var as a series of bytes so the uint16_t write for example writes the first byte to the page buffer, does the nvm write, then writes the second byte to the page buffer and another nvm write command.

Yes but I don't write the code that puts variables into RAM, I just write/read variables. How the compiler deals with putting in 2 btyes that make one value and taking them out again presented to me as one value is invisible to me. I guess this is a lot of what the eeprom library does for me but I have to deal in addresses instead of variable names. the closest I could get to a name is to #define the address with a name.
 

Offline T3sl4co1l

  • Super Contributor
  • ***
  • Posts: 22231
  • Country: us
  • Expert, Analog Electronics, PCB Layout, EMC
    • Seven Transistor Labs
Re: [Atmega 0-series] EEPROM workings
« Reply #13 on: July 04, 2021, 09:05:01 am »
You define the memory space just like any other.  Here's an example,

Code: [Select]
/** Zero intercept and gain for channels 0-3 */
const cal_t SetChCalibration[] ADDR(12) EEMEM = {
{121, 25031},
{ 56, 25068},
{243, 24583},
{280, 24707}
};

/** Presets for channels 0-3 x pushbuttons 1-4 */
const uint8_t ButtonPresets[] ADDR(28) EEMEM = {
// BLU WHI RED GRE
0, 15, 0, 0, // PB1
40, 40, 40, 40, // PB2
70, 100, 70, 70, // PB3
100, 100, 100, 100 // PB4
};

You can use structs (the first one) and arrays (both) just like anything else, and you can even assign a fixed location (that won't be rearranged by the compiler or linker) with the GNU ADDR() directive (or equivalents for other compilers).  EEMEM is a macro from <eeprom.h> which sets the attribute for which memory space (section) to use.

Using ADDR, beware that you get no help placing those objects, you're allocating them yourself.  It's probably not a bad idea once a project is mostly done -- you can keep settings between programming cycles, without corrupting or overwriting them every time objects are added.  It's not at all required, just keep in mind that EEPROM isn't overwritten unless you opt to do so, so it can easily get out of sync with what the program is expecting.

And yes, you can assign default values as above; these go into the .eeprom section of the ELF, or the .eep file if separate.  Either way is fine with normal programming tools.

The only thing you need to beware of, on the user/programming side, is to use the correct function to R/W the data.  In this case, cal_t presumably needs a dword or two word operations per element.  Or you can use eeprom_read_block((void*)cal_read, (void*)SetChCalibration[idx], sizeof(cal_t)) .

Tim
« Last Edit: July 04, 2021, 09:08:39 am by T3sl4co1l »
Seven Transistor Labs, LLC
Electronic design, from concept to prototype.
Bringing a project to life?  Send me a message!
 

Offline cv007

  • Frequent Contributor
  • **
  • Posts: 850
Re: [Atmega 0-series] EEPROM workings
« Reply #14 on: July 04, 2021, 11:27:10 am »
Quote
I guess this is a lot of what the eeprom library does for me but I have to deal in addresses instead of variable names.
The reason for the linker script change is to give the eeprom variables (marked as EEMEM, which puts them in the .eeprom section) a virtual address that matches the actual eeprom addresses. Just like ram variables. Your ram variables get their addresses from the linker script also, and on the avr0/1 the ram start addresses change for each mcu of a different ram size. You don't worry about where the ram is located because the linker script is giving the addresses, and the compiler is generating code based on those addresses.

In the case of eeprom.h and its eeprom library functions, they are still doing what they have done in the past and assume the eeprom as a 0 based address. In the past there was no direct access to eeprom so had to go through eeprom functions anyway. For the avr0/1 and others similar, the eeprom has an address that is not 0 based so now (still 0 based in linker script) have to convert the 0 based addresses being used to the actual eeprom addresses. This means you have to go through the eeprom library functions to read and write, or you would have to do your own address conversion from 0 based to 0x1400 based address. They have gone through the work of making the eeprom memory mapped, but want to keep the old way of accessing eeprom because... Windows 3.1 did it that way.

EEMEM char a;
or
__attribute(( section(".eeprom") )) char a;
//address of a is 0, as .eeprom section starts at 0 according to linker script

a = 1; // ldi r24, 1; sts a, r24 (sts 0x0000,r24)
//not good, as 0x0000 is VPORTA.DIR on avr0/1
//so need to use eeprom library to convert 0 to 0x1400, or convert on our own


//with linker script specifying VMA address of 0x1400 for .eeprom section
EEMEM char a;
or
__attribute(( section(".eeprom") )) char a;
//address of a is 0x1400, as .eeprom section starts at 0x1400 according to linker script

a = 1; // ldi r24, 1; sts a, r24; (sts 0x1400,r24)
//ok, we just wrote to page buffer, now need to do ERWP command to write page buffer to eeprom

//can also read eeprom directly as addresses used for .eeprom are correct
VPORTA.OUT = a; // lds a,r24; out 0x01,r24;  (lds 0x1400,r24)


So, you either choose to use the old method and use the eeprom library provided, or you do something on your own.



« Last Edit: July 04, 2021, 02:20:41 pm by cv007 »
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6742
  • Country: fi
    • My home page and email address
Re: [Atmega 0-series] EEPROM workings
« Reply #15 on: July 06, 2021, 02:05:34 am »
Linker script (ldscript) decides the address range of each ELF section.  It can either put them at fixed locations (like it does for e.g. the interrupt table), or concatenate them. It can even concatenate code in multiple sections to form one consecutive chunk of machine code; this is exactly how AVR startup (prior to main()) is generated in avr-libc.  It can also compute simple arithmetic expressions (most typical example being section sizes), and emit those into the final binary.

Do not confuse ELF sections with ELF memory segments or memory regions.  An ELF memory segment is just a continuous address range with a set of logical attributes (like read-only, or executable) the linker tries to honor.  The segments are defined in the MEMORY command of the linker script.  In general, linker scripts are easier to use than many initially think.  While GCC and LLVM/Clang toolchains use different linkers, they use the same linker scripts.  I recommend looking at Linker Scripts at the OSDev Wiki.

A very common use case is to provide the start address of something (like the start and end addresses of a section) to the compiled code using the and end addresses of a section address, simply by defining them in the linker script.

In your C and C++ code (using gcc, g++, or clang), you can use the __attribute__((section ("name"))) to put variables, constants, and functions in a specific section.  The compiler and linker will manage their addresses.

ATmega1608 is of the megaAVR0 family, which has an unified 16-bit address space accessible with the standard instructions; see Figure 7-1. Memory map in the datasheet on page 38.  I/O registers are at addresses 0x0000-0x003F, extended I/O at 0x0040-0x0FFF, NVM I/O registers and data at 0x1000-0x13FF, EEPROM starting at address 0x1400, Internal SRAM starting at 0x2800, and Flash starting at address 0x4000.

Your linker script should contain line
    eeprom (rw!x) : ORIGIN = 0x1400, LENGTH = 0x100
or equivalent in the MEMORY command, and
    .eeprom:
    {
        __eeprom_start = .;
        KEEP(*(.eeprom*))
        __eeprom_end = . ;
    } > eeprom
or equivalents in the SECTION command, so everything in sections whose names start with .eeprom is put into one output section called .eeprom (thus combining content from multiple sections into a single section), and placed in the eeprom memory segment.  Note that here, .eeprom refers to the section –– note the full stop at the beginning! ––, and eeprom –– without a full stop! –– refers to the memory segment.  The two symbols, __eeprom_start and __eeprom_end, will have addresses corresponding to the start of the EEPROM region and the first unused (by any global variables or constants) address in EEPROM. Your C or C++ code can access these via &__eeprom_start and &__eeprom_end, as long as you declare them in your code via
    extern TYPE  __eeprom_start, __eeprom_end;
noting that TYPE is entirely up to you; if you don't have any need for the address-of to yield a specific type of a pointer, use unsigned char.

Basically, the EEPROM on the ATmega1608 consists of eight objects (pages) of 32 bytes each.  You can pack 32 bytes, 16 two-byte objects, 10 three-byte objects, 8 four-byte objects, 6 five-byte objects, 5 six-byte objects, 4 seven-byte objects, 4 eight-byte objects, 3 nine-byte objects, 3 ten-byte objects, 2 objects between 11 and 16 bytes, or a single object between 16 and 32 bytes in size.  Crossing a page boundary within an object means an update has to update two EEPROM pages, so try to avoid that if at all possible.

Unfortunately, the C/C++ toolchain does not have a way to express "pack these objects in this region, but do it so that none crosses an 2k-byte boundary". 
What I do, is add an intermediate step in my build toolchain, just after compiling, but before linking, that examines the ELF object files.  The object files describe all objects to be placed in the .eeprom section, and their sizes.  I then have a script (Python or Awk for me, but whatever floats your boat) do the ol' Optimum Packing Problem work (for 8 containers of 32 bytes each); this is small enough Brute Forcing it will be essentially instantaneous, so no complicated work here.  The script then simply generates derivatives of the source ELF object files, with only the section names for these objects changed from .eeprom to .eeprom0 through .eeprom7, reflecting the respective EEPROM page.  (If the script is not utterly stupid, it also means that as a programmer, the section name you use determines whether that object will always be in that EEPROM page, or if the script has to pick the page to put it in. Given additional information like "estimated write rate" per object, and enough EEPROM to do so, the script can also make better decisions, like putting most often modified objects in their own pages, or combine them with the most rarely used ones, so that on average, all EEPROM pages have approximately the same write rate.)



Anyway, for each chunk of bytes within a single EEPROM page, you start by making sure the Page Buffer refers to the correct EEPROM page.

You do that by writing 255 to any byte within that page.  Writes to the Page Buffer do not set the page buffer contents; they do a binary AND with the page buffer contents and the written value.  In other words, when you write a value, only its zero bits are copied over to that byte in the Page Buffer.  There is only 32 bytes of Page Buffer, and the NVMCTRL keeps track which page it refers to. So, writing 255 to anywhere in the EEPROM does not change any data or contents of the Page Buffer, it only makes sure the Page Buffer refers to that page.

Next, you use the NVMCTRL to issue the Page Buffer Clear command (9.3.2.4.4, takes seven cycles).  After this, the Page Buffer contains all ones.

Next, you write the new data to the target EEPROM address normally, essentially a memcpy() (but don't use one, just in case it is "optimized": use a loop, and make sure the destination is volatile unsigned char to stop the compiler from doing stupid assumptions).  Because the Page Buffer values are all ones, this sets them to the desired new values, even though the operation is a binary AND and not a set.  We must take care to not cross a page boundary, howeve!

Next, we copy the existing data for the rest of the page.

If the new data did not start at the 32-byte boundary, then there is one or more bytes before the new data.  Similarly, if the new data did not end at the 32-byte boundary, there is one or more bytes after the new data.  We do this by simply reading and writing the EEPROM data in place, i.e. given pointer p to such a byte, we do *(volatile unsigned char *)p = *(const volatile unsigned char *)p;.

Compilers are idiots and [... rant voluntarily pre-emptively deleted ...] so one may have to resort to assembly, though.  Not a big hassle, both GCC and LLVM/Clang support extended inline assembly, so writing a suitable low-level function for AVR assembly is actually easy.  It boils down to writing to each of those 32 bytes in the EEPROM page, either using the current or the new data.  It is easiest to do in three pieces, where the prefix and suffix parts are optional, and the middle part is the new data.

When you have done the above, you have tricked the Page Buffer into containing the data you want the EEPROM page to have.

Next, you use the NVMCTRL to issue a Erase and Write Page command (9.3.2.4.3).  The CPU will be halted for the duration of the operation, so this update will be atomic.
The only thing that can mess it up is something interrupting the above copy stuff, by trying to *write* to anywhere in the EEPROM.  Any interrupt or such *reading* from the EEPROM does absolutely no harm.

If you want example code, describe to me what you want to store in the EEPROM, and I can chuck together a help function.  Making a generic one that just writes data to the EEPROM is not what you should be interested in, because that is the wrong way to use EEPROM anyway.  If you want to treat it as a log for a data object, and spread it all over the EEPROM instead of using a fixed page for it, that takes a different approach altogether, because turning EEPROM bits to 1 is costly, but to 0 cheap: it boils down to encoding the data so that an all ones bit pattern is invalid, and to prefer sequences where updates that only turn ones to zeros but not zeros to ones.  Since there are only 8 pages, we only need to reserve four bits per page (out of the 32 bytes, or 256 bits, per page) to tell which one is the latest one.  If we initialize all EEPROM pages to all ones, and use the latest one for the new value (overwriting the first entry having all ones in that page), we can do NVMCTRL Write Page commands without wearing them down with Erases.  We only need to do an Erase whenever we run out of room in the newest page, and don't have any pages left that are still all ones; and then we erase the oldest one of them (that's where that four-bit counter comes in) to all ones.  However, if we do that Erase at least one step before we run out of entries, we won't need to reserve the four bits per page: we know the latest page is the one with unused entries, or if all pages are either full or empty, the page before the first empty page is the latest one.  Using an extra bit in the counter lets us use the pages in a random order (of consecutive, incremented counter values, excluding the all-bits-ones counter value that indicates an unused page), since the last one in the consecutive sequence is the latest one, and the oldest one is the first one in the consecutive sequence.  (For a robust implementation that detects an EEPROM page is no longer working like it should, this approach allows ignoring such a page with just a shorter log.  I'd reserve extra 8 bits per EEPROM page for a Page Broken map, though, with a zero bit indicating a broken page, in this case.)

So many options, so many good patterns, and so long a description you don't have the time to read about them.  Oh well.  :-//
« Last Edit: July 06, 2021, 02:11:50 am by Nominal Animal »
 
The following users thanked this post: Silenos

Offline cv007

  • Frequent Contributor
  • **
  • Posts: 850
Re: [Atmega 0-series] EEPROM workings
« Reply #16 on: July 06, 2021, 01:14:31 pm »
Quote
Next, we copy the existing data for the rest of the page.
You do not need to fill the page buffer for eeprom, as the nvm will only update the eeprom from the page buffer bytes that were written. If you write to only 1 byte in the page buffer, only that 1 byte is written to eeprom when the ERWP command is given.

Although you set the eeprom memory region to 0x1400, you then have also prevented your programmer from using any eeprom init data in the hex file as the programmer was looking for the 0x810000 based addresses in the hex file for the eeprom data (I'll assume the hex file is being used by programmer, but is possible something smarter is being used that uses the elf file and can see the .eeprom section and does the right thing). Instead, you leave the region address the same as before, provide a virtual address of 0x1400 (and 0x1300 for user_signatures which I rename in the previous example to .userrow/userrow), then use the region address as the lma address (AT>) so the hex file ends up using the 0x810000 based address for the init data.

For most uses, I would skip the whole page crossing problem and write a byte at a time, at least until there is a need to write a bunch of bytes at a time. The cpu keeps running so interrupts will keep working, and any isr code will block on an eeprom read if eeprom is busy no matter what write method you choose. So the worst case scenario is you want to write all 256 bytes of eeprom, and are now blocking on that function which could take ~1.2sec, although interrupts are still working.

In C++, you can make eeprom writing transparent-
https://godbolt.org/z/W86r9o7oh
but your data type cannot be a struct. In this case the linker script could be left as-is since the compiler is doing the address translation at compile time.
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6742
  • Country: fi
    • My home page and email address
Re: [Atmega 0-series] EEPROM workings
« Reply #17 on: July 07, 2021, 12:01:10 am »
Quote
Next, we copy the existing data for the rest of the page.
You do not need to fill the page buffer for eeprom, as the nvm will only update the eeprom from the page buffer bytes that were written. If you write to only 1 byte in the page buffer, only that 1 byte is written to eeprom when the ERWP command is given.
Have you verified this behaviour in practice?  Some sequences in the datasheet give me the same impression, but others seem to contradict.

We do need to reset the page buffer, because we cannot turn bits in the page buffer to 1, only to 0.  So, we have to do Page Buffer Clear command after selecting the correct page.

9.3.2.4.4 Page Buffer Clear Command in the datasheet (page 76) says "The contents of the page buffer will be all '1's after the operation".

9.3.2.4.1 Write Page Command on the preceding page says "The Write Page (WP) command of the Flash controller writes the content of the page buffer to the Flash or EEPROM.".

And earlier on that same page, indeed, "The EEPROM programming is similar, but only the bytes updated in the page buffer will be written or erased in the EEPROM."

So, exactly what does the Page Buffer Clear NVMCTRL command do: does it only reset all page buffer bits 1, or does it also mark some kind of internal per-byte marker for the EEPROM Write Page machinery to "no update needed", with any write to the page buffer resetting it to zero?  What does it mean by only the bytes updated?

Without knowing the internals, I think it is more likely that they are just comparing the existing value to the page buffer value instead of having an extra bit per byte, and use that to determine whether the EEPROM byte needs updating or not.  I could be wrong, and they might have those extra 32 bits (because those bits are cheap to maintain in their correct state via the page buffer write mechanism, and having them means EEPROM-to-Page Buffer comparisons do not need to be done, speeding up the update process).  I just do not know, and can't tell from the datasheet.

If the latter, then I certainly agree that there is no need to "copy" the unmodified data, as you said.  I just cannot tell from the datasheet alone.

I am only asking if you have verified this in practice, because if I had a megaAVR0 (or any megaAVR), it'd be a quick Arduino check to check which one it is: if the copy is not necessary, unrelated data on the same page are retained; if the copy is necessary, then using your scheme the unrelated bytes will always and all turn -1 (because of the Page Buffer Clear command, which in turn is needed whenever you want to turn any zero bits in EEPROM back to one).
« Last Edit: July 07, 2021, 12:03:14 am by Nominal Animal »
 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 17940
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: [Atmega 0-series] EEPROM workings
« Reply #18 on: July 07, 2021, 07:21:04 am »
If by default all bits are 1 then only 0's that differ need changing. It could be done on a bit by bit basis maximizing the life.
 

Offline cv007

  • Frequent Contributor
  • **
  • Posts: 850
Re: [Atmega 0-series] EEPROM workings
« Reply #19 on: July 07, 2021, 01:33:50 pm »
Quote
We do need to reset the page buffer
Unless you are doing something unusual, the page buffer clears on any erase/write so no need to worry about that. If you think you are somehow randomly writing to mapped eeprom in your code, then I guess you would want to clear the page buffer manually to make sure your page buffer is fresh, although its not going to be of much help if your other code still randomly writes to the page buffer while doing your eeprom writes.

Quote
Have you verified this behaviour in practice?
Yes, it works as described in the datasheet. Whatever the details are on how it determines which bytes were touched in the page buffer, don't really know or care. You write to a byte in the page buffer, that byte gets written to eeprom when you finish the job via an nvm command (and by 'written', I don't necessarily mean that is the result you will get since you may not be using ERWP). Using the ERWP command means you will get the eepom byte(s) of interest erased, then written, so you have no need to deal with figuring out when you need to erase. Writing 1 byte at a time means you do not have to deal with page crossing. If you want to do something other than 1 byte at a time with ERWP, then you will have to deal with pages, erasing, writing, and all the rest of the normal nvm flash writing things. I would rather live the limitation of the slower writing for multiple bytes just to keep it simple, as eeprom writing is a relatively infrequent event.

The c++ code I posted also works, but the compiler seems to gets confused when one of these ee vars is used as a fprintf argument- for example a %u should push 2 bytes on the stack, and if a uint8_t is the var it will push a 0 for the upper byte (uint8_t converted to uint16_t), but the same uint8_t as an eevar will only push 1 byte on the stack for some reason. A simple cast to a uint8_t fixes it, so not sure what is happening.
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6742
  • Country: fi
    • My home page and email address
Re: [Atmega 0-series] EEPROM workings
« Reply #20 on: July 07, 2021, 04:49:04 pm »
If you think you are somehow randomly writing to mapped eeprom in your code, then I guess you would want to clear the page buffer manually to make sure your page buffer is fresh
That, or if the code ever mixes EEPROM and Flash writes, for example.

(In general, I much prefer starting with the overly careful approach, and then paring it down to the minimum necessary.  In my experience, the opposite approach, starting with the minimum and assuming the corner cases never occur, just leads to implementations with fundamental issues that need a refactor or a full redesign and rewrite to fix.  Then again, a couple of decades of my experience comes from security-sensitive systems level stuff like service daemons and them maintaining proper privilege separation, making it fair to call my approach 'paranoid' :-//)

Like I said, I think it would be much better to discuss some particular need, an use case, than "writes to EEPROM" as the task: look at the uses of the tool, instead of the properties of the tool, to find out how to best wield it.

I do believe keeping it simple even at the cost of taking more time and so on is a good and proper approach. I just want it to be rock solid to start with, and have your approach be one possible branch of doing it, not the other way around.
 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 17940
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: [Atmega 0-series] EEPROM workings
« Reply #21 on: July 14, 2021, 06:04:29 pm »
Given how much RAM there is it makes sense to copy the EEPROM to RAM, do everything there then write it back to EEPROM when the thing is about to be turned off.
 

Offline T3sl4co1l

  • Super Contributor
  • ***
  • Posts: 22231
  • Country: us
  • Expert, Analog Electronics, PCB Layout, EMC
    • Seven Transistor Labs
Re: [Atmega 0-series] EEPROM workings
« Reply #22 on: July 14, 2021, 06:21:58 pm »
Sure, if you're doing frequent writes, shadow RAM is good.  Be very careful that you have enough warning, and hold-up time, on power down!

Or if you need more (capacity or write frequency), FeRAM is good, which I don't think AVR anything has it(??) but a bunch of MSP430s do, and there's always SPI/I2C/etc. external stuff.

Tim
Seven Transistor Labs, LLC
Electronic design, from concept to prototype.
Bringing a project to life?  Send me a message!
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf