NAME REG NUMBER
AINEMBABAZI RUTH 2023/DEE/EVE/1388
2023/DEE/DAY/0420
2023/DEE/EVE/1381
2023/DEE/EVE/1722
2023/DEE/EVE/1216
2023/DEE/EVE/1689
2023/DEE/DAY/0387
2023/DEE/EVE/1021
2023/DEE/DAY/0932
2023/DEE/EVE/1683
2023/DEE/EVE/1792
2023/DEE/DAY/0348
Embedded Systems Coursework 2025 DEE.
Qn 1 Part A: Analyse the following Arduino timing
functions and explain which ATmega328P timer
peripheral each one uses:
delay () and millis (): Use Timer 0
millis () counts milliseconds since the program
started using Timer 0
delay () uses millis () inside i.e. it just pauses the
code until enough time has passed.
tone () function: Uses Timer 2
It relies on Timer 2 to control the frequency of the
tone.
analogWrite () PWM on pins 5 and 6 vs pins 3, 9,
10, 11
Pins 5 and 6: use Timer 0
Pins 9 and 10: use Timer 1
pins 3 and 11: use Timer 2
Explain why delay () is blocking while millis () allows non-
blocking timing.
delay () is blocking because it stops the CPU from
doing anything else while it waits.
millis () is non-blocking because it keeps counting
time in the background so your loop can do other
tasks i.e. blink LEDs.
What timer overflow interrupt enables millis () to work?
The Timer 0 overflow interrupt
Qn 1 Part B:
const int ledPin = 13;
const int buttonPin = 2;
unsigned long previousMillis = 0;
unsigned long previousButtonCheck = 0;
unsigned long previousSerialPrint = 0;
const long ledInterval = 500;
const long serialInterval = 2000;
const int debounceDelay = 20;
bool ledState = false;
bool lastButtonState = HIGH;
bool buttonPressed = false;
int buttonCount = 0;
void setup() {
pinMode(ledPin, OUTPUT);
pinMode(buttonPin, INPUT_PULLUP);
[Link](9600);
void loop() {
unsigned long currentMillis = millis();
// Blink LED
if (currentMillis - previousMillis >= ledInterval) {
previousMillis = currentMillis;
ledState = !ledState;
digitalWrite(ledPin, ledState);
// Debounced button reading
if (currentMillis - previousButtonCheck >= debounceDelay) {
previousButtonCheck = currentMillis;
bool reading = digitalRead(buttonPin);
if (lastButtonState == HIGH && reading == LOW) {
// Button just pressed
buttonPressed = true ;
lastButtonState = reading;
if (buttonPressed) {
buttonCount++;
buttonPressed = false;
// Serial output every 2s
if (currentMillis - previousSerialPrint >= serialInterval) {
previousSerialPrint = currentMillis;
[Link]("Button presses: ");
[Link](buttonCount);
Question 2:PART A
Arduino's analogRead () function utilizes the ATmega328P's 10-bit ADC
peripheral.
Explain how analogRead () configures the ADC peripheral
internally
analogRead () sets the ADC multiplexer (MUX) to select the
input channel i.e. A0 and starts a conversation.
The default Arduino ADC reference is AVCC (5V). Calculate
what voltage corresponds to analogRead () values of 0,
512, and 1023
Voltage for ADC = (ADC eading1023 ) × Vref𝑎𝑛𝑎𝑙𝑜𝑔𝑅𝑒𝑎𝑑
= 0 × 5V = 𝟎. 𝟎𝟎𝑽
analogRead()= 0 → 0 × 5𝑉
𝑎𝑛𝑎𝑙𝑜𝑔𝑅𝑒𝑎𝑑() = 512 → 5121023 × 5𝑉
≈ 512
≈ 𝟐. 𝟓𝟎𝑽
1023 × 5𝑉
𝑎𝑛𝑎𝑙𝑜𝑔𝑅𝑒𝑎𝑑() → 1023
1023 × 5𝑉
= 5.00V
Why is the first reading inaccurate after changing channels?
• After switching ADC channels (MUX bits), residual charge or capacitor
effects can lead
to a wrong first reading.
• It's recommended to perform one dummy read and discard it when
switching channels
to stabilize.
4. What is the default ADC prescaler and why?
• Default ADC prescaler = 128, set via ADPS bits in ADCSRA.
• With 16 MHz system clock:
ADC clock = 16 MHz/128 = 125 kHz
• The ADC requires 50–200 kHz for accurate conversion. So, 125 kHz is in the
ideal
range.
Part B (2 marks)
Arduino sketch:
• Light sensor on A0
• Use analogReference(INTERNAL) (1.1V)
• Inverse brightness: darker → brighter LED on pin 9
• LED turns on only if it's "dark enough"
• Print light % to Serial Monitor
const int lightSensorPin = A0;
const int ledPin = 9;
int lightThreshold = 800; // depends on test conditions
void setup() {
analogReference(INTERNAL); // 1.1V reference
pinMode(ledPin, OUTPUT);
[Link](9600);
void loop() {
int sensorValue = analogRead(lightSensorPin); // 0–1023 (with 1.1V)
int brightness = map(sensorValue, 0, 1023, 255, 0); // Inverse mapping
// Only light LED when dark enough
if (sensorValue > lightThreshold) {
analogWrite(ledPin, brightness);
} else {
analogWrite(ledPin, 0);
}
// Light level as percentage
int lightPercent = map(sensorValue, 0, 1023, 100, 0);
[Link]("Light Level: ");
[Link](lightPercent);
[Link]("%");
delay(200);
}
Question 3:
Serial Communication and Command Interpreter (Total: 4 marks)
Part A (2 marks)
1. What happens internally when you call [Link](9600)?
• Initializes the USART peripheral (Universal Synchronous/Asynchronous
Receiver/Transmitter) in the ATmega328P.
• Sets baud rate registers (UBRR0H, UBRR0L) to match the desired speed.
• Enables transmit and receive by setting TXEN0 and RXEN0 in UCSR0B.
• Sets data frame format: 8-bit data, no parity, 1 stop bit (default).
• Enables the serial buffers and prepares the TX and RX lines.
2. Why can't you use Serial and SoftwareSerial on pins 0 and 1 at the same
time?
• Serial uses hardware UART connected to pins 0 (RX) and 1 (TX).
• SoftwareSerial is a bit-banged implementation that emulates serial
communication.
• Using both simultaneously on the same pins conflicts at the hardware level
and causes
data corruption or crashes.
3. Calculate UBRR value for 115200 baud at 16MHz:
Using formula for asynchronous normal mode:
UBRR= 𝐹𝑐𝑝𝑢
16×𝐵𝑎𝑢𝑑𝑟𝑎𝑡𝑒 −1
= 16000000
16×1152000 −1
=7.68
≈8
4. Difference between [Link]() and [Link]():
Function
Purpose Example
[Link]() Sends data as human-
readable text
[Link](65) →
sends "65"
[Link]() Sends raw bytes (binary
data)
[Link](65) →
sends ASCII 'A'
Part B (2 marks)
Arduino sketch for command interpreter:
Handles:
• "LED_ON", "LED_OFF" → control LED on pin 13
• "READ_TEMP" → reads analog temperature from A1
• "STATUS" → shows LED state + temperature
• Invalid commands → "ERROR: Unknown command"
const int ledPin = 13;
const int tempPin = A1;
bool ledState = false;
void setup() {
pinMode(ledPin, OUTPUT);
[Link](9600);
void loop() {
if ([Link]()) {
String command = [Link]('\n');
[Link](); // remove whitespace
if (command == "LED_ON") {
digitalWrite(ledPin, HIGH);
ledState = true;
[Link]("OK: LED turned ON");
else if (command == "LED_OFF") {
digitalWrite(ledPin, LOW);
ledState = false;
[Link]("OK: LED turned OFF");
else if (command == "READ_TEMP") {
int val = analogRead(tempPin);
float voltage = val * (5.0 / 1023.0);
float temperatureC = (voltage - 0.5) * 100.0; // For LM35 or TMP36
[Link]("TEMP: ");
[Link](temperatureC, 1);
[Link]("°C");
else if (command == "STATUS") {
int val = analogRead(tempPin);
float voltage = val * (5.0 / 1023.0);
float temperatureC = (voltage - 0.5) * 100.0;
[Link]("LED is ");
[Link](ledState ? "ON" : "OFF");
[Link]("Temperature: ");
[Link](temperatureC, 1);
[Link]("°C");
else {
[Link]("ERROR: Unknown command");
Question 4:
PWM and RGB LED Control (Total: 4 marks)
Part A (2 marks)
1. Which timer generates PWM for each Arduino PWM pin?
Pin Timer Used
3 Timer2
5 Timer0
6 Timer0
Pin Timer Used
9 Timer1
10 Timer1
11 Timer2
2. Why do pins 5 and 6 have different PWM frequency than others?
• Pins 5 & 6 (Timer0): Used for millis(), delay(), micros(), so its frequency is
altered.
o PWM frequency ≈ 976 Hz (default mode)
• Pins 3 & 11 (Timer2) and 9 & 10 (Timer1) use separate timers, typically
with higher or
adjustable frequencies.
• Timer0 is shared with core timing functions, so it uses a prescaler that
results in a lower
PWM frequency.
3. What happens when tone() is used simultaneously with PWM?
• tone() uses Timer2.
• If you use tone() while also using PWM on pins 3 or 11 (which use Timer2),
the PWM
signal will be disrupted or disabled.
• Avoid using tone() and PWM on pins 3 or 11 at the same time.
5. analogWrite(pin, 255) vs digitalWrite(pin, HIGH)
Feature analogWrite(pin, 255) digitalWrite(pin,
HIGH)
Signal Type PWM (high-frequency
square wave) Constant HIGH (5V)
Voltage Output (Average) 5V (average) 5V (constant)
Current Flow PWM-controlled Always on
Application Difference Dimmable control (motors,
LEDs) Simple on/off logic
So, although both output high voltage, analogWrite uses PWM, while
digitalWrite is static
logic.
Part B (2 marks)
Sketch Requirements:
• RGB LED on pins 9 (Red), 10 (Green), 11 (Blue)
• setColor() accepts 0–255 for R, G, B
• Smooth transition: Red → Yellow → Green → Cyan → Blue → Magenta → Red
• Each transition: 2 seconds
• Potentiometer on A0 adjusts speed (0.5x to 3x)
const int redPin = 9;
const int greenPin = 10;
const int bluePin = 11;
const int potPin = A0;
void setup() {
pinMode(redPin, OUTPUT);
pinMode(greenPin, OUTPUT);
pinMode(bluePin, OUTPUT);
analogReference(DEFAULT); // 5V
void loop() {
float speedFactor = map(analogRead(potPin), 0, 1023, 50, 300) / 100.0; //
0.5x–3x
fadeColor(255, 0, 0, 255, 255, 0, speedFactor); // Red → Yellow
fadeColor(255, 255, 0, 0, 255, 0, speedFactor); // Yellow → Green
fadeColor(0, 255, 0, 0, 255, 255, speedFactor); // Green → Cyan
fadeColor(0, 255, 255, 0, 0, 255, speedFactor); // Cyan → Blue
fadeColor(0, 0, 255, 255, 0, 255, speedFactor); // Blue → Magenta
fadeColor(255, 0, 255, 255, 0, 0, speedFactor); // Magenta → Red
void setColor(int r, int g, int b) {
analogWrite(redPin, r);
analogWrite(greenPin, g);
analogWrite(bluePin, b);
void fadeColor(int r1, int g1, int b1, int r2, int g2, int b2, float
speedFactor) {
int steps = 100;
for (int i = 0; i <= steps; i++) {
int r = map(i, 0, steps, r1, r2);
int g = map(i, 0, steps, g1, g2);
int b = map(i, 0, steps, b1, b2);
setColor(r, g, b);
delay(20 * speedFactor); // base delay * speed multiplier
}
Question 5:
External Interrupts and Rotary Encoder (Total: 4 marks)
Part A (2 marks)
1. Which ATmega328P pins support attachInterrupt()?
• Arduino Uno uses ATmega328P:
o INT0 → Digital Pin 2
o INT1 → Digital Pin 3
• Use with:
• attachInterrupt(digitalPinToInterrupt(pin), ISR, mode);
2. Compare the four interrupt modes:
Mode Trigger Condition
LOW Interrupt occurs as long as pin is LOW
CHANGE On any level change (LOW to HIGH or
HIGH to LOW)
RISING When signal goes from LOW to HIGH
FALLING When signal goes from HIGH to LOW
RISING/FALLING are most used for edge-triggered events.
LOW keeps triggering as long as pin is low—can cause ISR flooding.
CHANGE is useful for rotary encoders.
3. Why must ISRs be short and avoid delay()?
• ISRs block other interrupts and pause normal execution.
• delay() uses Timer0, which is disabled in ISRs → causes program to freeze
or behave
unpredictably.
• Keep ISRs fast: set flags or update small variables only.
4. Difference between hardware interrupts (attachInterrupt()) and pin change
interrupts:
Feature attachInterrupt() Pin Change Interrupts
Trigger Pins Only pins 2 and 3 Almost any digital pin
Speed Faster, direct vector Slightly slower
Configuration Simple via
attachInterrupt More complex to configure
Flexibility Limited pins More pins but shared
vectors
Part B (2 marks)
Sketch for a rotary encoder:
• Encoder on pins 2 (A) and 3 (B) → interrupt-driven
• Button on pin 4 resets position
• Use volatile variables for encoder count
const int pinA = 2;
const int pinB = 3;
const int buttonPin = 4;
volatile int encoderPos = 0;
volatile bool lastAState;
void setup() {
pinMode(pinA, INPUT_PULLUP);
pinMode(pinB, INPUT_PULLUP);
pinMode(buttonPin, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(pinA), handleEncoder, CHANGE);
[Link](9600);
}
void loop() {
// Button to reset position
if (digitalRead(buttonPin) == LOW) {
encoderPos = 0;
[Link]("Encoder reset to 0");
delay(200); // Debounce delay
}
// Display position every 250ms
static unsigned long lastPrint = 0;
if (millis() - lastPrint >= 250) {
lastPrint = millis();
[Link]("Encoder Position: ");
[Link](encoderPos);
}
}
void handleEncoder() {
bool A = digitalRead(pinA);
bool B = digitalRead(pinB);
if (A != lastAState) {
if (B != A) {
encoderPos++; // Clockwise
} else {
encoderPos--; // Counterclockwise
}
}
lastAState = A;
}