setting bootloader start address

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

I am writing a bootloader application but can't seem to get it to consistently launch from either 0x0000 or the Boot Reset Address (0x3800 - from here, Table 27-13, I am using an AtMega328). Here is a simplistic setup to show my issue:

 

In prog.c (it just sets PB1 high and loops forever), I have

#include <avr/io.h>
int main() {
    DDRB = (1 << DDB1);
    PORTB = (1 << PORTB1);
    while (1);
}

and in boot.c (it just sets PB2 high and loops forever), I have

#include <avr/io.h>
asm("  .section .text\n");
int main() {
    DDRB = (1 << DDB2);
    PORTB = (1 << PORTB2);
    while (1);
}

My hypothesis is if I follow these steps:

 

  1. set fuses so that the program counter starts at 0x3800 on startup (set BOOTRST = 0, see Table 28-8 and 27-4 from the above link)
  2. program boot.c using avrdude to address 0x3800
  3. program prog.c using avrdude to address 0x0000

 

Then PB2 should high on and PB1 be low (since boot.c should be run on startup and never jumps to 0x0000). Instead, after step 2, PB2 goes high. Then after step 3, PB1 goes high and PB2 goes low. In other words, it seems that whenever I program either prog.c or boot.c, the chip just runs the latest one I programmed. However, I want it to start at address 0x3800, from which I will then run actual bootloader logic.

 

Here is how I accomplish step 1:

avrdude -e -c avrisp2 -p m328 -P usb -U hfuse:w:0xD8:m

Here is how I accomplish step 2:

#creating object file...
avr-gcc -g -Wall -Os -mmcu=atmega328p -Wl,--section-start=.text=0x3800 -c boot.c

#creating elf file file...
avr-gcc -g -Wall -Os -mmcu=atmega328p -Wl,--section-start=.text=0x3800 -o boot.elf boot.o

#creating hex file...
avr-objcopy -j .text -j .data -O ihex boot.elf boot.hex

#write to device
sudo avrdude -c avrisp2 -p m328 -P usb -U flash:w:boot.elf

Here is how I accomplish step 3:

#creating object file...
avr-gcc -g -Wall -Os -mmcu=atmega328p  -c prog.c

#creating elf file file...
avr-gcc -g -Wall -Os -mmcu=atmega328p  -o prog.elf prog.o

#creating hex file...
avr-objcopy -j .text -j .data -O ihex prog.elf prog.hex

#load onto chip
sudo avrdude -c avrisp2 -p m328 -P usb -U flash:w:prog.elf

Can you help me tell the chip to start at the bootloader address? Thanks!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
asm("  .section .text\n");

What on earth do you hope to achieve with that? By default the text of your C code wil be placed into .text anyway (look at the .s from -save-temps if you want to check!)

 

Anyway your fault is the perennial fault when dealing with flash addressing. Atmel choose to use word addressing while GCC (being generic across many processors) always uses byte addressing as the lowest common denominator.

 

While programmers always work in hex on this occasion think in decimal for a moment. Just consider what the number 0x3800 actually is.  In decimal it is 14,336. Yet this is supposed to be an address near the "top" of a 32K (32,768) byte micro! 14K is a whole lot less than 32K so the number simply is not right for GCC. In fact to convert Atmel's 0x3800 number from word to byte you just double it to 0x7000. In decimal that is 28,672 which sounds a much more plausible number for 4K short of 32,768.

 

In Atmel land a 32KiB micro has 16KiW's and the 0x3800 is 2KiW's short of 16KiW. (yes I know "KiW" is not a valid SI unit - I use it merely to suggest "Kibi Word").

 

Oh and when you compile boot.c there's no point setting .text=0x7000, it's only the linker that cares about memory locations. BTW there is a short for of --section-start=.text=07000 and that is -Ttext=0x7000 so I'd suggest:

#creating object file...
avr-gcc -g -Wall -Os -mmcu=atmega328p -c boot.c

#creating elf file file...
avr-gcc -g -Wall -Os -mmcu=atmega328p -Wl,-Ttext=0x7000 -o boot.elf boot.o

#creating hex file...
avr-objcopy -j .text -j .data -O ihex boot.elf boot.hex

#write to device
sudo avrdude -c avrisp2 -p m328 -P usb -U flash:w:boot.elf

 

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

Thank you for your thorough reply. I have changed boot.c to

#include <avr/io.h>
int main() {
    DDRB = (1 << DDB2);
    PORTB = (1 << PORTB2);
    while (1);
}

and changed the boot make to

#creating object file...
avr-gcc -g -Wall -Os -mmcu=atmega328 -c boot.c

#creating elf file file...

avr-gcc -g -Wall -Os -mmcu=atmega328 -Wl,-Ttext=0x7000 -o boot.elf boot.o

#creating hex file...
avr-objcopy -j .text -j .data -O ihex boot.elf boot.hex

#load onto mcu
avrdude -v -v  -c avrisp2 -p m328 -P usb -U flash:w:boot.elf

The -v -v was to check for where avrdude was loading. I get back

avrdude: Considering PT_LOAD program header entry #0:
    p_vaddr 0x7000, p_paddr 0x7000, p_filesz 140

I also changed the avr-gcc calls to use atmega328 rather than atmega328p since I am not using the pico version.

 

WIth these changes, I am still getting the same behavior. Making and loading boot.c using the instructions just listed turns PB2 high. Afterwards loading prog.c using the same instructions from the first post turns PB1 high and PB2 low. The fuses are still set so that BOOTRST points to the boot space (high fuse is 0XD8).

 

Shouldn't the above always run boot.c first even when prog.c is programmed?

 

Thanks again for your help.

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

Afterwards loading prog.c using the same instructions from the first post turns PB1 high and PB2 low.

Why are you surprised by this. When you load the prog.c program avrdude (or any ISP programming software) starts by erasing the flash so the previously programmed boot.c program is removed and then replaced (admittedly at a different place in memory) by the prog.c program.

 

One thing you may not know/realise about AVR processors is that unprogrammed (erased) flash contains 0xFF and to an AVR the opcode 0xFFFF (it's 16 bit opcodes remember) will execute a bit like NOP (in fact it's something like SBRS R31,7 but in general that will be a bit like NOP unless bit 7 in R31 really is set in which case it will NOP every second instruction).

 

So after you program prog.c (and wipe boot.c) the new code goes in at 0x0000. But at power on, because of BOOTRST, the AVR really does start to execute at 0x7000. Because boot.c has been erased what it finds there is a sea of 0xFFFF, 0xFFFF, ... so it keeps executing at 0x7000, 0x7002 and so on. Now another fact you may not know about AVRs is that the PC register contains just enough bits to address the flash of the device. In a 32K AVR like this there are 14 active bits in the PC register and they can address from 0x0000 to 0x3FFF (word) or 0x0000 to 0x7FFE (bytes). When the PC counter holds 0x3FFF (word) and execute the 0xFFFF (NOP) opcode from that location it increments and wraps from 0x3FFF to 0x0000. Lo and behold!  That just happens to be where prog.c is located so it now starts to execute the prog.c program just as if it had started opcode fetching at 0x0000 not 0x3800.

 

And sorry for the mix of 3800 and 7000 in that - sometimes it makes sense to "think" in bytes and sometimes in words. (which is where the Atmel confusion comes from in the first place!).

 

if you wanted to try an experiment where the AVR flash contained BOTH prog.c and boot.c what you need to do is build each then join the two .hex files and program that as one. While you can use fancy tools like srec_cat to join two smal l.hex files it might be as easy to load one into an editor (it's ASCII text after all) then paste the other onto the end. Right at the end of the first one (in fact both) there is a line that looks something like :01000000FF (I can't remember and I'm not near an example) which is the termination record that says "this HEX file ends here". You need to remove the one that is now in the middle of the two files you have joined. Write the result as something like "combined.hex" then program that into the flash.

 

All being well what you will find is that it operates as if only boot.c is present. If you want to be real clever have boot.c sit in a loop (_delay_ms() will work for this) for maybe 5 seconds flashing an LED then have it do:

asm("JMP 0");

(there are other ways but this is simple!) which will then transfer control to prog.c and have it do something different with the LED(s).

 

When run you should see boot.c operate for a while then prog.c - this is effectively what a fully implemented bootloader will be doing so it's a useful learning experience.

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

Thanks again for the great explanation. I combined the hex files (removing the :00000001FF line in the middle) and it worked great!

 

I think my misunderstanding came from prior experience using the "Burn bootloader" in the Arduino. The Arduino "Burn bootloader" seemed to take about a minute whereas on the command line it was on the order of a second. I figured the latter wasn't erasing the entire chip since the time was so much shorter and instead was just programming the area needed (in particular, I figured the Arduino had something in the avrdude.conf file that said reprogram all). The avrdude output may be a bit misleading since it says "140 bytes" when writing "prog".

 

I seem to be having an issue with the next step, actually writing flash from the bootloader. Since this is a separate issue though, I'll start another thread. Thanks again for all your help. I was at a bit of a loss without the feedback.

Last Edited: Sat. Nov 15, 2014 - 03:55 AM