Super simple bootloader?

It worked properly for far too long, so it was about time for me to run into another seemingly impossible problem with this otherwise excellent bootloader.

I’m trying to figure out why, but there is a particular function I’m trying to add to my helicopter control code that disagrees with the bootloader. I don’t think the function itself has anything to do with it, but just for completeness, the function is:

PPM[2]=control[2]*(vmin+vlog)/(voltage+vlog);

PPM is a volatile global array of unsigned characters
control is a volatile global array of long ints
vmin and vlog are constant global unsigned ints
voltage is a volatile long int

The code compiles fine, uploads fine over an AVRISP, and runs fine. When I try to upload the exact same .hex file using the bootloader it seems to upload fine, in that it passes all the checksum tests, but after reseting it crashes immediately. When I read the hex file back off of the Baby O over an AVRISP, one line always has this difference in it:

:10148000BB1FA617B70710F0A61BB70B881F991F25 - Original Compiled Hex
:10148000001F2617B70710F0A61BB70B881F991F60 - Read Back After Bootloading

Well, of course the checksum is recalculated when I read it back, so that doesn’t count as a change, but why on earth would this be happening?

I monitored the serial bytes going out of my com port while I bootload, and they match the original compiled file. I’m pretty sure the intended bytes are arriving at my Baby-O anyway (not getting messed up by the serial radios for example), since they pass the checksum test. If I change the function slightly to:

PPM[2]=control[2]*(vmin+vlog)/(voltage);

Or move it to another part of the code, it bootloads fine. Of course, this does me no good, since I need it how it is where it is, but when I move it back and recompile it I get the same problem.

Uhhh, and suddenly it’s working fine. I just put the code back the way I wanted it, bootloaded it, and it’s working fine. It also passes a verification test, so it perfectly matches the compiled hex code. Man, I hate intermittent problems, they always come back to byte you later!

The only thing I can think of is that maybe I’m getting close to the AVR’s flash cycle life. I do flash it a LOT. But is ISP flashing it with a external programmer really that different from bootloading? I imagine the same physical write process is occurring, only drawing it’s source bytes from the SPI hardware rather than the USART hardware. Something to ponder anyway. Any ideas?

-Adam

I discovered a problem like this recently when I upgraded to the newest version of AVR Studio. I’m not sure if this applies to your case, but in my case AVR Studio was sometimes generating hex files with strange line brakes in them. Most of the hex file would look normal, e.g.:

:100000000C9451000C946E000C946E000C946E00D5
:100010000C946E000C946E000C946E000C946E00A8
:100020000C946E000C946E000C946E000C946E0098
:100030000C946E000C946E000C94FA080C946E00F4
:100040000C946E000C946E000C946E000C946E0078
:100050000C946E000C946E000C946E000C946E0068
:100060000C946E000C946E007D0397039D038B0728

But near the end there was a section that looked strange:

:1012300030915B0180914F018823B1F7C901089576
:101240009C0110924F0130935B0120935A018091D1
:0A1250004F018823B1F70895FFCF86
:10125A00203E20537720766572202878782E787879
:10126A00293F2000203E2048772076657220287882

For some reason, AVR Studio decided to generate a hex file line that wasn’t fully filled. It still programs correctly because the line’s header information says it has 0x0A data bytes instead of 0x10 data bytes, but apparently this bootloader cannot handle this. When I tried to use the bootloader to program the file, the programming went through without an error, but the program did not run as expected. I had never seen a hex file like this until I upgraded my AVR Studio with the latest service pack.

I spent some time looking over the bootloader to see if I could understand why it would fail, but the source of the bug was not obvious to me. The way I got around the problem is somewhat tedious, but it worked. I used a programmer to program the AVR with the strangely formatted hex file and then read the hex file back off the chip. The read-back file was formatted so that every line was full, with the previously strange section appearing now as:

:1012300030915B0180914F018823B1F7C901089576
:101240009C0110924F0130935B0120935A018091D1
:101250004F018823B1F70895FFCF203E2053772018
:10126000766572202878782E7878293F2000203EF5
:101270002048772076657220287878293F200063FF

The bootloader was able to program this read-back hex file without any problems.

It’s possible that when you changed the code, the hex file generated by AVR Studio lost its rogue line-break and hence the bootloader was able to work.

- Ben

I’m a little confused about using interrupts (specifically pin-change interrupts, but interrupts in general too) from a bootloader. I tried to just use them as I would normally, and they either don’t do anything at all, or they freeze up the bootloader. The datasheet makes some vague mention of moving the interrupt vectors to the bootloader section, but I’m not sure this is the real solution (the code I’m bootloading should still be able to use interrupts too). Has anyone figured this particular pickle out?

I’m trying to make the bootloader respond to a button press at any time, without having to poll for it from every part of the code!

-Adam

I haven’t encountered a situation where I needed to use interrupts from a bootloader, so I can’t really help you here. I vaguely remember reading something in the datasheet about moving the interrupt vectors to the bootloader section (as you mentioned), but it’s not immediately obvious to me why this would be necessary. It seems like execution should be able to jump out of the bootloader to an ISR and then back (assuming you’re not currently writing to program memory). Maybe this kind of thing is not allowed because an interrupt that occurs during a flash write could cause major problems if it jumped you out of the bootloader?

I’m sorry I can’t be of more help here, but I’m definitely interested to hear about any solution you come up with.

- Ben

I did some more reading and the answer seems to just be that it’s complicated, so I ended up putting a polling function in all of the otherwise empty while loops that wait for things to happen.

Now that that’s working, suddenly I’m having that problem again where even though the bootloaded data passes all of the checksum tests, some of the bytes are different in the flash than they were in the source hex file (and the program operation goes totally berserk). I think you’re right about the phantom short lines being the cause, but I’m not sure what to do about them.

:10116000F50720F0A21BB30BE40BF50B661F771FEE
:10117000881F991F1A9469F76095709580959095CE
:0E1180009B01AC01BD01CF010895F894FFCF93
:10118E0056455259204C4F572042415454455259BE
:10119E00002048494748204350552054454D502083

Any more thoughts on that? I’ll let you know if I figure it out.

-Adam

I don’t know what causes those short lines, but the way I fix them is to program an AVR with the application code using an AVR ISP and then I get a new hex file by reading the flash back off the MCU. When it’s read back, the short lines are gone and the bootloader seems to work fine.

A less tedious approach might be to write a program that parses the hex files put out by AVR studio and generates new ones that don’t have any short lines. It could probably be a pretty simple program since the hex file format is not very complicated.

An even better approach might be to figure out why the bootloader can’t handle the short lines, but I’ve spent some time looking at it and it’s definitely not obvious to me what’s going wrong. Since I’m not using the bootloader to program AVRs very often, my first solution works best for me.

- Ben

GOT IT! And boy was it confusing.

AVR bootloaders can only write program memory a page at a time from the page buffer. Rather than build up an entire page in the buffer, this bootloader loads a single line of the hex file into the page buffer, fills the rest of the buffer with 0xFF bytes, then writes the whole page. This is potentially a little slower, but MUCH SIMPLER to implement and still quite fast, so I’m happy with it. Filling out the buffer with all 0xFFs is important (as in it won’t work if you don’t), I think because the page write command can only clear bits, which were all previously set when the bootloader erased the program memory space.

Normally AVR studio makes lines that are 16 data-bytes wide, so these lines fit evenly into the pages (usually 128 or 256 bytes wide, depending on the AVR you’re using). The problem caused by lines of different widths is not that the bootloader can’t handle the widths (in general all the lines are much less than a full page wide, and the last line is usually a different width from all of the other lines), it’s that the lines will no longer align with the pages.

Lets say I have an AVR with 256 byte pages and a hex file with mostly 16 data byte lines, but in the middle of a page I load a line that has only 8 data bytes. That line will get written to memory fine, and maybe the next one will to, but soon I will get to a 16 byte line with only eight bytes left in the current page. It’s not just that the last eight bytes of the line won’t be properly written by the page write operation. So for the rest of the file the first eight bytes of every page will be totally wrong!

The trick is to keep track of when lines of the hex file straddle the border between two pages, and break them up into two separate buffered write operations. I just got a version of this working on an ATMega324p (and it still fits, at 1014 bytes!). The big change to the original version is in the main while(1) loop:

while (1){	
	serialTransmit('B'); // Notify that we are in the bootloader code

	unsigned char in=serialReceive();

	if (in=='u'){
		unsigned char memory[SPM_PAGESIZE];// create temporary page
		unsigned int address,tempAddress;
		unsigned char count, type, i, j;
		unsigned char newPage=0;
		unsigned int mem_data;

		type=0;
		address=0;// erase application portion
		while(address<APP_END){
			boot_page_erase(address);//perform page erase
			boot_spm_busy_wait();//wait until the memory is erased.
			address+=SPM_PAGESIZE;
		}

		// echo capital letter back once application portion is erased
		serialTransmit('U');

		do{
			if(serialReceive()!=':'){//get the packet start
				serialTransmit('E');//start byte error
				goto top;
			}

			checksum=0;	
			count=bootloaderGetData();//get the packet size
			address=bootloaderGetData();//get the memory address
			address<<=8;
			address|=bootloaderGetData();
			tempAddress=address;
			type=bootloaderGetData();//get the packet type

			for(i=0;i<count;i++){//get the data
				memory[i]=bootloaderGetData();
			}

			bootloaderGetData();//get the checksum

			if (checksum){//check the checksum
				serialTransmit('C');// checksum error
				goto top;
			}

			serialReceive();//get the LF

			if (type==0){
				i=count;
				while(i<SPM_PAGESIZE){//fill rest of buffer
					memory[i++]=0xFF;
				}
				
				for(i=0;i<SPM_PAGESIZE;){// store in temp buffer
					if(address/SPM_PAGESIZE!=tempAddress/SPM_PAGESIZE){
						newPage=1;
						break;
					}
					mem_data=memory[i++];
					mem_data|=(memory[i++]<<8);
					boot_page_fill(address,mem_data);
					address+=2;
				}

				boot_page_write(address-SPM_PAGESIZE);//write to flash
				boot_spm_busy_wait();
				boot_rww_enable();//re-enable the RWW section
				
				if(newPage){
					newPage=0;

					j=0;
					while(i<count){
						mem_data=memory[i++];
						mem_data|=(memory[i++]<<8);
						boot_page_fill(address,mem_data);
						address+=2;
						j+=2;
					}

					for(;j<SPM_PAGESIZE;j+=2){
						boot_page_fill(address,0xFFFF);
						address+=2;
					}

					boot_page_write(address-SPM_PAGESIZE);//write to flash
					boot_spm_busy_wait();
					boot_rww_enable();//re-enable the RWW section

				}

			}
			serialTransmit('.');//signify valid packet
		}while(type!=1);

		if(type==1){
			serialTransmit('*');// signify valid upload
			goto *0x0000;// jump to the start of flash
		}

	}//end if (in=='u')
	else if(in=='x'){
		serialTransmit('X');
		goto top;
	}

 }//end while(1)

I don’t want to post the whole code right now because some of it is specific to my board and the ATMega324p. I’ll be writing an ATMega168 version to throw on my helicopter, probably sometime next week, and I’ll be sure to post that in it’s entirety.

-Adam

P.S. I thought this problem had stopped cropping up in my helicopter code, but I just looked at the last hex file I sent to it and there’s a short line right before the end. I’m sure that’s not the only thing keeping it from free-flying, but it may explain some of the crazy behavior I was seeing from time to time.

That’s fantastic, Adam! Thanks for taking the time to figure this out! I was pretty sure it had something to do with the page write, but it was never clear to me why the current formulation didn’t work.

- Ben

I never like posting incomplete or untested code, so here’s a complete version for the ATMega168 that can handle short hes file lines:

#include <avr/boot.h>

/* Select Boot Size (select one, comment out the others) */
//#define _B128
//#define _B256
#define _B512// boot size is 512 words (1024 bytes)
//#define _B1024

#include "chipdef.h"

//#define SERIAL_BAUD_RATE  	10//115200 @ 20MHz: CLOCK/BAUD/16 - 1
//#define SERIAL_BAUD_RATE  	129//9600 @ 20MHz: CLOCK/BAUD/16 - 1
#define SERIAL_BAUD_RATE  	32//38400 @ 20MHz

#define BL_VERSION_MAJOR	'1'
#define BL_VERSION_MINOR	'1'

unsigned char checksum;

#define	serialReceiveReady()		(UCSR0A&(1<<RXC0))
#define serialDataRegisterReady()	(UCSR0A&(1<<UDRE0))
#define	serialReceiveNow()			(UDR0)
#define	serialTransmitMacro(byte)	while(!serialDataRegisterReady());\
									UDR0=byte

void serialTransmit(unsigned char byte){
	while(!serialDataRegisterReady());	// Wait for empty transmit buffer
	UDR0=byte;//send byte
}

unsigned char serialReceive(void){
	while(!serialReceiveReady());
	return UDR0;//Get byte
}

unsigned char serialReceiveHexNibble(void){
	unsigned char in=serialReceive();
	if(in>='A')
		in-='A'-10;
	else
		in-='0';
	return in;
}

unsigned char bootloaderGetData(void){
	unsigned char in;

	in=serialReceiveHexNibble() << 4;
	in|=serialReceiveHexNibble();
	checksum+=in;
	return in;
}

void reset(){
	WDTCSR|=(1<<WDE);
	while(1);
}

int main(void){
	// Serial Init
	UBRR0=(unsigned int)SERIAL_BAUD_RATE;// Set baud rate
	UCSR0C=(3 << UCSZ00);				// 1 stop bit, 8-bit characters
	UCSR0B=_BV(RXEN0)|_BV(TXEN0);// enable UART receiver and transmitter

	// *** Do something here to decide whether to remain in the bootloader
	//		to leave the bootloader, call jumpToApp()

	top:// jump back here if there's an error receiving upload

	// *** Require some sort of handshake before entering main bootloader code

	while (1){	
		serialTransmit('B'); // Notify that we are in the bootloader code

		unsigned char in=serialReceive();

		if (in=='u'){
			unsigned char memory[SPM_PAGESIZE];// create temporary page
			unsigned int address,tempAddress;
			unsigned char count, type, i, j;
			unsigned char newPage=0;
			unsigned int mem_data;

			type=0;
			address=0;// erase application portion
			while(address<APP_END){
				boot_page_erase(address);//perform page erase
				boot_spm_busy_wait();//wait until the memory is erased.
				address+=SPM_PAGESIZE;
			}

			// echo capital letter back once application portion is erased
			serialTransmit('U');

			do{
				if(serialReceive()!=':'){//get the packet start
					serialTransmit('E');//start byte error
					goto top;
				}

				checksum=0;
				count=bootloaderGetData();//get the packet size
				address=bootloaderGetData();//get the memory address
				address<<=8;
				address|=bootloaderGetData();
				tempAddress=address;
				type=bootloaderGetData();//get the packet type

				for(i=0;i<count;i++){//get the data
					memory[i]=bootloaderGetData();
				}

				bootloaderGetData();//get the checksum
	
				if (checksum){//check the checksum
					serialTransmit('C');// checksum error
					goto top;
				}

				serialReceive();//get the LF
	
				if (type==0){
					i=count;
					while(i<SPM_PAGESIZE){//fill rest of buffer
						memory[i++]=0xFF;
					}
					
					for(i=0;i<SPM_PAGESIZE;){// store in temp buffer
						if(address/SPM_PAGESIZE!=tempAddress/SPM_PAGESIZE){
							newPage=1;
							break;
						}
						mem_data=memory[i++];
						mem_data|=(memory[i++]<<8);
						boot_page_fill(address,mem_data);
						address+=2;
					}

					boot_page_write(address-SPM_PAGESIZE);//write to flash
					boot_spm_busy_wait();
					boot_rww_enable();//re-enable the RWW section
					
					if(newPage){
						newPage=0;

						j=0;
						while(i<count){
							mem_data=memory[i++];
							mem_data|=(memory[i++]<<8);
							boot_page_fill(address,mem_data);
							address+=2;
							j+=2;
						}

						for(;j<SPM_PAGESIZE;j+=2){
							boot_page_fill(address,0xFFFF);
							address+=2;
						}

						boot_page_write(address-SPM_PAGESIZE);//write to flash
						boot_spm_busy_wait();
						boot_rww_enable();//re-enable the RWW section
					}

				}
				serialTransmit('.');//signify valid packet
			}while(type!=1);

			if(type==1){
				serialTransmit('*');// signify valid upload
				reset();// jump to the start of flash
			}

		}//end if (in=='u')
		else if(in=='x'){
			serialTransmit('X');
			reset();
		}
		else if(in=='v'){
			serialTransmit(BL_VERSION_MAJOR);
			serialTransmit('.');
			serialTransmit(BL_VERSION_MINOR);
		}

	 }//end while(1)
	
	 return 0;
}

Other than the check for page-crossing lines of code it’s mostly the same, the baud rate is set for 38400bps, and I changed the version to 1.1.

I also made an addition to chipdef.h, should anyone else want to use this bootloader on an ATMega324p:

#ifndef CHIPDEF_H
#define CHIPDEF_H

#include <avr/io.h>

// TODO: make use of RAMEND in the avr-libc io-files and
// avoid a lot of by-device definitions here

#if defined(__AVR_ATmega169__)
  #define sig_byte3 0x1E
  #define sig_byte2 0x94
  #define sig_byte1 0x05
 
  #define devtype 0x79       // Mega 169 device code
 
//  #define PAGESIZE 128  // 2*64 Words = Size in Bytes
 
  #ifdef _B128
    #define APP_PAGES ((2*8192 / SPM_PAGESIZE)- (2*128 / SPM_PAGESIZE ))
    #define APP_END APP_PAGES * SPM_PAGESIZE
  #endif
  #ifdef _B256
    #define APP_PAGES ((2*8192 / SPM_PAGESIZE)- (2*256 / SPM_PAGESIZE ))
    #define APP_END APP_PAGES * SPM_PAGESIZE
  #endif
  #ifdef _B512
    #define APP_PAGES ((2*8192 / SPM_PAGESIZE)- (2*512 / SPM_PAGESIZE ))
    #define APP_END APP_PAGES * SPM_PAGESIZE
  #endif
  #ifdef _B1024
    #define APP_PAGES ((2*8192 / SPM_PAGESIZE)- (2*1024 / SPM_PAGESIZE ))
    #define APP_END APP_PAGES * SPM_PAGESIZE
  #endif
  #ifdef _B2048
    #error "_B2048 not suppoted on this device"
  #endif   

#elif defined(__AVR_ATmega16__)

  #define sig_byte3 0x1E
  #define sig_byte2 0x94
  #define sig_byte1 0x03
 
  #define devtype 0x75       // Mega16 device code
 
//  #define PAGESIZE 128       // Size in Bytes
 
  #ifdef _B128
    #define APP_PAGES ((2*8192 / SPM_PAGESIZE)- (2*128 / SPM_PAGESIZE ))
    #define APP_END APP_PAGES * SPM_PAGESIZE
  #endif
  #ifdef _B256
    #define APP_PAGES ((2*8192 / SPM_PAGESIZE)- (2*256 / SPM_PAGESIZE ))
    #define APP_END APP_PAGES * SPM_PAGESIZE
  #endif
  #ifdef _B512
    #define APP_PAGES ((2*8192 / SPM_PAGESIZE)- (2*512 / SPM_PAGESIZE ))
    #define APP_END APP_PAGES * SPM_PAGESIZE
  #endif
   #ifdef _B1024
    #define APP_PAGES ((2*8192 / SPM_PAGESIZE)- (2*1024 / SPM_PAGESIZE ))
    #define APP_END APP_PAGES * SPM_PAGESIZE
  #endif 
  #ifdef _B2048
    #error "_B2048 not suppoted on this device"
  #endif   

#elif defined(__AVR_ATmega168__)

  #define sig_byte3 0x1E
  #define sig_byte2 0x94
  #define sig_byte1 0x03
 
  #define devtype 0x75       // Mega16 device code
 
//  #define PAGESIZE 128       // Size in Bytes
 
  #ifdef _B128
    #define APP_PAGES ((2*8192 / SPM_PAGESIZE)- (2*128 / SPM_PAGESIZE ))
    #define APP_END APP_PAGES * SPM_PAGESIZE
  #endif
  #ifdef _B256
    #define APP_PAGES ((2*8192 / SPM_PAGESIZE)- (2*256 / SPM_PAGESIZE ))
    #define APP_END APP_PAGES * SPM_PAGESIZE
  #endif
  #ifdef _B512
    #define APP_PAGES ((2*8192 / SPM_PAGESIZE)- (2*512 / SPM_PAGESIZE ))
    #define APP_END APP_PAGES * SPM_PAGESIZE
  #endif
   #ifdef _B1024
    #define APP_PAGES ((2*8192 / SPM_PAGESIZE)- (2*1024 / SPM_PAGESIZE ))
    #define APP_END APP_PAGES * SPM_PAGESIZE
  #endif 
  #ifdef _B2048
    #error "_B2048 not suppoted on this device"
  #endif   

#elif defined(__AVR_ATmega8__)

  #define sig_byte3 0x1E
  #define sig_byte2 0x93
  #define sig_byte1 0x07
 
  #define devtype 0x77       // Mega8 boot device code
 
//  #define PAGESIZE 64        // Size in Bytes
 
  #ifdef _B128
    #define APP_PAGES ((2*4096 / SPM_PAGESIZE)- (2*128 / SPM_PAGESIZE ))
    #define APP_END APP_PAGES * SPM_PAGESIZE
  #endif
  #ifdef _B256
    #define APP_PAGES ((2*4096 / SPM_PAGESIZE)- (2*256 / SPM_PAGESIZE ))
    #define APP_END APP_PAGES * SPM_PAGESIZE
  #endif
  #ifdef _B512
    #define APP_PAGES ((2*4096 / SPM_PAGESIZE)- (2*512 / SPM_PAGESIZE ))
    #define APP_END APP_PAGES * SPM_PAGESIZE
  #endif
   #ifdef _B1024
    #define APP_PAGES ((2*4096 / SPM_PAGESIZE)- (2*1024 / SPM_PAGESIZE ))
    #define APP_END APP_PAGES * SPM_PAGESIZE
  #endif 
  #ifdef _B2048
    #error "_B2048 not suppoted on this device"
  #endif   
#elif defined(__AVR_ATmega162__)

  #define sig_byte3 0x1E
  #define sig_byte2 0x94
  #define sig_byte1 0x04
 
// #define devtype 0x??       // Mega162 boot device code
 
//  #define PAGESIZE 128        // Size in Bytes
 
  #ifdef _B128
    #define APP_PAGES ((2*8192 / SPM_PAGESIZE)- (2*128 / SPM_PAGESIZE ))
    #define APP_END APP_PAGES * SPM_PAGESIZE
  #endif
  #ifdef _B256
    #define APP_PAGES ((2*8192 / SPM_PAGESIZE)- (2*256 / SPM_PAGESIZE ))
    #define APP_END APP_PAGES * SPM_PAGESIZE
  #endif
  #ifdef _B512
    #define APP_PAGES ((2*8192 / SPM_PAGESIZE)- (2*512 / SPM_PAGESIZE ))
    #define APP_END APP_PAGES * SPM_PAGESIZE
  #endif
   #ifdef _B1024
    #define APP_PAGES ((2*8192 / SPM_PAGESIZE)- (2*1024 / SPM_PAGESIZE ))
    #define APP_END APP_PAGES * SPM_PAGESIZE
  #endif 
  #ifdef _B2048
    #error "_B2048 not suppoted on this device"
  #endif   

#elif defined(__AVR_ATmega32__)

  #define sig_byte3 0x1E
  #define sig_byte2 0x95
  #define sig_byte1 0x02
 
  #define devtype 0x73       // Mega32 device code
 
//  #define PAGESIZE 128       // Size in Bytes
 
  #ifdef _B128
    #define APP_PAGES ((2*16384UL / SPM_PAGESIZE)- (2*128 / SPM_PAGESIZE ))
    #define APP_END APP_PAGES * SPM_PAGESIZE
  #endif
  #ifdef _B256
    #define APP_PAGES ((2*16384UL / SPM_PAGESIZE)- (2*256 / SPM_PAGESIZE ))
    #define APP_END APP_PAGES * SPM_PAGESIZE
  #endif
  #ifdef _B512
    #define APP_PAGES ((2*16384UL / SPM_PAGESIZE)- (2*512 / SPM_PAGESIZE ))
    #define APP_END APP_PAGES * SPM_PAGESIZE
  #endif
   #ifdef _B1024
    #define APP_PAGES ((2*16384UL / SPM_PAGESIZE)- (2*1024 / SPM_PAGESIZE ))
    #define APP_END APP_PAGES * SPM_PAGESIZE
  #endif 
  #ifdef _B2048
    #define APP_PAGES ((2*16384UL / SPM_PAGESIZE)- (2*2048 / SPM_PAGESIZE ))
    #define APP_END APP_PAGES * SPM_PAGESIZE
  #endif 


#elif defined(__AVR_ATmega324P__)

  #define sig_byte3 0x1E
  #define sig_byte2 0x95
  #define sig_byte1 0x08
 
// #define devtype 0x??			// Mega324p device code
 
//  #define PAGESIZE 128       // Size in Bytes
 
  #ifdef _B256
    #define APP_PAGES ((2*16384UL / SPM_PAGESIZE)- (2*256 / SPM_PAGESIZE ))
    #define APP_END APP_PAGES * SPM_PAGESIZE
  #endif
  #ifdef _B512
    #define APP_PAGES ((2*16384UL / SPM_PAGESIZE)- (2*512 / SPM_PAGESIZE ))
    #define APP_END APP_PAGES * SPM_PAGESIZE
  #endif
   #ifdef _B1024
    #define APP_PAGES ((2*16384UL / SPM_PAGESIZE)- (2*1024 / SPM_PAGESIZE ))
    #define APP_END APP_PAGES * SPM_PAGESIZE
  #endif 
  #ifdef _B2048
    #define APP_PAGES ((2*16384UL / SPM_PAGESIZE)- (2*2048 / SPM_PAGESIZE ))
    #define APP_END APP_PAGES * SPM_PAGESIZE
  #endif 
  

#elif defined(__AVR_ATmega128__)

  #define sig_byte3 0x1E
  #define sig_byte2 0x97
  #define sig_byte1 0x02
 
  #define devtype 0x44      // Mega128 device code
 
//  #define PAGESIZE 128       // Size in Bytes
 
  #ifdef _B512
    #define APP_PAGES ((2*65536UL / SPM_PAGESIZE)- (2*512 / SPM_PAGESIZE ))
    #define APP_END APP_PAGES * SPM_PAGESIZE
  #endif
  #ifdef _B1024
    #define APP_PAGES ((2*65536UL / SPM_PAGESIZE)- (2*1024 / SPM_PAGESIZE ))
    #define APP_END APP_PAGES * SPM_PAGESIZE
  #endif 
  #ifdef _B2048
    #define APP_PAGES ((2*65536UL / SPM_PAGESIZE)- (2*2048 / SPM_PAGESIZE ))
    #define APP_END APP_PAGES * SPM_PAGESIZE
  #endif
  #ifdef _B4096
    #define APP_PAGES ((2*65536UL / SPM_PAGESIZE)- (2*4096 / SPM_PAGESIZE ))
    #define APP_END APP_PAGES * SPM_PAGESIZE
  #endif 

#else
   #error "no definition for MCU available in chipdef.h"
#endif

#endif

Also, I finally got around to asking Atmel tech support why on earth AVR Studio started doing this. I have an open ticket on their support system now, I’ll post it here if they ever get back to me with an answer.

-Adam

I just heard back from Atmel tech support about the line-length problem:

At some point I’ll look a little more into this, but for now I’m happier with the revised bootloader that can handle any kind of hex file you might throw at it (well, unless the lines span more than two pages, but we’ll wait and see when that happens).

-Adam

Hi Adam!

I have a quick question. I’m using this boot loader on an ATmega168 and am using USART to transfer the code.

I keep getting the checksum error and have checked the bits going through. For some reason it’s saying the size is 1 instead of the correct size. This is causing the incorrect number of bits to be written to the memory buffer.

Is there something wrong with the hex converter or could it be that I’m receiving an incorrect bit? My boot loader should receive hex ‘10’ for the size but for some reason it’s only receiving ‘1’.

Thanks!

Hello TwoFace262,

Wow, it’s been a decade since Ben first posted the bootloader code! I’m a little foggy on it at this point, but I’m happy to take a swing at it. I don’t really understand the problem you’re having from your description though.

Could you give us more complete details about your setup (hardware and software), step-by-step of what you’re doing, what’s happening, and how you’re checking?

Thanks,

-Adam

Yes! Thank you for replying and sorry for bumping a zombie thread!

So far I have everything working on the boot loader, but once it finishes writing the program nothing happens.

For my boot loader I’m reading in a hex file bit by bit via UART and storing each page in a buffer. I then convert the hex and use the information to get the count, address, data, checksum.

That data is then used to write to the flash.

I have my boot loader’s make file setup to use fuses:
LFUSE = 0x62
HFUSE = 0xdf
EFUSE = 0xf8

With a start address of:
-Ttext=0x1C00 (I’m using the full 1024 for my boot loader)

The boot loader loads onto the chip correctly in the correct memory allocation, and it even reads in every page for my hex file and passes the checksum.

After successfully reading the last page of data I perform a jump to memory allocation 0000 using: void (*jumpToApp)(void) = 0x0000;

I’ve double checked each buffer and the information its receiving with each page matches the hex file. The hex is just a compiled version of a Blink LED example.

Is there something I should be checking for? I’ve been working on this for about a week now and can’t seem to figure out the issue.

The code I’m using is basically the exact thing as this except I’m loading each page into a buffer so I can verify integrity before writing.

Im also on the ATmega168!

Hi again TwoFace262,

I’m still a little confused, could you clarify what actual problem you’re having? Before you were saying that you were having checksum errors, but here it sounds like you’re saying that your bootloader is verifying the correct checksum for each page (or line?) in the .hex file, and that you think it’s just not successfully jumping over to run the application code.

Do you think that the bootloader is writing your application code correctly to memory (the Blink LED example)? Can you use a hardware programmer to read back the entire contents of the flash memory, and when you do, do you see both boot-loaded application code and the boot-loader at their expected memory addresses?

Thanks,

Hi Adam,

I corrected the issues with transmitting the file by using a buffer -…

After dumping my flash I’m getting –


00000000  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
00001c00  0c 94 34 0e 0c 94 51 0e  0c 94 51 0e 0c 94 51 0e  |..4...Q...Q...Q.|
00001c10  0c 94 51 0e 0c 94 51 0e  0c 94 51 0e 0c 94 51 0e  |..Q...Q...Q...Q.|
*
00001c60  0c 94 51 0e 0c 94 51 0e  11 24 1f be cf ef d4 e0  |..Q...Q..$......|
00001c70  de bf cd bf 11 e0 a0 e0  b1 e0 e2 e6 ff e1 02 c0  |................|
00001c80  05 90 0d 92 a2 30 b1 07  d9 f7 21 e0 a2 e0 b1 e0  |.....0....!.....|
00001c90  01 c0 1d 92 a7 30 b2 07  e1 f7 0e 94 d4 0e 0c 94  |.....0..........|
00001ca0  af 0f 0c 94 00 0e 10 92  c5 00 8c e0 80 93 c4 00  |................|
00001cb0  e0 ec f0 e0 80 81 82 60  80 83 88 e1 80 93 c1 00  |.......`........|
00001cc0  86 e0 80 93 c2 00 08 95  80 91 c0 00 87 ff fc cf  |................|
00001cd0  80 91 c6 00 08 95 90 91  c0 00 95 ff fc cf 80 93  |................|
00001ce0  c6 00 08 95 1f 93 cf 93  df 93 cd b7 de b7 c0 58  |...............X|
00001cf0  d1 09 0f b6 f8 94 de bf  0f be cd bf 0e 94 64 0e  |..............d.|
00001d00  18 2f e0 91 02 01 f0 91  03 01 81 e0 90 e0 8c 0f  |./..............|
00001d10  9d 1f e8 0f f9 1f 10 83  8a e2 0e 94 6b 0e 0e 94  |............k...|
00001d20  64 0e 20 91 02 01 30 91  03 01 2f 5f 3f 4f 30 93  |d. ...0.../_?O0.|
00001d30  03 01 20 93 02 01 1a 32  09 f7 ce 01 01 96 c0 58  |.. ....2.......X|
00001d40  df 4f 0f b6 f8 94 de bf  0f be cd bf df 91 cf 91  |.O..............|
00001d50  1f 91 08 95 20 91 00 01  30 91 01 01 82 0f 93 1f  |.... ...0.......|
00001d60  fc 01 80 81 81 34 10 f0  87 53 01 c0 80 53 2f 5f  |.....4...S...S/_|
00001d70  3f 4f 30 93 01 01 20 93  00 01 08 95 1f 93 cf 93  |?O0... .........|
00001d80  df 93 ec 01 0e 94 aa 0e  18 2f 12 95 10 7f ce 01  |........./......|
00001d90  0e 94 aa 0e 81 2b 90 91  06 01 98 0f 90 93 06 01  |.....+..........|
00001da0  df 91 cf 91 1f 91 08 95  cf 93 df 93 cd b7 de b7  |................|
00001db0  c0 58 d1 09 0f b6 f8 94  de bf 0f be cd bf 0e 94  |.X..............|
00001dc0  53 0e 8a e2 0e 94 6b 0e  ce 01 01 96 7c 01 6e 01  |S.....k.....|.n.|
00001dd0  92 e8 c9 0e d1 1c aa 24  a3 94 b1 2c 0e 94 64 0e  |.......$...,..d.|
00001de0  18 2f 8a e2 0e 94 6b 0e  0e 94 64 0e 15 37 09 f0  |./....k...d..7..|
00001df0  a3 c0 e0 e0 f0 e0 83 e0  80 93 57 00 e8 95 07 b6  |..........W.....|
00001e00  00 fc fd cf e0 58 ff 4f  e1 15 68 e3 f6 07 a1 f7  |.....X.O..h.....|
00001e10  55 24 5a 94 85 e0 88 2e  0e 94 72 0e 8c 01 fc 01  |U$Z.......r.....|
00001e20  80 81 8a 33 11 f0 85 e4  84 c0 10 92 06 01 c8 01  |...3............|
00001e30  0e 94 be 0e 98 2e c8 01  0e 94 be 0e 48 2e c8 01  |............H...|
00001e40  0e 94 be 0e 28 2e c8 01  0e 94 be 0e 38 2e 37 01  |....(.......8.7.|
00001e50  86 2d 8e 19 89 15 38 f4  c8 01 0e 94 be 0e f3 01  |.-....8.........|
00001e60  81 93 3f 01 f5 cf c8 01  0e 94 be 0e 80 91 06 01  |..?.............|
00001e70  88 23 11 f0 83 e4 5d c0  31 10 4c c0 97 fc 06 c0  |.#....].1.L.....|
00001e80  f7 01 e9 0d f1 1d 50 82  93 94 f8 cf 22 2d 30 e0  |......P....."-0.|
00001e90  34 29 de 01 12 96 c7 01  2e 19 3f 09 39 01 68 0e  |4)........?.9.h.|
00001ea0  79 1e ac 15 bd 05 79 f0  fc 01 40 81 6c 91 50 e0  |y.....y...@.l.P.|
00001eb0  56 2b 61 e0 f3 01 0a 01  60 93 57 00 e8 95 11 24  |V+a.....`.W....$|
00001ec0  02 96 12 96 eb cf f3 01  e0 58 f1 09 80 92 57 00  |.........X....W.|
00001ed0  e8 95 07 b6 00 fc fd cf  81 e1 80 93 57 00 e8 95  |............W...|
00001ee0  10 92 03 01 10 92 02 01  b0 92 01 01 a0 92 00 01  |................|
00001ef0  f8 01 01 90 00 20 e9 f7  af 01 41 50 51 09 40 1b  |..... ....APQ.@.|
00001f00  51 0b 60 e0 70 e0 c8 01  0e 94 a8 0f 83 e5 0e 94  |Q.`.p...........|
00001f10  6b 0e 82 cf 84 e4 0e 94  6b 0e f1 e0 3f 12 7c cf  |k.......k...?.|.|
00001f20  8c e4 0e 94 6b 0e e0 91  04 01 f0 91 05 01 09 95  |....k...........|
00001f30  8d e4 0e 94 6b 0e 52 cf  18 37 09 f0 4f cf 80 e5  |....k.R..7..O...|
00001f40  0e 94 6b 0e e0 91 04 01  f0 91 05 01 09 95 46 cf  |..k...........F.|
00001f50  dc 01 01 c0 6d 93 41 50  50 40 e0 f7 08 95 f8 94  |....m.APP@......|
00001f60  ff cf 01 00                                       |....|
00001f64

Since my boot loader starts at 0x1C00 it looks like the app memory is blank.

Any ideas why this could be?

Here is my loader code:

int main(void) {
	initUSART();
	serialTransmit('*');
	top:
	while(1) {
	    unsigned char in = serialReceive();
	    serialTransmit('*');
	    serialReceive();
	    if(in == 'u') {
	    	unsigned char memory[SPM_PAGESIZE];
	    	unsigned int address;
	    	unsigned char count, type, i;
	    	unsigned int mem_data;
	    	type = 0;
	    	address = 0; 
	    	// erase app partition pages
	    	while(address < APP_END) {
	    		boot_page_erase(address);
	    		boot_spm_busy_wait();
	    		address += SPM_PAGESIZE;
	    	}
	    	do {
	    		unsigned char* pageBuffer = getInputBuffer();
	    		
	    		if(pageBuffer[0] != ':') {
	    			serialTransmit('E');
	    			goto top;
	    		}
	    		
	    		checksum = 0;
	    		count = getBootData(pageBuffer);
	    		address = getBootData(pageBuffer);
	    		address <<= 8;
	    		address |= getBootData(pageBuffer);
	    		type = getBootData(pageBuffer);
	    		for(i = 0; i < count; i++)
	    			memory[i] = getBootData(pageBuffer);

	    		getBootData(pageBuffer);
	    		if(checksum) {
	    			serialTransmit('C');
	    			goto top;
	    		}

	    		if(type == 0) {
	    			while(count < SPM_PAGESIZE)
	    				memory[count++] = 0xFF; // Fill the buffer
	    			for(i = 0; i < SPM_PAGESIZE;) {
	    				mem_data = memory[i++];
	    				mem_data |= (memory[i++] << 8);
	    				boot_page_fill(address, mem_data);
	    				address += 2;
	    			}
	    			boot_page_write(address-SPM_PAGESIZE);
	    			boot_spm_busy_wait();
	    			boot_rww_enable();
	    			bufferIndex = 0;
					pageIndex = 1;
					memset(pageBuffer, 0 , strlen(pageBuffer));
	    			serialTransmit('S');
	    		} else {
	    			serialTransmit('D');
	    		}

	    	} while(type != 1);

	    	if(type == 1) {
	    		serialTransmit('L');
	    		jumpToApp();
	    		serialTransmit('M');
	    	}
	    } else if(in == 'x') {
	    	serialTransmit('P');
	    	jumpToApp();
	    }
	}

  return 0;
}

I’m like 99% sure the bits are being read in correctly and are being converted to hex/returned correctly.


unsigned char getHex(unsigned char* pageBuffer) {
	unsigned char data = pageBuffer[pageIndex];
	if(data >= 'A') {
		data -= 'A' - 10;
	} else {
		data -= '0';
	}
	pageIndex++;

	return data;
}

unsigned char getBootData(unsigned char* pageBuffer) {
	unsigned char data;
	data = getHex(pageBuffer) << 4;
	data |= getHex(pageBuffer);
	checksum += data;
	return data;
}

I wonder if its a fuse issue.

Also, thank you so much for your help and replying 9 years later!

Turned out I was setting the memory address of the boot loader in bytes not words.
After starting the write for the boot loader at 0x3e00 everything works!

I hope the updated code helps those out in future projects!

Fantastic! I’m so glad you were able to figure it out.

Can you tell us a little more about your project? It’s great to see that this code is still being actively customized and used.

Thanks,

-Adam

Hey Adam!

Sorry for getting back to you a year later -…

I was using this code as a proof of concept for a wirelessly flashed prototyping board. I initially had this connected to a serial bluetooth module and let is flash an embedded ATmega168.

Since then I’ve moved on to use wireless SPI flashing on my boards (no need for a boot loader!).

Our site is:
trynkit.us/info