Part 1 · Debugging your code with PlatformIO in VS Code (for Lab 3)
Part 2 · 30 interactive exam-prep exercises for next week's final
Until now you have probably debugged with Serial.print(). That works, but it is slow and it changes timing. A source-level debugger lets you freeze the chip and look inside it.
// Serial.print debugging — the old way
void loop() {
Serial.print("count = ");
Serial.println(count); // clutters the UART
// ...where exactly did it go wrong?
}
// PlatformIO debugger — the new way:
// • set a breakpoint on the line
// • watch `count` change in real time
// • step through one line at a time
// • no print statements needed
src/main.cpp; settings live in platformio.ini.avr-gcc, avr-objcopy, avrdude) for you — the same tools from Week 14.AlarmTimer/
├── platformio.ini ← build & debug settings
├── src/
│ └── main.cpp ← your program
├── lib/ ← your own libraries
└── .pio/ ← build output (auto)
platformio.iniThe Uno has no on-board hardware debugger. We add a tiny GDB “stub” that runs on the chip and talks to VS Code over the normal USB cable.
debug_tool = avr-stub — software debugger, no extra hardwarelib_deps pulls in the avr-debugger librarySerial at the same time while debugging.[env:uno]
platform = atmelavr
board = uno
framework = arduino
; enable source-level debug
debug_tool = avr-stub
lib_deps = jdolinay/avr-debugger
#include <avr8-stub.h>debug_init() as the very first line of setup()Serial.begin() / Serial.print() — they clash with the debugger's UART#include <Arduino.h>
#include <avr8-stub.h>
void setup() {
debug_init(); // ← MUST be first
// configure pins, timers, interrupts...
}
void loop() {
// your program
}
loop() on a line that reads a variable you care about.void loop() {
if (tickFlag) { // 🔴 breakpoint here
tickFlag = false;
seconds--; // ← yellow arrow when paused
updateDisplay();
}
}
When paused, the floating debug toolbar drives the chip one move at a time:
| Action | Key | What it does |
|---|---|---|
| Continue | F5 | Run until the next breakpoint |
| Step Over | F10 | Run this line; don't dive into calls |
| Step Into | F11 | Run this line; enter the function called |
| Step Out | ⇧F11 | Finish the current function, pop back out |
| Stop | ⇧F5 | End the debug session |
updateDisplay().seconds, tickFlag, OCR1A) to track it live.OCR1A or PINC — perfect for confirming a timer or button value matches what you calculated.WATCH
seconds : 30
tickFlag : 0 ← set in ISR, read in loop
OCR1A : 3905 ← should this be 15624?
PINC : 0x3F ← bit 0 = A0 button state
volatile)Symptom: the display never updates, even though the timer ISR is running.
tickFlag = 1; → confirm it does fire.loop() on if (tickFlag) → Watch tickFlag.loop() still reads 0 → the compiler cached it in a register.volatile uint8_t tickFlag; so every read comes from RAM.// BUG: missing volatile
uint8_t tickFlag = 0;
ISR(TIMER1_COMPA_vect) {
tickFlag = 1; // set in RAM
}
void loop() {
if (tickFlag) { ... } // reads a STALE cached copy
}
// FIX:
volatile uint8_t tickFlag = 0;
Add OCR1A to the Watch panel. You calculated 1 s at prescaler 1024 needs OCR1A = 16,000,000/1024 − 1 = 15624, but the watch shows 3905 (that's 250 ms → 4× fast). Fix the value.
Breakpoint at the button read; Watch the pin while you press. With INPUT_PULLUP, pressed = LOW. The code tests for HIGH → invert it.
// Bug #2 — wrong TOP value
OCR1A = 3905; // 250 ms ✗
OCR1A = 15624; // 1000 ms ✓
// Bug #3 — inverted button logic
if (digitalRead(BTN) == HIGH) { ... } // ✗ release
if (digitalRead(BTN) == LOW) { ... } // ✓ press
// (INPUT_PULLUP: idle = HIGH, pressed = LOW)
Tick everything you've done, then press Check. Use this before you start Lab 3.
Timers & PWM · ADC · Interrupts & PCINT · Off-chip peripherals · Register-level code.
These are practice questions — none are identical to the exam, but they cover the same skills.
Which hardware timer does the Arduino core use for delay(), millis() and micros()?
Timer 1, CTC mode, 16 MHz clock, prescaler 256. You want an interrupt every 250 ms. What is OCR1A?
In CTC mode (Timer 1, WGM = 4), which register holds the TOP value that the counter resets at?
Match each Timer 1 register to its job.
Which bit in TIMSK1 enables the Timer 1 overflow interrupt (the TIMER1_OVF_vect)?
Timer 1, Fast PWM mode 14, 16 MHz, prescaler 1, ICR1 = 799. What is the PWM frequency (Hz)?
(1 + ICR1), not ICR1.Same timer as Exercise 6: ICR1 = 799, OCR1A = 199. What is the duty cycle (%)?
You compute OCR1A = 7812.5 with a prescaler of 1024. The register can only hold whole numbers. Best course of action?
In Fast PWM mode 14, which register controls the frequency and which controls the duty cycle?
analogWrite() on pins 9 and 10 uses Timer 1.A 10-bit ADC reads 512 with V_ref = 5.0 V. What is the input voltage?
Timer 2, 8-bit Fast PWM, fixed TOP = 255, prescaler 256, 16 MHz. What is the PWM frequency (Hz)?
Which register contains the PCIE0 / PCIE1 / PCIE2 bits that switch a whole pin-change port group on or off?
Pin A3 is being set up for a pin-change interrupt. Complete each field.
Now do the same for digital pin D6.
A pin-change interrupt (PCINT) fires on…
Inside PCINT1_vect, complete the code that (1) finds which pins changed and (2) detects a press (LOW) on A0 = PC0.
Assign each pin to its pin-change group: Group 0 = Port B, Group 1 = Port C, Group 2 = Port D.
volatile?A counter is changed inside an ISR and read in loop(). Why must it be volatile?
Two buttons share PCINT1_vect. The XOR detection works the first time but never again. Which line is the defect?
To drop the Arduino's 5 V TX down for the ESP-01, you use a divider: R1 = 2.2 kΩ in series, R2 = 3.3 kΩ to GND, with the ESP RX on the junction. What is V_out?
Match each ESP-01 AT command to what it does.
You wire the Arduino Uno's 5 V TX pin directly to the ESP-01's RX pin. What is the problem and fix?
Which statement about the DHT11 is correct?
Complete the non-blocking pattern so the web server stays responsive between sensor reads.
delay(), which would freeze the ESP-01 serial handling.Match each bare-metal tool to its job in the build pipeline.
LEDs are on D8/D9/D10 (PB0–PB2). After this setup only D8 works and D9/D10 stop responding. Which line is wrong?
Set D8, D9, D10 (PB0, PB1, PB2) as outputs without disturbing the other PORTB bits.
Complete the falling-edge (press) test for button A1 = PC1, given changed and currentPinC.
attachInterrupt()?Why can't you use attachInterrupt() for buttons on A0/A1, so you use direct PCINT registers instead?
✅ You can set up the PlatformIO debugger and use breakpoints + Watch.
✅ You've practised every exam topic: timers, PWM, ADC, PCINT, peripherals & register-level code.
Revisit any exercise from the ☰ menu. Re-run the calculations until they're automatic. See you in the exam — you've got this.