boot.c


/*********************************************************
Serial Bootloader for Atmel megaAVR Controllers

boot.c
Copyright (c) 2005, Larry Barello
Cleaned up source, added timeout function, stripped out monitor stuff.
Rewrote to be much smaller (common waitspm(), all variables in registers.

Based upon ATmegaBOOT.c by E. Lins
based on stk500boot.c
Copyright (c) 2003, Jason P. Kyle
All rights reserved.
see avr1.org for original file and information

This program is free software; you can redistribute it
and/or modify it under the terms of the GNU General
Public License as published by the Free Software
Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will
be useful, but WITHOUT ANY WARRANTY; without even the
implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.  See the GNU General Public
License for more details.

You should have received a copy of the GNU General
Public License along with this program; if not, write
to the Free Software Foundation, Inc.,
59 Temple Place, Suite 330, Boston, MA  02111-1307 USA

Licence can be viewed at
http://www.fsf.org/licenses/gpl.txt

Target = Atmel AVR m128,m64,m32,m16,m8,m162,m163,m169,
m8515,m8535. ATmega161 has a very small boot block so
isn't supported.

Tested with m128,m8,m163 - feel free to let me know
how/if it works for you.

*********************************************************/

#include <inttypes.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/eeprom.h>
#include <avr/interrupt.h>


/* SW_MAJOR and MINOR needs to be updated from time to time to avoid warning message from AVR Studio */
#define HW_VER   0x02
#define SW_MAJOR 0x01
#define SW_MINOR 0x12

#if !defined(SW_DDR) && !defined(LED_DDR)
#error Apparently no hardware has been defined!
#endif

#define SIG1    0x1E    // Yep, Atmel is the only manufacturer of AVR micros.  Single source :(
#if defined __AVR_ATmega128__
#define SIG2    0x97
#define SIG3    0x02
#define PAGE_SIZE   0x80U   //128 words
#elif defined __AVR_ATmega64__
#define SIG2    0x96
#define SIG3    0x02
#define PAGE_SIZE   0x80U   //128 words
#elif defined __AVR_ATmega32__
#define SIG2    0x95
#define SIG3    0x02
#define PAGE_SIZE   0x40U   //64 words
#elif defined __AVR_ATmega16__
#define SIG2    0x94
#define SIG3    0x03
#define PAGE_SIZE   0x40U   //64 words
#elif defined __AVR_ATmega8__
#define SIG2    0x93
#define SIG3    0x07
#define PAGE_SIZE   0x20U   //32 words
#elif defined __AVR_ATmega162__
#define SIG2    0x94
#define SIG3    0x04
#define PAGE_SIZE   0x40U   //64 words
#elif defined __AVR_ATmega163__
#define SIG2    0x94
#define SIG3    0x02
#define PAGE_SIZE   0x40U   //64 words
#elif defined __AVR_ATmega169__
#define SIG2    0x94
#define SIG3    0x05
#define PAGE_SIZE   0x40U   //64 words
#elif defined __AVR_ATmega8515__
#define SIG2    0x93
#define SIG3    0x06
#define PAGE_SIZE   0x20U   //32 words
#elif defined __AVR_ATmega8535__
#define SIG2    0x93
#define SIG3    0x08
#define PAGE_SIZE   0x20U   //32 words
#else
#error Not a recognized CPU type
#endif

void start(void);
void putch(uint8_t);
uint8_t getch(void);
uint8_t checkchar(void);
void getNch(uint8_t);
void byte_response(uint8_t);
void nothing_response(void);
void waitspm(uint8_t);
void InitUART(void);
void DelayMS(uint16_t delay);
void my_eeprom_write_byte(uint8_t *addr, uint8_t data);
uint8_t my_eeprom_read_byte(const uint8_t *addr);

uint8_t buff[256];
#define app_start() asm volatile("jmp   0\n");

#if defined(LED_DDR)
    #define ToggleLed() (LED_PORT ^= _BV(LED))
    #define InitLed()   (LED_DDR |= _BV(LED))
    #define LedOn()     (LED_PORT &= ~_BV(LED))
    #define LedOff()    (LED_PORT |= _BV(LED))
#else
    #define ToggleLed()
    #define InitLed()
    #define LedOn()
    #define LedOff()
#endif

#if defined(SW_DDR)
    #define InitSW()    (SW_PORT |= _BV(SW))
    #define SWInput()   (SW_PIN & _BV(SW))
#else
    #define InitSW()
    #define SWInput()   (1)
#endif

void boot_1(void)
{
    asm volatile(
    "\tldi  R24, 1\n"
    "\trjmp boot\n"
    );
}

void boot_0(void)
{
    asm volatile(
    "\tldi  R24, 0\n"
    "\trjmp boot\n"
    );
}

void boot(char) __attribute__ ((noreturn));
void boot(char bCalled)
{
    uint8_t ch, ch2, bEEPROM, i, bAppStart;
    uint16_t w;

    union address_union
    {
        uint32_t dword;     // Use this to left shift & far memory access.
        uint16_t word;      // Read word address into here
        uint8_t byte[4];    // access rampz as byte[2]
    }
    address;

    union length_union
    {
        uint16_t word;
        uint8_t  byte[2];
    }
    length;

    // Set up bare minimum for C to operate...

    asm volatile(
    "\tcli\n"
    "\tclr  __zero_reg__\n"
    );
    SPL = (RAMEND-1)&0xFF;
    SPH = (RAMEND-1)>>8;

    // Now, ready to go.

    InitUART();
    InitSW();      // Enable BootLoader input pullup
    InitLed();

    if (bCalled)
    {
        putch(0x14);
        putch(0x10);
    }
    else
    {
        putch('\0');    // Why is this here?
        bAppStart = (pgm_read_byte_near(0x00) != 0xFF && SWInput());    // Forced override into bootloader.

        if (bAppStart)
            i = 30; // 3 seconds
        else
            i = 6;  // 1/2 second

        while(i--)
        {
            DelayMS(100);
            ToggleLed();
            if (checkchar())
            {
                if (getch() == '0')
                {
                    nothing_response();
                    bAppStart = 0;
                    break;
                }
            }
        }
        if (bAppStart)
            app_start();
    }
    LedOff();

    for (;;)
    {

        ch = getch();           /* get character from UART */
        if(ch=='0')             /* Hello is anyone home ? */
        {
            nothing_response();
        }
        else if(ch=='1')        // Request Programmer ID
        {
            if (getch() == ' ')
            {
                putch(0x14);
                putch('A');
                putch('V');
                putch('R');
                putch(' ');
                putch('S');
                putch('T');
                putch('K');
                putch(0x10);
            }
        }
        else if(ch == '@')  // AVR ISP/STK500 board commands  DON'T CARE so default nothing_response
        {
            ch2 = getch();
            if (ch2 > 0x85)
                getch();
            nothing_response();
        }
        else if(ch=='A')   // AVR ISP/STK500 board requests
        {
            ch2 = getch();
            if(ch2==0x80) byte_response(HW_VER);        // Hardware version
            else if(ch2==0x81) byte_response(SW_MAJOR); // Software major version
            else if(ch2==0x82) byte_response(SW_MINOR); // Software minor version
            else if(ch2==0x98) byte_response(0x03);     // Unknown but seems to be required by avr studio 3.56
            else byte_response(0x00);                   // Covers various unnecessary responses we don't care about
        }
        else if(ch=='B')        // Device Parameters  DON'T CARE, DEVICE IS FIXED
        {
            getNch(20);
            nothing_response();
        }
        else if(ch=='E')        // Parallel programming stuff  DON'T CARE  */
        {
            getNch(5);
            nothing_response();
        }
        else if(ch=='P' || ch == 'R')   // Ignore Enter, Leave and Erase
        {
            LedOn();
            nothing_response();
        }
        else if (ch == 'Q')
        {
            LedOff();
            nothing_response();
            DelayMS(2); // Wait for previous response to clear.
            start();    // Re-enter the bootloader
        }
#if 0   // Pointless since avrdude doesn't use these commands to access eeprom
        else if (ch == 'a')
        {
            my_eeprom_write_byte(address.word++, getch());  // Do these auto increment?
            nothing_response();
        }
        else if (ch == 'q')
        {
            byte_response(my_eeprom_read_byte(address.word++));
        }
#endif
        /* Set address, little endian. EEPROM in bytes, FLASH in words  */
        /* Perhaps extra address bytes may be added in future to support > 128kB FLASH.  */
        /* This might explain why little endian was used here, big endian used everywhere else.  */
        else if(ch=='U')
        {
            address.byte[0] = getch();
            address.byte[1] = getch();
            nothing_response();
        }
        else if(ch == 'V')      // Universal SPI programming command, disabled.  Would be used for fuses and lock bits.
        {
            getNch(4);
            byte_response(0x00);
        }
        else if(ch=='d')         /* Write memory, length is big endian and is in bytes  */
        {
            length.byte[1] = getch();
            length.byte[0] = getch();
            bEEPROM = 0;
            if (getch() == 'E')
                bEEPROM = 1;
            for (w=0;w<length.word;w++)
            {
                buff[w] = getch();   // Store data in buffer, can't keep up with serial data stream whilst programming pages
            }
            if (getch() == ' ')
            {
                if (bEEPROM)
                {                       //Write to EEPROM one byte at a time
                    for(w=0;w<length.word;w++)
                    {
                        my_eeprom_write_byte((uint8_t *)address.word,buff[w]);
                        address.word++;
                    }
                }
                else
                {                           //Write to FLASH one page at a time
                    address.dword = address.dword << 1;         //address * 2 -> byte location
#ifdef RAMPZ
                    RAMPZ = address.byte[2];
#endif

                    /* if ((length.byte[0] & 0x01) == 0x01) length.word++;  //Even up an odd number of bytes */
                    if ((length.byte[0] & 0x01))
                        length.word++;  //Even up an odd number of bytes
                    cli();                  //Disable interrupts, just to be sure
                    while(bit_is_set(EECR,EEWE));           //Wait for previous EEPROM writes to complete
                    asm volatile(
                        "StartAsm:\n\t"
                           "clr r17         \n\t"       //page_word_count
                           "mov r30, %A2\t\n"
                           "mov r31, %B2\t\n"
                           "mov r26, %A3\t\n"
                           "mov r27, %B3\t\n"
                           "ldi r28,lo8(buff)\n\t"      //Start of buffer array in RAM
                           "ldi r29,hi8(buff)\n\t"
                           "length_loop:    \n\t"       //Main loop, repeat for number of words in block
                           "cpi r17,0x00    \n\t"           //If page_word_count=0 then erase page
                           "brne    no_page_erase\n\t"
                           "ldi r24, 0x03\n\t"
                           "rcall   waitspm \n\t"
                    #ifdef __AVR_ATmega163__
                           ".word 0xFFFF    \n\t"
                           "nop             \n\t"
                    #endif
                           "ldi r24, 0x11\n\t"
                           "rcall   waitspm \n\t"
                    #ifdef __AVR_ATmega163__
                           ".word 0xFFFF    \n\t"
                           "nop             \n\t"
                    #endif
                           "no_page_erase:  \n\t"
                           "ld  r0,Y+       \n\t"   //Write 2 bytes into page buffer
                           "ld  r1,Y+       \n\t"
                           "ldi r24, 0x01\n\t"
                           "rcall   waitspm \n\t"

                           "inc r17         \n\t"   //page_word_count++
                           "cpi r17,%1      \n\t"
                           "brlo    same_page\n\t"  //Still same page in FLASH
                           "write_page:     \n\t"
                           "clr r17         \n\t"   //New page, write current one first
                    #ifdef __AVR_ATmega163__
                           "andi    r30,0x80\n\t"   // m163 requires Z6:Z1 to be zero during page write
                    #endif
                           "ldi r24, 0x05\n\t"
                           "rcall   waitspm \n\t"
                    #ifdef __AVR_ATmega163__
                           ".word 0xFFFF    \n\t"
                           "nop             \n\t"
                           "ori r30,0x7E    \n\t"   // recover Z6:Z1 state after page write (had to be zero during write)
                    #endif
                           "ldi r24, 0x11\n\t"
                           "rcall   waitspm \n\t"
                    #ifdef __AVR_ATmega163__
                           ".word 0xFFFF    \n\t"
                           "nop             \n\t"
                    #endif
                           "same_page:      \n\t"
                           "adiw    r30,2   \n\t"   //Next word in FLASH
                           "sbiw    r26,2   \n\t"   //length-2
                           "breq    final_write \n\t"   //Finished
                           "rjmp    length_loop \n\t"
                           "final_write:    \n\t"
                           "cpi r17,0       \n\t"
                           "breq    block_done  \n\t"
                           "adiw    r26,2   \n\t"   //length+2, fool above check on length after short page write
                           "rjmp    write_page  \n\t"
                           "block_done:     \n\t"
                           "clr __zero_reg__\n\t"   //restore zero register
                           : "=m" (SPMCR) : "M" (PAGE_SIZE), "r" (address), "r" (length) : "r0","r16","r17","r24","r25","r26","r27","r28","r29","r30","r31");

                    /* Should really add a wait for RWW section to be enabled, don't actually need it since we never */
                    /* exit the bootloader without a power cycle anyhow */
                }
                putch(0x14);
                putch(0x10);
            }
        }
        else if(ch=='t')         /* Read memory block mode, length is big endian.  */
        {
            length.byte[1] = getch();
            length.byte[0] = getch();
            if (getch() == 'E')
                bEEPROM = 1;
            else
            {
                bEEPROM = 0;
                address.dword = address.dword << 1;         // address * 2 -> byte location
#if defined RAMPZ
                RAMPZ = address.byte[2];
#endif
            }
            if (getch() == ' ')
            {                       // Command terminator
                putch(0x14);
                for (w=0;w < length.word;w++)
                {               // Can handle odd and even lengths okay
                    if (bEEPROM)
                    {                           // Byte access EEPROM read
                        putch(my_eeprom_read_byte((const uint8_t *)address.word));
                        address.word++;
                    }
                    else
                    {
#if defined(RAMPZ)
                        putch(pgm_read_byte_far(address.dword));
#else
                        putch(pgm_read_byte_near(address.word));
#endif
                        address.word++;
                    }
                }
                putch(0x10);
            }
        }
        else if(ch=='u')         /* Get device signature bytes  */
        {
            if (getch() == ' ')
            {
                putch(0x14);
                putch(SIG1);
                putch(SIG2);
                putch(SIG3);
                putch(0x10);
            }
        }
        else if(ch=='v')          // Read oscillator calibration byte
        {
            byte_response(0x00);
        }
    }  /* end of forever loop */
}

void putch(uint8_t ch)
{
#ifdef __AVR_ATmega128__
    while (!(UCSR1A & _BV(UDRE1)))
        ;
    UDR1 = ch;
#else
  /* m8,16,32,169,8515,8535,163 */
    while (!(UCSRA & _BV(UDRE)))
        ;
    UDR = ch;
#endif
}

uint8_t getch(void)
{
#ifdef __AVR_ATmega128__
    while ((UCSR1A & _BV(RXC1)) == 0)
        ;
    return UDR1;
#else
  /* m8,16,32,169,8515,8535,163 */
    while((UCSRA & _BV(RXC)) == 0)
        ;
    return UDR;
#endif
}

#if 1
uint8_t checkchar(void)
{
#ifdef __AVR_ATmega128__
    return (UCSR1A & _BV(RXC1))?1:0;
#else
  /* m8,16,32,169,8515,8535,163 */
    return (UCSRA & _BV(RXC))?1:0;
#endif
//      return 1;
//  else
//      return 0;
}
#endif

void getNch(uint8_t count)
{
    uint8_t i;
    for(i=0;i<count;i++)
        getch();
}

void byte_response(uint8_t val)
{
    if (getch() == ' ')
    {
        putch(0x14);
        putch(val);
        putch(0x10);
    }
}

void nothing_response(void)
{
    if (getch() == ' ')
    {
        putch(0x14);
        putch(0x10);
    }
}

void waitspm(uint8_t command)
{
#if 0
    while (SPMCR & _BV(SPMEN))  // in R0, 37
        ;                       // sbrc R0, 0, rjmp .-6
    SPMCR = command;            // out 37, R24
    asm volatile("spm");        // spm, ret
#else
    asm volatile(
    "\tlds  r25, %0\n"
    "\tsbrc r25, 0\n"
    "\trjmp .-8\n"
    "\tsts  %0, r24\n"
    "\tspm\n"
    : "=m" (SPMCR) );
#endif
}

// Copied out of stdlib since we need insruct the compiler to not link them in
// in order to get the code size down.

uint8_t my_eeprom_read_byte(const uint8_t *addr)
{
    while (EECR & _BV(EEWE))
        ;
    EEAR = (uint16_t)addr;
    EECR |= _BV(EERE);
    return EEDR;
}

void my_eeprom_write_byte(uint8_t *addr, uint8_t data)
{
    uint8_t sreg = SREG;
    while (EECR & _BV(EEWE))
        ;
    EEAR = (uint16_t)addr;
    EEDR = data;
    cli();
    EECR |= _BV(EEMWE);
    EECR |= _BV(EEWE);
    SREG = sreg;
    return;
}

void InitUART(void)
{
    /* initialize UART(s) depending on CPU defined */
#ifdef __AVR_ATmega128__
    UBRR1L = (uint8_t)(F_CPU/(BAUD_RATE*8L)-1);
    UBRR1H = (F_CPU/(BAUD_RATE*8L)-1) >> 8;
    UCSR1A = _BV(U2X);
    UCSR1C = 0x06;
    UCSR1B = _BV(TXEN1)|_BV(RXEN1);
#elif defined __AVR_ATmega163__
    UBRR = (uint8_t)(F_CPU/(BAUD_RATE*16L)-1);
    UBRRHI = (F_CPU/(BAUD_RATE*16L)-1) >> 8;
    UCSRA = 0x00;
    UCSRB = _BV(TXEN)|_BV(RXEN);
#else
    /* m8,m16,m32,m169,m8515,m8535 */
    UBRRL = (F_CPU/(BAUD_RATE*8L)-1);
    UBRRH = (F_CPU/(BAUD_RATE*8L)-1) >> 8;
    UCSRA = _BV(U2X);
    UCSRC = 0x86;
    UCSRB = _BV(TXEN)|_BV(RXEN);
#endif
}
void DelayMS(uint16_t delay)
{
    asm volatile(
    "\tldi  R26, lo8(%0/4000)\n"
    "\tldi  R27, hi8(%0/4000)\n"
    "\tsbiw R26, 1\n"
    "\tbrne .-4\n"
    "\tsbiw R24, 1\n"
    "\tbrne .-12\n"
    :: "i" (F_CPU) );
}

/* end of file ATmegaBOOT.c */

Back