NerdKits - electronics education for a digital generation

You are not logged in. [log in]

NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.

Microcontroller Programming » bootloader - AVR109_Noter

July 10, 2011
by Noter
Noter's Avatar

One thing I like to do when learning a new program like the bootloader is rewrite and improve where possible. So I did this early on with the bootloader and consolidated it all into a single source file with config data in the make file for the things like the chip, frequency, and baud to use. Now to put the bootloader on a different chip I just edit the make file and go. Well for the 168 and 328 anyway because for now it is setup for those two. To add another chip will require a couple of definitions in the program for the new signature and likewise in the makefile for the bootsection start address and fuse settings. Just follow along with the existing defintions and it shouldn't be too difficult.

As usual my makefile is not in standard form but it does show the configuration settings and how they are passed to the compiler. Maybe someone that implements with a standard nerdkit type makefile will post their's once it's working.

Following is the program and then the makefile ...

July 10, 2011
by Noter
Noter's Avatar
//
//
//  AVR109_Noter.c
//
//  Noter's v3.0 AVR109 (AVRBOOT) Bootloader
//
//  Based on ATmel's application notes:
//      AVR109 - Using Self Programming on tinyAVR and megaAVR devices 
//      AVR911 - AVR Open-source Programmer.
//
#include <avr/io.h>
#include <avr/boot.h>
#include <avr/eeprom.h>
#include <avr/pgmspace.h> 
#include <util/delay.h>
#include <stdbool.h>

// preprocessor macros for port/pin manulipulation
//
#define INPUT2(port,pin) DDR ## port &= ~_BV(pin) 
#define OUTPUT2(port,pin) DDR ## port |= _BV(pin) 
#define CLEAR2(port,pin) PORT ## port &= ~_BV(pin) 
#define SET2(port,pin) PORT ## port |= _BV(pin) 
#define TOGGLE2(port,pin) PORT ## port ^= _BV(pin) 
#define READ2(port,pin) ((PIN ## port & _BV(pin))?1:0)
//
#define INPUT(x) INPUT2(x) 
#define OUTPUT(x) OUTPUT2(x)
#define CLEAR(x) CLEAR2(x)
#define SET(x) SET2(x)
#define TOGGLE(x) TOGGLE2(x)
#define READ(x) READ2(x)
#define PULLUP_ON(x) INPUT2(x); SET2(x)
#define PULLUP_OFF(x) INPUT2(x); CLEAR2(x)

// define ports, pins
//
#define START_SWITCH        B,0

// local function prototypes
//
void uart_write(char data);
uint8_t uart_read(void);
void (*jump_to_application)(void);
void uart_write_PSTR(char *data);

// Loader variables/values/macros
//
#define MEMORY_TYPE_FLASH   'F'
#define MEMORY_TYPE_EEPROM  'E'

// programmer info
//
#define PROGRAMMER_ID       "AVRBOOT"
#define HARDWARE_VERSION    "01"
#define SOFTWARE_VERSION    "30"
#define SIG_LEN 3
#if defined (__AVR_ATmega168__)
                        prog_uint8_t signature[] = "\x1E\x94\x06";
                        #define DEVICE_CODE         0x02
                        #define PAGE_SIZE           128
                        #define PAGE_COUNT          128
#elif defined (__AVR_ATmega328P__)
                        prog_uint8_t signature[] = "\x1E\x95\x0F";
                        #define DEVICE_CODE         0x03
                        #define PAGE_SIZE           128
                        #define PAGE_COUNT          256
#else
    #error cpu not supported by AVR109_Noter bootloader!
#endif

// buffer for block writes to eeprom
uint8_t usart_buffer[PAGE_SIZE];

// --------------------------------------------------------------------------------------------------------
//
int main(void) {
    // initialize and check start switch
    PULLUP_ON(START_SWITCH);
    _delay_us(1); // let it rise
    // if the start switch is open, start the application
    if(READ(START_SWITCH)){
        // put reset vector back to flash address base
        MCUCR=_BV(IVCE);
        MCUCR=0;
        // and jump to it to start the application
        jump_to_application();
    }

    // set baudrate, initialize usart 
    UCSR0A = 0;
    UBRR0L = (F_CPU/(16*BAUD))-1;
    // enable tx, rx,
    UCSR0B = _BV(RXEN0)|_BV(TXEN0);
    // set 8N1
    UCSR0C = _BV(UCSZ01)|_BV(UCSZ00);

    // process commands from host
    uint16_t i;
    uint16_t address;
    uint16_t byte_count;
    uint8_t command;
    while(true){
                                        //  Commands                            | Host writes | Host reads |      |
                                        //  --------                            +-----+-------+------+-----+      |
                                        //                                      | ID  | data  | data |     | Note |
        command=uart_read();
        if(command=='P'){               // | Enter programming mode             | 'P' |       |      | 13d |   1  |
            // we're always in programming mode
            uart_write('\r');
        }

        else if(command=='L'){          // | Leave programming mode             | 'L' |       |      | 13d |   5  |
            // could jump start the application here ...
            uart_write('\r');
        }

        else if(command=='a'){          // | Report autoincrement address       | 'a' |       |      | 'Y' |      |
            uart_write('Y');
        }

        else if(command=='A'){          // | Set address                        | 'A' | ah al |      | 13d |   2  |
            // we get word address for flash
            // or byte address for eeprom
            address=(uart_read()<<8)|uart_read();
            uart_write('\r');
        }

        else if(command=='e'){          // | Chip erase                         | 'e' |       |      | 13d |      |
            // erase all lower memory pages - below the bootloader
            for(address=0;address<BOOT_SECTION_START;address+=PAGE_SIZE)
                boot_page_erase_safe(address);
            uart_write('\r');
        }

        else if(command=='b'){          // | check block support                | 'b' |       |dd dd |  Y  |      |
            // we only do block reads and writes
            uart_write('Y');
            // max block size is page size
            uart_write(PAGE_SIZE>>8);
            uart_write(PAGE_SIZE&0xFF);
        }

        else if(command=='B'){          // | flash or eeprom block write        | 'B' |dd(dd) |      | 13d |      |
            // get the byte count for this block
            byte_count=(uart_read()<<8)|uart_read();
            // is it for flash or eeprom?
            if(uart_read()==MEMORY_TYPE_FLASH){
                // remember, word address given for flash
                // read and load the page buffer 2 bytes at a time
                for(i=0;i<byte_count;i+=2)
                    // note: write words using byte buffer address
                    // but byte address must be on word boundary
                    // thus +2 each iteration and load two bytes
                    boot_page_fill_safe(i,uart_read()|(uart_read()<<8));
                // but no matter what, don't overwrite self (bootloader)
                if(address<(BOOT_SECTION_START-PAGE_SIZE))
                    // otherwise, write the page using byte address
                    boot_page_write_safe(address<<1);
                // re-enable the Read-While_Write (application) section
                boot_rww_enable_safe();
                // auto-increment with count in words
                address+=(byte_count>>1);
            }else{ 
                // a byte address is used for eeprom
                for(i=0;i<byte_count;i++)
                    usart_buffer[i]=uart_read();
                // write the buffer
                eeprom_write_block(usart_buffer,(void*)address,byte_count);
                address+=byte_count;    // and auto-increment the address
            }
            // tell host we're ready for next block
            uart_write('\r');
        }

        else if(command=='g'){          // | flash or eeprom block read         | 'g' |       |dd(dd)|     |      |
            // get byte count to read for this block
            byte_count=(uart_read()<<8)|uart_read();
            // get memory type - flash or eeprom
            if(uart_read()==MEMORY_TYPE_FLASH){
                // word address given for flash
                for(i=0;i<byte_count;i++)
                    // read and send all the bytes for this block
                    // using a byte address to read. <<1 converts
                    // the word address to a byte address.
                    uart_write(pgm_read_byte((address<<1)+i));
                // and auto-increment flash word address    
                address+=(byte_count>>1);
            }else{
                // byte address given for eeprom
                for(i=0;i<byte_count;i++)
                    // read and send bytes for this block,
                    // auto-increment byte address
                    uart_write(eeprom_read_byte((uint8_t *)(address++)));
            }
        }

        else if(command=='T'){          // | Select device type                 | 'T' |    dd |      | 13d |   6  |
            // read and ignore since we already know our
            // device type.
            uart_read(); 
            uart_write('\r');
        }

        else if(command=='s'){          // | Read signature bytes               | 's' |       | 3*dd |     |      |
            // send the string backwards, LSB first
            for(i=1;i<=SIG_LEN;i++)
                uart_write(pgm_read_byte(&signature[SIG_LEN-i]));
        }

        else if(command=='t'){          // | Return supported device codes      | 't' |       | n*dd | 00d |   7  |
            // we only have one, terminate list with null character
            uart_write(DEVICE_CODE);
            uart_write(0x00);
        }

        else if(command=='S'){          // | Return software identifier         | 'S' |       | s[7] |     |   8  |
            uart_write_PSTR(PSTR(PROGRAMMER_ID));
        }

        else if(command=='V'){          // | Return sofware version             | 'V' |       |dd dd |     |   9  |
            uart_write_PSTR(PSTR(SOFTWARE_VERSION));
        }

        else if(command=='v'){          // | Return hardware version            | 'v' |       |dd dd |     |   9  |
            uart_write_PSTR(PSTR(HARDWARE_VERSION));
        }

        else if(command=='p'){          // | Return programmer type             | 'p' |       |   dd |     |  10  |
            // [S]erial programmer
            uart_write('S');
        }

        else if(command=='E'){          // | Exit bootloader                    | 'E' |       |      | 13d |      |
            // avrdude gives an error message if we don't do this
            uart_write('\r');
        }

        else if(command==0x1B){         // | ESC - sync char                    | x1B |   dd  |      |     |      |
            // consume, no reply
        }
    }
}
// --------------------------------------------------------------------------------------------------------

// USART write a character
void __attribute__ ((noinline)) uart_write(char data) {
    while ((UCSR0A & _BV(UDRE0))==0);
    UDR0 = data;
}

// USART read a character
uint8_t __attribute__ ((noinline)) uart_read(void){
    while(!(UCSR0A & _BV(RXC0)));
    return UDR0;
}

// USART write a string from flash
void __attribute__ ((noinline)) uart_write_PSTR(char *data){
    int i;
    for(i=0;pgm_read_byte_near((char*)&data[i]);i++)
        uart_write(pgm_read_byte_near((char*)&data[i]));
}
July 10, 2011
by Noter
Noter's Avatar
PROGRAM=AVR109_Noter
# AVRBOOT (butterfly) bootloader

#
# settings for the programmer that will load the bootloader
#
PGMR_SPI_PORT=0
#PGMR_COM_PORT=COM9
#PGMR_COM_BAUD=250000
PGMR_COM_PORT=COM2
PGMR_COM_BAUD=115200
PGMR_ID="AVR HV "
#PGMR_ID="AVR ISP"

#
# settings for the bootloader compile and link
#
MCU=atmega328p
#MCU=atmega168

#F_CPU=20000000UL
F_CPU=18432000UL
#F_CPU=14745600UL
#F_CPU=8000000UL

#BAUD=9600UL
#BAUD=19200UL
#BAUD=38400UL
#BAUD=57600UL
BAUD=115200UL
#BAUD=250000UL

#
#   Fuse settings and start address of the bootsection
#   Use a fuse calculator like http://www.engbedded.com/fusecalc
#
ifeq ($(MCU),atmega168)
#   0x1E00 word addr is 0x3C00 byte addr (1kb bootloader) efuse 0xFA
#   0x1C00 word addr is 0x3800 byte addr (2kb bootloader) efuse 0xF8
    BOOT_SECTION_START = 0x3C00 
    LFUSE = F7 
    HFUSE = D5
    EFUSE = FA
    LOCK = EF
else ifeq ($(MCU),atmega328p)
#   0x3E00 word addr is 0x7C00 byte addr (1kb bootloader) hfuse 0xD4
#   0x3C00 word addr is 0x7800 byte addr (2kb bootloader) hfuse 0xD2
    BOOT_SECTION_START = 0x7C00

#   external crystal and preserve eeprom on chip erase
    LFUSE = F7
    HFUSE = D4
    EFUSE = FD
    LOCK = EF

#   8 mhz internal oscillator, no eeprom preservation
#   LFUSE = E2
#   HFUSE = DA
#   EFUSE = FF
#   LOCK = EF

endif

all: $(PROGRAM).hex

program: $(PROGRAM).pgm

.SECONDARY:

%.o: %.c 
    avr-gcc -g -Os -mcall-prologues -mmcu=$(MCU) -std=gnu99 \
        -L/usr/local/avr/avr/lib \
        -Wl,--section-start=.text=$(BOOT_SECTION_START) \
        $< -o $@ \
        -DF_CPU=$(F_CPU) -DBAUD=$(BAUD) -DMCU=$(MCU) \
        -DBOOT_SECTION_START=$(BOOT_SECTION_START)

%.ass: %.o
    avr-objdump -S -d $< > $@

%.hex: %.o %.ass
    avr-objcopy -j .text -O ihex $< $@

%.pgm: %.hex
#   avr911_noter -d$(MCU) -c$(PGMR_COM_PORT) -B$(PGMR_COM_BAUD) \
#   -if$< -pf -vf -e -f$(HFUSE)$(LFUSE) -l$(LOCK) -E$(EFUSE) -Z0
#
    avr911_noter -d$(MCU) -c$(PGMR_COM_PORT) -B$(PGMR_COM_BAUD) \
    -if$< -pf -vf -e -f$(HFUSE)$(LFUSE) -l$(LOCK) -E$(EFUSE) -D
#
#   avrdude -C ../libNoter/avrdude.rs -c $(PGMR_ID) -p $(MCU) -b $(PGMR_COM_BAUD) \
#   -P $(PGMR_COM_PORT) -U flash:w:$<:a  \
#   -U lfuse:w:0X$(LFUSE):m \
#   -U hfuse:w:0X$(HFUSE):m \
#   -U efuse:w:0X$(EFUSE):m \
#   -U lock:w:0X$(LOCK):m

clean: 
    -rm $(PROGRAM).o $(PROGRAM).hex $(PROGRAM).ass
August 17, 2011
by missle3944
missle3944's Avatar

Hi Noter,

So what is the main difference from the other code that you posted? Does this make it easier to put another bootloader onto a new chip?

-Dan

Post a Reply

Please log in to post a reply.

Did you know that any circuit of voltage sources and resistors can be simplified to a "Thevenin" equivalent circuit? Learn more...