Introduction to Embedded Systems

IntroductionLet’s be honest, for most, the phrase Embedded System does not elicit much excitement…which is unfortunate. This somewhat sterile term is used to describe a broad category of computer applications that are an integral part of our everyday lives. For me, embedded systems represent some of the most exciting computer engineering applications where the proverbial computing ‘rubber’ hits the real-world ‘road.’

There are many unofficial definitions of embedded systems but most boil down to some version of the following:

A computer system that is optimized to perform a finite set of specific tasks.

It is worth pointing out that this definition is focused on the functional purpose of a computer system. It does not include any reference to the system size, number of components, or end product. Also, this definition does not include small single-board computers like Raspberry Pi and PC104. Both are powerful general-purpose, stand-alone computers capable of performing many tasks but not optimized for a set of specific tasks.

So why are embedded systems so important? As I mentioned above, embedded systems are where the rubber hits the road. These are the computer systems that operate in the world beyond a personal workstation, laptop, or gaming console. Embedded Systems are also where the vast majority of the small CPUs and micro-controllers are used. According to this Wikipedia article, 98% of all micro-controllers are part of embedded systems.

Wikipedia contains an overview and history of embedded systems here. I always find formal definitions of limited value and prefer to teach using examples. Some examples of embedded systems we encounter every day are:

Digital Clocks
Digital Cameras
Computer Disk Drives

Computer Modems
Electronic Toys
Personal Music Players
DVD Players

The list is nearly endless.

Now that we’ve established what defines an embedded system lets dive into the components of an embedded system and some of the challenges faced when designing them.

My Raspberry Pi 4 + JTAG setup — Part 2 — The Hardware Setup

First, we’ll walk through the HW setup. More specifically, the portion of the diagram below that connects the two green boxes and the USB connection from the Host System to the FT232. This is a fairly simple setup. The HW consists of the Raspberry Pi 4, a FT232H breakout board, 6 jumper wires and a USB-C cable.

** WARNING **: There should be no power connected to the Raspberry Pi 4 and NO USB connected to the FT232 board while hooking up the Jumper wires. Hook these up first. This will be safer for you and the HW. There are no high voltages here, but doing this with the power off is always a good idea.

Probably the most complicated part of this is soldering the pin headers onto the FT232 board. However, I will not cover the soldering of the pin headers.

The headers can be oriented toward either side of the board. The picture below is my setup. I put the headers ‘under side’ of the board and mounted the board in a ‘dead roach’ configuration (on its back with the pins/legs up). Installing the FT232 board on a breadboard is another option you might consider. In this photo, the raspberry pi is mounted on an oak plank and the FT232 board is held in place with standoffs.

The HW setup is simply connecting the 6 jumper wires from the FT232 Breakout board to the Raspberry Pi 4. From the Table below we are hooking up the following

  • D0 to Pin 22 RPI4 J8
  • D1 to Pin 37 RPI4 J8
  • D2 to Pin 18 RPI4 J8
  • D3 to Pin 13 RPI4 J8
  • C0 to Pin 15 RPI4 J8
  • GND to Pin 39 RPI4 J8

Once again, you should do this before connecting power or a USB cable.

AdaFruit Board
PIN Name
Pin Name
JTAG FunctionRPI 4 GPIORPI 4 J8 Pin
D4ADBUS4GPIONot Connected
D5ADBUS5GPIONot Connected
D6ADBUS6GPIONot Connected
D7ADBUS7GPIONot Connected
C1ACBUS1/SRSTNo Connected
C2ACBUS2GPIONot Connected
C3ACBUS3GPIONot Connected
C4ACBUS4GPIONot Connected
C5ACBUS5GPIONot Connected
C6ACBUS6GPIONot Connected
C7ACBUS7GPIONot Connected
C8ACBUS8func definedNot Connected
C9ACBUS9func definedNot Connected
JTAG – Raspberry Pi Connection Details

Once these 6 wires are connected, you can connect USB-C cable between the FT232 and your PC. The AdaFruit FT232 board has two sockets on it, the image blow is of the USB-C connector.

Although the FT232 uses a USB-C connector, this is just a regular USB connection and you can use a USB-C to USB-C or USB-C to USB-A cable. Connecting the cable to your computer should be something you are already comfortable with.

Here is another image of my setup with the USB attached.

For information about connecting power to the Raspberry Pi 4, please consult the documentation that came with your Raspberry Pi 4.

My Raspberry Pi 4 + JTAG setup — Part 1 — introduction

In our Tools series, we will discuss software and hardware tools that facilitate embedded systems development. For each tool, we will discuss the primary purpose, typical installations, and demonstrate a common use.

JTAG (Joint Test Action Group) is a standard system for verifying and testing circuits after fabrication. Originally developed in 1985 as a validation tool, engineers quickly realized that its ability to control a CPU and communicate system state information could be used in conjunction with a debugger to create a powerful development and debug environment that could rival an ICE. In fact, many of these setups were and are still referred to as JTAG ICE setups. This series of articles will describe how to create a JTAG debug setup for bare metal and OS development and testing on a Raspberry Pi 4 board.

Specifically, we will cover:

    • Setting up “openocd”
    • Setting up gdb
    • Setting up VSCode to use gdb so you have a full IDE for debug of HW.

The hardware consists of a FT232H breakout board from Adafruit (under $20 at the time of writing this article), your Raspberry Pi 4, some jumper wires and a USB-C cable. This hardware is combined with several Open Source software tools to create complete development system.

The software stack for this article includes openOCD (Open On Chip Debugger), GDB (Gnu Debugger) and Visual Studio Code. In part 3 we will cover turning on the jtag in the Raspberry Pi 4 and a “Null” boot image for the Raspberry Pi. Then we will cover setting up the software in Parts 4, 5 and 6


Real Time Systems

Many embedded systems must react or respond to events in a well defined, limited amount of time. We call these real-time computing systems. While efficient code design is always important, most computer programs do not face real-time constraints. For most applications, the exact execution time does not define success or failure as long as the program generates the expected results. If a block of code executes in X milliseconds or Y milliseconds, as long as the code provides the correct output. For example, most users don’t care if a disc drive system responds to a read request in 0.5 milliseconds or 0.55 milliseconds, as long as the program returns the correct data. Similarly, if your embedded system is monitoring the temperature of a cooler and turning on and off a refrigeration system to maintain temperature, it probably doesn’t matter if the control system checks the temperature every 5 seconds or every 10 seconds.

In contrast, for real-time computing systems, timing is everything. Real-time computing systems have well-defined time constraints for the time that can pass before they respond to an event. If the program cannot meet the timing constraints, the system will not function properly. Looking back at our disc drive example, while the exact time required to retrieve data varies, the servo loop that controls the head location is a real-time system. The head position control algorithm is designed to run at a specific interval. During time interval the software must read the head position, calculate an error, and then calculate a control output to adjust the head position. If these calculations are not complete before the end of the interval, the control system will fail. Similarly, if the ignition control in a car engine does not fire at the correct instant in the compression cycle, the car’s engine could be severely damaged.

In each of these real-time examples, the computer system must respond to event. For the servo control example, the event is the expiration of a timer. For the engine control example, the event might be a sensor detecting piston position in its stroke. In each case, the real-time constraints start with a trigger. The maximum time allowed for a computer system response is the deadline.

Wikipedia describes three categories for real-time systems:

    • Hard – missing a deadline is a total system failure.
    • Firm – infrequent deadline misses are tolerable, but may degrade the system’s quality of service. The usefulness of a result is zero after its deadline.
    • Soft – the usefulness of a result degrades after its deadline, thereby degrading the system’s quality of service.

For the purposes of the projects discussed on this site, we will use the hard deadline real-time system definition.

Note that real-time programming does not simply mean maximizing program throughput.  The event trigger and response deadline mark the beginning and end of a time constraint that is defined by an external system and measured in clock time.

These concepts are closely related to the concept of Event Driven – Asynchronous Programming, and I encourage you to read that post.

101.1 Bare Metal Introduction – ‘Look Mom, No OS!’

Ask John or Jane Doe off the street for a definition of Bare Metal and you would likely hear about AC/DC crossed with the Full Monte. In embedded systems parlance, Bare Metal refers to systems that run on the hardware platform without the support of a commercial operating system. Bare Metal systems make up the vast majority of applications in the real world. Running programs native on the CPU or micro-controller creates some interesting challenges that developers don’t face when they have the support of an operating system.

    • How does the program start?
    • How can I tell if the program is working?
    • How do I trace bugs in my program?
    • What happens if there is a fundamental error in my program?
    • Who’s keeping track of time?

What happens before main()?

For some who are new to bare metal, this may sound like an existential philosophical question, but trust me, it’s not. In fact, this is a question that I often use as an interview question. Or maybe better said, what happens and what needs to happen before main(). While some details are platform-dependent, the process is similar for most systems. After power-up, the processor starts execution at some known address. In ARM’s case, it starts by securing the reset exception/reset vector and runs the code at that program address.

Many platforms have some kind of non-volatile memory where a small startup program called the boot loader is stored. This boot loader ‘catches’ the processor right after power-up and loads a second-level, more sophisticated boot loader or the main embedded firmware. Other systems simply look to a specific address for the first instructions.

As an embedded system programmer, this is where your job begins. Whether you are taking over at the second level boot loader or just at the start of your program, you are now in complete control and everything that happens next (or doesn’t) is up to you.

In future installments we will discuss some basic foundational components that embedded programs should provide and how to go about setting them up.

Embedded System Components

Let’s dive a little deeper into the components that make up an embedded system. The Introduction To Embedded Systems post gave a broad definition of an embedded system, along with some real-world examples. All embedded systems have hardware components and firmware. Rarely, for larger embedded systems, there may also be a software component. While technically firmware is software, for this post and the posts on this site, we will distinguish between firmware and software.


The CPU or micro-controller in any embedded system provides the foundation for the entire design. The selection must satisfy all the design requirements listed in the Introduction. There are literally thousands of options for CPUs. Designers typically pay close attention to new offerings from chip manufacturers. 

The application may require additional peripherals beyond the CPU. The designer will select other devices that fit within the overall design plan and support communications compatible with the CPU. Instruments exist to measure or sense almost anything. It is up to the designer to identify the right device and create the circuit for their application.


Firmware is a hardware-specific program that is closely coupled to the hardware and essential for proper operation of the system.  It is stored on the hardware portion of the system and is loaded when the system is powered on.  The firmware programs must be compiled (translated) into chip level instructions (machine code) that is specific to the hardware platform.  Because we are talking about compact systems optimized for a specific application, the firmware typically only contains the logic required to complete the required task.  Optimizing the firmware to the minimum required functionality can pay dividends by minimizing the memory used.


As I mentioned above, the firmware is software designed specifically to manipulate the hardware of the embedded system.   For most embedded systems, the firmware is the only software.  A few larger embedded systems use general-purpose programs to complete their task. Obviously, any program that runs on a CPU or micro-controller must run in chip-specific instructions at its base level. On this site, we will differentiate Software as a program written without platform-specific hardware knowledge.

Putting It All Together
Now that we understand each of the components of an embedded system let’s look at how they fit together.  The diagram below shows a pictorial representation of all these pieces and how they fit together.  The hardware provides the foundation, the firmware acts as an interface to the hardware, and software sits on top of the firmware.