Page erase in AS7 simulator doesnt work: asm / bootloader / spm

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

Hi

 

Im trying a bit of asm/bootloader/spm stuff for the first time, but it just doesnt work ( even though i think everything is correct ). Im writing my code in AS7 for an atmega328p and im only using the simulator to test it.

I try to do a page erase but it doesnt do what it should do. I put my spm code in the bootloader section and setup some test data in different regions of the flash to see how the page erase behaves. The datasheet says that the address of the page is put into the Z pointer and that bits 13:6 (datasheet page  349/334) are used for that and all others are ignored. Since the atmega328 has 256 pages, every page can be addressed by those 8 bits. So if i put the value "0x40" in the lower Z byte, then it should address flash page 1(well it is flash page 2 in that case, depents how you count) and erase only the part with "hello2". But it doesnt behave like that. When i return from "pageErase", page 0 is erased (hello1 is gone) and then i spend 4129 cycles in "isFlashRdy" and when that is done, also page 1 (hello2) is erased. Im very confused...

 

.ORG 0x0000	;page 0
	t1:
	.db "hello1"

.ORG 0x0040 ;byte addr 128:0x80 | page1

	t2:
	.db "hello2"

.ORG 0x0080 ;byte addr 256:0x0100 | page2

	t3:
	.db "hello3"

.ORG 0x00c0 ;byte addr 384:0x0180 | page3

	t4:
	.db "hello4"

jmp LARGEBOOTSTART

.ORG LARGEBOOTSTART

loop:
	cli
	ldi zh, 0x00
	ldi zl, 0x40 ;page 1 ?
	rcall pageErase
	rcall enableRWW
	rjmp loop

isFlashRdy:
	in  r16, SPMCSR
	sbrc r16, SPMEN
	rjmp isFlashRdy
	ret

enableRWW:
	rcall isFlashRdy
	ldi r16, (1<<RWWSRE) | (1<<SPMEN)
	out SPMCSR, r16
	SPM
	ret

pageErase:
	rcall isFlashRdy
	ldi r16, (1<<PGERS) | (1<<SPMEN)
	out SPMCSR, r16 ;set erase and spm enable bit
	spm
	ret

 

Last Edited: Fri. Oct 5, 2018 - 01:13 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Unless BOOTRST is set the first opcode fetch at power on for an AVR are from location 0x0000. So your AVR is going to try to execute whatever opcode "he" translates into when it powers on . Not a good plan!

 

Even if you do set BOOTRST and it resets into the "high code" at some point a bootloader is going to pass execution back to the application and this is usually a "JMP 0" which will then suffer the same fate.

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

I duplicated his problem here.  Single-stepping in the disassembly window DOES eventually get to the bootloader code, and it behaves as described in the OP.

It looks OK to me; is the simulator expected to support SPM?

 

See also https://www.avrfreaks.net/forum/simulator-and-fuses to set the fuses in the simulator...

 

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

clawson wrote:
Unless BOOTRST is set the first opcode fetch at power on for an AVR are from location 0x0000

 

Yes i know. I have not set any fuse, but since after i setup the test data in flash beginning at 0x0000, after "t4" i jump to the bootloader flash section the use the SPM command it should still work...or not ? Its not that i want to write a bootloader at this stage, i know that a bootloader has to pass execution back to the application at some point, but thats not what im trying todo here at the moment. I just want to play a bit with asm and in this case to try erase pages in flash memory. Im sitting here for days now trying to figure out why the code doesnt work.

Even though i dont fully understand " westfw " answer, what im taking from it is that my code is ok but only the simulator is doing funny stuff ?

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

avrfreakonaut wrote:
after "t4" i jump to the bootloader
I still must be missing something. The AVR will execute these opcodes from a power on at 0x0000:

 

"he"

"ll"

"01"

"he"

"ll"

"02"

"he"

"ll"

"03"

"he"

"ll"

"04"

JMP Largebootstart

cli 

etc.

 

So you really intend that it should execute the data in the t1..t4 strings as opcodes before it reaches the JMP? Surely what you require is:

.ORG 0x0000	;page 0

        RJMP skip_the_data

	t1:
	.db "hello1"

.ORG 0x0040 ;byte addr 128:0x80 | page1

	t2:
	.db "hello2"

.ORG 0x0080 ;byte addr 256:0x0100 | page2

	t3:
	.db "hello3"

.ORG 0x00c0 ;byte addr 384:0x0180 | page3

	t4:
	.db "hello4"

skip_the_data:
        jmp LARGEBOOTSTART

.ORG LARGEBOOTSTART

loop:
	cli
etc.

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

clawson wrote:
The AVR will execute these opcodes from a power on at 0x0000:

Of course you are absolutely right, it will try to execute these "opcodes" but since it is nonsense it will do nothing. I took this into account, i dont intend this, but since it does nothing anyways i left it/did it like that. And after

 jmp LARGEBOOTSTART

it is in the bootloader section and spm can be used. But something is going on because it doesnt erase the pages like it should.

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

avrfreakonaut wrote:
but since it is nonsense it will do nothing.
What utter tosh. How can the AVR CPU that simply fetches and executes 16 bit opcode patterns tell "he" from a valid opcode? Almost all 65536 possible patterns for AVr opcodes ARE used so "he" will execute as something!!

 

EDIT: OK so perhaps it is benign?...

--- c:\users\uid23021\Documents\Atmel Studio\7.0\Asmtest\Asmtest\main.asm ------
	.db "hello1"
00000000  ORI R22,0x58		Logical OR with immediate
00000001  ORI R22,0xCC		Logical OR with immediate
	.db "hello1"
00000002  CPI R22,0x1F		Compare with immediate
00000003  NOP 		Undefined
00000004  NOP 		Undefined
00000005  NOP 		Undefined
00000006  NOP 		Undefined 

     snip

0000003E  NOP 		Undefined
0000003F  NOP 		Undefined
	.db "hello2"
00000040  ORI R22,0x58		Logical OR with immediate
00000041  ORI R22,0xCC		Logical OR with immediate
	.db "hello2"
00000042  CPI R22,0x2F		Compare with immediate
00000043  NOP 		Undefined
00000044  NOP 		Undefined 

etc up to

000000BF  NOP 		Undefined
	.db "hello4"
000000C0  ORI R22,0x58		Logical OR with immediate
000000C1  ORI R22,0xCC		Logical OR with immediate
	.db "hello4"
000000C2  CPI R22,0x4F		Compare with immediate
jmp LARGEBOOTSTART
000000C3  JMP 0x00003800		Jump

Apart from corrupting R22 and setting SREG flags as a result of the CPIs it just so happens that the text in these strings does not turn in to anything too dangerous.

Last Edited: Fri. Oct 5, 2018 - 12:33 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You are right, i just assumed that the possibility that it matches a valid opcode was low, thats why i said that. But if almost all possible patterns are used, then ofc it must do something. Maybe my thinking was wrong thats why i said that it is nonsense . But even then, the code in the bls should work, or not?

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

So the remaining question is:  does the simulator actually simulate a page erase???

 

 

Jim

 

Click Link: Get Free Stock: Retire early!

share.robinhood.com/jamesc3274

 

 

 

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

clawson wrote:

Apart from corrupting R22

 

Yes, i noticed that and i wondered why is something in r22 even though i didnt put something in there. Then i looked at the assembled file and saw those

	.db "hello1"
00000000  ORI R22,0x58		Logical OR with immediate
00000001  ORI R22,0xCC

First i thought it was the way the assembler puts "hello1" into the flash section ( which ofc is bs ). But yes..nothing dangerous is happening appart from that.

 

 

EDIT: i also put

jmp LARGEBOOTSTART

after those tables because at that moment i didnt fully understand this concept. I didnt understand what does the assembler for me and what is actual code that gets executed. If i put the jump to BLS as the first instruction then it skips those tables that obviously get executed as instructions and start right at the BLS.

Last Edited: Fri. Oct 5, 2018 - 12:58 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

avrfreakonaut wrote:
First i thought it was the way the assembler puts "hello1" into the flash section ( which ofc is bs ).
Err no - that is EXACTLY what is happening. Take "he" for example the ASCII character codes are 0x68, 0x65 which is what are assembled into the flash. The AVR fetches this as a 0x6568 opcode.

 

The opcode manual says this about ORI:

 

 

So the the 0x6568 opcode is 0110 0101 0110 1000. That means KKKKKKKK is 0101 1000 and dddd is 0110. dddd is offset by 16 so it is 6 + 16 = 22. So this will do an ORI R22, 0x58

 

And that is exactly what it is decoded as:

 

 

(interesting that the disassembler in Studio lists them at 0x68,0x65 - the individual bytes in order - when it's really a little endian 16 bit opcode fetch so the actual word it decodes is 0x6868)

Last Edited: Fri. Oct 5, 2018 - 12:57 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You are right. But at that moment i was just thinking in actual TEXT, that it puts these asci chars in the flash memory. I was not thinking in terms of code/opcodes. I thought it puts the text "hello1" in there, but ofc this translates to something else since the mcu doesnt know what actual text is.

EDIT: Well it does place the text in there...but i hope you get what i try to say

Last Edited: Fri. Oct 5, 2018 - 01:05 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

avrfreakonaut wrote:
puts these asci chars in the flash memory. I was not thinking in terms of code/opcodes.
They are all just numbers. In fact they are all (ultimately) just 0's and 1's. Whether it is decoded as ASCII or binary numbers or AVR opcodes is all just a matter of interpretation depending on what happens to be reading it at the time.

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

ki0bk wrote:

So the remaining question is:  does the simulator actually simulate a page erase???

 

Thats the question. So basically my SPM code is fine and should work ? but it seems that the simulator is doing funny stuff and thats why it seems that it doesnt work ?

 

clawson wrote:
is all just a matter of interpretation

 

Yes, and my interpretation at that moment was different than what the mcu actually interpretes. I got confused by that.

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

The simulator (in fact the entire debugger) caches flash contents as (in normal usage) it doesn't believe flash can change - SPM is actually quite a unusual operation - when using a real debugger this matters because otherwise each time execution halts (which means after each step when single stepping) the debugger might have to re-read flash to update "memory" windows and so on. So there is an option buried deep in AS7 where you can switch off this "caching" that is meant for exactly the occasion when you are working on SPM code.

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

Where can i find this ? Normaly under "project Properties/Tool " there is an option "Cache all flash memory except" box. But if i select "simulator" as the debugger/programmer the option is gone.

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

Ah, maybe they don't duplicate this into the simulator? To be honest, when I've done SPM I've just used an ICE to a real chip.

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

clawson wrote:
interesting that the disassembler in Studio lists them at 0x68,0x65 - the individual bytes in order - when it's really a little endian 16 bit opcode fetch so the actual word it decodes is 0x6868
That has always bothered me!

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience.  Experience comes from bad judgement."

"Wisdom is always wont to arrive late, and to be a little approximate on first possession."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"Read a lot.  Write a lot."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

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

Since i dont have a real debugger and the simulator doesnt seem to work with spm/page erase, i tried the following. I uploaded the hex to a real atmega328 and then read the flash memory back after erase.

 

I uploaded each hex with:

zl = 0x00 ( page 0 = hello1 )

zl = 0x40 ( page 1 = hello2 )

zl = 0x80 ( page 2 = hello3 )

zl = 0xc0 (page 3 = hello4 )

and even though i dont have a hello5 in page 4, i also tried

zh = 0x01

 

But after downloading the flash and comparing it i noticed that it also doesnt work on a real atmega328p. So...what am i doing wrong ?

Here is what it looks like:

 

zl = 0x00: page 0  is gone (ok)

zl = 0x40: page 0 is gone, but it should be page 1

zl = 0x80: page 1 is gone, but should be page 2

zl = 0xc0: page 1 is gone, but should be page 3

zh = 0x01: page 2 is gone

 

 

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

Ah.  While everything is is specified in words, the address that is actually in Z is a byte address (where the LSB is always zero.)

I should have noticed this sooner - optiboot receives the "word address" via the stk-500 protocol, and immediately doubles it to a byte address, which is used thereafter.  (after perhaps propagating the high bit into RAMPZ for those chips with 128k of flash.)

 

See fig 25-1:

 

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

Im not sure if i understand you correctly but im using the byte addr in the Z pointer. ".ORG 0x0040 = hello2" is the word addr and i use zl = 0x80 as byte addr for it.

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

No one got an idea on what is wrong ?

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

i use zl = 0x80 as byte addr for it.

That's not what was in your code...

 

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

westfw wrote:
That's not what was in your code...

 

I know, i wrote zl = 0x40 in my first post...with 0x80 i just wanted to address another page. Maybe i misunderstood something, but isnt bit 6 to 13 in the Z reg responsible for holding the page addr ? So if i put 0x40 in zl, this means that in bit 6 is now a 1, which schould address page 1 (hello2) and if all bits are zero then it should address page 0 (hello1) ?

 

EDIT: " ".ORG 0x0040 = hello2" is the word addr and i use zl = 0x80 as byte addr for it.  " well forget that pls, its nonsens i guess, i was confused..and i am confused. But still, am i not right with what i wrote here at the top ?

Last Edited: Tue. Oct 9, 2018 - 10:48 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

isnt bit 6 to 13 in the Z reg responsible for holding the page addr ?

Page size is 64 WORDS on Atmega328.  128 bytes.  bit 6 to 13 of the PC are the page number, but the PC doesn't have low bit...

Bit 0 of Z must always be zero, so everything is shifted left a bit from your expectations - bit 7 to 14.

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

Pls excuse me, but i just dont get it. I know that the page size is 64 words and the whole atmega328 has therefore 256 pages which can be addressed with 8 bits. With "PC" do you mean "page counter" or "program counter" ? I mean in the datasheet on page 349 it clearly states PCPAGE = PC[13:6] so thats bit 6 and 7 in zl and bits 8 to 13 in zh. That also matches the picture you posted here. And yes it also says that bit 0 in reg z in (any?) operation must be (always?) zero, and it was zero in my examples. I just dont get how i am supposed to know that i have to shift the numbers 1 bit to the left , so instead of  PC[13:6] i have to use  PC[14:7] when it clearly states on page 349 and in your picture that it is PC[13:6]...and even considering that bit 0 has to be zero...which is zero anyways.

 

EDIT: Till this day i could make anything i wanted by just reading the datasheet whithout consulting external help. I already read the whole datasheet i few times and especially about this topic (spm). I want to refuse that im that stupid to get this. I mean im aware that they dont write the datasheet in a way that even fish could understand it, since they expect that a user playing around with a mcu has already some basic level of intelligence/understanding...but i just dont get it.

I know that bit 0 in reg z has to be zero because reg z uses a byte addr. To get the byte addr you shift left by 1 which is the same as mul by 2. I got it. But thats (only?) the case when i use the lpm command for example.

But in my case it is not obv to me that i also have to shift 1 left, since the datasheet states that for PCWORD = PC[5:0] and PCPAGE = PC[13:6]. With the 6 bits (bits 0 to 5) in PCWORD i can address each of the 64 words in one page and with the 8 bits (6 to 13) in PCPAGE i select one of the 256 pages if i want to write to the flash.

So is it me, or is the datasheet not clear ?

Last Edited: Wed. Oct 10, 2018 - 10:24 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This should not be as complex as you are making it!

 

You program pages at a time. The actual address will go up in SPM_PAGESIZE increments. The 328 as you say has 64 word, 128 byte pages. If you read C then the "API example" on this page:

 

https://www.nongnu.org/avr-libc/...

 

shows an example of how it is done:

#include <inttypes.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
void boot_program_page (uint32_t page, uint8_t *buf)
{
    uint16_t i;
    uint8_t sreg;
    // Disable interrupts.
    sreg = SREG;
    cli();
    eeprom_busy_wait ();
    boot_page_erase (page);
    boot_spm_busy_wait ();      // Wait until the memory is erased.
    for (i=0; i<SPM_PAGESIZE; i+=2)
    {
        // Set up little-endian word.
        uint16_t w = *buf++;
        w += (*buf++) << 8;

        boot_page_fill (page + i, w);
    }
    boot_page_write (page);     // Store buffer in flash page.
    boot_spm_busy_wait();       // Wait until the memory is written.
    // Reenable RWW-section again. We need this if we want to jump back
    // to the application after bootloading.
    boot_rww_enable ();
    // Re-enable interrupts (if they were ever enabled).
    SREG = sreg;
}

The functions there are Asm implemented but first at what's happening at the highest level here. Say this code is called with:

void boot_program_page (0x6200, buffer);

this is saying "program the 64 words/128 bytes from 0x6200 onwards" (that is 0x6200..0x627F). It doesn't really matter what the 128 bytes in the buffer[] array actually are. The real core of this entire routine is:

    for (i=0; i<SPM_PAGESIZE; i+=2)
    {
        // Set up little-endian word.
        uint16_t w = *buf++;
        w += (*buf++) << 8;

        boot_page_fill (page + i, w);
    }
    boot_page_write (page);     // Store buffer in flash page.

So that takes 2 bytes at a time from the buffer and puts them into a 16 bit register pair then calls boot_page_fill() with the 0x6200 base address and an offset that goes up in 2's so it will be 0x6200, 0x6202, 0x6204 and so on. The library cod for boot_page_fill() is:

#define __boot_page_fill_normal(address, data)   \
(__extension__({                                 \
    __asm__ __volatile__                         \
    (                                            \
        "movw  r0, %3\n\t"                       \
        "sts %0, %1\n\t"                         \
        "spm\n\t"                                \
        "clr  r1\n\t"                            \
        :                                        \
        : "i" (_SFR_MEM_ADDR(__SPM_REG)),        \
          "r" ((uint8_t)(__BOOT_PAGE_FILL)),     \
          "z" ((uint16_t)(address)),             \
          "r" ((uint16_t)(data))                 \
        : "r0"                                   \
    );                                           \
}))

This is rather awful syntax but it's basically moving the input 16 bit "data" value into R1:R0, storing _BOOT_PAGE_FILL (ie just "SPMEN") in the SPM_REG register (likel SPMCSR or SPMCR) then executing SPM. That stores 2 bytes into the buffer. The for() loop then loops around for the next 2 bytes and so on until a complete SPM+PAGESIZE has been filled. 

 

It then calls:

  boot_page_write (page);     // Store buffer in flash page.

The parameter here is "page" which was the entry value to this entire function. In this example case it is 0x6200. The code of boot_page_write() (for the "normal" case) is:

#define __boot_page_write_normal(address)        \
(__extension__({                                 \
    __asm__ __volatile__                         \
    (                                            \
        "sts %0, %1\n\t"                         \
        "spm\n\t"                                \
        :                                        \
        : "i" (_SFR_MEM_ADDR(__SPM_REG)),        \
          "r" ((uint8_t)(__BOOT_PAGE_WRITE)),    \
          "z" ((uint16_t)(address))              \
    );                                           \
}))

Again try to not worry too much about this awful syntax but the bottom line is that ZH:ZL will simply be loaded with 0x6200 so 0x62 in ZH and 0x00 in ZL. Then, after it has written __BOOT_PAGE_WRITE to the SPMCR/SPMCSR it then just executes an SPM. The __BOOT_PAGE_WRITE value is just SPMEN | PGWRT

 

That's all there is to it. So at the moment of the SPM that actually does the Page Write the ZH:ZL register should be holding the byte address of the page that is being written. Bit 0 is bound to be 0 because the SPM_PAGESIZE are aligned on large boundaries such as 32, 64, 128 or 256 byte boundaries in which the 5,6,7,8 bottom bits are all 0 anyway.

 

EDIT: So if you are trying to write to word address 0x0040, byte address 0x0080 then ZH:ZL should contain 0x0080 at the moment you do the SPM to trigger the PGWRT.

Last Edited: Wed. Oct 10, 2018 - 12:58 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

It seems that i completely misunderstood the whole concept. I thought that PC[13:6] should be used as an 8 bit counter beginning at value 0 which stands for page 1(or zero, depends on how you count), then increments to value 1, which stands for page 2, then inc to 2 for page 3 etc.

 

 

                  bit13       bit6 in Z

                     l               |

Like that: 0000 0000 0000 0000 = page 1,   0000 0000 0100 0000 = page 2,   0000 0000 1000 0000 = page 3 etc

 

Thats what i was doing.

But you actually have to put the byte addr in it and since byte addr is double the word addr it gets shiftet left by 1 so that bit 6 in reg z is always zero.

wow

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

Probably just text alignment but in your diagram above what you label as "bit 6 in Z" looks an awful lot like bit 4 to me ! (also what says "bit 13" is bit 12).

 

Then again this may just be about proportional fonts? It think it should be:

  13        6
   |        |
 0000 0000 0000 0000 = page 1,   
 0000 0000 0100 0000 = page 2,   
 0000 0000 1000 0000 = page 3

As you can see it's counting up from 00 to 01 to 10 and so on at bit position 6.

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

You are right, couldnt use tab so i hoped for the best when using space key. I guess it works in "code" modus.

I think i got it know,  page erase code is now working it seems, i will make further tests when i have more time.

But all in all i think that the datasheet is not very clear on that part, but perhaps its just me idk.

 

Thanks for the help and time so far.

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

The datasheet is FREQUENTLY unclear when it comes to word vs byte addresses in flash...