[TUT][SOFT][ASM] Writing to the EEPROM on the AVR-0/1 series

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

This example shows how to use the NVM controller to write single bytes to the EEPROM (from the CPU, the UPDI interface can also access the NVM controller). It's also possible to do block writes, but that's a tale for another day...

The program writes the classic "Hello World!" string to the first byte positions of the EEPROM. Syntax is AVR assembler.

I hope the comments are clear enough to make sense of what's going on.

 

; replace by the correct include file for the MCU you are using
; or trust the IDE auto-include and delete this line
#include <m4809def.inc>

.CSEG
    rjmp start

    ; Skip interrupt vectors
    .org INT_VECTORS_SIZE

message:
    .db "Hello World!"
message_end:
    .equ message_len = 2 * (message_end - message)
    ; Translate program memory word address to memory mapped byte address
    .equ message_addr = MAPPED_PROGMEM_START + (2 * message)

start:
    ; Load address of the "hello World!" string in X
    ldi	xl, low(message_addr)
    ldi	xh, high(message_addr)
    ; Load EEPROM relative address 0 in r17:r16
    ldi	r16, 0
    ldi	r17, 0
    ; Load string lenght in r19
    ldi	r19, message_len
    ; Copy string to EEPROM
loop:
    ld	r18, X+
    rcall	EEPROM_write
    subi	r16, low(-1)
    sbci	r17, high(-1)
    dec	r19
    brne	loop
halt:
    rjmp halt

; EEPROM_write function
; On entry:
;	r17:r16 - relative address of data inside the EEPROM area
;	r18 - byte to be written to EEPROM
; Clobbers:
;	zl (r30), zh (r31)
; All other registers preserved
EEPROM_write:
    ; Wait for completion of previous write
    lds	zl, NVMCTRL_STATUS
    sbrc	zl, NVMCTRL_EEBUSY_bp
    rjmp	EEPROM_write                    ; see edit #1 note
    ; Translate relative EEPROM address from r17:r16 to mapped address in Z
    movw	Z, r17:r16
    subi	zl, low(-EEPROM_START)
    sbci	zh, high(-EEPROM_START)
    ; Store EEPROM data (r18) to write buffer
    st	Z, r18
    ; Execute NWM erase/write command
    in	zh, CPU_SREG				; preserve interrupt flag
    cli						; make sure interrupts are disabled for critical section
    ldi	zl, CPU_CCP_SPM_gc			; unlock NVM command register
    out	CPU_CCP, zl
    ldi	zl, NVMCTRL_CMD_PAGEERASEWRITE_gc	; execute NVM erase/write
    sts	NVMCTRL_CTRLA, zl
    out	CPU_SREG, zh				; restore interrupt flag
    ret

 

edit #1: according to suggestion from post #11

Last Edited: Wed. Apr 17, 2019 - 10:36 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 2

Thank you El Tangas, looks good yes

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

So, unlike flash, which is available at two separate addresses (one (mapped into the data space) for LD, one for LPM) in the 0/1 chips, EEPROM is only available at the mapped address in the data space, using LD for read access and the NVM controller for writes?

(I hadn't noticed that.  Thanks for posting the code.)

 

	subi	zl, low(-EEPROM_START)
	sbci	zh, high(-EEPROM_START)

 

You can probably optimize that.   :-)  I'm pretty sure that low(EEPROM_START) will always be zero...

 

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

westfw wrote:
You can probably optimize that.   :-)  I'm pretty sure that low(EEPROM_START) will always be zero...

 

True. If we assume the low byte is always zero, it can be just

	subi	zh, high(-EEPROM_START)
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm starting with the 0-series. Normally I do pretty much everything in C. I was hoping to keep it that way. Is EEPROM not supported in avr-libc? I just tried building a C project with eeprom_read_byte/eeprom_write_byte calls for ATmega4809. It builds. Should I be concerned that it might not actually work? I don't have an actual micro yet, therefore, I cannot just try.

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

Don't worry, of course those functions work. This is just a commented example, for those who are curious about the inner workings of writing to the EEPROM.

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

Is the eeprom_read_byte/eeprom_write_byte source available anywhere? I just downloaded avr-libc from https://www.microchip.com/mplab/.... Looks like atmega4809 is not there. As a matter of fact, none of the new devices are there (those are that are only in the Device Family Packs). Is the source for the Device Family Packs available?

 

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

eeprom_read_byte/eeprom_write_byte source available anywhere?

I think the 4809 uses the same __AVR_XMEGA__ code that has existed for quite a while at http://svn.savannah.gnu.org/viewvc/avr-libc/trunk/avr-libc/libc/misc/

The library code tries not to be based on individual chip types, instead using broad defines that work for many chips (though it BUILDS individual .a files these days,

they're based on the same source modules...)

 

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

Note that the avr-gcc/avr-libc you can most easily see on the internet are not the one that Atmel supply. They have heir own branch where they do development work in advance of the FSF open copy. They then, by degrees, push their local work back onto the master copy. So just because:

 

http://svn.savannah.gnu.org/viewvc/avr-libc/trunk/avr-libc/libc/misc/

 

may not have AVR-0/1 implementations of eeprom_*() it doesn't mean that the copy that comes with AS7 (or "Atmel Toolchain") won't have it. 

 

I keep forgetting the name of the site where Atmel deliver their GPL responsibility of publishing their derived work.

Anyway if I build this (for 4809):

#include <avr/io.h>
#include <avr/eeprom.h>

int main(void)
{
	eeprom_update_word((void *)0x1234, 12345);
}

I get this:

	eeprom_update_word((void *)0x1234, 12345);
00000060 69.e3                LDI R22,0x39		Load immediate 
00000061 70.e3                LDI R23,0x30		Load immediate 
00000062 84.e3                LDI R24,0x34		Load immediate 
00000063 92.e1                LDI R25,0x12		Load immediate 
00000064 0e.94.6b.00          CALL 0x0000006B		Call subroutine 
00000066 80.e0                LDI R24,0x00		Load immediate 
00000067 90.e0                LDI R25,0x00		Load immediate 
--- D:\\test\\test\\Debug/.././main.c ------------------------------------------
}
00000068 df.91                POP R29		Pop register from stack 
00000069 cf.91                POP R28		Pop register from stack 
0000006A 08.95                RET 		Subroutine return 

--- C:\home\toolsbuild\workspace\avr8-devices-pack\src\avr-libc\libc\misc\eeupd_word.S 

0000006B 01.96                ADIW R24,0x01		Add immediate to word 
0000006C 27.2f                MOV R18,R23		Copy register 
0000006D 0e.94.72.00          CALL 0x00000072		Call subroutine 
0000006F 0c.94.71.00          JMP 0x00000071		Jump 

--- C:\home\toolsbuild\workspace\avr8-devices-pack\src\avr-libc\libc\misc\eeupd_byte.S 

00000071 26.2f                MOV R18,R22		Copy register 
00000072 0e.94.94.00          CALL 0x00000094		Call subroutine 
00000074 00.80                LDD R0,Z+0		Load indirect with displacement 
00000075 02.16                CP R0,R18		Compare 
00000076 19.f0                BREQ PC+0x04		Branch if equal 
00000077 0e.94.7d.00          CALL 0x0000007D		Call subroutine 
00000079 01.97                SBIW R24,0x01		Subtract immediate from word 
0000007A 01.97                SBIW R24,0x01		Subtract immediate from word 
0000007B 08.95                RET 		Subroutine return 

--- C:\home\toolsbuild\workspace\avr8-devices-pack\src\avr-libc\libc\misc\eewr_byte.S 

0000007C 26.2f                MOV R18,R22		Copy register 
0000007D af.93                PUSH R26		Push register on stack 
0000007E bf.93                PUSH R27		Push register on stack 
0000007F e0.e0                LDI R30,0x00		Load immediate 
00000080 f0.e1                LDI R31,0x10		Load immediate 
00000081 32.81                LDD R19,Z+2		Load indirect with displacement 
00000082 31.fd                SBRC R19,1		Skip if bit in register cleared 
00000083 fd.cf                RJMP PC-0x0002		Relative jump 
00000084 dc.01                MOVW R26,R24		Copy register pair 
00000085 a0.50                SUBI R26,0x00		Subtract immediate 
00000086 bc.4e                SBCI R27,0xEC		Subtract immediate with carry 
00000087 2c.93                ST X,R18		Store indirect 
00000088 2d.e9                LDI R18,0x9D		Load immediate 
00000089 24.bf                OUT 0x34,R18		Out to I/O location 

--- C:\home\toolsbuild\workspace\avr8-devices-pack\src\avr-libc\libc\misc\eewr_byte.S 

0000008A 23.e0                LDI R18,0x03		Load immediate 
0000008B 20.83                STD Z+0,R18		Store indirect with displacement 
0000008C 01.96                ADIW R24,0x01		Add immediate to word 
0000008D bf.91                POP R27		Pop register from stack 
0000008E af.91                POP R26		Pop register from stack 
0000008F 08.95                RET 		Subroutine return 

--- C:\home\toolsbuild\workspace\avr8-devices-pack\src\avr-libc\libc\misc\eerd_byte.S 

00000090 03.d0                RCALL PC+0x0004		Relative call subroutine 
00000091 80.81                LDD R24,Z+0		Load indirect with displacement 
00000092 99.27                CLR R25		Clear Register 
00000093 08.95                RET 		Subroutine return 
00000094 fc.01                MOVW R30,R24		Copy register pair 
00000095 e0.50                SUBI R30,0x00		Subtract immediate 
00000096 fc.4e                SBCI R31,0xEC		Subtract immediate with carry 
00000097 08.95                RET 		Subroutine return 

 

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

I don't know where the source code is, but I do know the whereabouts of the subroutine, it's supplied as a precompiled library.

In my system, the library files for the Mega4809, for example, are here:

C:\Program Files (x86)\Atmel\Studio\7.0\packs\atmel\ATmega_DFP\1.3.300\gcc\dev\atmega4809

 

 

With the help of objdump, the disassembly for eeprom_write_byte is:

 

00000000 <eeprom_write_byte>:
   0:   26 2f           mov     r18, r22

00000002 <eeprom_write_r18>:
   2:   af 93           push    r26
   4:   bf 93           push    r27
   6:   e0 e0           ldi     r30, 0x00       ; 0
   8:   f0 e1           ldi     r31, 0x10       ; 16
   a:   32 81           ldd     r19, Z+2        ; 0x02
   c:   31 fd           sbrc    r19, 1
   e:   00 c0           rjmp    .+0             ; 0x10 <eeprom_write_r18+0xe>
  10:   dc 01           movw    r26, r24
  12:   a0 50           subi    r26, 0x00       ; 0
  14:   bc 4e           sbci    r27, 0xEC       ; 236
  16:   2c 93           st      X, r18
  18:   2d e9           ldi     r18, 0x9D       ; 157
  1a:   24 bf           out     0x34, r18       ; 52
  1c:   23 e0           ldi     r18, 0x03       ; 3
  1e:   20 83           st      Z, r18
  20:   01 96           adiw    r24, 0x01       ; 1
  22:   bf 91           pop     r27
  24:   af 91           pop     r26
  26:   08 95           ret

 

It's pretty similar to my example, the difference is that it doesn't handle the interrupt flag, so you will have to do it manually, i.e., disable interrupts before calling this function.

Or maybe interrupts are disabled temporarily when the unlock register is written? I guess I will have to check the datasheet more carefuly.

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

El Tangas wrote:

 

EEPROM_write:
	; Wait for completion of previous write
	lds	zl, NVMCTRL_STATUS
	sbrc	zl, NVMCTRL_EEBUSY_bp
	rjmp	PC-4

 

 

 

If I were me, I'd change the rjmp line to "rjmp EEPROM_write"

 

Easier to understand and won't do horrible things if you stuff a few more lines into the loop.  Let the compiler do the math.   S.

 

PS - "Let the compiler do the math" should be an aphorism somewhere.  S.

Edit - removed spurious colon

Last Edited: Wed. Apr 17, 2019 - 10:22 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You're absolutely right, I'll edit the code.

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

"I think the 4809 uses the same __AVR_XMEGA__ code" - no.

 

"it doesn't mean that the copy that comes with AS7 (or "Atmel Toolchain") won't have it" - correct, the Atmel toolchain has it. Well, the packs (ATmega_DFP/1.3.300).

 

"I keep forgetting the name of the site where Atmel deliver their GPL responsibility of publishing their derived work" - well, there is this: http://distribute.atmel.no/tools... . But the packs are not there.

 

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

After some searching, I found the source code for xmega. The function is similar, but clearly not the same. So I have no idea where Microchip is hiding the source code for the AVR-0/1 version, which, as pointed out, must be made publicly available.

 

http://svn.savannah.gnu.org/view...

 

https://savannah.nongnu.org/proj...

 

https://www.microchip.com/webdoc/ (check the AVR Libc section)

Last Edited: Wed. Apr 17, 2019 - 12:36 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Perhaps the packs are no considered a "derived" work and therefore they don't have to publish it?

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

"Or maybe interrupts are disabled temporarily when the unlock register is written?" - yes, the atmega4809 datasheet says "Writing the correct signature to this bit field allows changing protected I/O registers or executing protected instructions within the next four CPU instructions executed".

 

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

>> "I think the 4809 uses the same __AVR_XMEGA__ code"

- no.

Are you sure?  I compiled a trivial program for both xmega32e5 and mega4809, and the resulting object code sure looks like it could have come from the same source code...

The savanah code doesn't quite match the latest  Atmel Download, though; slightly different placement of #endif for the EEMAPEN stuff (the binary produced matches the Atmel code...)

 

Here's the Atmel Source, annotated to show what I think is happening.  It matches up pretty good with the disassembly that Cliff showed...

 

ENTRY    eeprom_write_r18

#if  __AVR_XMEGA__    /* --------------------------------------------    */

# ifndef CCP_IOREG_gc
#  define CCP_IOREG_gc    0xD8    /* IO Register Protection    */
# endif
# ifndef NVM_CMD_READ_EEPROM_gc
#  define NVM_CMD_READ_EEPROM_gc        0x06
# endif
# ifndef NVM_CMD_LOAD_EEPROM_BUFFER_gc
#  define NVM_CMD_LOAD_EEPROM_BUFFER_gc        0x33
# endif
# ifndef NVM_CMD_ERASE_WRITE_EEPROM_PAGE_gc
#  define NVM_CMD_ERASE_WRITE_EEPROM_PAGE_gc    0x35
# endif
# ifndef  NVM_CMD_ERASE_EEPROM_BUFFER_gc
#  define NVM_CMD_ERASE_EEPROM_BUFFER_gc    0x36
# endif

#if    ! defined (NVM_EEMAPEN_bm)
  ; Saving X register because it might contain source address of block
    push XL
    push XH

#endif

  ; Prepare base address of NVM.
    ldi    ZL, lo8(NVM_BASE)
    ldi    ZH, hi8(NVM_BASE)

  ; Wait until NVM is not busy.
1:    ldd    r19, Z + NVM_STATUS - NVM_BASE
    sbrc    r19, NVM_NVMBUSY_bp
    rjmp    1b

  ; It has to be noted that for some Xmega parts (Eg. Xmega E family) EEPROM
  ; is always memory mapped. So it is not possible to disable EEPROM mapping
  ; explicitly.
  ; The presence of NVM_EEMAPEN_bm macro (from the part header file) can be
  ; checked to find out whether the device supports enabling/disabling of
  ; EEPROM mapping. Absence of NVM_EEMAPEN_bm could be interpreted safely as
  ; EEPROM always memory mapped and explicit memory mapping of EEPROM is not
  ; required/invalid.
#if     defined (NVM_EEMAPEN_bm)
  ; Disable EEPROM mapping into data space.
    ldd    r19, Z + NVM_CTRLB - NVM_BASE
    andi    r19, ~NVM_EEMAPEN_bm
    std    Z + NVM_CTRLB - NVM_BASE, r19

  ; Check the clearance of EEPROM page buffer.
    ldd    r19, Z + NVM_STATUS - NVM_BASE
    sbrs    r19, NVM_EELOAD_bp
    rjmp    3f            ; erase is not required

  ; Note that we have only four clock cycles to write to the CCP
  ; protected register NVM_CTRLA, after writing to CCP.  The 'ldi'
  ; instruction always takes one clock to execute and 'std' instruction takes
  ; two clock cycles.  We fall within the four cycles that the CCP leaves
  ; us to write the command execution start bit to the NVM_CTRLA
  ; register.  Note that r18 must be preserved until written to NVM_DATA0

  ; Issue EEPROM Buffer Erase:
    ldi    r19, NVM_CMD_ERASE_EEPROM_BUFFER_gc
    std    Z + NVM_CMD - NVM_BASE, r19
    ldi    r19, CCP_IOREG_gc
    out    CCP, r19
    ldi    r19, NVM_CMDEX_bm
    std    Z + NVM_CTRLA - NVM_BASE, r19

  ; Wait until NVM is not busy.
2:    ldd    r19, Z + NVM_STATUS - NVM_BASE
    sbrc    r19, NVM_NVMBUSY_bp
    rjmp    2b

  ; Issue EEPROM Buffer Load command.
3:    ldi    r19, NVM_CMD_LOAD_EEPROM_BUFFER_gc
    std    Z + NVM_CMD - NVM_BASE, r19

    std    Z + NVM_ADDR0 - NVM_BASE, addr_lo
    std    Z + NVM_ADDR1 - NVM_BASE, addr_hi
    std    Z + NVM_ADDR2 - NVM_BASE, __zero_reg__

    std    Z + NVM_DATA0 - NVM_BASE, r18
#else
      movw     XL, addr_lo
    subi    XL, lo8(-MAPPED_EEPROM_START)
    sbci    XH, hi8(-MAPPED_EEPROM_START)
    st    X, r18

#endif

  ; Issue EEPROM Erase & Write command.
#if defined(NVMCTRL_CTRLA)
    ldi    r18, CCP_SPM_gc
    out CCP, r18
    ldi    r18, NVMCTRL_CMD_PAGEERASEWRITE_gc
    std    Z + NVMCTRL_CTRLA - NVM_BASE, r18

#else
    ldi    r18, NVM_CMD_ERASE_WRITE_EEPROM_PAGE_gc
    std    Z + NVM_CMD - NVM_BASE, r18
    ldi    r18, CCP_IOREG_gc
    ldi    r19, NVM_CMDEX_bm
    out    CCP, r18
    std    Z + NVM_CTRLA - NVM_BASE, r19
#endif

  ; Increment address.
    adiw    addr_lo, 1

#if    ! defined (NVM_EEMAPEN_bm)
  ; Restoring X register
    pop XH
    pop XL
#endif    

    ret

#else        /* ----------------------------------------------------    */

// non-xmega code

#endif        /* ----------------------------------------------------    */

ENDFUNC

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

"Are you sure?" - I was pretty sure based on what I could find. But your "Atmel Source" is different. Where did you get it from?

 

Edit: Never mind, found it. At http://distribute.atmel.no/tools.... I thought I looked there before. What probably happened was that I first looked in http://distribute.atmel.no/tools... . That looked different. I then found newer versions. But instead of looking at the source code I just looked for new filenames, atmega4809 was not there. So I didn't even bother looking at the source code.

 

Thanks!

 

 

Last Edited: Thu. Apr 18, 2019 - 03:37 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ezharkov wrote:

Edit: Never mind, found it. At http://distribute.atmel.no/tools.... I thought I looked there before.

 

Oh, so that's where the source is. Thanks for sharing.

 

westfw wrote:
Here's the Atmel Source, annotated to show what I think is happening.  It matches up pretty good with the disassembly that Cliff showed...

 

Thanks for the annotated code.

The AVR-0/1 version of the code could be better optimized, using the absolute addressing instructions lds and sts (like I did in my code) would avoid the need of using Z as base. These are 2 word instructions, but there would be no drawback, because this branch of the code only needs 1 load (the busy wait loop) and 1 store (issuing the erase/write command) from the NVM registers (unlike the xmega branch, which needs several, so it may pay off to use Z as base and use relative addressing in that case).

This would in turn free the X register, Z could be used instead, saving 2x push and 2x pop.

Last Edited: Thu. Apr 18, 2019 - 11:30 AM