Learn about developing embedded firmware using a simple 8-bit AVR microcontroller.
An embedded system is a self-contained intelligent system designed to run a set of tasks from the moment it is turned on.
This is in contrast to the way an application is run on a desktop or similar device, because with an embedded system, the user does not need to specifically load anything.
An example of a built-in system can be a household washing machine. Once the correct wash cycle is selected and started, the programmed sequence of events takes place.
The intelligent part determines the water level, performs wash, rinse and spin cycles and other related tasks based on the user’s selection.
This shows several aspects of a typical embedded system.
The washing machine must receive and respond to user choices, sense the water level and determine the appropriate run time for each operating mode.
The washing machine also needs to control the water shut-off valve and the motor.
Most embedded systems contain a microcontroller at the center of all these operations. This microcontroller is a single silicon chip that can be programmed to perform all the operations your application requires.
This is significantly different from more advanced microprocessors that are designed to run complex applications. The programming in our washing machine example would be written by someone in an embedded programming language and downloaded to the microcontroller during manufacturing.
Let’s take a closer look at these types of embedded systems. It also includes a simple embedded program in C, one of the most popular languages for writing embedded applications.
C and to a lesser extent C++ are widely used in embedded firmware programming.
This is because C is probably still the language closest to the hardware, except for the symbolic instruction language.
Although assembly language is closer, it is very specific to the actual underlying hardware and differs from microcontroller to architecture. C, on the other hand, is much more standardized while still offering enough control over the underlying hardware.
In order to follow the example code, I assume that the reader already has some knowledge of programming languages other than C.
So in the code example, I won’t spend time on basic concepts like variables, loops, conditionals or functions, at least not the concepts behind them.
What is a microcontroller?
At the heart of a microcontroller is a central processing unit, or CPU, which is not unlike those found in desktop computers or laptops, except that they are generally less powerful.
This CPU executes a set of instructions or a program written by the original human programmer. Closest to the CPU are some registers. These are temporary storage units that have very fast access times corresponding to the time of the CPU itself.
These registers have many functions that the CPU needs to function properly.
There is a Program Counter register, sometimes called the Instruction Pointer, which contains the address of the next instruction to be executed by the CPU.
There is a Stack Pointer register that accesses a special area of memory called the Stack, more on that a little later.
There is a flag register that stores the status of the results of some CPU operations, such as positive or negative results of an arithmetic operation.
Then there are the general purpose registers, which are used to hold the operations the CPU is working on, as well as the results of such operations.
In addition to CPU registers, the CPU is also connected to various peripherals such as IO ports, interrupt controllers, timers, USARTS, SPI, I2C, and in more advanced microcontrollers, video input or output peripherals and memory management units.
More about some of these peripherals will be introduced later.
In addition, the CPU has access to Flash, RAM and EEPROM memory. All of this is integrated into a single chip or integrated circuit.
This single chip with all these integrated peripherals and memory is a microcontroller.
In contrast, a microprocessor is basically just a very powerful CPU with its own registers and maybe some advanced peripherals on a chip.
All other peripherals are separate chips that are external to the microprocessor. Of course, they are more powerful and have more features compared to those in a single microcontroller chip.
For example, a desktop computer may have 16MB or more of memory, while a microcontroller may have only 2KB, a factor of 8000x more.
As mentioned earlier, a built-in application is just a set of tasks that are executed when they are invoked. Take our example of a regular washing machine.
The washing machine accepts user selection for a given wash cycle, which controls things like the water inlet valve, the time interval and temperature for the wash and rinse cycles, the water pump that drains the used water, etc.
In addition, the user is shown the status or remaining time of each operation. All these tasks are controlled by a microcontroller built into the washing machine.
Continuing with the example, the driver repeats three steps:
Step 1: Receives inputs such as start button, timer countdown, water fill level, etc.
Step 2: Processes inputs, decides what actions to take and when to take them.
Step 3: Acts on the actions decided in step 2 and controls some outputs such as water pump, water shut-off and fill valve, digital display, etc.
Of course, the actual actions taken will vary for different applications. In this example, step 2 is executed by the CPU running a pre-programmed set of instructions.
While steps 1 and 3 are performed by the peripherals controlled by the CPU, some of which we will cover in the next section below.
Microcontroller memory and peripherals
Before we get into the proper peripherals, it pays to first understand the memory system of microcontrollers. All microcontrollers have at least two types of memory: flash and SRAM.
Flash memory is where a user-written program is stored. Like a traditional hard drive in a desktop computer, flash memory is non-volatile and is used to store the program that the CPU will execute.
However, writing to flash memory is quite slow, and that is the reason for SRAM memory. It can be accessed – written to or read from – much faster.
However, it is volatile and will therefore lose its contents if the microcontroller is powered off.
SRAM is usually divided into three areas: a general area used to store variables, a heap, and a stack.
The heap is an area of memory that a running program can access piecemeal upon request, and then return when no longer needed, allowing another part of the running program to claim it again.
A stack is a special part of SRAM used to nest function calls, the building blocks of all programs, as well as passing arguments to said functions.
Some microcontrollers also have EEPROM, which is also non-volatile memory, separate from flash memory, which is usually used to store user settings or calibration values. Some microcontrollers can actually use the flash section to do this.
Moving on to the actual peripherals, one thing to note is that they can work in many modes and configurations.
To select the different modes, the first thing you need to do is read the data sheet.
All peripherals have configuration registers, several of them. These are different from CPU registers and each peripheral has its own set that can be programmed to make the peripheral behave in a certain way.
Since the peripheral registers are not directly accessible to the user, the way to program them is to actually have the CPU run some setup code that in turn writes the correct values to the selected peripheral registers.
Here is a brief description of some common peripherals:
GPIO – These general purpose input/output peripherals can be programmed for logic level inputs or outputs.
Timers – These can be programmed to provide precise timing and can output timed pulses or continuous trains of pulses or can measure the time intervals between two pulse edges.
USARTS – Used for two-way serial communication between two devices where data is transmitted or received bit by bit.
I2C – This is an interface used by many modules such as sensors and displays. There can be many such devices on the same communication bus; each can be addressed individually.
SPI – This is another interface with similar functionality to I2C but is much faster. Choosing I2C or SPI is often dictated by what the particular module uses.
In a real microcontroller, these pins are shared between different peripherals because most microcontrollers have a limited number of externally accessible pins.
For example, a pin can be programmed as a GPIO or USART pin, but not both at the same time.
Application firmware development for microcontrollers
Application software development is usually done on a cross development platform like Windows PC, Linux box or Mac.
The general process is to write the code in an integrated development environment or IDE in an embedded language such as C, compile and link the code modules with libraries if used, and download the binary file to the microcontroller for testing and debugging.
This is usually an iterative process.
To extend the process just described, an IDE simply provides a convenient all-in-one platform where the process of actually entering source code, compiling, linking, and loading can be done in one place.
Compiling and linking requires a compiler/linker that can generate binary code suitable for the target microcontroller.
Loading can be done in several ways. One is to have an external device programmer into which the target microcontroller is embedded to load the compiled binary.
The programmed microcontroller is then inserted into the intended HW module for testing.
Another way is to build a programming interface in the HW board and program the microcontroller when it is already connected to its hardware.
This method is usually called In-System Programming or ISP. This is usually referred to as in-system programming.
Yet another way, for some microcontrollers, is to download the binary file to the microcontroller via one of its peripherals, usually the USART.
For this to work, a preloaded program called a bootloader must be running on the microcontroller, which accepts the new program and updates itself.