During the early days in my career as an embedded software engineer, I was hearing the term ‘bootloader’ very often. Even though I had a basic understanding of the term, I felt that my knowledge about it is incomplete. For example, when they say the system is ‘booting up’ I knew it meant the system is getting ready for the user to start using its applications, but how exactly is it ‘getting ready’? As days passed by, I read some books, used some development boards, read some forums and with all that my knowledge of bootloader improved to a stage where it did not feel incomplete anymore. I have put together this article keeping in mind professionals and hobbyists, much like my younger self, so that you don’t have to go through the same convoluted path of learning that I had to go through! So let’s begin!
What is an embedded bootloader? An embedded bootloader is a piece of software that takes a system from a power-up state to a usable state. To be more specific, it is the code that runs from the moment you press the power button, till the point in time where it reaches the main function in your code.
This is the simplest explanation of an embedded bootloader. But what does it that the bootloader code actually do during this short time and how does it make the system ready? Let’s explore this process in more detail in this article.
Need for a bootloader
Why can’t we press the power button and go straight into the login screen?
In this section, let’s see what is the actual need for a bootloader.
The entire software that runs on any system can be divided into 2 big pieces
- bootloader (power on to just before the ‘main’ function)
- firmware (from main, the user application)
Let’s take a familiar example of a windows pc. You push the power button and usually wait for the login screen. The code that runs from the point you push the power button, till the point the login screen comes up is called the bootloader.
On Linux systems, the hundreds of lines of texts that show up before you are asked to enter your login credentials are the output of the bootloader software running behind the scenes.
On a typical microcontroller, a microprocessor’s neighbors include SRAM, DRAM, flash, EEPROM, timers, GPIO controllers and serial controllers for serial communication protocols like USB, UART, Ethernet, SPI and I2C. These neighbors are normally referred to as on-chip peripherals. (You can read more about peripherals in this article. No need to jump there as I have made every article to be self-contained for the topic presented and I will put the link again at the bottom of the page so you can go read it then!)
Embedded systems are designed to forget everything on power-down (except for the data that is already stored in their non-volatile memory like flash or EEPROM.)
At the moment the power is applied, the microprocessor inside an SoC does not remember any of its neighbors. The only thing that an embedded microprocessor remembers is which instruction it should execute as soon as it gets turned ON. This code is usually a single instruction, usually called the reset code. This code usually does nothing but transfers the execution to another piece of code called the hardware initialization code. The hardware initialization code is where the magic happens and it is explained in the following section.
Stage 1: Hardware initialization
A familiar example from the PC world here is the software that runs in the short period of time between you pushing the power button on your computer and the screen actually lighting up with the logo of your motherboard vendor.
I used to think that pressing the power button just gives electricity to the computer and as a result, it turns screen ON just like a light bulb in our homes! I never had a clue that some code is being executed even to do this simple process and that there is a reason for its existence.
The name of this piece of code is hardware initialization code and it does all the hard work of making the system ready for the next stage of booting. It wakes the processor up, which in turn wakes up some of the other peripherals on the computer’s motherboard and one of them is the graphics card which is connected to our favorite output device ‘the monitor’.
An important point to note here is that only just the peripherals that are needed for the next stage of the booting process are initialized. These are also known as critical peripherals. In the case of a PC usually this list of critical peripherals usually just include the display and the keyboard though these days trend among the motherboard manufacturers is to add the mouse peripheral too to this list of critical peripherals. You will understand why keyboard, mouse, and display are necessary in a bit.
As I said earlier a microprocessor does not remember any of its peripherals at the moment the power is applied. So basically, you can think of the hardware initialization process like this, it is the code that introduces the microprocessor to its neighbors (peripherals) and then it makes the processor initialize/wake up the peripherals so that they are all ready to work.
Another way of thinking about this is that Hardware initialization code takes in individual modules as input and gives out a working system as output.
Stage 2: Bootloader mode or application mode? Decision time!
The next decision is to choose a mode. Once the individual components in the motherboard start working together and give us a “computer system”, the second stage of the bootloader starts. In this 2nd stage of the booting process, a decision needs to be made by the user of the system. Either go into the
- “default mode” aka “application mode” or
- “special mode” aka “bootloader mode”
On a PC, the default mode is the mode that takes you to the login screen and the bootloader mode takes you into BIOS settings. You can decide which mode to choose by either pressing one of the buttons as given at the bottom-right of the screen on your motherboard manufacturers logo screen.
This decision of which mode to enter is usually made using the state of an input to the system. Let’s go back to our computer booting example. If you are thinking about the “press ESC button to go into BIOS” message you guessed it right. Yes, that’s the decision making phase!
This decision is usually left to the user for a short duration, during which the system waits to see if the user wants it to go into the “bootloader mode”. If it does not see any of these inputs, it takes the user to “application mode”. Keys F2, F6, F11 and delete in the above pic are examples of the boot-keys. (These keys can vary depending upon the motherboard manufacturer.) Using these inputs that the system makes its decision on whether to enter application mode or bootloader mode.
On embedded systems, the simplest method to get this input is via GPIOs. If a particular GPIO pin reads zero (depending upon the implementation, it can be a zero or a one), then the system can go into “bootloader mode”, else, the system can go into “application mode”. But since there can be noise on the GPIO pins, the widely adopted method is to read a value at a particular location in the non-volatile memory (say at address 0). And based on the value at this location, the system can choose to enter a particular mode.
Other peripherals can also be used here depending upon the application and capabilities of the product. For example, manufacturers of microcontrollers usually provide bootloader drivers that can be customized to make the system listen to Serial ports, USB and Ethernet for entering the bootloader mode as the state of GPIOs are susceptible to noise voltages.
Uses of bootloader mode in embedded systems
On typical PCs, you can use the bootloader to set up things like the which disk to use for booting the OS, the RAM speed, CPU speed and many more like in the picture below.
On embedded systems, the options available are fairly simple.
The user can perform tasks like
- setting up some default options for the application software to use
- to reflash the application software during the product development phase. This happens every time we load software into the microcontroller.
- Updating the software, usually to add features or clear bugs after the product release phase.
At this point the job of a bootloader is complete and the application mode code can start executing.
Stage 3: The Startup Code
Once you exit the hardware initialization phase, the system is still not ready to be to execute the application program. On embedded systems, the microprocessor executes the startup code to make the system ready. The main duty of this piece of software is to prepare the execution environment for the application written in higher-level languages. (Up until this point the code is usually written in assembly language, and all the manufacturers of microcontrollers usually make this code themselves and us application programmers don’t have to deal with it!)
Languages like C and C++, which is used the most in embedded system development, need a run time environment with stacks and heaps to execute the program.
A generic startup code has the following functions.
- It allocates space for and copies all the global variables in the code into the RAM
- Stack and Stack pointer is initialized
- Heap is initialized
- Call the main function in your program
Once this process is done, the system is ready for executing code written in higher-level languages like C.
Stage 4: OS Starting Process (Optional)
Once the runtime environment is ready, the next step is to start the operating system. This step is optional in embedded systems as most of them are designed without an Operating System.
While we are turning on our PCs, there is a brief time gap between the boot screen and the user login screen. Usually, this gap is filled with a logo in windows and Mac, and some lines of text in Linux systems. This is when our PCs execute the code for our starting the operating systems!
Here all the other drivers are loaded, like our network interface cards and other important peripherals needed for the end-user to make full use of our system.
In the case of an embedded system, operating systems like RTOS or Embedded Linux or Windows IoT usually do something similar to PCs, the only difference being that the peripherals are different. Once it is done with this phase it calls the main task to execute the code written by us the developers.
On embedded systems without an Operating System, this further initialization takes place inside the main function, to start up all the necessary peripherals to get a fully functioning system.
If it’s possible and not so complicated, what is stopping us reprogramming the embedded devices in our home appliances?
The bootloader is usually placed at the beginning of the flash or ROM. This particular area of ROM/flash is usually protected with special mechanisms against hacking so that unauthorized users cannot upload their own software into a given device. These protective mechanisms are usually turned on just before the product is sold and this makes the bootloader is locked to the end-user. For example, if you wish to hack into your friend’s washing machine and reprogram it to give out wet clothes out as a prank, you cannot do that if the bootloader is locked! (yes this example is stupid, but it gets the point across and I couldn’t think of a better one!)
This is a simple explanation of what a bootloader does and I guess this is enough for even most professional embedded developers who write application code as this piece of code is usually developed by the manufacturers of the particular microcontroller you are using and given to you as a free download along with their recommended IDEs. Just remember that there is this piece of code that sits between the power ON button and the main function and its called the bootloader!
How to get into bootloader mode? It depends on the specific device you are using, so you need to go look at the datasheets and user guides of a specific device
Can I reprogram the bootloader software of my device? Yes, many manufacturers do give access to developers to the bootloader code, so that you can have some control over it. But unless you have a proper reason, I would suggest to just use the manufacturer given code.
If you liked the post, feel free to share this post with your friends and colleagues!
If you are interested to learn more about bootloader programming, please try the resources pointed out in the references section.
- Programming Embedded Systems in C and C++ by Micheal Barr
- Bootloader Design for Microcontrollers in Embedded Systems by Jacob Beningo