Midterm week: six weeks, one toolkit
420-302-VA · WEEK 7 · FALL 2026

Review 2 of 3 · Week 4 · plan one session

Code review

Week 4 joined two worlds: a language that manipulates names and objects, and a circuit that obeys Ohm's law. The exam will test both ends and the joint between them, so this page reviews the stack top to bottom, with the mathematics re-worked on fresh numbers.

The stack: from your line to the electron

Five layers from top to bottom: your Python line led.on(), the gpiozero library, the Linux kernel's GPIO driver, pin 11 carrying GPIO17 at 3.3 volts, and the circuit where about 4 milliamps flow through the resistor and LED to ground. your codeled.on() gpiozero · the library translates intent to pin operations Linux kernel · the GPIO driver owns the hardware GPIO17 on physical pin 11 goes to 3.3 V the circuit · pin → 330 Ω → LED → GND I = (3.3 − 2.0) / 330 ≈ 3.9 mA one call, five layers
Why both halves matter. A bug can live at any layer: a Python error at the top, a wrong pin in the middle, a reversed LED at the bottom. Week 4's debugging ladder worked down this exact stack, and exam questions about "what is wrong here" are questions about which layer the fault lives in.

Python, the load-bearing core

The execution model in three sentences: Python runs a script top to bottom; an assignment binds a name to an object (count = 0 is "make count refer to the integer 0", not algebra); control flow bends the top-to-bottom path. Indentation is the syntax that marks a block's body, not decoration: the lines indented under an if, while, for or def are its body, and shifting one line's indent changes the program's meaning. The construct set the exam draws on:

ConstructShapeThe fact that costs marks
Conditionalif cond: / elif / else:= binds, == compares; the condition takes ==
While loopwhile cond:Something in the body must move toward ending the condition, or the loop is forever (which while True: does on purpose)
For loopfor n in range(5):range(5) yields 0 to 4: five values, never 5 itself
Functiondef name(params): ... return valueWithout a return, the call's value is None; printing is not returning
f-stringf"Reaction: {ms:.0f} ms"The braces evaluate; :.0f formats to zero decimals

Tracebacks and the error families

The reading rule is mechanical and worth two marks every time it applies: read the traceback bottom-up. The last line names the error and its message; the lines above it locate the file and line; anything higher is the call chain that led there. The families you can identify on sight:

ErrorMeansUsual cause
SyntaxErrorPython cannot even parse the lineMissing colon, unclosed quote or bracket; the arrow points at or just after the fault
IndentationErrorThe block structure is inconsistentMixed or wrong indent; one level = one consistent step
NameErrorA name was used before any bindingTypo, or a variable only ever assigned inside an unexecuted branch
TypeErrorRight name, wrong kind of useMixing types ("5" + 3), wrong argument counts, calling the uncallable
ValueErrorRight type, impossible valueint("abc"); or Week 6's min() of an empty list

Environments, in four facts

One, the system Python belongs to the operating system, and modern Raspberry Pi OS enforces that (PEP 668): pip install outside an environment is refused by design, not by accident. Two, a virtual environment is a project-private Python: python3 -m venv --system-site-packages .venv creates it, with the flag letting it see the system's pre-installed gpiozero. Three, source .venv/bin/activate enters it, the (.venv) prompt is the telltale, and pip now installs privately and safely. Four, machine-made clutter stays out of Git: .gitignore lists .venv/, __pycache__/, *.pyc, which is why the habit was installed the same day as the first venv. Full reasoning: Week 4's environment page.

The five GPIO rules

  1. 3.3 V logic, never 5 V

    The Pi's GPIO pins speak and survive 3.3 V. The 5 V power pins exist to feed accessories, not logic: 5 V onto a GPIO pin is how Pis die quietly.

  2. Every LED gets a series resistor

    An LED does not limit its own current; without a resistor it takes whatever the pin can give until one of them fails. The resistor sets the current on purpose (the mathematics below).

  3. Respect the current budget

    A few milliamps per pin, modest in total across all pins. GPIO drives indicators and signals; anything hungrier (motors, strips) gets its own supply and a driver in between.

  4. Never short 3.3 V or 5 V to ground

    A direct power-to-ground path is a dead short: maximum current, no limiter. Every circuit must put resistance between power and ground along every path.

  5. Rewire with outputs off

    Change the circuit only when the script is stopped and outputs are low, ideally powered down. A live rewire is where slips become shorts.

The LED mathematics, re-worked

The one calculation the midterm has openly promised. The model: the pin supplies 3.3 V; a red LED in conduction drops about 2.0 V (its forward voltage) and the resistor absorbs the rest, so Ohm's law applied to the resistor gives the loop current:

I = (V_supply − V_LED) / R = (3.3 − 2.0) / 330 ≈ 0.0039 A ≈ 3.9 mA

Comfortably inside the budget, bright enough to see: why 330 Ω is the course's default. The same equation runs in every direction the exam can turn it:

Asked forRearrangementWorked, fresh numbers
Current, given RI = (3.3 − V_LED) / R470 Ω, red LED: I = 1.3 / 470 ≈ 2.8 mA (dimmer, safer, still visible)
Resistor, given a current capR ≥ (3.3 − V_LED) / I_maxCap at 3 mA: R ≥ 1.3 / 0.003 ≈ 433 Ω → next standard value up, 470 Ω
Effect of a different LED colourChange V_LED, same loopBlue LED, V_LED ≈ 3.0 V, 330 Ω: I = 0.3 / 330 ≈ 0.9 mA, visibly dimmer at the same R

Marking habit that earns partial credit even on a slip: write the formula, substitute with units, then compute, and round sanely (two significant figures is plenty). Series position does not matter: resistor before or after the LED, same single loop, same current everywhere in it. Polarity does: the LED's long leg (anode) toward the pin, short leg (cathode) toward ground, or the loop simply does not conduct. Original walkthrough: Week 4's GPIO page.

Inputs, pull-ups and debounce

An input pin connected to nothing is floating: it reads electrical noise, not a value. The fix is a pull resistor giving it a defined idle level, and gpiozero's Button enables the Pi's internal pull-up by default: idle reads high, and pressing the button connects the pin to ground, which is why the button wires between the pin and GND with no resistor of its own. Mechanical contacts also bounce, closing and opening dozens of times in a few milliseconds; Button(27, bounce_time=0.05) tells gpiozero to treat 50 ms of chatter as one press. The gpiozero working set: LED(17) with .on() .off() .blink() and .is_lit; Button(27) with .is_pressed, .wait_for_press() and the callback attribute when_pressed, which is assigned a function without parentheses; pause() from signal to keep an event-driven script alive.

Two numbering systems

Every pin has two names: a physical position on the 40-pin header (counted 1 to 40) and a BCM/GPIO number (the processor's own naming), and gpiozero speaks BCM. The course pair to know cold: LED(17) is GPIO17 at physical pin 11; Button(27) is GPIO27 at physical pin 13. Confusing the systems produces the most honest bug in the course: a program that runs perfectly while the wrong wire does nothing. When in doubt at the bench, the Week 2 pinout settles it.

Self-test: the numbers

1 · A red LED (V_LED ≈ 2.0 V) with a 220 Ω resistor on a 3.3 V pin: the current, two significant figures.
I = (3.3 − 2.0) / 220 = 1.3 / 220 ≈ 5.9 mA. Brighter than 330 Ω's 3.9 mA, still inside the budget: the Week 4 exercise, now cold.
2 · You must keep a red LED at or under 2.5 mA. Minimum resistor, and the standard value you would pick?
R ≥ 1.3 / 0.0025 = 520 Ω; next standard value up is 560 Ω. Rounding down (e.g., to 470 Ω) would break the cap, so the rule is always round up.
3 · print(ms) raises NameError: name 'ms' is not defined even though ms = ... appears earlier, inside an if. Explain.
The binding lives in a branch that did not execute this run, so the name was never created. NameError means "no binding happened", not "the line does not exist". Fix: bind a default before the branch, or restructure.
4 · Predict the output: for n in range(3): print(n)
0, 1, 2 on separate lines. range(3) yields three values starting at 0; 3 itself never appears.
5 · Why does button.when_pressed = flash() break the program silently, and what is the correct line?
The parentheses call flash immediately, once, and assign its return value, None, as the callback; afterward presses invoke nothing and no error ever appears. Correct: button.when_pressed = flash, the function itself.
6 · The script drives GPIO17 but your LED is wired to physical pin 13. What happens, and why is this bug "honest"?
The program runs flawlessly and the LED never lights: physical 13 is GPIO27, a different pin entirely. Honest because nothing errors; only the mapping between the two numbering systems is wrong, which is why wiring is checked against BCM numbers, not positions, before blaming code.

Checklist for this review

Check yourself

Why is the voltage divided by R in the LED formula 1.3 V and not 3.3 V?
The LED drops its forward voltage (~2.0 V red) across itself; the resistor only absorbs the remainder, 3.3 − 2.0 = 1.3 V, and Ohm's law applies to the component the voltage is across. Dividing 3.3 by R is the classic error, and it overestimates the current.
Rule 2 and rule 4 are cousins. State the shared principle.
Every path from power to ground must contain deliberate resistance. The LED resistor enforces it for one loop; "never short power to ground" states it for all loops.
Why does the course's venv get --system-site-packages here, when many tutorials omit it?
The Pi ships gpiozero system-wide; the flag lets the private environment see it instead of reinstalling it. The environment still isolates everything you add with pip, which was the point.
What does a pull-up resistor give an input pin, and what does bounce_time add?
A defined idle level (high) so the pin never floats on noise; the press then pulls it to ground, a clean transition. bounce_time collapses the mechanical chatter around that transition into one logical event.
A function computes the right value, prints it, and the caller gets None. Diagnose.
It printed instead of returning: without return, every call evaluates to None. Output to a human and a value to a caller are different channels, and the exam likes the distinction.
Which layers of the stack diagram can Week 4's bugs live in? Give one example per layer.
Your code (a TypeError), the environment/library boundary (importing outside the venv), the pin mapping (BCM vs physical), and the circuit (reversed LED, missing resistor, loose jumper). The ladder works down them in order, cheapest checks first.