[TUT] [C] Bootloader using SD/MMC card

40 posts / 0 new
Last post
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

(This tutorial is based on modified code from http://www.mikrocontroller.net/articles/MMC/SD_Bootloader_für_AT_Mega. In addition, it uses modified sd/mmc reader code from E.Chan.)

Introduction

Bootloaders can be very confusing. Lots of posts make references to the possibility of using an SD card, but I couldn't find any that actually discussed how to do it. This tutorial is meant to allow beginners to quickly modify and use an memory-based bootloader, without having to go through all the stress and development I had to.

Bootloaders are actually quite simple, but when you're just starting out they can be a little overwhelming. The first thing to understand is that bootloaders are actually fully separate, independently flashed, programs. It might be surprising, but you actually need two main()s: one in the bootloader and one in the "real" program.

The crucial difference comes from the fact that the bootloader is located at the end of the program flash, instead of the beginning. This is why the MCU must be configured to send the reset vector to the beginning of the bootloader instead of the absolute beginning of the flash.

What this means is that with an ISP, JTAG, or other programmer you'll first flash the MCU with the bootloader and then use the bootloader to load the "real" program into the lower flash section. When the chip boots, it goes directly to the bootloader executable, and from there the bootloader instructs the chip to start the main application. This is easier than you think: all the bootloader does is tell the MCU to do whatever instruction is located at 0x0000, i.e. the beginning of the flash. Since this is precisely what happens when you don't have a bootloader, the program runs just as it would if it had been flashed on directly with a programmer.

Configuring the MCU

Since the bootloader is about 100 bytes over 2K (can anyone help me get it under 2K?), we have no other choice than to configure the MCU for a 4K bootloader. For this, two fuses need to be set: BOOTRST and BOOTSZ. BOOTRST simply needs to be turned on, but BOOTSZ needs to be properly configured. As we need a 4K boot sector, we have to choose the Boot Flash size = 2048 words. (Confusing, eh? 2 bytes equals 1 word. Not sure why Atmel counts in this way, I'm certain there's a good reason. Maybe one of our 10K+ posters can tell us?) If you're using AVRStudio, it will even nicely tell you the start address-- again in words, not in bytes; otherwise, you can find that information in the chip docs.

That's all there is to configuring the chip. Write the fuses, and henceforth the MCU will go to the start address instead of 0x0000 after a reset. Now comes the harder part.

Bootloader

The bootloader is completely independent from the main application. This has two consequences, one good, one bad. The good: you can debug the bootloader separately without worrying about what the main application needs to do. The bad: if you use a programmer to flash the bootloader, you erase the main program and vice-versa. (Of course this isn't strictly true, as you can choose not to erase the chip when you flash, but this probably makes life worse, not better.)

Settings

In order for the included code to work, several settings must be made. Almost all settings are in defines.h.

defines.h

/*Firmware version*/
#define DEVID 0x00000001 		//Arbitrary ID

The device ID (DEVID) is how the bootloader tells if the firmware is meant for that device. This is an arbitrary number and can be set as you like. If the device ID in the main program firmware does not correspond to that of the bootloader, the bootloader will ignore it.

/*MCU bootloader info*/
#define BOOTLDRSIZE  0x1000	//Size of bootloader in [bytes]. 0x1000 = 4096 bytes

This is the size of the bootloader section in [bytes]. It must be equal to the Boot Flash size (times two if the BFs is expressed in words).

/*FAT version*/
//#define USE_FAT12 				//Uncomment for FAT12 support. Requires additional flash

The bootloader can also read FAT12 partitions. Uncomment to activate this, but note it increases the program size.

/*Watchdog*/
//#define WATCHDOG 					//Uncomment if Watchdog is used in normal code

If the "real" application activates a watchdog, you'll need to make sure you deactivate it in the bootloader. Otherwise, this can (and will) lead to inexplicable behavior that drives you crazy. Enabling this section marginally increases bootloader size.

/*SPI configuration*/
#define DD_MOSI		DDB5
#define DD_SCK			DDB7
#define DDR_SPI		DDRB
#define DD_SS_PORT	PORTB
#define DD_SS			DDB4

These must be set to match your chip settings. These parameters are defined in the chip docs.

/* Port configuration for SD card SPI access */
#define SD_CS_BLOCK		DDRC
#define SD_CS_PORT		PORTC
#define SD_CS_PIN			7

Configure this for where you have connected the SD card's chip select line.

/*Visual feedback*/
#define USE_FLASH_LED 				//Uncomment if you want visual feedback

/* Port configuration for visual feedback*/
#ifdef USE_FLASH_LED
#define FLASH_LED_POLARITY 0 //1 if HI turns LED on. 0. If LO turns LED on.
#define FLASH_LED_DDR		DDRA
#define FLASH_LED_PORT		PORTA
#define FLASH_LED_PIN		0
#endif

If you have an LED, you can enable this module and configure it for where the LED is connected. Note that this increases the bootloader program size.

Flash location

The programmer must put the bootloader into the bootloader section. This is quite easy and just requires reassinging .text (default 0x0000) to the bootloader start address.

NB: The hex address for .text is in words, which exactly translates to half bytes. i.e. two bytes to a word. This means that for a bootloader location of 0x7000, the word address is 0x3800. This is the exact same address as the "start address" from the fuse setting dialog in AVRStudio.

Lastly, in Project-->Configuration Options-->Memory Settings, click Add, and configure

Memory type: flash
Name: .text
Address(Hex): 0x3800

This can be done with a the linker command -tText, too, although I forget the exact syntax. A quick search on the forum will find you plenty of examples.

Main program

Fortunately, the main program is to be written exactly as if there were no bootloader, with one tiny exception as detailed immediately below. What that means is that you can debug it without the bootloader and only add the bootloader when the final program is ready to go out the door.

In order for the bootloader to know what version a given firmware is, we need to include that data in the file. This bootloader looks for that information at the end of the binary hex file. To that end, we have to add it there ourselves. We do this with the following code, added outside main().

const bootldrinfo_t bootlodrinfo __attribute__ ((section (".bootldrinfo"))) = {DEVID, SWVERSIONMAJOR << 8 | SWVERSIONMINOR, 0x0000};

This defines a section in the flash with three elements. (The last one, 0x0000, is for the CRC word, which will come later in the tutorial.). We now have to place this element in the correct part of the flash. This section must be the last 8 bytes immediately before the bootloader. Since for me, the bootloader starts at 0x7000, this means 0x6FF8.

NB: The hex address for .bootldrinfo is in words, which exactly translates to half bytes. i.e. two bytes to a word. This means that for a bootloader location of 0x6FF8, the word address is 0x37FC.

Similar to above, in Project-->Configuration Options-->Memory Settings, click Add, and configure

Memory type: flash
Name: .bootldrinfo
Address(Hex): 0x37FC

You'll also need to actually define DEVID, SWVERSIONMAJOR, and SWVERSIONMINOR. You can do this immediately before the __attribute__ section, or as I do in a separate file. DEVID must be the same as in the bootloader, or else it will not work. The firmware version is split into major (SWVERSIONMAJOR) and minor (SWVERSIONMINOR) fields. Again, these are arbitrary and can be defined as you wish. Remember, though, that the bootloader only flashes the firmware if the new version number is greater than the existing one.

/*Firmware version*/
#define DEVID 0x00000001 		//Arbitrary ID
#define SWVERSIONMAJOR 0x01 	//Major version
#define SWVERSIONMINOR 0x00 	//Minor version

Lastly, you'll need to compile and add the CRC to the file. Unfortunately, compiling with AVRStudio is not completely straightforward, as AVRStudio unhelpfully does not give you the option of creating a binary hex file (why, oh, why Atmel???). So you'll have to do it in two steps. Compile it as you normally would from within AVRStudio (F7, for instance), and then find the project Makefile (most likely in the "default" directory in the project source). Here, toward the end you'll find a line similar to the folowing:

%.hex: $(TARGET)
	avr-objcopy -O ihex $(HEX_FLASH_FLAGS)  $< $@

Replace ihex with binary.

%.hex: $(TARGET)
	avr-objcopy -O binary $(HEX_FLASH_FLAGS)  $< $@

Then load a command window and run the command make clean and make all. This should create a hex file that is in binary instead of intel hex.

(If you don't use AVRStudio, and instead use makefiles, now's your chance to laugh.)

Still using the command window, finish up by running crcgen.exe on the hex file. This will automatically add the CRC to the very last two bytes of the hex file, an absolutely necessary step as otherwise the bootloader's internal CRC check will fail.

There you go, all done now! Just copy the hex file onto an SD card and you're ready to go.

Problems

There are several flaws in this firmware. First and foremost, there are no previsions for downgrading. This could be written in by reserving a special number that overrides flashing, no matter what. Still, this makes things difficult in the field. A better solution might be to press a button during the booting, but that could take extra hardware. If anyone has ideas or experience, please post them here.

Secondly, the firmware has no way of knowing if a file is valid firmware unless it's the exact right size. This is less than optimal, but saves coding space.

Lastly, if your MCU project has an SD card slot, there's a very good chance that the main application reads the slot, too. This means a duplication of a quite large (>1K?) chunk of code. That's really a shame, and can make a big difference amongst the smaller MCUs. I understand there are ways to reference the code in the bootloader from the main application, but those are beyond the scope of 1) this tutorial and 2) my abilities.

Included files
I have attached an AVRStudio project along with the necessary code for generating the CRC. If you don't use AVRStudio, but instead makefiles, you're advanced enough to build your own makefile for this project. Note that the project is configured for an ATMega324p, so make sure to change that for your processor.

Questions, comments, criticisms?

Good luck,
Kenn Sebesta

[Edited for clarity reasons.]

Attachment(s): 

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

Thank you very much for the tutorial.

I am using Bascom.

I have many questions as I like to learn but will try to limit them :-)

Not knowing enough C (yet!!) I am unable to fluently understand the program so my question is:

Can this be used to flash a compiled Bascom file from the SD to the MCU flash?

I can run crcgen.exe on the compiled file, but how do I add the version info etc to accomplish
In order for the bootloader to know what version a given firmware is

Is it easier to just strip out the version check functionality from the bootloader?

Thanks very much

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

ozra wrote:
Can this be used to flash a compiled Bascom file from the SD to the MCU flash?

I plead ignorance here, not really even understanding what a Bascom is. However, *most* of the program is processor independent. The logic for how to communicate with an SD card and a FAT partition are universal, although the individual commands you must write in order to use the SPI bus might vary. Looking at sdIO.c, only the sections labeled "platform dependent" would have to be changed.

Likewise for the bootloader instructions (although these are not marked with "platform dependent", it should be very easy to figure out which commands need to be changed).

ozra wrote:
I can run crcgen.exe on the compiled file, but how do I add the version info etc to accomplish
In order for the bootloader to know what version a given firmware is

You add the version info once in the defines.h file and once in the "real" application. When you add

const bootldrinfo_t bootlodrinfo __attribute__ ((section (".bootldrinfo"))) = {DEVID, SWVERSIONMAJOR << 8 | SWVERSIONMINOR, 0x0000};

to the "real" application, it will fail to compile if you haven't somewhere defined DEVID!

However, device ID is very different from checksum. The checksum ensures data integrity. In this implementation, it's two bytes added to the very end of the hex file (by the program crcgen.exe, not manually).

Quote:
Is it easier to just strip out the version check functionality from the bootloader?

Errr... Easier, yes, but if for some reason the code is corrupted or the flash fails, you won't know it until things crash.

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

Thanks the tutorial is short and nice , but I do not understand how to use it . I think it will be nice to have an example application with the ID and CKSM in order to show how it works.

Can you tell me if below statements are correct ?

1) The bootloader will always load an hex file with an exact size such that the last three bytes ends just before the bootloader code starts.

2) The bootloader will load if and only if the version is higher than the loaded info in the bootloader code

I would like, for instance, write a flashing LEDs program , then load it with the bootloader but no idea yet on how to do it.

The more similar thing I've found so far is here

http://yuki-lab.jp/hw/MMCboot/index.html

This is nice because allow you to browse on the files. of course the source code needs to be modified , we need to change the LED printing to UART messages for instance. But this bootloader has like two entry points like the start of the boot section plus another section. They overlayed when I tried to compile it. So I am still struggling to understand it. To complicate things the comments are in japanese, I've translated most of them.

If you have some links or know a book that may bring some info on this kind of bootloaders please let me know.

Thanks ,

Jose

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

Nice tutorial! The reason Atmel counts in words over bytes is that the AVR's program memory has a 16 bit data bus. It doesn't support high/low byte reading/writing so any operation has to go as 16 bits.

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

Quote:

It doesn't support high/low byte reading/writing so any operation has to go as 16 bits

Then you haven't heard of the LPM opcode then?

 

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

Great tutorial; Very well written and explained. Thank you for sharing the code with the rest of the community.

Quick question hoping someone could assist with:

If I were to use this bootloader with my Teensy++ 2.0, I take it that I could no longer use the Teensy's supplied USB programmer/program and I would need to flash my Teensy using this bootloader and will need to put the new firmware .HEX file on the SD card, correct? It seems so, just wanted to confirm.

Thanks,
Moe

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

The Teensy comes preloaded with a boot loader so, yes, it's going to be either/or and you are going to need an ISP to program one or the other.

 

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

I have installed Atmel Studio 6 (I assume AVR Studio was renamed?) but I don't seem to be able to load the .aws or .aps files, which I assume are the project files.

Can anyone explain how I should set this project up so I can compile it for a 1284P? I've never used Atmel Studio before, I usually just compile stuff from the Arduino IDE.

[edit]

Actually, I just found the "import" option on the file menu (Ugh. Why don't they just make things easy and show those files on the project loader and ask if you want to convert them there?) but I got some warnings when I imported the project. I'm not sure if they're important or not:

08:12:00: [WARNING] Skipped reading Build Dependency settings from source project. It is either invalid or not provided., ModuleName: ProjectImporter.Core
08:12:00: [WARNING] Skipped reading Pre/Post build event settings from source project. It is either invalid or not provided., ModuleName: ProjectImporter.Core
08:12:03: [WARNING] Include Path not found : include
08:12:03: [WARNING] Include Path not found : include-fixed
08:12:03: [WARNING] Include Path not found : C:\WinAVR-20080610\avr\include
08:12:03: [WARNING] Include Path not found : include
08:12:03: [WARNING] Include Path not found : include-fixed
08:12:03: [WARNING] Include Path not found : C:\WinAVR-20080610\avr\include
08:12:03: [WARNING] Include Path not found : include
08:12:03: [WARNING] Include Path not found : include-fixed
08:12:03: [WARNING] Include Path not found : C:\WinAVR-20080610\avr\include
08:12:03: [WARNING] Include Path not found : include
08:12:03: [WARNING] Include Path not found : include-fixed
08:12:03: [WARNING] Include Path not found : C:\WinAVR-20080610\avr\include
08:12:03: [WARNING] Include file not found : C:\WinAVR-20080610\avr\include\avr\iom324p.h
08:12:12: [WARNING] Include Path not found : include
08:12:12: [WARNING] Include Path not found : include-fixed
08:12:12: [WARNING] Include Path not found : C:\WinAVR-20080610\avr\include
08:12:12: [WARNING] Include Path not found : include
08:12:12: [WARNING] Include Path not found : include-fixed
08:12:12: [WARNING] Include Path not found : C:\WinAVR-20080610\avr\include
08:12:12: [WARNING] Include Path not found : include
08:12:12: [WARNING] Include Path not found : include-fixed
08:12:12: [WARNING] Include Path not found : C:\WinAVR-20080610\avr\include
08:12:12: [WARNING] Include Path not found : include
08:12:12: [WARNING] Include Path not found : include-fixed
08:12:12: [WARNING] Include Path not found : C:\WinAVR-20080610\avr\include
08:12:12: [WARNING] Include file not found : C:\WinAVR-20080610\avr\include\avr\iom324p.h

[edit]

That missing include at the end seems to be processor dependent and I know when I create a new project I can select the processor, but I can't seem to figure out how to change the processor this project is configured for or if I even need to.

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

Quote:

which I assume are the project files.

.aws and .aps were the name of project files in AS4 not AS6. In theory AS6 can "import" and AS4 project but there are many threads in the AS5/6 forum that seem to suggest this may not always work the best.

BTW not to hijack the thread but there is another SD/MMC bootloader option (and it's an AS6 project):

http://spaces.atmel.com/gf/project/sdbootloader/

 

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

I appreciate the suggestion, but I've already modified the other bootloader to suit my needs and it sounds like all the bugs haven't been worked out of that one yet. I also can't seem to find the link to actually download that one on the website.

I just need to know how to configure Atmel studio to compile the project for my chip.

Pages