Circuits and Code
Circuits and Code
Contents
1 Introduction 8
1.1 Target Audience . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.2 How to Use This Guide . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.3 Extra Practice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.4 Breadth of Topics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.4.1 Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.4.2 Latex . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.5 Acronyms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.6 About the Authors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.6.1 Other Work . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.7 Acknowledgements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.8 Disclaimer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
4 What is an interrupt service routine (ISR), and how do they differ from regular functions in
implementation? 21
4.1 Interrupts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
4.1.1 Theory of Operation Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
4.2 Interrupt Service Routines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
Page 3 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
7 What type of signal would be best for transferring data from a sensor located 1 meter away to
a microcontroller? 33
7.1 Digital Signalling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
7.2 Analog Signalling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
7.3 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
9 Compare and contrast I2C (Inter-Integrated Circuit) and SPI (Serial Peripheral Interface). 41
9.1 I2C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
9.1.1 Physical Layer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
9.1.2 Data Format . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
9.2 SPI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
9.3 Comparison . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
9.4 Follow-ups . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
Page 4 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
15 How would you sense how much current is flowing through a PCB to a load? 67
15.1 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
15.2 Resistive Current Sensing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
15.2.1 Resistor Selection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
15.2.2 Current Sense Amplifiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
15.2.3 Low Side vs. High Side Sensing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
15.3 Magnetic Sensing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
15.4 Follow-ups . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
16 Given the following datasheet and code snippet, initialize the ADC and write a polling function
to read the ADC voltage. 71
16.1 Supporting Problem Information . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
16.1.1 Sample Datasheet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
16.2 Setting Up the Problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
16.3 Initialization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
16.4 Conversion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
16.5 Follow-ups . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
Page 5 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
17 Given a 64-bit timer consisting of two 32-bit count registers, implement a function to get the
64 bit count. 76
17.1 Timer Background . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
17.2 Simple Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
17.3 Correct Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
20 What are the differences between a mutex and a semaphore, and in what use cases are each
typically employed? 85
20.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
20.2 Semaphore . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
20.3 Mutex . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
20.4 Putting it together . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
20.5 Priority Inversion and Inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
20.5.1 Priority Inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
20.6 Follow-ups . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
23 Extra Practice: Propose a simple circuit to disable current consumption when the voltage di-
vider is not needed. 100
23.1 Quiescent Current . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
Page 6 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
24 Extra Practice: Determine the step response of the following circuits. 102
24.1 Circuit E: Series Capacitor with Current Source . . . . . . . . . . . . . . . . . . . . . . . . . 103
24.2 Circuit F: Series RC with Current Source . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
24.3 Circuit G: Series Inductor with Shunt Capacitor . . . . . . . . . . . . . . . . . . . . . . . . . 103
24.4 Circuit H: Series Inductor and Capacitor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
25 Extra Practice: Solve the transfer function of the following circuits. 105
25.1 Solutions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
Page 7 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
1 Introduction
This guide was created by two Waterloo Engineering students, Daniel Puratich and Sahil Kale. Want-
ing to support our peers in their co-op journeys, we noticed that we were often answering the same
questions when asked about co-op interview prep. Noticing that many firmware and hardware co-
op interviews focus on fundamental concepts, we decided to compile a comprehensive list of com-
monly asked questions and answers to help students better prepare for their interviews. The book
also has an accompanying website, which includes additional content and resources. You can find it
at circuits-and-code.github.io.
It’s important to note that many questions in this guide have multiple correct answers. We’ve provided
detailed explanations for each question to help you understand the concepts and reasoning behind
our answers, and pointed out context-specific considerations where applicable. The ’best’ answer will
depend on the context of the question and the interviewer’s expectations - remember that engineering
is about problem-solving and tradeoff decision-making, and there are often multiple ways to approach
a problem.
Page 8 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
1.4.1 Code
Code snippets are included to illustrate various embedded software concepts. The code is written in C
and can be copied and compiled at your discretion, though some snippets may require the inclusion of
additional header files and minor restructuring. Note that while snippets can be compiled, they are not
intended to be standalone programs or run unless a main function is defined.
1.4.2 Latex
This guide was written using Latex and the source code can be found at Circuits and Code Book Github.
This shows the code we used to generate plots, draw circuits, and create diagrams as well as comments
containning more pilosophical thoughts for those interested in peering behind the curtain.
1.5 Acronyms
This guide features numerous acronyms and phrases that are commonly accepted in industry. We will
provide definitions for these acronyms the first time we use them, but will assume you understand them
in subsequent questions as understanding these terms will be integral to understanding common inter-
view questions.
Daniel interned at Tesla, Anduril Industries, and Pure Watercraft, focusing on power electronics and board
design.
We met at the Waterloo Aerial Robotics Group (WARG), a student team that designs and builds au-
tonomous drones. Uniquely, we have experience in hiring and interviewing multiple co-op students,
giving us insight into the interview process from both sides, as well as an understanding of what re-
sponses are expected from candidates. We value mentorship, enjoy sharing our knowledge, and take
pride in helping others succeed in their co-op journeys.
• The Sahil and Daniel Co-op Resume Guide - focuses on resume writing and tailoring for hardware
and firmware roles.
• The Sahil and Daniel Co-op Process Guide - offers our tips and tricks to landing a co-op role in
hardware and firmware.
1.7 Acknowledgements
We’d like to thank the several individuals for taking the time to review and provide feedback on this
guide. Their names can be found in the acknowledgements page on our book’s website. Please feel free
to reach out to us with feedback as we are looking to improve the guide over time!
Page 9 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
1.8 Disclaimer
This book is designed to be an educational resource, drawing from the authors’ experiences and research.
While we’ve done our best to ensure accuracy, readers are encouraged to use their own judgment and
explore additional resources as needed. The authors and publisher are not responsible for any errors or
omissions. Please note, the content is for informational purposes only and is not intended as profes-
sional advice.
Page 10 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
Page 11 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
Page 12 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
While effective, this approach is not efficient. It requires 8 iterations to count the number of 1’s in a byte.
We can improve this by using a technique called Brian Kernighan’s Algorithm.
1. For every byte, count the number of 1’s (for this example, we’ll use Brian Kernighan’s Algorithm).
2. Determine the expected parity bit based on the parity scheme (even or odd).
Listing 4 shows the implementation of the check_parity function that implements the steps men-
tioned above.
1 #include "bitstream_parity.h"
2 #include "count_ones_bk.h"
3
Page 13 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
Page 14 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
Page 15 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
3.1 Implementation
At first glance, an analog-to-digital converter (ADC) could be used to directly sample the battery volt-
age. While this is a valid solution, it is not a standard practice as high voltage ADCs are less commonly
included in microcontrollers and are more expensive to implement discretely. For this reason, the use
of a low voltage ADC commonly found in microcontrollers is preferred.
To do this, a voltage divider circuit can be used to scale the voltage of the battery by a fixed ratio, specified
by the two resistors, such that the ADC can sample the signal without exceeding the maximum input
voltage threshold.
Vin
Rt
Vout
Rb
3.2 Motivation
Electronic systems often require high power actuators and batteries to operate - relatively high battery
bus voltages are often selected by engineers to minimize current and conduction losses. Microcon-
trollers are often designed with low input voltages (i.e. 1.8V, 3.3V, 5V) in order to reduce power con-
sumption and transistor size during operation. As chemical batteries charge and discharge the battery,
the battery’s voltage varies due to numerous factors (notably, state of charge). As a result, many battery-
based embedded systems feature a battery-voltage sensing circuit hooked into a microcontroller to
monitor the battery’s voltage, and to take action when the battery voltage is too low or too high.
Vin − Vout = I · Rt
Vin − Vout
I= (1)
Rt
Vout − 0 = I · Rb
Vout (2)
I=
Rb
Solving the system of equations to eliminate I gives Equation (3) as follows.
Page 16 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
However, there are additional considerations in selecting Rt and Rb , as the voltage divider circuit draws
current proportional to the sum of its resistance (Rsum = Rt + Rb ). Electrical power dissipation is given
2
by P = V · I. Substituting in Ohm’s Law to describe the power dissipation of a resistor gives P = VR and
P = I 2 · R. Note that if the input voltage of the battery is roughly fixed at some nominal Vin , then the
circuit’s power dissipation increases as Rsum decreases. This power dissipation (referred to as quiescent
current) occurs constantly as voltage is always supplied to the circuit and can be significantly wasteful.
If the Rsum is too large, then the current flowing in the resistor divider is so small that noise coupling into
the signal or input bias current into the ADC can result in significant measuring error.2
• A simple case of the voltage divider circuit is when both resistors have the same value. R = Rt =
Rb . In this case, VVout
in
R
= R+R R
= 2·R = 21 which means Vin = 2 · Vout or Vout = 12 · Vin .
• Because Rt > 0 Ω and Rb > 0 Ω are required (as negative resistors do not exist), in all cases of the
circuit being employed we observe that 0 < VVout in
< 1. This indicates that Vout < Vin always holds
for the voltage divider so the circuit always scales a voltage down from its input to its output.
• This circuit assumes no source impedance from Vin and no loading connected to Vout . However,
this assumption is not always valid in practical circuits (and explored later in this answer).
Page 17 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
Vin
Rt
IRt
Vout
Rb Ibias
IRb
Vout Rb Iload Rt · Rb
= − · (4)
Vin Rt + Rb Vin Rt + Rb
Using the circuit in Figure 3 results in an adjusted transfer function shown in Figure 4. Input bias cur-
rent is also hard to model and can vary significantly so the simplest method of compensating for this
·Rb
by reducing Rsum = Rt + Rb which increases Rt ||Rb = RRtt+R b
and decreases the entire second term of
Figure 4.
Another method to compensate for a large input bias current is to use an external voltage buffer, aka a
unity gain op-amp (Operational Amplifier) to repeat the voltage, but buffer the current.
Vin
Rt
+
Vout
Rb
−
Op-amps also have input bias current, however, this can be compensated for it using the circuit in Figure
5 where Rc = Rt ||Rb .
Page 18 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
Vin
Rt
+
Vout
Rb
−
Rc
Microcontroller
IO Pin
These clamping diodes, shown in Figure 6, will conduct current when a voltage is applied that exceeds the
microcontroller’s supply voltage, Vcc , and when a voltage is applied that is below the microcontroller’s
ground reference, Vss . They will conduct current unless if the current becomes excessive resulting in
damage to the diodes and consequently damage to the device. Allowing the microcontroller clamping
diodes to sink some current during an overvoltage event is permissible. Note that when an ADC pin is
overvolted, accurate ADC readings cannot be expected.
External TVS diodes are also used when faster response times are required. A disadvantage of exter-
nal protection diodes is that they consume some leakage current which will result in less accurate ADC
measurements. This leakage current is often difficult to model (non-linear) and can be dependent on
numerous factors.
Page 19 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
To avoid aliasing, it is common to see a low pass filter (LPF) added to voltage divider circuits (a capacitor
added in parallel to the Vout signal) to filter out higher frequency noise. The cutoff frequency of a LPF
is the frequency in which the circuit will attenuate to half its input power or √12 of its input voltage. The
cutoff frequency of this low pass filter is usually selected to be roughly five times lower than the sampling
frequency to avoid aliasing4 .
Vin
Rt
Vout
Rb C
• The Vout trace should be as short as possible to avoid noise from coupling into the signal.
• The low pass capacitor should be placed near the ADC pin so it can filter out noise that couples
into Vout before the ADC samples it.
3.9 Follow-ups
• Reducing the quiescent current of a voltage divider can be done by increasing Rsum , however, this
only gets you so far. Propose a simple circuit to disable current consumption when the voltage
divider is not needed.5
• What if all analog input pins of the microcontroller are already in use?
4
Refer to LPF theory as to why the cutoff frequency is chosen to be higher - the need arises due to the cutoff being -
20dB/decade
5
A solution is given in extra practice question 23
Page 20 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
4 What is an interrupt service routine (ISR), and how do they differ from
regular functions in implementation?
———— Answers Ahead ————
Page 21 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
4.1 Interrupts
An interrupt is a signal that interrupts the currently-executing process on the CPU to handle a specific
event. Interrupts are often used to handle time-sensitive tasks, such as input/output (I/O) operations,
and are essential for real-time systems. This section summarizes general principles of interrupt service
routines; more information can be found in embedded systems literature (e.g., [2], [3], and [4]). Some
examples are as follows:
An interrupt is a powerful construct as it allows the CPU to handle events without necessarily polling for
whether an event has occurred. This allows the CPU to perform other tasks while waiting for an event
to occur.
Elaborating on the button press interrupt example: the CPU can continue executing other tasks until
the button is pressed, at which point the interrupt is triggered and the CPU can handle the button press.
This is in contrast to polling, where the CPU would have to continuously check if the button is pressed,
which is inefficient and wastes CPU cycles.
Broadly speaking, there are 2 types of interrupt handling mechanisms. Depending on the system archi-
tecture and interrupt source type, one or both may be used [3]:
• Non-Vectored/Polled Interrupts: Interrupts are handled by a common ISR, which then deter-
mines the source of the interrupt. An example would be when 3 unique button interrupts are
handled by a single ISR, which then determines which button was pressed.
• Vectored Interrupts: The interrupting device directly specifies the ISR to be executed; the address
of the ISR to call is usually stored in a table of function pointers called the vector table. An example
would be when 3 button interrupts are handled by 3 separate ISRs, avoiding the need for the ISR
to explicitly determine the button issuing the interrupt.
Several other concepts are important to understand when working with interrupts, but are not directly
related to the question. These include:
• Interrupt Priority: Determines which interrupt is serviced first when multiple interrupts occur si-
multaneously.
• Interrupt Nesting: The ability to handle interrupts while another interrupt is being serviced.
Page 22 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
• Execution Time: ISRs should be kept as short as possible, as they can block other interrupts from
being serviced and stall the main program from running. This is especially important in real-time
systems, where missing an interrupt can have serious consequences. In an RTOS, a key assumption
made by deadline scheduling algorithms is that ISRs are extremely fast when compared to task
execution time [4]. As a corollary, ISR’s should avoid blocking operations.
• Concurrency: ISR’s, by their very nature, are concurrent with the main program. This means that
they can interrupt the main program at any time, and the main program must be written with this
in mind.
– In particular, attention should be paid to shared variables and resources between the ISR and
the main program, and may require the use of queues, semaphores, or other synchronization
mechanisms.
– In addition, ISR’s should avoid non-reentrant (reentrant functions are functions that can be
called again, or re-entered, before a previous invocation completes) functions, as they can be
interrupted and cause unexpected behavior. Examples of non-reentrant functions include
those that use global variables or static variables, as well as malloc() and printf().
• No Return Value: ISRs do not return a value, as they are not called by the program but by the
hardware.
Page 23 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
Page 24 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
5.1 Context
Discrete LEDs (Light Emitting Diodes) are often used on circuit boards indicators to end users and firmware
developers about the state of an embedded system6 . A common first program executed during board
bring-up by firmware developers is to blink the onboard LEDs as an indicator the microcontroller is alive
and functional. For end users, it is very common to use LEDs to indicate that the embedded system is
powered on and operating nominally. Often, in electronic circuits, an LED is connected in some fashion
to a microcontroller GPIO (General Purpose Input / Output) pin in order to turn it on and off via firmware.
Vs − Vf = Rl · If +
Vs − Vf Vf
Rl = (5)
If − If
Note that for LEDs, the brightness is roughly proportional
to the current flowing through the LED. Consequently, the
brightness of the LED can be varied by changing the resis- Rl
tance value or the voltage to the LED.7 In practice, an LED’s
forward voltage is somewhat dependent on If and device
temperature. "I-V curves" across temperature are usually
given by LED manufacturers in the LED’s datasheets, how- Figure 8: Voltage Source Powering an LED
ever, an assumption of a constant Vf is enough for approxi-
mate solutions.
5.3 Transistors
Transistors are three terminal, electronically-controlled switches in which one terminal is used to control
the switching between the other two terminals. The two most commonly used transistors are MOSFETs
(Metal Oxide Semiconductor Field Effect Transistors) and BJTs (Bipolar Junction Transistors), though there
are other types. For a BJT, a small current to the base allows a large current to flow between emitter and
collector terminals. For a FET (Field Effect Transistor), a voltage potential difference between the gate and
the source allows current to flow between drain and source. These devices can be drawn with a variety
of schematic symbols, but are most commonly seen as:
6
LEDs can also be a primary feature of a device - an example case is high power LEDs, such as automobile headlights, which
require more complex circuitry to drive. This question will address only the simpler case of lower power LEDs.
7
To give a reference, a small, surface-mounted LED are usually rated for 20mA max (so 20mA ∗ 2V = 40mW), and are
visible indoors at just 1mA. For a firmware debugging LED, ≈ 2.5mA, is very common.
Page 25 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
FETs, ideally, do not require any power consumption to keep them enabled, whereas BJTs require cur-
rent to be supplied constantly. This means FETs are typically preferred when power consumption is a
critical consideration; this is primarily for higher power circuits in which excessive power consumption
directly results in a need for expensive cooling systems. BJTs are a much older technology and are easier
to fabricate, making them far preferred when optimizing for cost.8
Another consideration is low vs. high side switching when using a transistor to enable and disable (aka
switch) a load. The solution given in Figure 10 demonstrates low side switching. There are numerous
implications of this design decision that are explored further in Section ??.
+3.3V
+
Vf
−
Rl
Vc IC
Vb Rb VGP IO
MCU
IB
An NPN BJT is used to switch the LED on and off. This type of transistor has the governing equation:
IC = IB · β. IC represents current into the collector pin, IB represents current into the base pin, current
out of the emitter, IE , is given by IE = IB + IC . Common parameters for this BJT are VBE ≈ 0.7V ,
β ≈ 100, where VBE is the forward voltage drop from the base to the emitter, and β is the current gain
8
A solid understanding of both types of transistors is important for common interview questions!
Page 26 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
of the transistor. From this circuit drawing, the emitter voltage (VE ) is connected to ground so VE = 0V
meaning VBE = VB . Note that these are approximations and vary based on the part number selected.
When the GPIO pin is at logic low (VGP IO = 0 V), the base voltage (VB ) is approximately 0 V. Conse-
quently, the base current (IB ) and collector current (IC ) are both 0 A, and the LED remains off. When
the GPIO pin is at logic high (VGP IO = 3.3 V), the goal is to fully enable the transistor and allow more
than 10 mA of current through the collector (IC ).
IBmax
To achieve this, the base current is selected as IB ≈ = 5 mA/2 = 2.5 mA, which allows a maxi-
2
mum collector current of IC = 2.5 mA · 100 = 250 mA. Since 250 mA ≫ 10 mA, the LED will turn on,
and the collector voltage (VC ) will approach 0 V9 . Note that the current flowing through the LED can be
adjusted by setting Rl to an appropriate value.
The value of Rb can be solved by using Ohm’s law where VGP IO −VB = Ib ·Rb . For this circuit, VB = VBE
- therefore, the equation becomes:
VGP IO − VB 3.3V − 0.7V
Rb = = = 1040Ω (6)
Ib 2.5mA
Rounding to commonly available resistor values gives Rb = 1kΩ as a potential solution.
The forward voltage drop, Vf , is given as 2V, so Ohm’s law can be used to solve for the value of Rl . Ohm’s
Vs − Vf 3.3V − 2V
Law gives Vs − Vf = If · Rl which can be rearranged into Rl = = = 130Ω. This
If 10m
resistor can be found in the E24 resistor series as a common resistor value, so no rounding is needed.
PWM waveforms are usually created by hardware via dedicated timers, where the frequency is set to a
constant, high value and the timer’s duty cycle is adjusted (in this case, to control the brightness of an
LED). An example of a PWM waveform with a duty cycle of 80% is shown in Figure 11.
Page 27 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
5.6 Follow-ups
• Prpose a solution using a MOSFET instead of a BJT.
Page 28 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
Page 29 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
6.1 Volatile
The volatile keyword is used to tell the compiler that there are unexpected side-effects associated with
reads and writes of a variable, and as a result, should not optimize reads/writes to a variable declared as
volatile. From [5], it tells the compiler to:
• Not remove or add any reads/writes to the variable, even if it appears unused in the program.
Specifically, this ensures that the compiler does not optimize away accesses or cache the variable’s
value, forcing every read and write to occur exactly as written. This prevents incorrect behavior,
especially when interacting with hardware registers or shared memory.
• Not to reorder the reads/writes to the variable with respect to other reads/writes in the program.
Note that using volatile where not necessary can lead to worse performance, as the compiler may not
optimize the variable’s access.
The main loop checks if the timer_counter’s modulo (remainder) has reached a certain value and
then performs an action. If the timer_counter variable is not declared as volatile, the com-
piler may optimize the loop and cache the value of timer_counter after reading it only once as the
ISR_Timer function does not appear to be called, causing the if statement to never be true. Effectively,
the compiler assumes that you, the human, has written dead code (term for code that is unreachable) and
thinks it can optimize it away. By declaring timer_counter as volatile, the compiler will always
read the variable from memory, ensuring the loop works as expected.
Note: A major use-case of volatile is to access memory-mapped I/O registers in embedded systems.
Page 30 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
These registers can change due to external events by the hardware (HW) rather than code, and the com-
piler should not optimize the reads/writes to these registers. Memory-mapped I/O registers should be
declared as such (ex: volatile uint32_t * const UART_DR = (uint32_t *)0x40000000;)
to avoid the aforementioned issues.
6.2 Const
The const keyword refers to a variable that is read-only [6]. The compiler will throw an error if an attempt
is made to modify a variable labeled as const. Importantly, const does not mean that the value is
constant, but rather that the variable cannot be modified by the program. The distinction is important,
as it’s possible to have values that are declared as const, but are not constant.
Const’s primary use is to make code more readable and maintainable. By declaring a variable as const,
the programmer can signal to others that the variable should not be modified and have the compiler en-
force this behaviour. This can help prevent bugs and make the code easier to understand. As a general
rule, it’s good practice to declare variables as const whenever possible.
6.3 Static
The static keyword has different meanings depending on the context in which it is used.
Page 31 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
11
For more information, see this FAQ entry on static variables.
Page 32 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
7 What type of signal would be best for transferring data from a sensor lo-
cated 1 meter away to a microcontroller?
The sensor is a temperature sensor and readings are needed at a rate of 100 Hz, with a precision of 0.1C
and a temperature range of -40C to 125C.
Page 33 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
• Differential vs. Single Ended: Single ended signals use a single wire and a reference ground to
transmit data, while differential signals use two complementary wires (and optionally also use a
reference ground). Differential signalling does offers increased noise immunity for the same logic
level voltages.
• Network Topology: Different network topologies connect devices in different ways. Point-to-
point connections are the simplest, but more complex topologies, such as a Bus allow for multiple
devices to communicate over the same signal lines.
• Full vs. Half Duplex: Duplex means that both devices are capable of transmitting data. Half-duplex
means that only one device can transmit to another at a time, whereas full-duplex means both
devices can simultaneously transmit at the same time.
• Push Pull vs. Open Drain (Drive Type): Push-Pull drive means that the devices are capable of
pushing the signal lines high and pulling the signal lines low. This is in contrast to an open drain
protocol, where the devices are only capable of pulling the signal lines low and resistors are used
to pull the line high when no drivers are asserting them. Typically, a protocol with an open drain
drive type will have slower speeds than a push-pull protocol12 .
• Synchronous vs. Asynchronous: A synchronous protocol makes use of a clock signal to ensure
the sender and receiver are synchronized together. Clock signals can be on their own dedicated
wire or be encoded as a part of the data. Asynchronous protocols assume that the sender’s and
receiver’s individual clocks are sufficiently synchronized to ensure successful data reception. This
assumption holds for protocols with lower data rates or those that include transmission pauses for
re-alignment; however, it can introduce errors in high data rate applications.
Page 34 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
A B
• All signals from sensors begin as "analog" values. Any IC (integrated circuit) that outputs a digital
value is doing so because it features an on-board ADC (Analog to Digital Converter). Because digital
signals from sensors begin as analog signals, working only with analog signals can simplify the
design process.
• Analog signalling circuitry is often cheap to implement in a variety of embedded system contexts.
Microcontrollers often feature internal ADC’s, and may only require a few additional components
to directly interface with an analog sensor.
• Simple signal processing can be performed in hardware before digital computation is required. An
example of analog signal processing in hardware is first order low pass filter.
• More complex analog circuits are used in applications where higher bandwidth is needed in control
loops. Analog feedback loops are very common in power electronics, but the flexibility and easy
modification of digital control loops is becoming more common on highly integrated embedded
systems.
A drawback of analog signalling techniques is that they are quite often less resilient when faced with
noise compared to digital signalling techniques. While a digital signal is tolerant to small amounts of
noise without affecting the signal transmission at all, analog signals directly realize the effects of noise.
For this reason, virtually all long distance data transmission systems employ some form of digital trans-
mission.
Differential signalling is also an option in the analog world to compensate for common-mode noise for
cost optimized analog circuitry. This is uncommon however as conversion to a digital protocol is often
preferred when optimizing for noise immunity.
7.3 Conclusion
For a sensor located 1 meter away from a microcontroller, a digital signal would likely be the best can-
didate for transferring data as it is less susceptible to noise over long distances, while likely offering a
simpler implementation on the microcontroller due to the availability of on-board digital protocol pe-
ripherals. The high data-rate requirement of 100 Hz is easily achievable with most digital protocols, and
the precision requirement of 0.1C is usually achievable with most digital sensors. A protocol like UART
Page 35 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
It’s important to keep in mind that when asked about the best type of (something) for a given application, the
best answer is often "it depends". The interviewer is looking for you to weigh the pros and cons of different
solutions and make a recommendation based on the information given. For this problem, many solutions
could be valid depending on the specific circumstances of the application.
Page 36 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
Page 37 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
8.1 Introduction
A PID (Proportional-Integral-Derivative) controller is common type of feedback controller. It’s ubiquity
comes from its relatively easy implementation and effectiveness in a wide range of control systems.
Its typical applications include motor speed/position control, temperature control, lighting regulation,
and many more. The theory behind PID controllers is considered to be out of scope for this guide, but
Tim Wescott’s article titled PID Without a PhD provides a great introduction to PID controllers without
delving into linear control theory.
First, a C structure definition is created to hold the PID gains, as well as temporary variables for calculating
the integral and derivative terms. This structure is shown in Listing 9.
1 typedef struct {
2 float kp; // Proportional gain constant
3 float ki; // Integral gain constant
4 float kd; // Derivative gain constant
5
Breaking apart the problem into smaller functions, we can implement the terms of the equation as fol-
lows in C. Note that dt is the timestep (period) between control loop iterations.
• The proportional term is simply the product of the proportional gain and the error. It is responsible
for increasing system responsiveness, but can cause overshoot.
const float p_term = pid->K_p * error;.
• The integral term accumulates the error over time, summing up all past errors. Applying a control
signal proportional to the integral-error helps reduce steady-state error. To approximate this inte-
gral, we use the commonly-chosen Backward Euler method [9], which updates the integral (sum)
by adding the product of the current error and the timestep. Note that the computation of the
i_term comes after the addition of the integral in this approximation. See Figure 13 for a visual
representation of the Backward Euler method.
pid->integral += error * dt;.
const float i_term = pid->K_i * pid->integral;.
13
If the terms integral and derivative are unfamiliar, an excellent resource is Khan Academy’s calculus courses here. These
concepts are typically covered in an introductory calculus course.
Page 38 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
• The derivative term is the rate of change of the error, and can help reduce overshoot. To ap-
proximate this derivative, we use the Backward Euler method, which calculates the derivative
(slope) as the difference between the current error and the previous error, divided by the timestep
rise
(slope = ). See Figure 14 for a visual representation of the Backward Euler method.
run
const float d_term = pid->K_d * ((error - pid->prev_error) / dt);.
Putting it all together, the PID controller step function is shown in Listing 10. Note the added check for
a NULL pointer and positive timestep as either can cause the function to not run correctly.
1 #include "pid_typedef.h"
Page 39 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
2 #include <stddef.h>
3
4 void pid_init(pid_t *pid) {
5 if (pid != NULL) {
6 pid->integral = 0.0F;
7 pid->prev_error = 0.0F;
8 }
9 }
10
11 float pid_step(pid_t *pid, float setpoint, float measured_output, float dt) {
12 float ret = 0.0F;
13 // Check for NULL pointer and positive time step
14 if ((pid != NULL) && (dt > 0.0F)) {
15 const float error = setpoint - measured_output;
16 pid->integral += error * dt;
17
8.3 Follow-ups
• Anti-windup: What is integral windup, and how can it be prevented in a PID controller?
• Tuning: What effect does adjusting the gains Kp , Ki and Kd have on the system’s response?
• Filtering: What are possible implications of using a poorly filtered signal with a PID controller?
Page 40 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
9 Compare and contrast I2C (Inter-Integrated Circuit) and SPI (Serial Periph-
eral Interface).
———— Answers Ahead ————
Page 41 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
9.1 I2C
I2C is a half-duplex (can either transmit or receive, but not both simultaneously) digital protocol devel-
oped by Phillips in 1982 [10]. It enables a host device14 (referred to as a master 15 ) to communicate with
multiple peripheral devices (referred to as slaves) over a two-wire serial bus.
Page 42 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
4. Acknowledge Bit: After each byte is transmitted, the receiving device (master or slave) sends an
acknowledge bit. If the receiving device pulls the SDA line low, it indicates that it has received the
byte and is ready for the next byte. If the SDA line remains high, it indicates that the receiving
device is not ready, or that an error occurred.
5. Data Byte(s): Data bytes are transmitted in 8-bit chunks, with each byte followed by an acknowl-
edge bit.
6. Stop Condition: The master device signals the end of the transfer by, in the case of a write, releas-
ing the SDA line while the SCL line is high, or in the case of a read, sending a NACK (Not Acknowl-
edge) bit followed by a stop condition.
9.2 SPI
SPI was developed by Motorola [12] and is a full-duplex (simultaneous transmit and receive) synchronous
serial communication protocol. It is commonly used in embedded systems to communicate between
a master device and one or more slave devices. SPI is a four-wire protocol, consisting of the following
signals: MISO (Master In Slave Out), MOSI (Master Out Slave In), SCLK, and CS (Chip Select). A timing
diagram of a 1-byte SPI transaction is shown in Figure 17.
Page 43 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
9.3 Comparison
• Bus Speed: SPI is faster than I2C, with speeds in the MHz range (ex: 1-40 MHz) compared to I2C’s
400 kHz range due to the differences in drive type. As a result, SPI is often used in applications
where the transfer speed is required to be high.
• Wiring Complexity: I2C requires, at most, two wires, regardless of the number of devices on the
bus owing to its addressing scheme. A SPI master requires at least four wires, plus an additional
wire for each slave device (each slave will require 4 wires).
• Frame Format: I2C has a more complex frame format than SPI, with start and stop conditions,
address bytes, and acknowledge bits, which can assist in debugging unresponsive slave devices.
However, SPI has a simpler frame format, with no addressing scheme and no acknowledge bits,
which reduces the overhead of each transaction and affords more flexibility in the data format.
9.4 Follow-ups
• What are strategies for dealing with conflicting I2C addresses?
• What are strategies for dealing with the possibly-large number of CS lines in SPI?
Page 44 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
Page 45 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
• When a hardware peripheral (ex: SPI/I2C/UART) is unavailable on the microcontroller, usually be-
cause it’s not supported, or the EE (Electrical Engineer) gave the pins a ’creative reassignment’ (ac-
cidentally routed incorrect pins during schematic capture).
• When a custom or non-standard protocol is required, which cannot be supported by existing hard-
ware peripherals.
Page 46 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
• CPOL (Clock Polarity): Determines the idle state of the clock signal. CPOL = 0 means the clock is
low when idle, while CPOL = 1 means the clock is high when idle.
• CPHA (Clock Phase): Determines when data is sampled and changed. CPHA = 0 means data is
sampled on the leading edge (first transition) of the clock, while CPHA = 1 means data is sampled
on the trailing edge (second transition) of the clock.
• Generate a square wave on the SCLK line, ensuring the correct CPOL and CPHA settings.
• Loop through each bit of the tx byte starting from the most significant bit (MSB), and transmit the
logical value of the bit on the MOSI line on every SCLK falling edge.
• In parallel, read the MISO line on the rising edge of SCLK line to receive the slave’s response, and
write it to the corresponding bit in the received byte buffer.
• Terminate the SPI transaction by pulling the CS and SCLK line high to deselect the slave device.
Listing 13 shows a basic implementation of a bit-banged SPI master transceive.
1 #include "bitbang_spi_header.h"
2
3 #define NUM_BITS_IN_BYTE 8
4 #define NS_PER_SECOND 1000000000
5
6 bool bitbang_spi_transceive(float clk_freq_hz, uint8_t const *const tx_data,
7 uint8_t *const rx_data, size_t len_bytes) {
8 const bool ret = (tx_data != NULL) && (rx_data != NULL) && (len_bytes > 0);
9
10 if (ret) {
11 // clock calcs - integer division is used, but error is acceptable
12 const uint32_t period_ns = NS_PER_SECOND / clk_freq_hz;
13 const uint32_t half_period_ns = period_ns / 2;
14
15 HAL_GPIO_write(PIN_NUM_SCLK, true);
16 HAL_GPIO_write(PIN_NUM_CS, false);
17 delay_nanoseconds(period_ns); // CS setup time, arbitrary value
18
Page 47 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
28 HAL_GPIO_write(PIN_NUM_SCLK, true);
29 delay_nanoseconds(half_period_ns);
30 rx_data[byte] |= HAL_GPIO_read(PIN_NUM_MISO) << (7 - bit);
31 }
32 }
33
34 HAL_GPIO_write(PIN_NUM_CS, true);
35 }
36
37 return ret;
38 }
Page 48 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
Page 49 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
11.1 Oscilloscopes
An oscilloscope (in shorthand, just scope) is a device that plots the voltage of a probed signal with respect
to time. It does so by utilizing an ADC (Analog to Digital Converter) to discretize a the signal into quantized
samples that can be plotted on a monitor. As a result, the oscilloscope can perform many digital logic
operations on the probed signal and offers a variety of features for engineers (such as logic analysis,
protocol decryption, and frequency domain analysis).
11.2 Probes
The most basic oscilloscope probes have a ground clip and probe hook connected through a coaxial ca-
ble to a BNC connector that connects to the oscilloscope. These probes are designed to have a relatively
high impedance and affect the circuit under test minimally16 .
A current clamp is another type of probe that can be used to measure the current going through a wire - it
works by outputting a voltage that is proportional to the current going through the wire. When a current
clamp is connected to an oscilloscope, the oscilloscope can mathematically convert the voltage output
by the current clamp to a current measurement. Current clamps work on the principle of magnetic
current sensing and have an active amplifier in them.
11.3 Triggering
Since the signals being analyzed typically operate at frequencies much higher than humans can process
(e.g., in the MHz range), oscilloscopes provide a ’trigger’ function that establishes a t = 0 s reference
point based on a specific signal event.
16
In reality, probes often specify the capacitance the probe will add when it is connected to a circuit. This capacitance may
have a noticeable impact on the circuit, especially at higher frequencies.
17
This is because the step down is performed in the probe tip so a lower voltage (and therefore less energy) signal propagates
in the coaxial cable and into the scope. This phenomenon is often described as the probe having less effective/apparent
capacitance as it pulls less current from the circuit under test.
Page 50 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
The most common form of trigger is known as edge triggering. When rising edge trigger mode is used,
the oscilloscope begins a displayed image at t = 0s every time the sampled signal rises across a volt-
age threshold. Other trigger types occur, specifically for digital protocols, to trigger on more complex
conditions such as a specific series of bytes in a digital protocol. Untriggered waveforms can appear in-
consistent, as illustrated in Figure 20, where the sampled waveform appears highly distorted.18 See also
"Demystifying Oscilloscope Triggering" by "w2aew" on Youtube for a more detailed video explanation.
11.4 Bandwidth
The bandwidth of a low pass filter is defined by the frequency in which half the power, or √1 of the
2
voltage, of an input signal passes to the output signal19 .
This means if you probe a 10 MHz signal with an
oscilloscope with 10 MHz bandwidth, you will be seeing √12 of the voltage! A common rule of thumb used
to ensure accurate measurements is that the oscilloscope bandwidth should a few times higher than the
highest frequency needing to be measured. Oscilloscopes and measurement probes each have their
own bandwidth specifications - the lower of these values should be used as the bandwidth for compar-
ison.
18
Another parameter of the trigger is the mode. In normal mode the oscilloscope will only trigger when a trigger event
occurs. In auto mode the oscilloscope will automatically trigger if a trigger event has not occurred for some period of time. The
oscilloscope can also be stopped which means no trigger events are allowed meaning the display does not update.
19
In the context of wide-band measurements and oscilloscopes, the bandwidth of the circuits is always discussed in this
regard though broader definitions of bandwidth are used in analyzing more complex circuitry.
Page 51 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
11.5 DMM
Digital multi-meters (DMMs) are also capable of sampling voltage with respect to time, however, their
sample rate and bandwidth is significantly lower than that of an oscilloscope. However, DMMs feature
circuitry to determine the root-mean square of a signal which allows the measurement of AC signal
amplitude, DC signals, and PWM duty cycle.
11.6 Follow-ups
• What are the implications of oscilloscope sample rate?
Page 52 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
Page 53 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
12.1 Summary
Controller Area Network (CAN) is a popular communication protocol developed by Bosch in the 1980’s
[15]. It’s widely used in the automotive and robotics industries due to its robustness, reliability, and tailor-
made features for automotive-type environments.
Note that CAN is a fairly complex protocol, and depending on the interviewee’s knowledge level about the pro-
tocol, can cause an answer to this question to get quite deep and involved. However, this answer describes
some of the primary features of CAN that are integral to its operation, as many of those features are asked as
standalone questions or elaborated upon in an interview setting.
Page 54 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
Figure 24: Differential Pair Physical Layer with Noise, Source: Stack Overflow [18]
Another advantage of this configuration is immunity to common mode voltage offsets. Ideally, CAN
transceivers are designed to output a dominant state with CAN_H at 3.5V and CAN_L at 1.5V, and a re-
cessive state with CAN_H and CAN_L both at 2.5V. However, an advantage of differential signalling is
that these values can be offset (referred to as voltage shifting) if two transceivers have a voltage poten-
tial difference between their ground planes. More accurately, we can define a dominant state as CAN_H
at 3.5 + N V and CAN_L at 1.5 + N V, and a recessive state as CAN_H and CAN_L both at 2.5 + N V,
where N is a common mode voltage offset.21
Page 55 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
The bus is terminated with 120 ohm resistance at both ends to prevent signal reflections in the transmis-
sion line and to ensure that a recessive bus state can be caused when no device is asserting a dominant
state. A schematic of a simple bus topology is shown in Figure 25 with a termination at each end of
the bus and a small branch length. CAN is a very resilient protocol, especially for lower data-rates, so
optimizing electrically is often unnecessary for simple/small busses as long as at least one termination
is present. For production applications guidelines for bus topologies, terminations, and termination val-
ues are given in standards, but are often optimized by testing from EMC (Electro-Magnetic Compliance)
engineers in the lab.
• Arbitration Field/Message identifier: This field contains the message identifier, which is used to
determine message priority. The lower the value of the identifier, the higher the priority of the
message.
• Control Field: This field contains information about the message, such as the message length.
22
Note that CAN is very forgiving and outside of production applications and more complex bus topologies, twisting CAN
wires is unnecessary for successful data transmission.
23
Physically, they may be connected differently, however, this answer only focuses on the electrical connection.
Page 56 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
• Data Field: This field contains the actual data being transmitted. In CAN 2.0, this field can contain
up to 8 bytes of data.
• CRC Field: This field contains a cyclic redundancy check (CRC) to ensure the message’s data in-
tegrity.
Typically, a microcontroller will have a CAN peripheral that handles the low-level details of the CAN
protocol, such as message transmission, reception, error detection, etc. The microcontroller’s firmware
will interact with the CAN peripheral to send and receive messages, as well as configure key parameters
such as the bitrate, message filters (only receive certain message ID’s), etc.
12.5 Follow-ups
• Explain how CAN can detect TX errors using TX/RX loopback?
Page 57 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
R R1
Vin Vout Vin Vout
R2
R C
Vin Vout Vin Vout
C R
Page 58 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
13.1 Passives
For a question like this, assuming ideal components is expected as further information has not been
provided to aid in a more complex analysis. In a real interview, it’s likely you would be given just one
of these circuits. Table 2 provides a summary of the key properties of ideal resistors, capacitors, and
inductors.
• The relationship between angular frequency and frequency is given by: ω = 2πf .
• Resistors have no frequency dependence and do not store any energy though they do dissipate
V2
power, P = I · V = I 2 · R = .
R
• An open circuit (nets that are completely isolated from each other) is represented as connected
by an impedance of value Z = ∞.
• A short circuit (nets that are at the same voltage potential) is represented as connected by an
impedance of value Z = 0.
Page 59 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
13.3 Circuit A
In this circuit we note Ohm’s law, V = I · R, however, as there is no load, I = 0 on the resistor in this cir-
cuit we note there is no voltage drop, V = I · R = 0 · R = 0, across the resistor, consequently Vin = Vout
for this circuit.
Note for this circuit, and for all subsequent circuits as well, an assumption is made that there is no extra
loading on Vout and there is no source impedance on Vin . This is reasonable as the question shows no
extra loading drawn and the question does not state otherwise.
13.4 Circuit B
This circuit is a voltage divider depicted slightly differently than in a previous question24 , but the same
Vout R2
equations hold: = . In this case, to produce a step response plot an assumption is required
Vin R1 + R2
about the values of R1 and R2 . For simplicity, it is assumed that R1 = R2 , resulting in the following
Vout
transfer function: = 1/2.
Vin
Page 60 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
13.5 Circuit C
Circuits with inductors and capacitors are dependent on time and frequency and can be analyzed in ei-
ther domain. For the circuit in Figure 29 analysis will be performed in the time domain.
dVout
Analyzing the two elements in the circuit in time domain shows I = C · and Vin −Vout = I ·R. As I
dt
is equivalent in both elements, it can be cancelled out when solving the system of equations, resulting in
dVout dVout dt
Vin −Vout = R·C · . This expression can be algebraically manipulated into − =− .
dt Vout − Vin R·C
Vout − Vin t
Integrating both sides, the expression becomes ln =− .
Vout R·C
After re-arranging the above expression, the solution to the differential equation is given by Equation 8.
−t
!
Vout
=1−e R·C (8)
Vin
This circuit is commonly referred to as a RC low pass filter where a constant τ = R · C is defined as the
time constant which represents the time it takes 1 − e−1 or 63 percent of the step size. As the question
allows assumptions to be made for component values, a logical assumption is to select R and C that
Vout
τ = R · C = 1 for simplicity as the transfer function becomes = 1 − e−t .
Vin
Page 61 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
13.6 Circuit D
For this question, a complex impedance approach will be used rather than time domain analysis (though
Vout Rb
both will produce the correct solution). The transfer function of a voltage divider, = ,
Vin Rt + Rb
Vout Zb
can be applied to impedance values, resulting in = . For this circuit Zb = Zr = R and
Vin Zt + Zb
1 Vout R
Zt = Zc = can be substituted in to get = .
j·ω·C Vin 1
+R
j·ω·C
Page 62 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
Vout
When in the time domain t ≈ ∞ in the frequency ω ≈ 0, this the transfer function becomes =
Vin
R R 1
= = = 0. Additionally, at t = 0, the frequency domain representation
1 ∞+R ∞
+R
j·0·C
Vout
of the edge contains very high frequencies where ω ≈ ∞, so the transfer function becomes =
Vin
R R
= = 1.
1 0+R
+R
j·∞·C
Interpreting these results, we see that this circuit exhibits no gain at low or zero frequency and a gain of
1 at high frequencies. Consequently, it allows high-frequency signals to pass, which is why it is called a
high-pass filter. A step input contains a wide range of frequency components, including high frequencies,
which the filter initially allows to pass. This results in a sharp initial response, which then tapers off over
time as lower-frequency components dominate. This matches the behavior observed in Figure 35.
13.7 Follow-ups
• Given an unknown discrete capacitor find the capacitance? How would you determine the capac-
itance at a given DC bias voltage?
• For circuit C, does changing the value of R (assuming all else remains constant) change the total
energy dissipated in the resistor?
Page 63 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
Byte Description
0 Start of Frame (0x55)
1 Temperature data (Celsius) - Integer part + 32 (Tinteger = Byte1 − 32)
2 Temperature data (Celsius) - Fractional part (0-100)
3 Pressure data (Pa - Integer Part) - MSB
4 Pressure data (Pa - Integer Part)
5 Pressure data (Pa - Integer Part)
6 Pressure data (Pa - Integer Part) - LSB
7 Checksum - Lowest byte of the sum of bytes 0-6
Page 64 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
14.2.1 Checksum
A checksum is a simple error-detection method25 that involves summing the bytes of a packet and
comparing the result to a predefined value. If the checksum is incorrect, the packet is considered corrupt.
In this case, the checksum is the sum of bytes 0-6. A simple algorithm for checksum calculation is shown
in Listing 15.
1 #include <stdbool.h>
2 #include <stddef.h>
3 #include <stdint.h>
4
5 bool verify_checksum(const uint8_t *buf, size_t len, uint16_t expected) {
6 uint16_t checksum = 0; // note: overflow possibility if len > 255
7 for (size_t i = 0; i < len; i++) {
8 checksum += buf[i];
9 }
10
11 return checksum == expected;
12 }
Listing 15: Checksum Calculation Example
For this example, the checksum is calculated by summing the bytes from 1 to 6. However, the checksum
that is transmitted is the lowest byte of the sum. This is done to save bandwidth and reduce the number
of bytes transmitted. It is equivalent to saying that checksum_transmitted = checksum_calculated
& 0xFF.
Page 65 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
1 #include "packet_parsing_header.h"
2 #define NUM_BITS_IN_BYTE (8U)
3
4 bool parse_packet(const uint8_t *packet, size_t len,
5 weather_data_t *weather_data) {
6 bool success = false;
7 // Let the compiler do the work of calculating len
8 const size_t expected_len = (1U + 2U + 4U + 1U);
9 if ((len == expected_len) && (packet != NULL) && (weather_data != NULL)) {
10 const bool SOF_match = (packet[0] == 0x55);
11
12 uint16_t sum_of_bytes = 0;
13 for (size_t i = 0; (i < 7) && SOF_match; i++) {
14 sum_of_bytes += packet[i];
15 }
16 const uint8_t received_checksum = packet[7];
17 // Only compare the least significant byte of the sum
18 const bool checksum_match = ((sum_of_bytes & 0xFF) == received_checksum);
19 success = checksum_match && SOF_match;
20
21 if (success) {
22 float temperature_degC = 0.0f;
23 temperature_degC += (float)packet[1] - 32.0f; // integer part;
24 temperature_degC += (float)packet[2] / 10.0f; // fractional part;
25 float pressure_Pa = 0.0f;
26 pressure_Pa += (float)((uint32_t)packet[3] << (NUM_BITS_IN_BYTE * 3));
27 pressure_Pa += (float)((uint32_t)packet[4] << (NUM_BITS_IN_BYTE * 2));
28 pressure_Pa += (float)((uint32_t)packet[5] << (NUM_BITS_IN_BYTE * 1));
29 pressure_Pa += (float)(packet[6]);
30 weather_data->temperature_degC = temperature_degC;
31 weather_data->pressure_kPa = pressure_Pa / 1000.0f; // Convert Pa to kPa
32 }
33 }
34 return success;
35 }
14.3.1 Testing
In some interviews, an interviewer may ask you to write a test function to verify that the parsing function
works correctly. The list of edge cases mentioned above can be used to write test cases - this is further
elaborated upon in Extra Practice: Write a unit test for a packet parsing function.
14.4 Follow-ups
• How would you modify the parsing function to handle a packet with a different checksum algo-
rithm?
• How would you modify the parsing function to handle asynchronous data transmission (i.e. frag-
mented packets)?
Page 66 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
15 How would you sense how much current is flowing through a PCB to a
load?
———— Answers Ahead ————
Page 67 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
15.1 Motivation
Embedded systems often need to track current consumption of loads for a variety of reasons, including:
• Detecting if a load is drawing an excessive amount of power, indicating it has failed or an anomaly
is occurring.
• Ensuring a system does not overload a source power supply as it could cause the entire system to
lose power.
• Determine battery state of charge by integrating the power (P = I · V ) the battery has been
charged and/or discharged with over time.
Note that power sensing is a related concept that can be done by multiplying the current and voltage
sensed across a load (as P = I · V ). Previous questions have covered voltage sensing, so this question
will focus on current sensing - combining both techniques allow for power sensing.
Vsource Vload
Rs
Source + Load
Vsense
−
Page 68 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
On the other hand, if R is too small, then the induced voltage drop across the resistor will be so small
that it becomes difficult to measure. Common resistances employed in this technique include values
ranging from 0.1 mΩ to 100 mΩ, depending on the current range being measured and allowable power
dissipation.
Source Load
+
Vsense
−
Rs
Page 69 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
on it, forming the secondary winding of a transformer that can then measure AC amplitudes.
This method also works with DC currents if an extra winding around the core is used to actively cancel
the magnetic field generated by the sensed DC current. Based on the turns ratio and the current using
to cancel the induced magnetic field, the sensed current can be calculated by a meter.
15.4 Follow-ups
• What is kelvin sense?
• What are four-wire digital multi-meter probes and why would you use them?
Page 70 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
16 Given the following datasheet and code snippet, initialize the ADC and
write a polling function to read the ADC voltage.
16.1 Supporting Problem Information
Assume that the ADC is to be run with an ADC conversion clock frequency of 1 MHz. Implement the
functions shown in Listing 17 to initialize the ADC and read the ADC voltage.
1 #include <stdbool.h>
2 #include <stdint.h>
3 /**
4 * @brief Initialize the ADC.
5 * @note Blocking function
6 */
7 void adc_init(void);
8 /**
9 * @brief Read the ADC value
10 * @param channel The channel to read from
11 * @param voltage The voltage at the specified channel
12 * @return true if the read was successful, false otherwise
13 */
14 bool adc_read_channel_V(uint8_t channel, float *voltage);
ADC Configuration Register (WRITE) - ADDR 0x4000007C The ADC configuration register is used
to configure the ADC module with the key system parameters. The ADC must be enabled and ready
before any conversions can be performed.
ADC Status Register (READ) - ADDR 0x4000007D The ADC status register is used to check the
status of the ADC module. The ADC is ready to perform a conversion when the ADC_INITIALIZED
Page 71 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
bit is set. The ADC_BUSY bit is set when the ADC is performing a conversion, and cleared when the
conversion is complete.
ADC Data HIGH Register (READ) - ADDR 0x4000007E The ADC Data H register contains the upper
4 bits of the 12-bit ADC conversion result.
ADC Data LOW Register (READ) - ADDR 0x4000007F The ADC Data L register contains the lower
8 bits of the 12-bit ADC conversion result.
Page 72 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
Note the use of volatile in the register definitions. This keyword tells the compiler that the value of
the variable can change at any time, which is essential for memory-mapped registers.
16.3 Initialization
Initializing the ADC can be broken down into the following steps. The code is shown in Listing 19.
1. Set the ADC Clock Frequency: Calculate the ADC clock prescaler value to achieve a 1 MHz ADC
conversion clock frequency given the 8 MHz MCU clock frequency. We can use the formula
fADC_CLK = fM CU /(ADC_CLK_DIV + 1) to calculate the prescaler (ADC_CLK_DIV )
value (7).
2. Enable the ADC: Write to the ADC configuration register to enable the ADC.
3. Poll the ADC Status Register: Check if the ADC is ready for a conversion.
1 #include "adc_init_registers.h"
2 #include <stdbool.h>
3
4 void adc_init(void) {
5 // Set the prescaler to 3
6 const uint8_t PRESCALER_1MHZ_ADC_CLK = 7;
7 *ADC_CONFIG_REG = PRESCALER_1MHZ_ADC_CLK;
8
9 // Enable the ADC
Page 73 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
16.4 Conversion
The conversion problem can be addressed as follows. The code is shown in Listing 20.
1. Set the ADC Multiplexer Channel: Write to the ADC configuration register to select the desired
ADC multiplexer channel.
2. Begin the ADC Conversion: Write to the ADC configuration register to begin an ADC conversion.
3. Poll the ADC Status Register: Wait for the ADC to finish the conversion by polling the ADC status
register.
4. Read the ADC Data Registers: Read the ADC data registers to get the 12-bit ADC conversion result.
We need to read the ADC data registers in two separate reads (high and low) and combine the
results by shifting the high bits left by 8 and ORing with the low bits.
5. Calculate the ADC Voltage: Calculate the ADC voltage from the 12-bit ADC conversion result by
LT ·VREF
using the formula VADC = ADC_RESU212 −1
. Note that the 212 − 1 term is the maximum value
of a 12-bit number, and corrosponds to the ADC reference voltage.
1 #include "adc_init_registers.h"
2 #include <stdbool.h>
3 #include <stddef.h>
4
Page 74 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
16.5 Follow-ups
• Avoiding Polling: What are strategies for avoiding polling in the ADC conversion function (ex:
DMA, interrupts, etc)?
• Sample Frequency: Generally, what is the impact of the ADC clock frequency on the ADC con-
version time and resolution?
• ADC Error Sources: What are sources of error in an ADC and how are they mitigated?
Page 75 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
1 #include <stdint.h>
2
3 volatile uint32_t *CNT_LOW = (uint32_t *)0x40000000;
4 volatile uint32_t *CNT_HIGH = (uint32_t *)0x40000004;
5
6 /**
7 * @brief Get the 64 bit time from the timer
8 * @return uint64_t The 64 bit time
9 */
10 uint64_t get_64_bit_time(void);
Page 76 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
However, this implementation fails to consider that the 32-bit registers are updated by timer hard-
ware asynchronously. If the timer updates the CNT_HIGH register between reading CNT_LOW and
CNT_HIGH, the resulting 64-bit value will be incorrect. Consider the following scenario:
1. Assume that at the start of the function, the timer count register values are as follows: CNT_LOW
= 0xFFFFFFFF, while CNT_HIGH = 0x00000000.
3. In between reading CNT_LOW and CNT_HIGH, the timer increments. This causes CNT_LOW to
wrap around to 0x00000000 and consequently CNT_HIGH to increment to 0x00000001.
5. The resulting 64-bit value is 0x00000001 FFFFFFFF, which is incorrect. The correct value
should be 0x00000001 00000000.
The implications of this is that the 64 bit time value is no longer monotonic (constantly increasing), aside
from being straight up incorrect. Consumers of this function may experience unexpected behavior if the
timer is used for timekeeping or scheduling, as the time value may appear to jump backwards if called
near a timer update/roll-over. Note that this issue would also occur if the read order of the high and low
COUNT registers were swapped in the implementation.
30
This is 232 − 1 in hex - 0x means hexadecimal notation in C and is commonly used in datasheets and embedded software
manuals as a result of the industry’s adoption of C and C++.
Page 77 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
Page 78 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
Page 79 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
Consider the following C snippet in this example, on a system where the stack grows downwards (from
higher addresses to lower addresses).
1 #include <stdint.h>
2 #include <stdio.h>
3
4 uint8_t multiply(uint8_t c, uint8_t d) { return c * d; }
5
6 uint8_t add_and_multiply(uint8_t a, uint8_t b) {
7 return a + b + multiply(a, b);
8 }
9
10 int main() {
11 uint8_t a = 10;
12 uint8_t b = 20;
13 uint8_t c = add_and_multiply(a, b);
14 printf("Result: %u\n", c);
15 return 0;
16 }
Generally speaking, the stack might look something like what is found in Table 8 if we paused inside the
multiply function. Note the stack grows downwards in this example system, hence the decrease in
address values. Also note that a new stack frame is created for each function call.
Page 80 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
1. Create a main function that calls a dummy function. Pass in a pointer to a stack-allocated variable
to the dummy function.
2. In the dummy function, compare the address of the stack-allocated variable to the address of a
local variable in the dummy function.
3. If the address of the stack-allocated variable is less than the address of the local variable, the stack
grows downwards. If the address of the stack-allocated variable is greater than the address of the
local variable, the stack grows upwards.
Page 81 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
Vin +
Vout
−
Rf
Ri
Page 82 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
I+ +
V+
Iout
I− Vout
−
V−
19.2 Solving
When analyzing circuits, begin by defining equations for simple elements and build from there. In this
circuit, Ohms’ law can be applied to the Rf and Ri resistors, which results in the following relationships:
Vout − V− = I · Rf and V− = I · Ri . Note in this case, I− = 0 as the current (I) through resistor Rf is
the same as the current through Ri .
Next, because Rf connects a current path from Vout to V− , the virtual short assumption holds for the
op-amp - therefore, V+ = V− . From the circuit, it’s seen that Vin = V+ , so Vin = V+ = V− . This
conclusion can be substituted into the above-derived resistor equations to get Vout − Vin = I · Rf and
Vin = I · Ri .
These equations can be substituted into each other to cancel out I and algebraically rearranged to de-
termine Equation 10. The solution for VVout
in
is known as the transfer function of the circuit. The concept
of a transfer function is used to analyze numerous circuits.
Vout Rf
=1+ (10)
Vin Ri
19.2.1 Intuition
To understand this circuit better, consider how this circuit operates.
• Because negative resistors don’t exist, Rf > 0 and Ri > 0, VVout in
> 1 always. As the gain of the
circuit is always positive, this circuit is referred to as a Non-inverting Amplifier.
Vout
• When Rf = Ri = R the transfer function simplifies into Vin = 2.
Vout
• When Rf >> Ri the gain becomes very large, Vin ≈ ∞.
Vout
• When Ri >> Rf the gain approaches unity, Vin ≈ 1.
Page 83 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
Vin +
Vout
−
Vout
In this case the transfer function becomes unity, Vin = 1 which can be simplified into Vout = Vin .
This circuit acts as a current buffer. Because IV+ = 0 and Vin = V+ we see that Iin = 0 so the circuit
doesn’t load the input at all. Instead, any current required by the load is provided by the op-amp!
19.4 Follow-ups
• What is an inverting amplifier?
Page 84 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
Page 85 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
20.1 Introduction
A mutex and a semaphore are both synchronization primitives that can be used to signal and/or syn-
chronize access to shared resources in a multi-threaded environment. This section summarizes general
principles of interrupt service routines, as commonly described in embedded systems literature - DigiKey
has excellent articles on the implementation of real-time operating systems (RTOS) and synchroniza-
tion primitives [20].
20.2 Semaphore
A semaphore (also known as a counting semaphore) is a signalling mechanism that allows a thread to sig-
nal one or more waiting threads that a particular event has occurred. It can be thought of as a shared
non-negative integer counter.
– Ex: A server thread with a limited number of connections - the semaphore count would rep-
resent the number of available connections, with the semaphore being decremented when a
connection is acquired and incremented when a connection is released. A client thread would
wait for the semaphore to be incremented before attempting to acquire a connection.
• Signalling to thread(s) that a particular event has occurred. In the context of an RTOS, where there
is only one core available, a semaphore can be used to signal an event to a higher priority task
from a lower priority task, and have the higher priority task run instantly. This is because the higher
priority task will preempt the lower priority task once it is no longer ’blocked’ by the semaphore as
it will be available for the higher priority task to acquire.
– Ex: An interrupt service routine (ISR) can signal to a worker thread that an event has occurred.
Note: Event flags are also commonly used for this purpose, if provided by the OS.
Page 86 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
20.3 Mutex
A mutex (short for mutual exclusion) is a signalling mechanism that is used to prevent multiple threads
from accessing a shared resource simultaneously. It can be thought of as a lock that is either locked or
unlocked.
• lock_mutex(timeout) - attempts to lock the mutex. If the mutex is already locked, the func-
tion blocks the calling thread for the duration of the timeout until the mutex is unlocked.
Consider a UART driver that is being accessed by multiple threads. If the UART driver is not thread-safe,
it is possible that two threads could attempt to write to the UART at the same time, causing garbled
output. In this case, a mutex could be used to ensure that only one thread can access the UART at a
time. Pseudocode for this scenario is shown in Listing 27.
1 mutex_lock(uart_mutex, DELAY_INFINITE);
2 uart_write("Hello");
3 mutex_unlock(uart_mutex);
• A mutex only has 2 states, locked and unlocked, while a semaphore can count.
• A mutex has a concept of ownership, while a semaphore does not. This means that the thread that
locks a mutex must be the one to unlock it, while any thread can signal or wait on a semaphore.
• A mutex is typically used to protect access to a shared resource, while a semaphore is typically
used to signal an event or control access to a pool of resources.
Page 87 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
20.6 Follow-ups
• Describe other RTOS features that can be used for inter-task communication.
• Explain how deadlock can occur in an RTOS and how it can be prevented.
Page 88 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
Page 89 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
Vin Vout
+ Vref
21.1.1 Selection
When selecting an LDO for a given application, consider optimizing the following parameters in com-
ponent selection to save board space and cost while ensuring functionality.
• Dropout Voltage: To maintain a stable output voltage, the LDO requires sufficient input voltage.
Mathematically, this is expressed as Vin > Vout + Vdropout . The Vdropout varies between compo-
nents but is usually around 0.5 V.
• Maximum Input Voltage: The semiconductor pass element inside the transistor is rated to handle
a specific maximum input voltage.
• Maximum Output Current: The pass element semiconductor has a specific limit to the maximum
output current it can provide. Exceeding this limit can lead to overheating or failure. This relates to
the maximum power dissipation of the package as there is a limit to the amount of heat that can
be dissipated into the environment.
• Output Voltage: Some LDOs feature adjustable output voltages with a feedback pin, while others
have fixed output voltages.
21.1.2 Losses
In an LDO, the pass element transistor (usually implemented as a PMOS FET or NPN BJT) acting simi-
larly to a variable resistor is actively controlled to maintain a fixed output voltage, regardless of changes
to input voltage, load current, and temperature. This variable resistance gives rise to conduction losses
within this type of regulator. Assuming a fixed input voltage and load current, the power losses in an
LDO can be calculated as P = Vdrop · Iout = (Vin − Vout ) · Iout . Note that the power is dissipated as
heat in the pass element - this is why LDOs are often equipped with a heat sink, and attention may be
Page 90 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
Another source of losses in an LDO is quiescent losses - the LDO requires a small amount of current
to operate, even when the load current Iout = 0. This is known as the quiescent current, and is usually
negligible compared to conduction losses.
L
Vin Vout
21.2.1 Operation
When a buck converter is operational, the high side switch turns on during Ton and off during Tof f . The
duty cycle of a buck converter is defined as Don = TonT+T
on
of f
. The switching frequency of a buck converter
1
is given by fSW = Ton +T of f
. Buck converters generally operate under a roughly constant switching
frequency, but modulate their duty cycle with an active control loop to produce a stable output voltage.
The current paths during Ton and Tof f are depicted by Figure 46a and Figure 46b respectively.
L Il L Il
Vin Vout Vin Vout
C C
Ic Ic
(a) Buck Converter with High Side Switch ON (b) Buck Converter with Low Side Switch ON
Figure 46: Buck Converter Operation in Different Switching States
Recall from Question 13 that the energy stored within an inductor and capacitor is given by E = 21 · L · I 2
and E = 12 ·C·V 2 respectively. Conceptually, when the high side switch is ON, energy is being transferred
from the input source to the inductor and capacitor such that when the input source is disconnected,
these energy storage elements can supply energy to the load. Specifically, when the high side switch
is on, the current through the inductor and the voltage accross the capacitor increases. Consequently,
when the high side switch is off, the current through the inductor decreases (or stops at zero) and the
Page 91 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
Buck converters usually operate in CCM (Continuous Conduction Mode), meaning there is constantly cur-
rent flowing forward in the inductor. In CCM, Don = VVout
in
= TonT+T
on
of f
describes the duty cycles of an
ideal buck converter as shown in the following figure.
Page 92 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
21.2.2 Switching
The switch depicted in Figure 45 is commonly referred to as a high side switch. In practice, the high side
switch is usually implemented with a MOSFET (Metal-oxide semiconductor field effect transistor) that fea-
tures fast switching speeds, low switching losses, low conduction losses, low cost, and low leakage. The
gate of this MOSFET requires active control from a feedback loop to maintain a stable output voltage.
An asynchronous buck converter is depicted in Figure 45 depicts the low side switch as a diode. The
diode is nice to use because it does not require control and is cheaper than a transistor. Diodes, due to
their forward voltage drop, have higher conduction losses than MOSFETs.
Synchronous buck converters replace this low side diode with a MOSFET. In order to avoid shorting out
the input voltage source dead-time, a short period of time in which both high side and low side transistors
are off, is inserted into the control loop. During the dead-time, current continues to flow through the
body diode of the low side MOSFET.
32
If the oscillation was undamped, it would have a peak of exactly Vout · 2 and would not decrease in amplitude over time.
In this case, damping is mostly provided by the parasitic resistance of the inductor.
Page 93 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
21.2.3 Losses
There are two major forms of loss in a buck converter: switching losses and conduction losses. Switching
losses is power dissipated every time the FETs are switched (transitioned from off to on, or vice-versa)
and scale proportionally with switching frequency. Conduction losses are due to parasitic resistance of
elements in the converter and consequently scale proportionally to load current. Buck converters have
some quiescent current associated with their active control loop, however, these losses are negligible
compared to switching and conduction losses and are not usually analyzed.
21.3 Comparison
Buck converters are more efficient than LDOs in a majority of applications though an LDO may be op-
timal in applications where Vin ≈ Vout and/or Iout ≈ 0.
Buck converters require more physical space than LDOs on a circuit board, mostly because they need an
inductor to operate. The need for an inductor and switching circuitry also makes buck converters more
expensive than LDOs.
LDOs produce a more stable output voltage and can have higher control loop bandwidth as they do not
have a switching stage nor an output filter as compared to a buck converter. Both converter topologies
require input and output capacitance to produce relatively stable output voltages.
Consider a hypothetical controller in an automobile tasked with powering a 24V motor and a 5V sensor
from a 48V source. A solution could be to convert 48V to 24V for the motor and use another converter to
convert 48V to 5V. However, a more efficient approach is to convert 48V to 24V and supplying the 24V
to the motor and another converter from 48V to 5V intended solely for the sensor. There are numerous
tradeoffs between operating cascaded versus parallel converters in a power tree including: failure modes,
low power states, and conversion efficiency.
33
The inductor is often the largest single component in a buck converter so increasing switching frequency to reduce buck
converter overall circuit board area is a common practice.
Page 94 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
21.4 Follow-ups
• How would you measure the efficiency of a buck converter? What does the test setup look like?
• When would you use Diode Emulation Mode and Forced Pulse Width Modulation Mode for a Buck
Converter?
• Why are input capacitors required? What happens if you do not have input capacitors?
Page 95 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
Page 96 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
22.1 Solution
As mentioned in Question 14 (Implement a C bytestream parsing function for a weather data sensor), unit
tests are a crucial part of software development. They help ensure that the code behaves as expected
and catches bugs early in the development process. In this section, an example of a unit test framework
and unit tests for the packet parsing function are presented. Note that the tests and framework are crude
to demonstrate the type of code that would be written in an interview setting. In real-life, a dedicated
test framework, like CppUTest or GoogleTest, is strongly recommended.
The unit tests in principle work by the expected output being the extracted values as well as the return
value of the parsing function. Listing 28 shows example unit tests and crude test framework for testing
the packet parsing function.
1 #include "packet_parsing_header.h"
2 #include <math.h>
3 #include <stdio.h>
4
5 // Use a macro to compare floating point numbers since == is not reliable for
6 // floats
7 #define FLOAT_EQUALS(a, b) (fabsf(a - b) < 0.0001f)
8
9 bool test_invalid_args(void) {
10 weather_data_t weather_data;
11 uint8_t data[8U] = {0};
12 bool success = parse_packet(NULL, 0, &weather_data);
13 success |= parse_packet(data, 0, &weather_data);
14 success |= parse_packet(data, 8, NULL);
15
16 return !success;
17 }
18
19 bool test_incorrect_SOF(void) {
20 uint8_t packet[] = {0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54};
21
22 weather_data_t weather_data;
23 bool success = parse_packet(packet, sizeof(packet), &weather_data);
24
25 return !success;
26 }
27
28 bool test_incorrect_checksum(void) {
29 uint8_t packet[] = {0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53};
30
31 weather_data_t weather_data;
32 bool success = parse_packet(packet, sizeof(packet), &weather_data);
33
34 return !success;
35 }
36
37 bool test_correct_packet(void) {
38 uint8_t packet[] = {0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
39
40 uint8_t fake_temperature_degC = 12U;
Page 97 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
69 return success;
70 }
71
72 int main() {
73 bool success = true;
74 if (test_invalid_args() == false) {
75 printf("test_invalid_args failed\n");
76 success = false;
77 }
78
79 if (test_incorrect_SOF() == false) {
80 printf("test_incorrect_SOF failed\n");
81 success = false;
82 }
83
84 if (test_incorrect_checksum() == false) {
85 printf("test_incorrect_checksum failed\n");
86 success = false;
87 }
88
89 if (test_correct_packet() == false) {
90 printf("test_correct_packet failed\n");
91 success = false;
92 }
93
94 if (success) {
Page 98 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
Page 99 circuits-and-code.github.io
Circuits & Code: Mastering Embedded Co-op Interviews
Vin
Rpullup
Rt
Vout
Rb
Vctrl
R
Vout
Vout
Iin C
Iin C
L C
L
Vin Vout Vin Vout
Figure 54: Step Response of a Circuit with a Series Capacitor and Current Source
Figure 55: Step Response of a Circuit with a Series Inductor with Shunt Capacitor
Rf
Vin +
Vout Ri
− Vin −
Vout
+
R3
Vin +
Vout R1
− I1
Vin− −
Rf R2 Vout
I2
Vin+ +
Ri R4
25.1 Solutions
Vout
• Circuit A is a unity gain amplifier where Vin = 1.
Vout R
• Circuit B is an inverting amplifier where Vin = − Rfi .
Vout Rf
• Circuit C is a non-inverting amplifier where Vin =1+ Ri .
References
[1] S. E. Anderson, Bit twiddling hacks, Accessed: 2024-12-23, n.d. [Online]. Available: https : / /
graphics.stanford.edu/~seander/bithacks.html.
[2] E. White, Making Embedded Systems, 2nd. O’Reilly Media, 2024.
[3] C. J. Myers, Lecture 9: Interrupts in the 6812, Lecture notes for ECE/CS 5780/6780: Embedded Sys-
tem Design, n.d. [Online]. Available: https : / / www . rose - hulman . edu / class / ee /
hoover/ece331/old%20stuff/my%20csm12c32%20downloads/lec9-2x3.pdf.
[4] P. Koopman, Better Embedded System Software, 1st Edition, Revised 2021. 2021.
[5] Wikipedia, Volatile (computer programming). [Online]. Available: https : / / en . wikipedia .
org/wiki/Volatile_(computer_programming).
[6] J. Beningo, “Embedded basics: Peculiarities of the keyword const,” 2015, Accessed: 2024-12-23.
[Online]. Available: https://www.beningo.com/embedded-basics-peculiarities-
of-the-keyword-const/.
[7] J. Beningo, Using the static keyword in c, Accessed: 2024-12-23, 2014. [Online]. Available: https:
/ / community . arm . com / arm - community - blogs / b / embedded - blog / posts /
using-the-static-keyword-in-c.
[8] E. Staff, The c keyword: static, Accessed: 2024-12-23, 2014. [Online]. Available: https : / /
www.embedded.com/the-c-keyword-static/.
[9] D. Y. Abramovitch, “A unified framework for analog and digital pid controllers,” pp. 1492–1497,
2015. DOI: 10.1109/CCA.2015.7320822.
[10] SparkFun Electronics. “I2c.” Accessed: 2024-12-26. (n.d.), [Online]. Available: https://learn.
sparkfun.com/tutorials/i2c/a-brief-history-of-i2c.
[11] Tim Mathias. “Example i2c schematic.” Accessed: 2024-12-26, CC-BY-SA 4.0. (2021), [Online].
Available: https : / / en . wikipedia . org / wiki / I % C2 % B2C # /media / File : I2C _
controller-target.svg.
[12] Total Phase. “Spi background.” Accessed: 2024-12-26. (n.d.), [Online]. Available: https://www.
totalphase.com/support/articles/200349236-spi-background/?srsltid=
AfmBOooNHnQYWKPxz4YWdKyz9Bp4tEq3lw87j9EO6zr8YzMyRQkNYJz1.
[13] User:Cburnett. “Spi three slaves.” Accessed: 2024-12-26, CC-BY-SA 3.0. (2006), [Online]. Avail-
able: https://commons.wikimedia.org/wiki/File:SPI_three_slaves.svg.
[14] L. K. Seong, Introduction to bit-banging, Accessed: 2024-12-23, 2020. [Online]. Available: https:
//medium.com/@kslooi/introduction-to-bit-banging-46e114db3466.
[15] Cadence. “Can bus history at a glance.” Accessed: 2024-12-25. (n.d.), [Online]. Available: https:
//resources.pcb.cadence.com/blog/2022-can-bus-history-at-a-glance.
[16] John Griffith - Texas Instruments. “What do can bus signals look like?” Accessed: 2024-12-25.
(2023), [Online]. Available: https : / / www . ti . com / document - viewer / lit / html /
SSZTCN3#:~:text=As%20you%20can%20see%2C%20in,potential%20(approximately%
201.5V)..
[17] Gutten på Hemsen. “Differential signalling.” Accessed: 2024-12-25, CC-BY-SA 4.0. (n.d.), [On-
line]. Available: https://en.wikipedia.org/wiki/Differential_signalling#
/media/File:Differential_signal_fed_into_a_differential_amplifier.
svg.