The Embedded / Firmware Engineering (Internship) Interview

Urban Pistek
12 min readJan 14, 2023

--

The Blackpill, a STM32 based board used in prototyping applications

While undertaking my Mechatronics Engineering degree at the University of Waterloo I have always had some interest in the embedded / firmware world. However, when preparing for interviews I could not find any overall summary convering what to expect for these roles specifically. With this in mind, my aim is to provide a overview of what to expect when preparing for a firmware / embedded engineering role. Also note that this guide touches on many relms of knowledge where the rabbit hole goes quite deep; there are many places to explore from here, it can be overwhelming and often requires a the equivalent of a full engineering degree to understand just the surface level of it. Do not be discourged, the obstacle is the way.

Note: This is based on my experience preparing for embedded / firmware engineering / developer roles at the internship level. I am merely a 4th year mechatronics student and the knowledge presented is by nature limited; so please do take everything you read with a grain of salt. I will try my best to present everything as accurately as possible. My intent with this article is for it to also be a learning experience for myself as much as it is to share some small sliver of knowledge I have gained.

A quick note on embedded / firmware…

There is not a standard definition of what the difference is between firmware and embedded roles, but here is how the two differ in my experience:

Firmware: This is a specific class of software that focuses on the low level control and operations for specific hardware. Firmware is largely software focusing on controlling I/O, handling the operations for serial communication such as SPI (Serial Peripheral Interface) and defining the overall system operations; often as a state machine, RTOS (Real-time operating system) or even a lightweight operating system.

Embedded: Largely overlapping with firmware the main distinction is that embedded will often focus more on the electrical / hardware system, requiring that you have a better understanding of it. What I mean by this is that an embedded role will require you understand electrical circuits and schematics, and even require your input on the design and testing. Firmware does benefit and to some degree require this knowledge. However from my experience embedded is more of a hybrid between hardware and firmware engineering. Further, embedded often deals with devices that are more, well, embedded — such as mircocontrollers and low powered processors.

There is alot of overlap between these two, however in short firmware is the low level software that controls and deals with the hardware and embedded is the middle ground between hardware and firmware with a mix of engineering tasks on the software and hardware sides.

The Main Components

Below is a list of the main groups of concepts and skills I found where asked about (this list is designed to cover both firmware/embedded):

  1. Algorithms and Data Structures
  2. Digital Logic
  3. C / C++ Programming
  4. RTOS
  5. Hardware
  6. Tools, Debugging, Linux & Other

At the end of this article I’ll cover some additional topics that may be helpful, however these topics from my experience are the core of what you’ll be asked about.

Algorithms and Data Structures

Data Structures

This section is similar to what many say you need to know for any software engineering interview. In general, you should be familar with all the core data structures such as linked lists, stacks, queues, binary trees, arrays and hash tables. See this article for a great source on all these important data structures.

However, out of these I found the most asked about to be arrays, stacks and queues (I found that ring/circular buffers came up a couple times). In particular, I found a large amount of techincal questions around array manipulation. Below are some examples.

Example Questions:

  1. Remove Duplicates from Sorted Array
  • Given an integer array sorted in non-decreasing order, remove the duplicates in-place such that each unique element appears only once. The relative order of the elements should be kept the same.
  • Do not allocate extra space for another array.

Two Pointers Solution
- Time: O(n) => Only at most n interations needed
- Space: O(1) => Space remains constant — only using 2 pointers regradless of input size

// Returns the size of the filtered array
int removeDuplicates(int* nums, int numsSize){

// Input checks
if (numsSize == 0){
return 0;
}

if (numsSize == 1){
return 1;
}

// For numsSize > 1
int i = 0;
for(int j = 1; j < numsSize; j++){

// If the numbers are equal, skip over
// If not equal, store
if(nums[i] != nums[j]){
i++;
nums[i] = nums[j];
}
}

return i + 1;
}

2. Determine single number in array of numbers

  • Given an array of numbers determine which value only occurs once
// Return value that occurs once
int singleNumber(const int* A, int n1) {
int res=0;
int i;
for(i = 0; i < n1; ++i) {
res = res ^ A[i]; // ^ = XOR
}

return res;
}

In general, many questions I have been asked involved manupulating, searching or performing some operation with an array. That being said, it is important to review the other data structures and know their applications as well. For practice I would recommned leetcode, but I also found interviewbit to have a great variety of questions. Lastly, practice and learn these questions in C/C++, I’ll get to this topic specifically later.

Algorithms

Typically, I was presented a particular problem and asked to desgin an algorithm for it, less so about specific algorithms. However, having a general knowledge about the main search and sorting algorithms is helpful to have a good understanding of how to approach a problem. Again, I’ll refer to another article to review some of the most common algorithms to have an understanding of.

In particular, I found that Binary Search and Hash Tables were the most asked about. Also, I would recommend being somewhat comfortable with recursion as a lot of tree based search techniques and problems can leverage a recursive implemenation and I found knowing one approach will help you figure out others.

Example Questions:

  1. Tree Traversal

There are 3 ways a binary search tree can be traversed, each can be implemented using recursion:

  • Pre-order: Node/Left/Right
  • In-Order: Left/Node/Right
  • Post-Order: Left/Right/Node

Below is a In-Order traversal, the order can be changed to alter the traversal method.

void traverse_bst(BSTnode* node){
if (node == NULL)
return;
else {
traverse_bst(node->left);
printf("%d", node->val); // Access node
traverse_bst(node->right);
}
}

I would recommend the Tech Interview Handbook for a deeper dive into all these topics.

Digital Logic

Another great deal of questions and concepts revolved around digital logic, bitwise operations and manipulation.

Binary

Firstly, get intamately familar with bitwise operations, here is a good quick guide. Here is a short summary of the main operators to use in C/C++:

~a Bitwise complement of a (1’s complement).
!a Logical complement of a (NOT a).
a++ Return a, then increment by 1.
++a Increment by 1, then return.
<< Bitwise left shift.
>> Bitwise right shift.
& Bitwise AND.
| Bitwise OR.
^ Bitwise XOR.

And here is a quick template on some of the common types of operations:

// To manipulate bit n in val:
// Set:
val |= (1 << n)

// Clear:
val &= ~(1 << n)

// Flip:
val ^= (1 << n)

// Check:
if(val & (1 << n))

2’s Complement
Representation of a negative number in binary.

Conversion:
1. One’s Compliment: Invert all 1’s and 0's
2. Two’s Compliment: Add 1 to the one’s compliment

int val = 5; // 0101
int val_ones_compliment = ~val; // 1010
int val_twos_compliment = val_ones_compliment + 1; // 1011
printf("%d\n", val_twos_compliment); // prints -5

Endain

Endianness is the order in which bytes are stored in memory, given a value in hex as: 0x12345678

Little Endian: Least significant byte is stored first.

Little Endian

Big Endian: Most significant byte is stored first.

Big Endian

Determine Endianness
Since char is 1 byte whereas int is 2 or 4 bytes if the system is little endian then the LSB is stored first, which in this case is 1. Otherwise the MSB is stored first, which is 0.

bool is_little_endian(void){
unsigned int val = 1;

char *c = (char*)&val;
if (*c) {
return true;
} else {
return false;
}
}

C / C++ Programming

I am not going to cover the general content such as writing if statements and functions but rather focus on the unqiues aspects of C / C++ that I found were most questioned on. Firstly, form my experience usually most of the questions were for C, C++ was not asked about that much. I would also mention that you are more likely to be asked about C++ for a firmware role than an embedded one. In general, I would focus more on C as C++ is largely a superset of C so most of the knowledge is applicable across both.

C Specifics

Firstly, one benefit of C is that it is not a large langauge, therefore learning all the main components is much easier than some modern langauges. These are some of most important aspects to focus on from what I found:

  1. Pass by Reference / Pass by Value

Simple concept, are you copying the value or sending the address of where the value is stored. However application of this is important.


// Pass by Value
void foo(int val){
val++;
printf("%d\n", val);
}

// Usage
int a = 1;
foo(a); // a = 1, prints 2

// Pass by Reference
void foo(int* val){
*val++;
printf("%d\n", val);
}

// Usage
int a = 1;
foo(a); // a = 2, prints 2

2. Structs & Data types

Structs are used a lot to created simiple custom data structures, how to do this is simple. However, a question I have been often asked is given this struct, how many bytes is it? Understanding how much memory a certain struct or data type occupies is a common question and sometimes is helpful for solving others.

3. Pointers

Pointers are everywhere in C, be very comfortable with them. Here are some basic pointer concepts:

Pointer Arithmetic: Manipulate pointer address directly

int array[3] = {1, 2, 3};
int *ptr = &array[1];
ptr++; // Increment pointer's address by 1

printf("%d\n", *ptr); // Prints array[2]

Dangling Pointer: Pointer assigned to a memory location that is no longer valid

void foo(int *ptr){
int val = 10;
ptr = &val;
}

Void Pointer: Pointer which at compile time has an unknown type which is cast at runtime

void *ptr;

int val = 1;
ptr = &val;
printf("%d\n", *(int*)ptr); // Prints 1

4. Bit Manupilation

As touched on previously, knowning operations and how to perform various bit manipulation actions is critical.

5. Dynamic Memory

Know how to use Malloc, Realloc and Free

// Malloc Usage 
int *ptr = (int*)malloc(sizeof(int));

// Realloc Usage
int *first = (int*)malloc(sizeof(int));
int *second = realloc(first, 2*sizeof(int));

// Free Usage
free(ptr);

Example: Allocating Dynamic Memory for a 2D Array

// Allocate top level pointer 
int **top = NULL;
top = (int**)malloc(n*sizeof(int*));

// Allocate memory for each row
for (int i = 0; i < n; i++) {
top[i] = (int*)malloc(m*sizeof(int));
}

Memory Leak: Dynamic memory that has not been freed after being used

void foo(){
int *ptr = malloc(sizeof(int));
return; // Memory has not been freed/dellocated before return
}

Lastly, I’ll mention its good to have a basic understanding of the C build process:

C Build Process

1. Compile source files into object files
2. Link object files into a single object file (relocatable file)
3. Physical memory is mapped to the relocatable file
4. The locator creates the executable file

Sequnce of steps: .c (preprocessor)=> .i (compiler)=> .s (assembler)=> .o (linker)=> Relocatable File (locator)=> .exe

C++ Specifics

As mentioned before, I found C++ isn’t questioned about as much. The most common questions relove around classes; create a class to do this, for example. Understand the basic class structure and additionally some of object-oriented programming concepts such as Encapsulation, Data Hiding, Data Abstraction, Inheritance, Polymorphism.

RTOS

Many times you’ll need to develop code using a Real-time Operating System (RTOS). The name can be misleading, by definition the main feature of an RTOS is: It can garuntee that something will happen within a specific amount of time; it is deterministic by design.

The main components of a RTOS are:

  • Async/Sync I/O
  • Memory Locking
  • Semaphores
  • Shared Memory (Mapping Physical to Virtual Space)
  • Scheduling
  • Timers
  • Interprocess Communication (Information shared between threads)
  • Real-time files & threads

From experience, the main components I have been asked about are Mutexes / Semaphores, Message Queues.

Lastly, I wound learn the 10 Rules for Designing Safety Critical Systems. These provide good insight into good practices in general for developing low level code and are applicable outside of safety critical applications.

Hardware

Even though both the firmware and embedded roles focus mostly on software, a good understand of certain aspects of hardware are important. You may be able to find roles where you do not touch the hardware at all, but from my experience that is rare and it is very preferable to have good hardware understanding. Some key concepts to know:

  • Cache
  • Registers
  • RAM, Flash, EEPROM Memory
  • General-purpose input/output (GPIO)
  • Analog-to-Digital and Digital-to-Analog converters (in addition to having an understanding of how the conversion works)
  • Commuication protocols such as UART, I2C, SPI, CAN (understand the difference between synchronous and asynchronous)
  • Bluetooth (not as critical, but very common, having a better understanding of the bluetooth stack is helpful in my opinion)

Optional: Some more hardware specific concepts to know (these would be good for a more hardware heavy role)

  • Buck, Boost Converters
  • High, Low, Band Pass Filters
  • Op-Amps (Inverting, Integrator, Difference, Instrumentation)
  • Motor Controllers (Brushed & Brushless)

Tools, Debugging, Linux & Other

Lastly, I want to cover some additional topics that may come up and be useful to know.

Debugging

Have some understanding of how to run a debugger, GDB is a common one for C and embedded applications.

Linux

Knowing Linux is very helpful, just in general I have the best time developing in a Linux enviroment. You may often need to SSH into a server that is running linux and knowing your way around is very helpful.

Additionally, Embedded Linux is a commonly used in various applications.

Tools

Learn to use a oscilloscope, it will be handy.

Knowledge of python is useful for many small tasks. For instance, analyzing data collected from a device, creating a serial reader to read in data from your device over a serial connection. Create a BLE client to interface with your microcontroller. These are all quick examples of using python to create a auxillary script to suppliment your work.

Knowing message queue/message services such as Kafka / MQTT.

Rust

In short, I recommned you start learning Rust. It is not super common right now butthe trend appears to be that many new modern applications will be build using Rust. I don’t see C / C++ going away anytime soon. Personally I think C will be pretty sticky in the embedded space. However, Rust will in many ways be a large part of the future, in my opinion.

C/C++ Frameworks / Platforms

Below are some C based frameworks / platforms / SDK (I am not too sure what to call them) that I know are used in industry for various applications (these are largely used for developing software for embedded devices):

A few exmple questions

  1. Swap two numbers without using a temporary variable in the fastest way possible:
void swap(int *a, int *b){
*a ^= *b; // Combine a and b bitwise
*b ^= *a; // Remove original b component, leaving only a
*a ^= *b; // Remove original a component, leaving only b
}

2. Given an integer, determine the number of trailing zeros in its binary representation:

int solve(int A) {

int n = 0;
while(!(A & (1 << n))){
n++;
}

return n;
}

3. Given an unsigned integer, return the sum of its digits:

int digitSum(unsigned int num){
if((num % 10) == 0)
return 0;

return (num % 10) + digitSum(num/10);
}

4. Implement Matrix Multiplication in C

5. Explain the use of Volatile in C

6. Describe a deadlock and how to debug it

7. Explain why n++ is faster than n + 1

In summary, yes, there is a lot to know, and I tried to keep it concise. Firmware / Embedded sits on the intersection of software and electrical / computer engineering; so it requires knowledge from both pools often. This guide is not comprehensive by any means, more a summary to highlight what are some of the key topics to review. I hope it helps.

(If you found a error in the article, or have an suggestion, please do not hesitate to point it out and I will do my best to address it).

References

[1] https://en.wikipedia.org/wiki/Firmware

[2] https://en.wikipedia.org/wiki/Real-time_operating_system

[3] https://uk.rs-online.com/web/generalDisplay.html?id=ideas-and-advice/microcontrollers-guide

--

--

Urban Pistek
0 Followers

Engineering | ML & AI | R&D | Embedded | Software