simple bootloader

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

I am trying to write a bootloader for an atmega328 (datasheet here) but am having difficulty writing a simple program from boot. Here is my setup.

 

In prog.c, I am setting PB1 high:

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

This is then compiled as follows:

avr-gcc -g -Wall -Os -mmcu=atmega328p  -c prog.c
avr-gcc -g -Wall -Os -mmcu=atmega328p  -o prog.elf prog.o
avr-objcopy -j .text -j .data -O ihex prog.elf prog.hex

which creates a hex file here:

:100000000C9434000C943E000C943E000C943E0082
:100010000C943E000C943E000C943E000C943E0068
:100020000C943E000C943E000C943E000C943E0058
:100030000C943E000C943E000C943E000C943E0048
:100040000C943E000C943E000C943E000C943E0038
:100050000C943E000C943E000C943E000C943E0028
:100060000C943E000C943E0011241FBECFEFD8E04C
:10007000DEBFCDBF0E9440000C9444000C940000F1
:0C00800082E084B985B9FFCFF894FFCF6F
:00000001FF

My bootloader will eventually get data from an external source, but to simplify things I am just putting the above hex into ram. When I compile the below program, I expect to see PB2 go high for 1 second followed by PB1. Instead, PB2 just stays high.

#include <avr/io.h>
#define F_CPU 1000000UL
#include <util/delay.h>
#include <avr/boot.h>

#define PAGE_SIZE_BYTES 128
#define FLASH_APP_START_ADDR 0x0000

int main() {

    //saving result of prog.hex in ram
    //prog.hex is a program that sets PB1 high and loops forever
    //extra 0xFF's have been added to make sure the below fills 2 full pages
    uint8_t prog[] = {
        0x0C,0x94,0x34,0x00,0x0C,0x94,0x3E,0x00,0x0C,0x94,0x3E,0x00,0x0C,0x94,0x3E,0x00,
        0x0C,0x94,0x3E,0x00,0x0C,0x94,0x3E,0x00,0x0C,0x94,0x3E,0x00,0x0C,0x94,0x3E,0x00,
        0x0C,0x94,0x3E,0x00,0x0C,0x94,0x3E,0x00,0x0C,0x94,0x3E,0x00,0x0C,0x94,0x3E,0x00,
        0x0C,0x94,0x3E,0x00,0x0C,0x94,0x3E,0x00,0x0C,0x94,0x3E,0x00,0x0C,0x94,0x3E,0x00,
        0x0C,0x94,0x3E,0x00,0x0C,0x94,0x3E,0x00,0x0C,0x94,0x3E,0x00,0x0C,0x94,0x3E,0x00,
        0x0C,0x94,0x3E,0x00,0x0C,0x94,0x3E,0x00,0x0C,0x94,0x3E,0x00,0x0C,0x94,0x3E,0x00,
        0x0C,0x94,0x3E,0x00,0x0C,0x94,0x3E,0x00,0x11,0x24,0x1F,0xBE,0xCF,0xEF,0xD8,0xE0,
        0xDE,0xBF,0xCD,0xBF,0x0E,0x94,0x40,0x00,0x0C,0x94,0x44,0x00,0x0C,0x94,0x00,0x00,
        0x82,0xE0,0x84,0xB9,0x85,0xB9,0xFF,0xCF,0xF8,0x94,0xFF,0xCF,0xFF,0xFF,0xFF,0xFF,
        0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
        0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
        0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
        0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
        0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
        0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
        0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
    };

    //setting PB2 high to indicate it is still in boot.c
    DDRB = (1 << DDB2);
    PORTB = (1 << PORTB2);
    _delay_ms(1000);


    ////////////////////////////////////////////////////////////
    //write prog to flash area
    ////////////////////////////////////////////////////////////

    uint8_t* flash_ptr = FLASH_APP_START_ADDR;  //address used for page write
    uint8_t* temp_flash_ptr;                    //address used for page fill
    uint8_t* ram_ptr = prog;                    //address pointing to prog
    uint8_t i, j;                               //looping variables

    //prog is 256kb and a page is 128 bytes so loop twice
    for (j = 0; j < 2; j++) {

        temp_flash_ptr = flash_ptr;

        //erase the page about to be written
        boot_page_erase_safe(flash_ptr);
        boot_spm_busy_wait();

        //fill a page
        for (i = 0; i < PAGE_SIZE_BYTES; i+=2) {
            uint16_t word;

            word = *ram_ptr++;
            word = *ram_ptr++ << 8;
            boot_page_fill_safe(temp_flash_ptr, word);
            boot_spm_busy_wait();
            temp_flash_ptr += 2;

        }

        //write a page 
        boot_page_write_safe(flash_ptr);
        boot_spm_busy_wait();

        //update flash_ptr to the next page
        flash_ptr+=128;

    }

    //start the actual program
    asm("jmp 0");
    while (1);

}

This is then compiled and loaded using the following:

avr-gcc -g -Wall -Os -mmcu=atmega328 -c boot.c
avr-gcc -g -Wall -Os -mmcu=atmega328 -Wl,-Ttext=0x7000 -o boot.elf boot.o
avr-objcopy -j .text -j .data -O ihex boot.elf boot.hex
avrdude -e -v -c avrisp2 -p m328 -P usb -U flash:w:boot.elf

When the program runs, it just has PB2 high. PB1 stays low. I would have thought that PB2 would go high for about 1 second (the delay function) and then PB1 would go high (the "jmp 0" statement to prog.c that has just been placed into flash. I have verified in a simpler scenario (see here) that the code starts at 0x7000 and jumps appropriately to 0x0000 so I think the issue is in the section labeled "write prog to flash area"). However, from what I can tell, I have followed AVR109 and section 27-8 of the datasheet.

 

Can you help me understand where my code is not writing flash appropriately? Thanks.

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

Apart from the obvious suggestion of using a debugWire debugger like ATMEL-ICE, a good debugging technique when trying to write flash is to let the code do the writing then use ISP to read back the flash contents and study the hex (perhaps hex2bin it then a binary editor?) to see what,  if anything has actually been written. 

 

BTW the avr/boot.h header has an example "boot_program_page()" at the top. I've always just copied/used this as it simply works. All you are left with is a for() routine to write that wraps around this and feeds it the data a page at a time. Be warned the "page" parameter is really just a byte address not a page number as the parameter name might imply (don't ask me how I know!). 

Last Edited: Sat. Nov 15, 2014 - 11:16 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thank you so much. The avr/boot.h example was very helpful. It looks like I was missing the

boot_rww_enable ();

line. After adding it, the below code works great! While lengthy, I am posting the below code in case someone else comes along this thread.

#include <avr/io.h>
#define F_CPU 1000000UL
#include <util/delay.h>
#include <avr/boot.h>

#define PAGE_SIZE_BYTES 128

#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;
}

int main() {

    //saving result of prog.hex in ram
    //prog.hex is a program that sets PB1 high and loops forever
    //extra 0xFF's have been added to make sure the below fills 2 full pages
    uint8_t prog[] = {
        0x0C,0x94,0x34,0x00,0x0C,0x94,0x3E,0x00,0x0C,0x94,0x3E,0x00,0x0C,0x94,0x3E,0x00,
        0x0C,0x94,0x3E,0x00,0x0C,0x94,0x3E,0x00,0x0C,0x94,0x3E,0x00,0x0C,0x94,0x3E,0x00,
        0x0C,0x94,0x3E,0x00,0x0C,0x94,0x3E,0x00,0x0C,0x94,0x3E,0x00,0x0C,0x94,0x3E,0x00,
        0x0C,0x94,0x3E,0x00,0x0C,0x94,0x3E,0x00,0x0C,0x94,0x3E,0x00,0x0C,0x94,0x3E,0x00,
        0x0C,0x94,0x3E,0x00,0x0C,0x94,0x3E,0x00,0x0C,0x94,0x3E,0x00,0x0C,0x94,0x3E,0x00,
        0x0C,0x94,0x3E,0x00,0x0C,0x94,0x3E,0x00,0x0C,0x94,0x3E,0x00,0x0C,0x94,0x3E,0x00,
        0x0C,0x94,0x3E,0x00,0x0C,0x94,0x3E,0x00,0x11,0x24,0x1F,0xBE,0xCF,0xEF,0xD8,0xE0,
        0xDE,0xBF,0xCD,0xBF,0x0E,0x94,0x40,0x00,0x0C,0x94,0x44,0x00,0x0C,0x94,0x00,0x00,
        0x82,0xE0,0x84,0xB9,0x85,0xB9,0xFF,0xCF,0xF8,0x94,0xFF,0xCF,0xFF,0xFF,0xFF,0xFF,
        0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
        0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
        0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
        0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
        0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
        0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
        0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
    };

    //setting PB2 high to indicate it is still in boot.c
    DDRB = (1 << DDB2);
    PORTB = (1 << PORTB2);
    _delay_ms(1000);

    ////////////////////////////////////////////////////////////
    //write prog to flash area
    ////////////////////////////////////////////////////////////

    uint32_t page_addr = 0;                     //start at flash_start = 0x0000
    uint8_t* ram_ptr = prog;                    //address pointing to prog

    boot_program_page( page_addr, ram_ptr);
    page_addr += PAGE_SIZE_BYTES;
    ram_ptr   += PAGE_SIZE_BYTES;
    boot_program_page( page_addr, ram_ptr);

    //start the actual program
    asm("jmp 0");
    while (1);

}

 

 

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

Yeah I too have been bitten by that boot_rww_enable() thing in the past. In my case I solved it quickly because I was using a JTAG debugger and I could see that the flash held all 0xFFFF where I was supposed to be seeing app code. A quick head scratch revealed what was going on. I am a great fan of JTAG debuggers!

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

Why write yet another bootloader?

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

If there was a precompiled bl for ea of 10 avrs x 3 bauds x 4 xtal freqs, that would be 120 hex files. But I bet someone could throw another variable in there to add more complexity.

 

Imagecraft compiler user

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

Why write yet another bootloader?

It's what people do - like climbing Everest and writing HD44780 LCD code for the ten bazillionth time.

 

Not sure what Bob's point was - wouldn't you normally supply a bootloader as source and let the user configure it then build it? (but they don't actually have to write it because there are ten bazillion existing ones)