XMEGA EEPROM access standards

Go To Last Post
15 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I have just gotten memory-mapped EEPROM access working with an ATxmega256A3, and I'm looking for suggestions & consensus on the "right" way to support this.

Memory-mapped access for the EEPROM is great -- it's certainly convenient, and it's very cool to be able pass pointers in EEPROM to runtime like memcpy() or printf(). The only trick is in getting the starting offset of MAPPED_EEPROM_START (0x1000) right.

Using a macro to add the offset at each reference not only blows the convenience, but you lose type info. You must either cast or have macros for each type.

So instead EEPROM variables need to have the start offset built in. I tried doing this by modifying the linker script for the base of the .eeprom section:

eeprom (rw!x) : ORIGIN = 0x801000, LENGTH = 4K

This does work to get variable offsets correct. However, the AVR Studio debugger will not recognize this section as EEPROM data and so will not initialize the EEPROM. (The .eep file is correctly generated so in programming mode you can initialize the EEPROM.)

To work around this I created a struct for all my EEPROM data. I put one variable of this type in the .eeprom section (at its usual 0x810000 address) and initialized it. I created another section called .map_eeprom at 0x801000 and put another variable of the EEPROM struct there. It is this second variable I then reference throughout the program to access the EEPROM.

Is there a smoother solution to this? Should (would?) Atmel change the debugger so it can recognize EEPROM data by section name instead of a fixed address of 0x810000?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

DosMan wrote:

Using a macro to add the offset at each reference not only blows the convenience, but you lose type info. You must either cast or have macros for each type.

So instead EEPROM variables need to have the start offset built in.
...
Is there a smoother solution to this?


See if this tutorial on offsetof(), using XMega
as example, helps you out:

https://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=82824

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I fear I have done a poor job of introducing this discussion. (I am a very experienced C/C++/AVR programmer and quite familiar with offsetof().) Let me add some background regarding the new memory-mapped EEPROM feature of XMEGA.

On all previous AVR processors with EEPROM, you would use runtime calls/macros like eeprom_read_byte() to read data from EEPROM. This is no longer necessary with XMEGA. The EEPROM can be mapped into data space, where it can be read in the same way as RAM and I/O.

Consider the following definitions of EEPROM data:

uint8_t Mode EEMEM = 1;
uint16_t DelayFreq EEMEM = 0x188;

So the old way to read these was:

b = eeprom_read_byte(&Mode);
w = eeprom_read_word(&DelayFreq);

The new memory-mapped way should be (once the NVM controller is not busy):

b = Mode;
w = DelayFreq;

You can also pass the address to runtime functions, something not possible at all in the past:

memcpy(dest, &DelayFreq, sizeof DelayFreq);

The glitch to all this is that the compiler (or linker) starts assigning EEPROM data at offset zero, but when its memory-mapped, it really starts at offset 0x1000. This means that none of the memory-mapped examples given above will actually work because they will reference addresses near zero in data memory, which is I/O space and not EEPROM.

Here's one way to add the mapping offset:

b = *(&Mode + MAPPED_EEPROM_START);
w = *(uint16_t *)((uint8_t *)&DelayFreq + MAPPED_EEPROM_START);

This would be a pretty ugly solution. At this point you can pick up my original post in the third paragraph, "Using a macro to add the offset..."

I know the XMEGA is new and not many are using it yet. But I expect it to become very mainstream for new applications (e.g., why use ATmega324P when ATxmega32A4 is superior, 2X RAM & EEPROM & speed, for 25% less cost?). So I'm hoping as a community we will want to figure out the best way to do this and build it into the development tools.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

https://savannah.nongnu.org/patch/download.php?file_id=18468 is eewr_byte.S
that implements the old style I/O writes on the XMega.

See https://savannah.nongnu.org/patch/?6878 for the
full patch. This has been checked in to AVR-LibC CVS if you want to build your own. eerd_byte.S already does use the Memory Mapped version, and it adds the offset internally.

What muddies the waters for using Memory Mapped Writes is NVM controller paging. Recheck the data sheet to see if your version with all of the casts works, don't have data sheet at hand to check that it would. As I recall (this late in the day) it will not if you cross a page boundary. x128A1 is 32 byte page, don't know what the x32A4 uses.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

It's nice to see that work is being done on accessing the EEPROM with runtime calls. I did notice that eeprom_write_byte() turns off memory mapping, while eeprom_read_byte() turns it on. As a runtime user I would expect this state to be preserved, and definitely not slammed opposite directions on different functions. (I would also argue that eeprom_write_byte() would be simpler by using memory mapping, as a mapped store will automatically load the buffer - see below.)

What I'm trying to propose here is that EEPROM addresses have their correctly mapped values so that simple direct reads are possible. This would then mean that the mapping adjustment being made inside eeprom_read_byte() would not be necessary. The only reason eeprom_read_byte() should exist on XMEGA is for source compatibility with existing projects.

My version of eeprom_write_byte looks like this. Note that the NVM controller uses only the low 12 bits of the address, so it is not necessary to remove the mapping offset to write the page (nor to set NVM.ADDR2).

#define eeprom_is_busy() (NVM.STATUS & NVM_NVMBUSY_bm)
#define eeprom_busy_wait() do {} while (eeprom_is_busy())

void eeprom_write_page(uint16_t addr)
{
	NVM.ADDR0 = (uint8_t)addr;
	NVM.ADDR1 = (uint8_t)(addr >> 8);
	NVM.CMD = NVM_CMD_ERASE_WRITE_EEPROM_PAGE_gc;
	// Optimizations required for this to work
	CPU_CCP = CCP_IOREG_gc;
	NVM.CTRLA = 1;

	// Handle errata - XMEGA A3 only
	NVM.INTCTRL = NVM_EELVL_LO_gc;
	sleep_cpu();
}

void eeprom_write_byte(void *__p, uint8_t __value)
{
	eeprom_busy_wait();
	*(uint8_t *)__p = __value;
	eeprom_write_page((uint16_t)__p);
}
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
NVM.CMD = NVM_CMD_ERASE_WRITE_EEPROM_PAGE_gc;
// Optimizations required for this to work
CPU_CCP = CCP_IOREG_gc;
NVM.CTRLA = 1;

I'm sure someone will correct me if I'm wrong, but theoretically the compiler could reorder that code so that it could execute those lines in different orders depending on the optimization context. They should be in an ATOMIC() wrapper.

Isn't your code going to write a single byte while erasing
31 (128A1 page size) other bytes by the use of NVM_CMD_ERASE_WRITE_EEPROM_PAGE_gc?

I'll reserve comment until I reread the XMega manual, and get more sleep...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm not suggesting the runtime use C; the code was for illustration. (I inspect the disassembly of critical code like this and it works for my test case. I have not yet seen the compiler reorder assignments of volatile variables. ATOMIC_BLOCK() would not prevent reordering; its purpose is to prevent interrupts. Setting CPU_CCP automatically disables interrupts briefly.)

Assigning to a location in mapped EEPROM causes the page buffer to be loaded, and the written byte to be changed in the buffer. As described in app note AVR1315, the one changed byte is tagged. Additional bytes in the same page may be written and they will also be tagged. (This gives significant optimization potential for eeprom_write_word(), etc. when multiple bytes are in the same page.) Then a single erase-and-write will update only the tagged locations, so write/erase cycles are apparently counted on byte, not a page, basis when considering durability.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Having shown a specific example of the "ugly" way to use EEPROM memory mapping using casts, I thought I should expand the example to show how I really did it. This is illustrative code that I have not run, so pardon typos.

#define EEMAP __attribute__((section(".map_eeprom")))
#define EEMEM __attribute__((section(".eeprom"))) // standard definition

struct EepromData
{
	uint8_t	Mode;
	uint16_t	DelayFreq; 
};

EepromData EE_Init EEMEM = { 1, 0x188 };
EepromData eeprom EEMAP;

...

b = eeprom.Mode;
w = eeprom.DelayFreq;

The section .map_eeprom has a start address of 0x801000. The variable eeprom is in the .map_eeprom section, and has the correct offset for directly accessing EEPROM variables. The variable EE_Init exists solely to provide initialization of the EEPROM, and is not otherwise used in the program.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Seems reasonable for reading. Problems comes when writing, note what section "30.11.4.1 Addressing the EEPROM" has to say:

Quote:
When EEPROM memory mapping is enabled, loading a data byte into the EEPROM page buffer
can be performed through direct or indirect store instructions. Only the least significant bits of
the EEPROM address are used to determine locations within the page buffer, but the complete
memory mapped EEPROM address is always required to ensure correct address mapping.
Reading from the EEPROM can be done directly using direct or indirect load instructions. When
a memory mapped EEPROM page buffer load operation is performed, the CPU is halted for 3
cycles before the next instruction is executed.
When the EEPROM is memory mapped, the EEPROM page buffer load and EEPROM read
functionality from the NVM controller is disabled.
Memory Mapping the EEPROM has not turned it to novolitial SRAM. It is simply a shorthand way of loading the EEPROM page buffer. The NVM commands still need to be used to commit the page buffer to the physical EEPROM.

There is a dangerous subtlety in overlaying a structure over the page buffer, if the structure is larger than the size of the page buffer. The AVR architecture does not do word aligns. So if a 16-bit or larger entity splits at the end of a page boundary, the buffer load operation will be undefined. Most likely will wrap to the start of the page buffer, were the data sheet tells us this will cause errors.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
Seems reasonable for reading.

My communication skills may be showing their shortcomings. What I've been trying to say is that I consider this approach to be completely unreasonable -- a horrible kludge requiring me (and everyone else) to synthesize a single struct for all my EEPROM variables, even though they could be coming from different modules which have no knowledge of each other. (I suppose individual structs in each module would probably work, as long as the linker keeps adding to the .map_eeprom and .eeprom in the same order -- and every module defining EEPROM variables always initializes them [i.e., adds to both sections].)

I started this discussion thread in hopes we could figure how to change the tools so these gyrations would not be necessary.

As for writing to EEPROM, of course it must only be done with runtime calls. I have never intended to suggest otherwise.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

DosMan, I think I understand your request. I agree that for reading only, it ought to be completely possible technologically.

Semantically, I agree that such a system would be infinitely better than other dirty kludges such as runtime function calls or remapped struct pointers. Clearly, for the reasons bpaddock has raised (and you had already conceded), writes to such variables would still have to go through runtime function calls. Regrettable, but unavoidable.

On the other hand, though, I always do place my EEPROM variables inside a single struct for another practical reason: The C standard does not guarantee the order in which variables will be allocated by the linker. As such, if you ever change the order in which object files are presented to the linker, or if you ever migrate from one version of the linker to another, or make any other change such as adding new EEPROM variables that you intend to be "tagged on at the end", you may find the layout of your new EEPROM is entirely different than it used to be, and field-upgrading existing devices with the new firmware may invalidate all the previous EEEPROM contents. If all EEPROM variables are kept inside a single structure, then you can guarantee the order and packing of all EEPROM contents, every time you move from one build to the next.

Variables with the EEMEM attribute should also be inherently treated as "const" so that the compiler will complain if you do accidentally try to write to them directly; that will remind the programmer that they will still need to pass the variable through a runtime function call to do any writing. In addition, variables with the EEMEM attribute would also have to be inherently treated as "volatile" so that the compiler will recognize the fact that the variable's value must always be fetched from memory (rather than using a cached version) every time it's read, despite the fact that it's const.

I also agree that, in my opinion, the most attractive solution to your original query would be to modify the AVR Studio debugger (and the ELF parser specifically) to identify the start of the EEPROM section by name rather than by an absolute address.

- Luke

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Identifying the ELF parser as the component is a useful tidbit -- I assume (after looking over my AVR Studio installation) this would be AvrParserElfDwarf.dll (in the Parsers folder).

So is this proprietary Atmel code? Is the only way to push for this change to submit a ticket on the Atmel support site? Or is there open source so we can take this into our own hands?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

So is this proprietary Atmel code? Is the only way to push for this change to submit a ticket on the Atmel support site? Or is there open source so we can take this into our own hands?

It's proprietary, not open source so all you can do is raise a ticket and then wait a few years.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

There is a simple work-around that I'm going to be using. Change the linker scripts to include the 0x1000 mapping offset by setting the .eeprom section to start at 0x811000 (or 0x801000). Then use avr-objcopy to reset the .eeprom address to the location that AVR Studio understands:

avr-objcopy --change-section-vma .eeprom=0x810000 .elf

This seems to be working fine. But I suppose there's no way to add a build step like this for folks using the AVR GCC plug-in from AVR Studio.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Please anyone help me....
In XMega a1 xplained
i want to write a data byte in eeprom location using NVM controller.
i need some sample program in assembly language to do program in eeprom.