__ __  ___             ___              _ _
      / // / /   |_   _______/   |  __________(_|_)     ___  ___   _
     / // /_/ /| | | / / ___/ /| | / ___/ ___/ / /     (__ \/ __) / )
    /__  __/ ___ | |/ / /  / ___ |(__  ) /__/ / /      / __/\__ \/ , \
      /_/ /_/  |_|___/_/  /_/  |_/____/\___/_/_/       \___)(___/\___/
     ____        _           ____              _   _                 _
    | __ )  __ _| |__  _   _| __ )  ___   ___ | |_| | ___   __ _  __| | ___ _ __
    |  _ \ / _` | '_ \| | | |  _ \ / _ \ / _ \| __| |/ _ \ / _` |/ _` |/ _ \ '__|
    | |_) | (_| | |_) | |_| | |_) | (_) | (_) | |_| | (_) | (_| | (_| |  __/ |  
    |____/ \__,_|_.__/ \__, |____/ \___/ \___/ \__|_|\___/ \__,_|\__,_|\___|_|  
                       |___/  
                                                  
       __                       __          
      / /  __ __  ______ ______/ /__  ____ _
     / _ \/ // / / __/ // / __/ __/ |/ /  ' \
    /_.__/\_, /  \__/\_,_/_/  \__/|___/_/_/_/
         /___/






>ATmega88/168/16 and possibly others
>ANY terminal program will work
>HyperTerminal comes with every version of Windows
>serial port or USB to serial adapter required
>fits in 256 Words (512 bytes)
>upload a standard Intel hex file
>'key' trigger or 'pin' trigger
>watchdog reset to start application
>bootloader only attempts to run on power up only
>1/8 second watchdog timeout on power up
>watchdog compatible (if fused on)
>bootloader ALWAYS leaves I/O registers at reset state
>can 'jump' into bootloader from application
>Intel hex records up to SPM_PAGESIZE (max 128)
>USART error checking
>erase flash
>program flash with verify
>'go' command to start application
>flash read command
>read/write of eeprom can be done with 4AvrAsciiBootloaderBuddy
>reading of fuse/lock bytes can be done
with 4AvrAsciiBootloaderBuddy

>erase REQUIRED before programming (prevent 'forgetting' to erase)

>NEW! - now 'secure' (no unintended spm from app space)
>will be able to call spm function from app (some hoops to jump through)

TO DO-
>application for 'easy' programming (for terminal haters)
>application to 'encrypt' intel hex file with bootloader code

temp application for eeprom programming, reading fuses-
4AvrAsciiBootloaderBuddy

Download here ->
   4AvrAscii256.S
version 2008.07.23

Experimental >64K version UNTESTED - 4AvrAscii256_universal.S

Old C version here-> 4AvrAscii256

    //---------------------------------------------------------------------------------------------------------//
// __ __ ___ ___ _ _ //
// / // / / |_ _______/ | __________(_|_) ___ ___ _ //
// / // /_/ /| | | / / ___/ /| | / ___/ ___/ / / (__ \/ __) / ) //
// /__ __/ ___ | |/ / / / ___ |(__ ) /__/ / / / __/\__ \/ , \ //
// /_/ /_/ |_|___/_/ /_/ |_/____/\___/_/_/ \___)(___/\___/ //
// ____ _ ____ _ _ _ //
// | __ ) __ _| |__ _ _| __ ) ___ ___ | |_| | ___ __ _ __| | ___ _ __ //
// | _ \ / _` | '_ \| | | | _ \ / _ \ / _ \| __| |/ _ \ / _` |/ _` |/ _ \ '__| //
// | |_) | (_| | |_) | |_| | |_) | (_) | (_) | |_| | (_) | (_| | (_| | __/ | //
// |____/ \__,_|_.__/ \__, |____/ \___/ \___/ \__|_|\___/ \__,_|\__,_|\___|_| //
// |___/ //
// __ __ \|/ //
// / / __ __ ______ ______/ /__ ____ _ -{@}- //
// / _ \/ // / / __/ // / __/ __/ |/ / ' \ /^\ //
// /_.__/\_, / \__/\_,_/_/ \__/|___/_/_/_/ /.~,\ //
// /___/ /,:.\ //
// /,@'o~\ //
// Curt's ATmega88/ATmega168/ATmega16 4AvrAscii 256 BabyBootloader /.,;-"\ //
// Intel Hex Ascii Upload Bootloader in 256 Words /.^'.@.,\ //
// /o!*:,~.\ //
// Copyright 2007 by Curt Van Maanen /,=.'@-.o.\ //
// ^^^^^^^^^^^ //
// Version 2008.07.23 (Christmas in July Edition) [_] //
//---------------------------------------------------------------------------------------------------------//



/*-----------------------------------------------------------------------------------------------------------

3 sections to this file, in this order-
CONFIG - make your changes needed in this section
PROGRAM - program located here, no changes normally needed
HELP - help information section

CHECKLIST-

1. .text section is set to bootloader start address (see help section)
2. linker options set to -nostartfiles and -nodefaultlibs (see help section)
3. -mshort-calls added to custom options for avr's >8k (see help section)
4. change whatever needed in the config section (see config section)
5. make sure boot reset fuse is programmed (via your avr programming method)
6. make sure boot size is correct- 256 words (via your avr programming method)
7. for 'newer' avr's, make sure your application clears watchdog reset flag (and WDE if not using watchdog)
8. terminal program settings-
8 data bits, no parity, 1 stop bit
20msec transmit line delay no flow control used, so this is needed
incoming cr=cr+lf bootloader only sends cr, so terminal program needs to add lf
outgoing cr only (lf's will be flushed after cr anyway, so it doesn't matter)
no local echo bootloader will do the echo
9. goto 1

-----------------------------------------------------------------------------------------------------------*/



/*-----------------------------------------------------------------------------------------------------------
___ _ _ ___ _ _ _ ___ ___ ___
|_ _| \| |/ __| | | | | | \| __/ __|
| || .` | (__| |_| |_| | |) | _|\__ \
|___|_|\_|\___|____\___/|___/|___|___/

-----------------------------------------------------------------------------------------------------------*/

#include <avr/io.h>



//---------------------------------------------------------------------------------------------------------//
// ______ ______ .__ __. _______ __ _______ //
// / | / __ \ | \ | | | ____|| | / _____| //
// | ,----'| | | | | \| | | |__ | | | | __ //
// | | | | | | | . ` | | __| | | | | |_ | //
// | `----.| `--' | | |\ | | | | | | |__| | //
// \______| \______/ |__| \__| |__| |__| \______| //
// //
//---------------------------------------------------------------------------------------------------------//
// CONFIGURATION SECTION DEFINES (change defines as needed) //
//---------------------------------------------------------------------------------------------------------//



/*-----------------------------------------------------------------------------------------------------------
baud rate defines
page 197 of datasheet (mega88/168), 38,400 has little error, and works well with internal 8mhz clock
set to whatever you need for your clock and desired baud rate
high baud register is not used in bootloader, so no high cpu freq's with low baud rates
->its not that hard to look up the number needed in the datasheet, so no macros here<-
-----------------------------------------------------------------------------------------------------------*/

#define BAUD_LO_VALUE 12 // BAUD 38400 .2% error @ 8Mhz

/*-----------------------------------------------------------------------------------------------------------
set the 'key' (character) you want for 'triggering' the bootloader
(is set to 'escape' character here, use the hex number, not the slash thing like '\e')
-----------------------------------------------------------------------------------------------------------*/

#define BL_TRIGGER_KEY 0x1B // character to trigger bootloader

/*-----------------------------------------------------------------------------------------------------------
set commands here for 'go' and 'erase', and 'dump flash' (only when decrypt is off)
they have to be an odd number, so commands will not be confused with intel hex records (as they cannot have
odd length data records since words are programmed)
the command in hex will be the same as ascii-> for erase >:EF<enter>, for go >APP<enter>
-----------------------------------------------------------------------------------------------------------*/

#define BL_GO_COMMAND 0x99 // :pp (app - 0x0A,0x09,0x09)
#define BL_ERASE_COMMAND 0xEF // 'erase flash'
#define BL_READ_PGM 0xDF // 'dump flash'


/*-----------------------------------------------------------------------------------------------------------
if 'pin trigger' wanted instead of a 'trigger character' on the usart, turn it on here
and set the desired port and pin wanted, and what state will trigger the bootloader
-----------------------------------------------------------------------------------------------------------*/

#define USE_PIN_TRIGGER 0 // 1 = use a pin to trigger bootloader

#define PIN_TRIGGER PINB // which port you want to use
#define PINn_TRIGGER 5 // which pin number you want to use
#define PINn_TRIGGER_STATE 0 // 0 = LOW will trigger (HIGH = bypass)
// 1 = HIGH will trigger (LOW = bypass)

/*-----------------------------------------------------------------------------------------------------------
ATmega16 has UDR, ATmega88/168 has UDR0, so use defines here to get them the same
if you have an avr with udr1/udr2, change the number below to the desired usart number
-----------------------------------------------------------------------------------------------------------*/

#ifdef UDR // for avr's with just a 'plain' udr,
#define USART_NUM // leave this blank, nothing, empty
#else // for newer avr's, set usart number
#define USART_NUM 0 // usart number( here using usart0 )
#endif



/*-----------------------------------------------------------------------------------------------------------
___ ___ ___ ___ _ _ ___ ___
| \| __| __|_ _| \| | __/ __|
| |) | _|| _| | || .` | _|\__ \
|___/|___|_| |___|_|\_|___|___/

-----------------------------------------------------------------------------------------------------------*/



/*-----------------------------------------------------------------------------------------------------------
this defines the bootloader size in bytes, and the bootloader start address in bytes
also an address is needed at the end of flash+1 so we can rjmp to 0x0000 (APP_ZERO)
-----------------------------------------------------------------------------------------------------------*/

#define BL_SIZE_BYTES 512
#define BL_ADDRESS (FLASHEND - (BL_SIZE_BYTES-1))
#define APP_ZERO (main+BL_SIZE_BYTES)

/*-----------------------------------------------------------------------------------------------------------
concat macros, used in usart defines
-----------------------------------------------------------------------------------------------------------*/

#define MY_CONCAT3b(a,b,c) a ## b ## c // needed to expand USART_NUM
#define MY_CONCAT3(a,b,c) MY_CONCAT3b(a,b,c) // concat 3 'words'
#define MY_CONCAT2b(a,b) a ## b
#define MY_CONCAT2(a,b) MY_CONCAT2b(a,b) // concat 2 'words'

/*-----------------------------------------------------------------------------------------------------------
undefine older usart names for mega64, so usart macros below work correctly for the mega64
-----------------------------------------------------------------------------------------------------------*/

#if defined (__AVR_ATmega64__)
#undef RXEN
#undef TXEN
#undef UDR
#undef UDRE
#undef RXC
#undef FE
#undef DOR
#endif

/*-----------------------------------------------------------------------------------------------------------
usart definitions
-----------------------------------------------------------------------------------------------------------*/

#define BAUD_RATE_REG_LO MY_CONCAT3(UBRR,USART_NUM,L)
#define USART_CONTROL_B MY_CONCAT3(UCSR,USART_NUM,B)
#define USART_STATUS_A MY_CONCAT3(UCSR,USART_NUM,A)
#define RX_ENABLE MY_CONCAT2(RXEN,USART_NUM)
#define TX_ENABLE MY_CONCAT2(TXEN,USART_NUM)
#define RX_DATA MY_CONCAT2(UDR, USART_NUM)
#define TX_DATA MY_CONCAT2(UDR, USART_NUM)
#define TX_BUFFER_EMPTY MY_CONCAT2(UDRE,USART_NUM)
#define RX_COMPLETED MY_CONCAT2(RXC, USART_NUM)
#define FRAMING_ERROR MY_CONCAT2(FE, USART_NUM)
#define OVERRUN_ERROR MY_CONCAT2(DOR, USART_NUM)

#define MY_RX_FLAGS ((1<<RX_COMPLETED)|(1<<FRAMING_ERROR)|(1<<OVERRUN_ERROR))

/*-----------------------------------------------------------------------------------------------------------
IVSEL register
-----------------------------------------------------------------------------------------------------------*/

#ifndef IVSEL
#error This AVR is not supported, no IVSEL
#endif

#ifdef GICR
#define IRQ_CONTROL GICR
#else
#define IRQ_CONTROL MCUCR
#endif

/*-----------------------------------------------------------------------------------------------------------
reset flags register
-----------------------------------------------------------------------------------------------------------*/

#ifdef MCUCSR
#define RESET_FLAGS MCUCSR
#else
#define RESET_FLAGS MCUSR
#endif

/*-----------------------------------------------------------------------------------------------------------
set maximum size of data bytes we can receive in an intel hex record
limit by page size, or if page is greater than 128, limit to 128 (rx_buffer needs to stay < 256)
-----------------------------------------------------------------------------------------------------------*/

#if (SPM_PAGESIZE > 128) // max data words for a record
#define MAX_DD_BYTES 128 // limit to 128 bytes (64 words)
#else
#define MAX_DD_BYTES (SPM_PAGESIZE) // else limit to page size (words)
#endif

/*-----------------------------------------------------------------------------------------------------------
from boot.h, for spm stuff
-----------------------------------------------------------------------------------------------------------*/

#if defined (SPMCSR)
#define SPM_REG SPMCSR
#elif defined (SPMCR)
#define SPM_REG SPMCR
#else
#error AVR processor does not provide bootloader support!
#endif
#ifdef ASRE
#define COMMON_ASRE ASRE
#else
#define COMMON_ASRE RWWSRE
#endif
#define BOOT_PAGE_FILL (1<<SPMEN)
#define BOOT_PAGE_ERASE (1<<SPMEN)|(1<<PGERS)
#define BOOT_PAGE_WRITE (1<<SPMEN)|(1<<PGWRT)
#define BOOT_RWW_ENABLE ((1<<SPMEN)|(1<<COMMON_ASRE))

/*-----------------------------------------------------------------------------------------------------------
from wdt.h, for watchdog stuff
-----------------------------------------------------------------------------------------------------------*/

#if defined (WDTCSR)
#define WD_CONTROL_REG WDTCSR
#else
#define WD_CONTROL_REG WDTCR
#endif

#if defined (WDTOE)
#define WD_CHANGE_BIT WDTOE
#else
#define WD_CHANGE_BIT WDCE
#endif

#define WD_CHANGE_EN (1<<WD_CHANGE_BIT)|(1<<WDE)
#define WDTO_120MS 3

/*-----------------------------------------------------------------------------------------------------------
make sure not using an avr > 64k
-----------------------------------------------------------------------------------------------------------*/

#if FLASHEND > 0xFFFF
#error This bootloader is not designed to support an avr >64K
#endif

/*-----------------------------------------------------------------------------------------------------------
get PIN_TRIGGER define into an asm compatable address
-----------------------------------------------------------------------------------------------------------*/

#define PIN_TRIG _SFR_IO_ADDR(PIN_TRIGGER)

/*-----------------------------------------------------------------------------------------------------------
bootloader status defines
-------------------------------------------------------------------------------------------------------------

bl_status bits1-0 (Y=1, N=0)
------------------------------
| >After | Go? | Program?|
|------------------------------|
| Start | Y | N | after bootloader starts, can go, but not program (erase first)
|------------------------------|
| Erase | N | Y | after erase, cannot go (nothing to go to), can program
|------------------------------|
| Error | N | N | after error, cannot go (unkown flash state), cannot program
|------------------------------|
| EOF ok | Y | N | after eof, can go, but cannot program (erase first)
------------------------------
*/

#define BL_ERASE_OK 1 // erase ok state
#define BL_EOF_OK 2 // file ok state

/*-----------------------------------------------------------------------------------------------------------
_ ___ __ __ __ __ _ ___ ___ ___ ___
/_\ / __| \/ | | \/ | /_\ / __| _ \/ _ \/ __|
/ _ \\__ \ |\/| | | |\/| |/ _ \ (__| / (_) \__ \
/_/ \_\___/_| |_| |_| |_/_/ \_\___|_|_\\___/|___/

-----------------------------------------------------------------------------------------------------------*/

/*-----------------------------------------------------------------------------------------------------------
asm macros to take care of using the correct instructions for i/o and sram, and for transmitting

LOAD/STORE will use the instructions in/out/lds/sts as needed
LOAD_S/STORE_S will always use lds/sts (force lds/sts even when in/out would work)

STORE_S/LOAD_S is used in the bootloader anytime its a possibility the register could be in 'upper' i/o
(to keep all builds the same size for various avr's)
and LOAD/STORE is used only when it is known that all avr's use 'lower' i/o for the register

the bootloader will only use the macros below, and not use in/out/lds/sts instructions directly
-----------------------------------------------------------------------------------------------------------*/

.macro STORE addr,reg
.if \addr < 0x60
out \addr - 0x20,\reg
.else
sts \addr,\reg
.endif
.endm

.macro LOAD reg,addr
.if \addr < 0x60
in \reg,\addr - 0x20
.else
lds \reg,\addr
.endif
.endm

.macro STORE_S addr,reg
sts \addr,\reg
.endm

.macro LOAD_S reg,addr
lds \reg,\addr
.endm

.macro XMIT char
ldi r24,\char
rcall sendchar
.endm

.macro ADDI reg,num
subi \reg,-\num
.endm


/*-----------------------------------------------------------------------------------------------------------
___ _ ___ ___ _ _ __ ___ ___ ___ _ ___ _ ___ ___
/ __| | / _ \| _ ) /_\ | | \ \ / /_\ | _ \_ _| /_\ | _ ) | | __/ __|
| (_ | |_| (_) | _ \/ _ \| |__ \ V / _ \| /| | / _ \| _ \ |__| _|\__ \
\___|____\___/|___/_/ \_\____| \_/_/ \_\_|_\___/_/ \_\___/____|___|___/

-----------------------------------------------------------------------------------------------------------*/

/*-----------------------------------------------------------------------------------------------------------
receive buffer to hold intel hex record
-----------------------------------------------------------------------------------------------------------*/

.struct 0
colon: // ':' (colon)
.struct colon + 1
LL: // LL - record length
.struct LL + 1
AAh: // AAxx - data byte address (byte)
.struct AAh + 1
AAl: // xxAA - low byte of address
.struct AAl + 1
TT: // TT - record type (0 or 1 used here)
.struct TT + 1
DDDD: // DDDD - data bytes (words)
.struct DDDD + MAX_DD_BYTES
CC: // CC - used just for sizing of buffer
.struct CC + 1
CR: // CR - used just for sizing of buffer
.struct CR + 1
END_OF_BUFFER: // for checking if buffer overflow (size of buffer)

.section .noinit // set RX_BUFFER to .noinit section (in data section)

RX_BUFFER: // start of rx buffer in sram

.ds.b END_OF_BUFFER // set size of buffer, will use struct above



//---------------------------------------------------------------------------------------------------------//
// .______ .______ ______ _______ .______ ___ .___ ___. //
// | _ \ | _ \ / __ \ / _____|| _ \ / \ | \/ | //
// | |_) | | |_) | | | | | | | __ | |_) | / ^ \ | \ / | //
// | ___/ | / | | | | | | |_ | | / / /_\ \ | |\/| | //
// | | | |\ \----.| `--' | | |__| | | |\ \----./ _____ \ | | | | //
// | _| | _| `._____| \______/ \______| | _| `._____/__/ \__\ |__| |__| //
// //
//---------------------------------------------------------------------------------------------------------//
// PROGRAM (normally no changes needed in this section) //
//---------------------------------------------------------------------------------------------------------//



/*===========================================================================================================
__ __ _ ___ _ _
| \/ | /_\ |_ _| \| |
| |\/| |/ _ \ | || .` |
|_| |_/_/ \_\___|_|\_|

===========================================================================================================*/

.section .text
.func main

main:

/*
Reset Flags 'Logic' Table
----------------------------------------------------------------------
| MCU Status Register - MCUSR (1 = set 0 = clear x = don't care) |
|----------------------------------------------------------------------|
|x|x|x|x| Bit3 | Bit2 | Bit1 | Bit0 | Run Bootloader? |
|----------------------------------------------------------------------|
|x|x|x|x| WDRF | BORF | EXTRF | PORF | |
|----------------------------------------------------------------------|
|x|x|x|x| 0 | x | x | 1 | YES, this could be power up |
|----------------------------------------------------------------------|
|x|x|x|x| 1 | x | x | x | NO, watchdog reset |
|----------------------------------------------------------------------|
|x|x|x|x| 0 | x | x | 0 | NO, not a power up reset |
----------------------------------------------------------------------

-----------------------------------------------------------------------------------------------------
first three instructions will cause an exit from the bootloader if the watchdog flag is set
the fourth instruction sets up to enable ivsel change, the fifth sets ivce in MCUCR or GIFR

any 'errant' jump into the bootloader to any of the first five instructions will still have to
pass the PORF=1 test which takes place below the change enable instruction
---------------------------------------------------------------------------------------------------*/

/****************************/
#define tempF r0
#define tempI r31
/****************************/

LOAD tempF,RESET_FLAGS // get reset flags
sbrc tempF,WDRF // skip if wdrf=0,
rjmp APP_ZERO // else exit (wdrf=1)
ldi tempI,(1<<IVCE) // load (1<<ivce),
STORE IRQ_CONTROL,tempI // enable iv change

/*---------------------------------------------------------------------------------------------------
any 'errant' jump into the bootloader anywhere below this line will not cause ivsel to be set
---------------------------------------------------------------------------------------------------*/

LOAD tempF,RESET_FLAGS // clk+1, get reset flags again
ldi tempI,(1<<IVSEL) // clk+2, load (1<<ivsel)
sbrc tempF,PORF // clk+3, if porf=0 skip next
STORE IRQ_CONTROL,tempI // clk+4, ivsel=1

/*---------------------------------------------------------------------------------------------------
now check if the change occurred, if not, porf was 0
will leave bootloader with i/o registers untouched, including SREG, (GIFR or MCUCR will be 0
though, which IS the inital value of those registers), r0 will =flags,r31 will =irq_ctrl register
---------------------------------------------------------------------------------------------------*/

LOAD tempI,IRQ_CONTROL // get ivsel
sbrs tempI,IVSEL // if ivsel=1, skip next
rjmp APP_ZERO // else ivsel=0, change didn't work

/*---------------------------------------------------------------------------------------------------
we got this far, watchdog reset flag NOT set AND power on reset flag IS set (WDRF=0 AND PORF=1)
now clear r1, enable and set the watchdog timeout to 1/8 second
---------------------------------------------------------------------------------------------------*/

/****************************/
#define temp r24
/****************************/

jmp_start:

clr r1 // clear r1
ldi temp,WD_CHANGE_EN // setup watchdog change
STORE_S WD_CONTROL_REG,temp // enable watchdog change
ldi temp,(1<<WDE)|(WDTO_120MS) // setup timeout/on
STORE_S WD_CONTROL_REG,temp // enable watchdog with 120ms timeout

/*---------------------------------------------------------------------------------------------------
setup stack pointer
---------------------------------------------------------------------------------------------------*/

set_stack:

ldi temp,hi8(RAMEND) // stack high value
STORE SPH,temp // write it
ldi temp,lo8(RAMEND) // stack low value
STORE SPL,temp // write it

/*---------------------------------------------------------------------------------------------------
setup the serial port-> baud rate, tx/rx enable
---------------------------------------------------------------------------------------------------*/

start_usart:

ldi temp,BAUD_LO_VALUE // load baud lo value
STORE_S BAUD_RATE_REG_LO,temp // set it
ldi temp,(1<<RX_ENABLE)|(1<<TX_ENABLE) // tx/rx enable
STORE_S USART_CONTROL_B,temp // set it

/*---------------------------------------------------------------------------------------------------
if all reset flags were clear, skip this step, and just go to the bootloader

otherwise-
just keep checking rx data for an escape character (or whatever you define as BL_TRIGGER_KEY)
if no trigger character received, watchdog will timeout and cause a reset (setting WDRF flag)

no check of RXC flag is needed here, just keep reading udr over and over

watchdog timer was set to 1/8 second, so that's how long we have to receive the escape
the advantages of doing this with the watchdog is 1-that we use it as a timer that has its
own clock and will timeout to the value we want regardless of cpu frequency 2-it will also
take care of resetting the cpu so our application will start with a fresh restart, and all
I/O settings that the datasheet says are initial settings, will hold true

OR

if using a pin to trigger the bootloader, just keep checking the pin, if not in the correct
state, will just loop until watchdog causes a reset
---------------------------------------------------------------------------------------------------*/

trigger_check:

LOAD temp,RESET_FLAGS // get reset flags
tst temp // check if 0
breq enter_loop // was 0, no flags set, must be app jmp so skip trigger

trigger_loop: // trigger test loop

rcall safeT_check // make sure ivsel=1 inside any loop
#if (USE_PIN_TRIGGER)
#if (PINn_TRIGGER_STATE)
sbis PIN_TRIG,PINn_TRIGGER // skip if pin is 1 (PINn_TRIGGER_STATE=1)
rjmp trigger_loop // else loop to cause watchdog reset
#else
sbic PIN_TRIG,PINn_TRIGGER // skip if pin is 0 (PINn_TRIGGER_STATE=0)
rjmp trigger_loop // else loop to cause watchdog reset
#endif
#else
LOAD_S temp,RX_DATA // get rx data
cpi temp,BL_TRIGGER_KEY // check if same as trigger character
brne trigger_loop // if not, do again
#endif



/*---------------------------------------------------------------------------------------------------
ok, received escape, pin state correct, or just jumped from app, WE CAN NOW ENTER THE BOOTLOADER
bl_status will be BL_START initially (which means you HAVE TO erase before programming)
---------------------------------------------------------------------------------------------------*/

enter_loop: // now need to load bl_status with BL_START

rjmp eof_ok // which is the same as BL_EOF_OK



/*\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ bootloader main loop starts here ////////////////////////////////////
//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\/
/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/*/
/*---------------------------------------------------------------------------------------------------
error (erase verify failed, record length is odd number, checksum bad,
address went into bootloader area, programming verify error, rx framing/overrun
---------------------------------------------------------------------------------------------------*/

/****************************/
#define bl_status r2
/****************************/

_error:

XMIT '!' // send error character
clr bl_status // any error clears bl_status (can't go, can't program)
rjmp _prompt // back to command prompt

/*---------------------------------------------------------------------------------------------------
nothing was done, so show '?', command or record was not processed for some reason- first character
not a ':', cr was not the last character, cannot 'go', cannot 'program'
---------------------------------------------------------------------------------------------------*/

_did_nothing:

XMIT '?' // means nothing was done with input line

/*---------------------------------------------------------------------------------------------------
flush rx here, will flush out linefeeds from the hex file, and will also prevent buffer overruns-
if multiple 'bad' characters received at command prompt, we are receiving 1 character, then
transmitting 2 (cr + prompt), and we can't keep that up without overrun in the rx buffer, so just
flush it (line delay in terminal program will also prevent any other characters in buffer except the
linefeed) tx will continue to transmit error character above (!) when turned off (datasheet says
anything in shift register and buffer will still continue to transmit until completed)

command prompt- can ALWAYS erase and flash dump, any command prompt
command prompt- > can go, cannot program
command prompt- = can program, cannot go
command prompt- < can only erase
---------------------------------------------------------------------------------------------------*/

_prompt:

STORE_S USART_CONTROL_B,r1 // flush any LF, also prevent overrun
ldi temp,(1<<RX_ENABLE)|(1<<TX_ENABLE) // rx/tx bits
STORE_S USART_CONTROL_B,temp // enable them
XMIT '\r' // cr
mov temp,bl_status // get value of bl_status (0,1,2)
ADDI temp,'<' // add '<', bl_status 0-> '<' 1-> '=' 2-> '>'
rcall sendchar // send prompt


/*---------------------------------------------------------------------------------------------------
process intel hex record
intel hex record >:LLAAAATTDD...DDCC Always starts with a ':'
LL = Record length (in bytes, of DD bytes)
AAAA = memory address (in bytes)
TT = record type (00 = data,01 = end of file, others not used)
DD = data byte , number of bytes = LL
CC = checksum (add all bytes, byte sum only, invert, then add 1)
CC = (!(LL + AA + AA + TT + DD...DD )) + 1, checksum total including checksum byte = 0

get the whole record here (input limited by size of rx_buffer)
if rx_buffer overrun, last character will not be cr, and will ignore record (back to command prompt)
cr has to be the last char (and breaks out of do loop)
any 'control' character (0x00-0x1F) breaks out of loop
---------------------------------------------------------------------------------------------------*/

/****************************/
#define checksum r16
#define count r17
#define tempS r25
/****************************/

get_record:


ldi count,1 // keep track of nibbles (1=store completed byte)
ldi checksum,-0x0A // checksum (subtract 0x0A as ':' will also be added)
ldi YL,lo8(RX_BUFFER) // Y pointer-> &rxbuffer
ldi YH,hi8(RX_BUFFER)
st Y,r1 // clear colon buffer position, prevent repeats of
// previous record by a single cr

/*...................................................................................................
get rx status, wait until RXCn, FEn or DORn flag set (any one or more)
also clear watchdog, since we could be here a while
if any flag set, drop out of do loop, read udr to either get data or to flush rx & reset flags,
then check if RXCn is the only flag set, if not, framing or overrun flag(s) set, so goto _error
...................................................................................................*/

rx_loop: // rx loop

rcall safeT_check // safeT_check inside loops
wdr // reset watchdog
LOAD_S tempS,USART_STATUS_A // get usart status
andi tempS,MY_RX_FLAGS // mask off unwanted bits
breq rx_loop // if no flags, try again

read_rx:

LOAD_S temp,RX_DATA // read udr
cpi tempS,(1<<RX_COMPLETED) // check if rxc is the only flag set
brne _error // no, buffer overrun or framing error, got _error

/*...................................................................................................
if < ' ' (0x00-0x1F), its a 'control' character, done
else echo character
...................................................................................................*/

check_rx_char:

cpi temp,' ' // test if rx_data >= ' '
brcs check_cr // if not, done here
rcall sendchar // echo back character

/*...................................................................................................
is >= ' ' (0x20-0xFF), subtract 7 if >= 0x40, mask off high nibble to leave 0x00-0x0F
'0' (0x30) converts to 0x00, '9' (0x39) to 0x09, 'A' (0x41) to 0x0A, 'F' (0x46) to 0x0F
ANY characters above 0x1F are converted to 0x00-0x0F
...................................................................................................*/

convert_char:

cpi temp,0x40 // check if 'A'-'F' (0x41-0x46)
brcs 1f // if not, skip next
subi temp,0x07 // yes, subtract 7 ('A'=0x41->0x3A,etc)
1:
andi temp,0x0F // mask off bits3-7, make ALL 0x00-0x0F

/*...................................................................................................
first character (':') will be stored right away as a 'complete' byte, the second character will be
shifted left into the high nibble position, third character will be added to second character,
then saved, and so on (if buffer size is odd length, we save completed bytes on the odd count, if
buffer size is even length, we save completed bytes on the even count- normally buffer size will
be odd size if no changes were made to the buffer scruct)
...................................................................................................*/

put_in_buffer:

sbrc count,0 // skip if count=0
rjmp 2f // else jump ahead to save 'completed' hex pair
swap temp // low nibble to high nibble (like 0x0F->0xF0)
st Y,temp // save it to buffer
rjmp 3f // get next nibble
2: // odd 'position' in buffer
ld tempS,Y // get previous high nibble
add temp,tempS // merge high nibble + low nibble
st Y+,temp // store it in buffer, inc Y pointer
add checksum,temp // add to checksum
3: // now check if still in buffer and toggle count
com count // count = ~count (1->0xFE->1)
cpi YL,lo8(RX_BUFFER+END_OF_BUFFER) // buffer never > 255, so only need to check lo byte
brne rx_loop // if not at end of buffer can, get another character

/*---------------------------------------------------------------------------------------------------
if last rx character was not cr, go back to command prompt
could be rx_buffer overrun or a 'control' character other than cr (like esc key)
---------------------------------------------------------------------------------------------------*/

check_cr:

cpi temp,'\r' // check if last char was cr
brne 2f // if not, jump ahead (will rjmp to _did_nothing)

/*---------------------------------------------------------------------------------------------------
if first character was not ':', go back to command prompt
---------------------------------------------------------------------------------------------------*/

check_colon:

LOAD_S temp,RX_BUFFER+colon // get byte in colon postition
cpi temp,0x0A // if not ':' (0x3A & 0x0F = 0x0A)
brne 2f // skip ahead if not ':' (will rjmp to _did_nothing)


/*---------------------------------------------------------------------------------------------------
store record length - LL (data byte count)
---------------------------------------------------------------------------------------------------*/

/****************************/
#define reclength r21
/****************************/

get_reclength:

LOAD_S reclength,RX_BUFFER+LL // get recordlength

/*---------------------------------------------------------------------------------------------------
if LL is the 'go' command, loop and cause a wdt reset IF go bit set in bl_status
---------------------------------------------------------------------------------------------------*/

cpi reclength,BL_GO_COMMAND // check if LL= BL_GO_COMMAND
brne check_erase // nope, jump ahead
1: // loop if can 'go' (cause wdt reset)
rcall safeT_check // for any errant jumps into loop
sbrc bl_status,1 // if go bit clear, skip next
rjmp 1b // got bit set, so loop and cause watchdog timeout
2:
rjmp _did_nothing // show that nothing was done

/*---------------------------------------------------------------------------------------------------
Erase Flash loop
if LL=0xEF, erase flash, set bl_status
address will be a byte address, SPM_PAGESIZE is a byte size
---------------------------------------------------------------------------------------------------*/

check_erase:

cpi reclength,BL_ERASE_COMMAND // check if erase command
brne check_read // if not, jump ahead
ldi ZL,lo8(BL_ADDRESS) // set to bootloader start address
ldi ZH,hi8(BL_ADDRESS)

/*...................................................................................................
loop through all pages (including page 0)
...................................................................................................*/

erase_loop: // loop start

XMIT '.' // tx '.' every page
wdr // reset watchdog in loop
subi ZL,lo8(SPM_PAGESIZE) // back up a page
sbci ZH,hi8(SPM_PAGESIZE)
ldi r22,BOOT_PAGE_ERASE // pass command to do_spm
rcall do_spm2 // erase page
sbiw ZL,0 // check if was 0x0000 page
brne erase_loop // if not, do again

/*...................................................................................................
set bl_status, then back to command prompt
...................................................................................................*/

ldi temp,BL_ERASE_OK // set bl_status
mov bl_status,temp
rjmp _prompt // back to command prompt

/*---------------------------------------------------------------------------------------------------
Dump Flash
if LL=BL_READ_PGM, 'dump' (read) program flash (ascii output)
---------------------------------------------------------------------------------------------------*/

check_read:

/****************************/
#define bl_addrH r16
#define tempC r17
/****************************/

cpi reclength,BL_READ_PGM // check if LL=read command
brne check_pgm_bit // if not, jump ahead
ldi ZL,0 // Z=0, start reading at 0x0000
ldi ZH,0
ldi bl_addrH,hi8(BL_ADDRESS) // store bootloader address high byte for compare

read_loop: // loop start

wdr // reset watchdog in this loop
mov tempC,ZL // get ZL
andi tempC,0x1F // and with 0x1F
brne 1f // if not 0bxxx00000, skip next
XMIT '\r' // else tx cr (every 32 bytes)
1:
lpm r24,Z+ // get pgm flash byte, inc Z
rcall hex2ascii // display byte as ascii hex pair
cpi ZL,lo8(BL_ADDRESS) // check if Z < BL_ADDRESS
cpc ZH,bl_addrH
brcs read_loop // if so, do again
rjmp _prompt // else back to command prompt


/*---------------------------------------------------------------------------------------------------
if program bit not set in bl_status, do not continue from here (erase flash first)
---------------------------------------------------------------------------------------------------*/

check_pgm_bit:

sbrs bl_status,0 // if program bit is set, skip next
1:
rjmp _did_nothing // can't go any farther, so show nothing was done

/*---------------------------------------------------------------------------------------------------
only programming words, so if LL is an odd number, that is an error
(already limited to MAX_DD_SIZE by record receive loop, so no need to check size)
---------------------------------------------------------------------------------------------------*/

check_LL:

sbrc reclength,0 // if odd number DD bytes
2:
rjmp _error // show error

/*---------------------------------------------------------------------------------------------------
if checksum on all bytes received not 0, checksum error
---------------------------------------------------------------------------------------------------*/

check_CC:

tst checksum // check if 0
brne 2b // if not, branch back (to rjmp _error)

/*---------------------------------------------------------------------------------------------------
TT - Record type
if TT is 1, that is an EOF record, set bl_status, goto command prompt
---------------------------------------------------------------------------------------------------*/

/****************************/
#define indexV r23
/****************************/

check_TT:

LOAD_S indexV,RX_BUFFER+TT // get TT
cpi indexV,0x01 // check if 0x01
brne check_type0 // if not, jump ahead

eof_ok:

ldi temp,BL_EOF_OK // is 0x01, so set bl_status
mov bl_status,temp // to BL_EOF_OK
rjmp _prompt // back to command prompt

/*---------------------------------------------------------------------------------------------------
now check if TT is NOT 0 (we only do data records which are type 0x00)
---------------------------------------------------------------------------------------------------*/

check_type0:

tst indexV // check if 0 (use indexV, since we need 0 in it anyway)
brne 1b // if not, jump back (will rjmp to _did_nothing)

/*---------------------------------------------------------------------------------------------------
so far, so good. now write to program memory
index-> used to store rx_buffer.DDDD (word) array position
indexV-> store buffer position before page loop, used for verify loop
address-> used to determine page address (byte address)
addressV-> store address before page loop, used for verify loop
recordlength-> number of bytes to program (byte length)
---------------------------------------------------------------------------------------------------*/


/****************************/
#define index r20
/****************************/

setup_addr_index:

clr index // clear index (indexV will be 0 already from above)

/*---------------------------------------------------------------------------------------------------
setup address and addressV to the address fron the hex file
---------------------------------------------------------------------------------------------------*/

/****************************/
#define address r28
#define addressH r29
#define addressV r16
#define addressVH r17
/****************************/

LOAD_S addressV,RX_BUFFER+AAl // get AAl into low byte of addressV
LOAD_S addressVH,RX_BUFFER+AAh // get AAh into high byte of addressV
movw address,addressV // copy it to address

/*---------------------------------------------------------------------------------------------------
if address tries to get into our bootloader space-> error
---------------------------------------------------------------------------------------------------*/

pgm_loop:

ldi temp,hi8(BL_ADDRESS-1) // check if address is into our bootloader
cpi address,lo8(BL_ADDRESS-1)
cpc addressH,temp
brcc 2b // if so, jump back (will rjmp to _error)

/*...................................................................................................
fill page buffer
...................................................................................................*/

ldi XH,hi8(RX_BUFFER+DDDD) // X pointer to DDDD buffer position
ldi XL,lo8(RX_BUFFER+DDDD) // (should be 0x0065 or 0x0105)
add XL,index // add index (only add to XL, since will never overflow)
movw ZL,address // Z pointer to record address

ld r0,X+ // get low byte from buffer
ld r1,X+ // get high byte from buffer

ldi r22,BOOT_PAGE_FILL // command for spm
rcall do_spm2 // do page buffer fill (Z already setup)

ADDI index,0x02 // next DDDD (1 word,2 bytes)
adiw address,0x02 // next word address
subi reclength,0x02 // byte length -2 bytes (-1 word)

/*...............................................................................................
end of page loop, do while more data words AND page not crossed
...............................................................................................*/

breq write_page // if 0, recordlength now 0 so jump ahead
mov temp,address // get low byte of address
andi temp,(SPM_PAGESIZE-1) // check if on a new page
brne pgm_loop // not on a new page, do next byte

/*...............................................................................................
no more data bytes OR a page boundary was crossed
so write the page (Z is still on address of last page fill, so will be on correct page if a
page was crossed)
...............................................................................................*/

write_page:

ldi r22,BOOT_PAGE_WRITE // command for spm
rcall do_spm2 // do page write (Z still on address-2, nice)

/*...............................................................................................
now read back/verify what was just programmed
addressV holds the address before the page loading started
indexV holds the rx_buffer.DDDD position before the page loading started
do until we get to current DDDD position (indexV will = index again)
...............................................................................................*/

verify:

ldi XH,hi8(RX_BUFFER+DDDD) // buffer address to X (0x0105 or 0x0065)
ldi XL,lo8(RX_BUFFER+DDDD)
add XL,indexV // add indexV to buffer (only need to add to XL)
movw ZL,addressV // get addressV into Z
1:
lpm r18,Z+ // get flash byte low
lpm r19,Z+ // get flash byte high
ld r24,X+ // get low byte from buffer
ld r25,X+ // get high byte from buffer
cp r18,r24 // check if buffer equal to flash
cpc r19,r25
breq 2f
rjmp _error
2:
ADDI indexV,0x02 // indexV += 2
cp indexV,index
brcs 1b // if indexV < index, check next word
movw addressV,ZL // save Z position back to addressV

/*...............................................................................................
end of program flash loop
if a page was crossed, recordlength will not be 0, so there will be more data to program
...............................................................................................*/

tst reclength // check recordlength
brne pgm_loop // if not 0, do next page (page was crossed)

/*...............................................................................................
Yeah!, record programmed without error, go do it again, and again, and again...
...............................................................................................*/

rjmp _prompt // else done, back to command prompt


.endfunc


/*===========================================================================================================
___ _ _ _ _ ___ _____ ___ ___ _ _ ___
| __| | | | \| |/ __|_ _|_ _/ _ \| \| / __|
| _|| |_| | .` | (__ | | | | (_) | .` \__ \
|_| \___/|_|\_|\___| |_| |___\___/|_|\_|___/

===========================================================================================================*/


/*-----------------------------------------------------------------------------------------------------------
send a hex byte out as an ascii pair (0x12 -> '1' '2')

void hex2ascii(uint8_t h); -> r24=h
r24,r25 changed

void sendchar(uint8_t c); -> r24=c
r24 unchanged
r25 changed
-----------------------------------------------------------------------------------------------------------*/

.func hex2ascii

/****************************/
#define h r24
/****************************/

hex2ascii:

push h // save a copy of h
swap h // swap h to get high nibble->low nibble
rcall 1f // put pc on stack, skip next instruction
pop h // first time ret gets us here
1:
andi h,0x0F // h = h & 0x0F (strip off high nibble)
ADDI h,'0' // h += '0'
cpi h,'9'+1 // test if h < '9'+1 (0x3A)
brcs 2f // is not > '9', skip next
ADDI h,7 // h += 7 (was > '9', is now 'A'-'F')

.endfunc

/*.........................................................................................................*/

.func sendchar

/****************************/
#define c r24
#define tempU r25
/****************************/

sendchar:

2:
LOAD_S tempU,USART_STATUS_A // get usart status a
sbrs tempU,TX_BUFFER_EMPTY // skip if udr empty
rjmp 2b // else keep checking
STORE_S TX_DATA,c // store c to udr
ret // done

.endfunc



/*-----------------------------------------------------------------------------------------------------------
SPM function

r1:r0 will have to be setup before this function is called (page fill needs data in r1:r0)
r25:r24 will have the address, r22 will have the spm command

spm will fail if IVSEL=0 (and exit bootloader)
spm will fail on any 'errant' jump into the bootloader (assuming IVSEL=0)
spm will fail on any 'errant' jump into the bootloader (assuming spm register already setup) as an
rjmp/rcall takes 2 cycles, so if it 'lands' on anthing other than 'spm', it will have not met the 4 cycle
requirement, or it will exit the bootloader if it 'lands' on the rjmp instruction

so, in order for an 'errant' jump into the bootloader to do an spm, it will either have had to set IVSEL,
OR setup spm register, then 'jump' exactly on the spm instruction

this function could be used in an application if wanted- save sreg,cli,get IVSEL=1,setup r1:r0 if needed,
call this function,get IVSEL=0,restore sreg

if cmd=__BOOT_PAGE_FILL, no spm wait or rww enable will take place- spm wait not needed for page fill, and
enable of rww not wanted as it will erase the page buffer

void do_spm(uint16_t addr,uint8_t cmd);

r24,r25,r0 unchanged
r22 changed
r30,r31 changed (if using do_spm2 from bootloader, Z will not be changed)
r1 cleared

check if we got into the bootloader through the 'front door' (ivsel will =1)
void safeT_check(void);

r22 changed
r1 cleared

-----------------------------------------------------------------------------------------------------------*/

.func do_spm

/****************************/
#define addr r24
#define cmd r22
/****************************/

do_spm:

movw ZL,addr // addr->Z

do_spm2: // from bootloader only (Z already setup)

cpi cmd,BOOT_PAGE_FILL // if page fill,
breq 2f // no rww enable wanted, just spm
rcall 2f // call spm, return to next instruction
1:
LOAD_S cmd,SPM_REG // get spm register
sbrc cmd,SPMEN // if spmen=0, skip next
rjmp 1b // else check again
ldi cmd,BOOT_RWW_ENABLE // enable rww section
2:
STORE_S SPM_REG,cmd // load spm command

.endfunc

/*.........................................................................................................*/

.func safeT_check

safeT_check:

LOAD cmd,IRQ_CONTROL // clk+1, check if ivsel=1
sbrs cmd,IVSEL // clk+2+3, skip next if ivsel=1
rjmp main+BL_SIZE_BYTES // else ivsel=0, exit bootloader
spm // clk+4, one spm in bootloader, this is it
clr r1 // need to clear r1 in case was page fill
ret // ret (if page fill), or back to rww enable

.endfunc



/*-----------------------------------------------------------------------------------------------------------
jump table for functions

to use in applications, add the following-

for avr's <16k (mega8,88,etc)

extern void* __vectors;
#define HEX2ASCII(byte) ( (void(*)(uint8_t h)) &__vectors-8 )(byte)
#define SENDCHAR(byte) ( (void(*)(uint8_t c)) &__vectors-6 )(byte)
#define DO_SPM(addr,cmd) ( (void(*)(uint16_t a,uint8_t c)) &__vectors-4 )(addr,cmd)
#define BOOTLOADER() ( (void(*)(void)) &__vectors-2 )()

the __vectors is needed as a reference to 0x0000, otherwise the numbers will be in reference to the
section you are currently in (cannot put in absolute addresses like 0x1FFE)

if not using the gcc startup files, and you need a reference to 0x0000 (absolute), another option would
be to pass a symbol to the linker (-Wl,--defsym=__absolute_zero=0), then use it in the macros, like-
extern void* __absolute_zero; #define BOOTLOADER() ( (void(*)(void)) &__absolute_zero-2 )()

so, for example, to use the sendchar function, just use it as you normally would- SENDCHAR('a'); -which
will load r24 with 'a', then rcall 0x0000-6, which will be FLASHEND-5, which will rjmp to the
sendchar function, and when the return is reached, it will go back to your app code


for avr's >8k (mega168,16,32,etc)

#define HEX2ASCII(byte) ( (void(*)(uint8_t h)) FLASHEND-7 )(byte)
#define SENDCHAR(byte) ( (void(*)(uint8_t c)) FLASHEND-5 )(byte)
#define DO_SPM(addr,cmd) ( (void(*)(uint16_t a,uint8_t c)) FLASHEND-3 )(addr,cmd)
#define BOOTLOADER() ( (void(*)(void)) FLASHEND-1 )()

since a call will be used (instead of rcall), we can use FLASHEND

-----------------------------------------------------------------------------------------------------------*/

.func jump_table
.org main+(BL_SIZE_BYTES-(4*2)),0x00 // end of bootloader, fill empty space with nop

jump_table:

rjmp hex2ascii // FLASHEND-7 (for app use, no ivsel check)
rjmp sendchar // FLASHEND-5 (for app use, no ivsel check)
rjmp do_spm // FLASHEND-3 (for app spm use, will need ivsel=1)
rjmp jmp_start // FLASHEND-1 (for app jump into bootloader)

.endfunc



//---------------------------------------------------------------------------------------------------------//
// __ __ _______ __ .______ //
// | | | | | ____|| | | _ \ //
// | |__| | | |__ | | | |_) | //
// | __ | | __| | | | ___/ //
// | | | | | |____ | `----.| | //
// |__| |__| |_______||_______|| _| //
// //
//---------------------------------------------------------------------------------------------------------//
// 1. general information //
// 2. avr studio settings //
// 3. bootloader protection from bad apps //
// 4. reset flags logic //
// 5. application jump into bootloader //
// 6. encryption //
// 7. eeprom programming //
// 8. program and data memory lock bits //
//---------------------------------------------------------------------------------------------------------//


/*

-------------------------------------------------------------------------------------------------------------
1. general information
-------------------------------------------------------------------------------------------------------------


Upload intel hex file as a text file using any terminal program
set terminal program to 38400 baud (or whatever baud value you set)
8 bits, 1 stop bit, no parity, 20msec transmit line delay, incoming cr=cr+lf, outgoing cr only, no local echo

make sure boot reset fuse set to correct setting

only 4 things possible with this bootloader-
1. erase flash
2. program flash from an intel hex record
3. read program flash
4. 'go' (run application)

that's it.

program flash loop just processes an individual intel hex record that is done over and over (line by line)

to enter bootloader, press (hold down) 'trigger' key in terminal program while powering on the avr
(trigger key set to 'escape' key here, can change in defines)

command prompt will show up >

there are 3 command prompts- > = < which indicate the bootloader status, and what you can do
you can ALWAYS erase or read program flash, anytime
command prompt- > can go, cannot program
command prompt- = can program, cannot go
command prompt- < error(s), can only erase now

first command prompt > (indicates you can 'go' because app still in place, but cannot program until erase)

colon ':' character HAS to be the first character for any command, or start of hex record
(':' converts to 0x0A, these also convert to 0x0A- '*','A','a','Q','q', so you can substitute these for ':')

command to erase flash, can be changed in defines
>:EF (erase flash)
= (prompt indicates you cannot 'go' because nothing to 'go' to, can program as flash is now erased)

command prompt now =

to program, upload an intel hex file (text), however your terminal program handles text uploads
=:1002A000892B31F488E591E0E1DE84E67BDF089577
=

any errors will cause ! to be transmitted like so
=:1002A000892B31F488E591E0E1DE84E67BDF089577!
< (command prompt switches to < to indicate you are now in an error state, which requires an erase to clear)

after you send an EOF record, and no previous errors, the command prompt will change to > again

you can now 'go' ( :99 is default 'go' command, can be changed in defines, 'APP' can be used-converts to :99)

>:DF (dump flash) will output a hex/ascii image of the application section, if decrypt is NOT on
you can 'capture' the output to a file if needed

any time you enter a 'bad' command, or after a hex record produces an error, a '?' after the command or
record indicates nothing was done

your APPLICATION code will need to clear the watchdog reset flag, and if not used, turn off the watchdog
as recommended in the Atmel datasheet (page 51 of mega88) - because the bootloader uses the watchdog reset to
start the application code, here is sample code to do that

#include <avr/wdt.h> // read this file for more information
MCUSR = 0; // clear all reset flags, or save first if needed
wdt_disable(); // disable wdt (if fused on, you need to use wdt_enable(your_desired_timeout))

here is the paragraph in the datasheet page 51- (atmega88/168)-

Note: If the Watchdog is accidentally enabled, for example by a runaway pointer or brown-out
condition, the device will be reset and the Watchdog Timer will stay enabled. If the code is not
set up to handle the Watchdog, this might lead to an eternal loop of time-out resets. To avoid this
situation, the application software should always clear the Watchdog System Reset Flag
(WDRF) and the WDE control bit in the initialisation routine, even if the Watchdog is not in use.



-------------------------------------------------------------------------------------------------------------
2. avr studio settings
-------------------------------------------------------------------------------------------------------------


THIS WAS COMPILED USING WinAVR20060421 and Avr Sudio 4


to change the compiled bootloader to load in the bootloader section-

AVR Studio - Project-Configuration Options-
Memory Settings-

Add -> Flash .text 0xF00 (ATMega88)
---OR---
Add -> Flash .text 0x1F00 (ATMega168)
---OR---
Add -> Flash .text 0x1F00 (ATMega16)
---OR---
for your avr-
will be ((FLASHEND+1) - 0x200) / 2
(end of program memory +1, minus the bootloader size (in bytes), divided by 2 to get the word size)

**AVR studio uses a word size for this address (which is converted to a byte size for gcc)**

Use WORD address for this setting
\ /
||
\------------------------------\-----------------------------------------------\
-------------------------------------||------- -------------------------------------||-------
| ATMega88 | | ATMega168, ATMega16 |
---------------------------------------------- ----------------------------------------------
| Bootloader | BYTE Address | WORD Address | | Bootloader | BYTE Address | WORD Address |
| Size - WORDS | Start | Start | | Size - WORDS | Start | Start |
---------------------------------------------- ----------------------------------------------
| 1024 | 1800 | C00 | | 1024 | 3800 | 1C00 |
---------------------------------------------- ----------------------------------------------
| 512 | 1C00 | E00 | | 512 | 3C00 | 1E00 |
---------------------------------------------- ----------------------------------------------
| *256 | 1E00 | -> F00 <- | | *256 | 3E00 | -> 1F00 <- |
---------------------------------------------- ----------------------------------------------
| 128 | 1F00 | F80 | | 128 | 3F00 | 1F80 |
---------------------------------------------- ----------------------------------------------


to save space/prevent interrupt vectors (not using interrupts), and normal C startup code-
AVR Studio - Project-Configuration Options-
Custom Options- Linker Options

Add-> -nostartfiles
Add-> -nodefaultlibs

for avr's >8K, to make all calls 'rcall' instead of call (save some space- only need rcall in bootloader)
AVR Studio - Project-Configuration Options-
Custom Options- All Files

Add-> -mshort-calls


RESULTS COMPILED USING WinAVR20060421 and Avr Sudio 4

------------------------------------------------------------------------------------
| Device Data Bytes Program Bytes USE_PIN_TRIGGER=1 MAX |
|------------------------------------------------------------------------------------|
| ATmega88 71 466 -4 466 |
|------------------------------------------------------------------------------------|
| ATmega168 135 466 -4 466 |
|------------------------------------------------------------------------------------|
| ATmega16 135 466 -4 466 |
|------------------------------------------------------------------------------------|
| ATmega32 135 466 -4 466 |
|------------------------------------------------------------------------------------|
| all avr's should end up the same, as in/out is only used for registers that are |
| |
| always in lower i/o, otherwise lds/sts is used (ram use depends on SPM_PAGESIZE) |
------------------------------------------------------------------------------------

note: when compiling, the build output will show 512bytes used because we have the
jump table at the end, so to find out the 'true' size of the code, comment
out the '.org' line in the jump table (don't forget to uncomment when done)



-------------------------------------------------------------------------------------------------------------
3. bootloader protection from bad apps
-------------------------------------------------------------------------------------------------------------

the bootloader will be protected from inadvertant jumps into the bootloader, or inadvertant jumps from the
app into flash space beyond the application code that would end up entering the bootloader

the bootloader will also guard the spm command

the mechanism to do this is the IVSEL bit, since there would be no reason an application will ever switch
the vectors to the bootloader section (at least with this bootloader,which is what we are protecting here),
and the switch of IVSEL is also protected by the IVCE+4cycle requirement

the IVSEL bit will be set when entering the bootloader (if PORF=1 AND WDRF=0), and any loops in the
bootloader will check to make sure that the IVSEL bit is set- so anywhere the jump happens to land, will
end up getting to the safeT_check function, and get kicked out (to 0x0000) if IVSEL is not set

if the errant jump lands on one of the first 5 instructions in the bootloader, it will still at least have
to have the PORF flag still set, as the PORF check is done between the IVSEL change enable and the setting
of the IVSEL bit (and 1 of those instructions will rjmp to 0x0000, so only 4)

if the errant jump lands on code before the booloader, and 'enters' the bootloader from 'below', it will
also need to have PORF still be set

this same mechanism also protects any inadvertant jump from doing an spm command

there is only 1 spm instruction in the bootloader, and a check of IVSEL is done between the spm enable and
the spm instruction

if the errant jump lands on or before the spm enable, it will have to pass the IVSEL=1 test for the spm
instruction to execute

if it lands on the first or second instruction after the enable (in addition to the requirement that spmen
will have to be set one instruction before the errant jump), it will not meet the 4 cycle requirement
regardless of what IVSEL may be, as the errant jump will take at least 2cycles (rjmp/rcall/ijmp), which
then leaves the third instruction, but that one rjmp's back out to 0x0000, so the only way left for an
inadvertant jump to cause an spm command is- the spmen bit would need to be set 1 (or 2 cycles) before the
'bad' jump, and that bad jump would have to land right on the spm instruction in the bootloader

simply put- the odds are in your favor against any inadvertant jump accidentally wiping out any page in
app space


this may be overkill, but this will prevent any screwed up code from accidentally ending up in the
bootloader and then erasing a page in the app section (if it lands in the bootloader section where the
erase loop happens to be, it could be more than 1 page)


-------------------------------------------------------------------------------------------------------------
4. reset flags logic
-------------------------------------------------------------------------------------------------------------

the bootloader WILL 'run' IF the watchdog flag NOT set, AND the power up flag IS set (power up)


POWER UP-

on a power up, the watchdog flag will NOT be set, the power up flag WILL be set, so the
bootloader will then check for esc key for 1/8 second (or whatever key you define)

IF no esc key is pressed, the watchdog timer will cause a watchdog reset, which brings
us right back to the bootloader code, since the BOOTRST fuse will be programmed

the bootloader will see the watchdog reset flag IS set, so just rjmp to 0x0000

your APPLICATION will have to clear the WDRF flag and WDE control bit (if not using wdt) on startup as
recommended in the Atmel datasheet (to prevent an 'eternal loop of time-out resets' on newer avr's)

IF the APPLICATION clears NO other flags (besides WDRF), on a watchdog reset (if app enabled it, or
is set in fuses to be always on), the bootloader will NOT attempt to run (because WDRF is set)

IF the APPLICATION clears NO other flags (besides WDRF), on a brown out or external reset, the
bootloader WILL attempt to run because the application turned off the watchdog and did nothing to
the power reset flags

so, to prevent the bootloader from attempting to run (checking for 'trigger'), clear all reset flags in
your application startup code



-------------------------------------------------------------------------------------------------------------
5. application jump into bootloader
-------------------------------------------------------------------------------------------------------------

IF you want to 'jump' to the bootloader FROM your APPLICATION-
turn off interrupts cli(); // no interrupts in bootloader
clear MCU status register MCUSR=0; // clear reset flags (if not already cleared)
IF you have watchdog on wdt_reset(); // just in case its about to timeout
change vectors MCUSR = (1<<IVCE); // enable ivsel change (or use GICR, depending on avr)
MCUSR = (1<<IVSEL); // change it
'jump to FLASHEND-1'// however you want to do it


IF you use some other usart settings in your APPLICATION, you will need to make sure baud high,
usart control A and usart C are set correctly (only if you use something different than the
bootloader, which would be unusual, but possible if your 'app' uses different usart settings)

now just jump to FLASHEND-1 (jump table at the end of the bootloader)
this method, which works on 'any' avr (as jmp is not available on all avr's), and doesn't depend on C code
assuming 256word bootloader size-
asm volatile("ldi r31,%0" : : "M" ( (((FLASHEND-1)/2) >>8) );
asm volatile("ldi r30,%0" : : "M" ( (((FLASHEND-1)/2) &0x00FF) );
asm volatile("ijmp");
OR
goto *FLASHEND-1;
OR
//use macro to jump to FLASHEND-1 (see jmp_table code for more info)
BOOTLOADER();

so, altogether now-

cli(); // must
MCUSR=0; // if not already cleared
wdt_reset(); // if watchdog used in app, not a bad idea
MCUSR = (1<<IVCE);
MCUSR = (1<<IVSEL);
asm volatile("ldi r31,%0" : : "M" ((((FLASHEND-1)/2) >> 8)) );
asm volatile("ldi r30,%0" : : "M" ((((FLASHEND-1)/2) & 0x00FF)) );
asm volatile("ijmp");
OR
cli(); // must
MCUSR=0; // if not already cleared
wdt_reset(); // if watchdog used in app, not a bad idea
MCUSR = (1<<IVCE);
MCUSR = (1<<IVSEL);
goto *FLASHEND-1; //does the same thing as above, but easier
OR
cli(); // must
MCUSR=0; // if not already cleared
wdt_reset(); // if watchdog used in app, not a bad idea
MCUSR = (1<<IVCE);
MCUSR = (1<<IVSEL);
BOOTLOADER(); // using macro



-------------------------------------------------------------------------------------------------------------
6. encryption
-------------------------------------------------------------------------------------------------------------



-------------------------------------------------------------------------------------------------------------
7. eeprom programming
-------------------------------------------------------------------------------------------------------------

run bootloader, erase flash, upload 4AvrAsciiBootloaderBuddy, type 'go' command,
eeprom app runs (prompt will be #) , when done, type 'go' command
back to bootloader



-------------------------------------------------------------------------------------------------------------
8. program and data memory lock bits
-------------------------------------------------------------------------------------------------------------

All info here is from the ATmega88 datasheet

the lock bits can ONLY be 'erased' (back to a '1') with a chip erase command (serial/parallel programming)

--------------------------------------------------------------------------------
| Lock Bit Bytes (unprogrammed=1, programmed=0) |
|------------------------------------------------------------------------------|
| bit-> | 7 6 | 5 4 | 3 2 | 1 0 |
|------------------------------------------------------------------------------|
| name-> | - - | BLB12 BLB11 | BLB02 BLB01 | LB2 LB1 |
|------------------------------------------------------------------------------|
| default-> | 1 1 | 1 1 | 1 1 | 1 1 |
|------------------------------------------------------------------------------|
| applies to-> | unused | Bootloader | Application | Memory |
|------------------------------------------------------------------------------|
| app can't read the bootloader -> 0 0 <-no writing to bootloader area |
|------------------------------------------------------------------------------|
| bootloader is allowed to read application -> 1 1 <-write allowed |
|------------------------------------------------------------------------------|
| programmer is unable to read/write flash/eeprom (erase first) 0 0 |
|------------------------------------------------------------------------------|
| my secure-> | x x | 0 0 | 1 1 | 0 0 |
--------------------------------------------------------------------------------


-Boot Loader Section- BLB1 Mode 3 (BLB12=0,BLB11=0, both programmed)
this will prevent the bootloader from writing over itself (even though it already protects itself as
currently programmed), and prevent the APPLICATION from reading the bootloader section (security)

-Application Section- BLB0 Mode 1 (BLB02=1,BLB01=1, both unprogrammed)
these have to be left unprogrammed, otherwise the bootloader won't work (can't read or write to app space)

-Memory Lock Bits- LB Mode 3 (LB2=0,LB1=0, both programmed)
this will prevent programming and reading by a programmer, and the only way to read/program will be a
'chip erase' command (which will wipe out the bootloader and application)


*/