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
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:
| Construct | Shape | The fact that costs marks |
|---|---|---|
| Conditional | if cond: / elif / else: | = binds, == compares; the condition takes == |
| While loop | while cond: | Something in the body must move toward ending the condition, or the loop is forever (which while True: does on purpose) |
| For loop | for n in range(5): | range(5) yields 0 to 4: five values, never 5 itself |
| Function | def name(params): ... return value | Without a return, the call's value is None; printing is not returning |
| f-string | f"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:
| Error | Means | Usual cause |
|---|---|---|
SyntaxError | Python cannot even parse the line | Missing colon, unclosed quote or bracket; the arrow points at or just after the fault |
IndentationError | The block structure is inconsistent | Mixed or wrong indent; one level = one consistent step |
NameError | A name was used before any binding | Typo, or a variable only ever assigned inside an unexecuted branch |
TypeError | Right name, wrong kind of use | Mixing types ("5" + 3), wrong argument counts, calling the uncallable |
ValueError | Right type, impossible value | int("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
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.
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).
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.
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.
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 for | Rearrangement | Worked, fresh numbers |
|---|---|---|
| Current, given R | I = (3.3 − V_LED) / R | 470 Ω, red LED: I = 1.3 / 470 ≈ 2.8 mA (dimmer, safer, still visible) |
| Resistor, given a current cap | R ≥ (3.3 − V_LED) / I_max | Cap at 3 mA: R ≥ 1.3 / 0.003 ≈ 433 Ω → next standard value up, 470 Ω |
| Effect of a different LED colour | Change V_LED, same loop | Blue 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.
2 · You must keep a red LED at or under 2.5 mA. Minimum resistor, and the standard value you would pick?
3 · print(ms) raises NameError: name 'ms' is not defined even though ms = ... appears earlier, inside an if. Explain.
4 · Predict the output: for n in range(3): print(n)
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?
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"?
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?
Rule 2 and rule 4 are cousins. State the shared principle.
Why does the course's venv get --system-site-packages here, when many tutorials omit it?
What does a pull-up resistor give an input pin, and what does bounce_time add?
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.
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.
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.