- Where Lab 3 Sits in the Course
- Understanding the GPIO Port
- Driving an LED from a GPIO Pin
- Talking to the Pins from Python
- Timing: Why
sleep()Is Not a Clock - Flicker Fusion: When Blinking Becomes Brightness
- Pulse-Width Modulation
- RGB LEDs and the Breathing Effect
- Oscilloscope Technique for This Lab
- Common Mistakes and Bench Safety
- Self-Check
- Practical Engineering Connections
1Where Lab 3 Sits in the Course
Labs 1 and 2 were purely analog. You built an LED driver, measured a photodiode current, and multiplied two voltages together with an AD633. In every one of those circuits, the hardware decided what happened: a resistor set a current, a feedback loop set a gain, a multiplier produced a product.
Starting with Lab 3, software decides what happens. The Raspberry Pi is a full Linux computer with a row of pins that a Python program can switch between 0 V and 3.3 V. That single new ability — a line of code changing a voltage — is the foundation of everything left in this course: motor control (Lab 4), sensors (Lab 6), and your final project.
The catch, and the real subject of this lab, is that a general-purpose computer running Linux and Python is a surprisingly poor timekeeper. Asking for a 1-second delay does not give you exactly 1 second. Lab 3 is titled “LED Control and Timing Analysis” because the second half is the part that matters: you will use the oscilloscope to find out exactly how badly the Pi keeps time, and then learn the technique — PWM — that engineers use to get around it.
A GPIO pin is a voltage source that software can switch. It is a very good switch and a mediocre clock. Whenever timing accuracy matters, move the timing out of the Python loop and into dedicated hardware.
1.1 Lab 3 task map
Each task in the handout exercises a specific idea from these notes. Use this table to find the section you need while you are at the bench.
| Lab 3 task | What you build | Concept & section in these notes |
|---|---|---|
| 1–2 | Boot the Pi, check the Python version, make your working directory | Linux workflow — see the course Linux Commands Guide |
| 3 | Turn on one LED | GPIO numbering (§2), resistor sizing (§3), the setup–write–clean up pattern (§4) |
| 4 | Alternate a blue and a green LED every 2 s; push to the highest frequency; look for jitter | Loop period, scheduler jitter, maximum toggle rate (§5) |
| 5 | Three LEDs in sequence | Managing several pins; per-color resistor values (§3.4) |
| 6 | RGB LED flashing three colors | Common-cathode vs common-anode wiring (§8) |
| 7 | PWM brightness control; vary duty cycle and frequency; measure on the scope | Duty cycle, average voltage, software vs hardware PWM (§7) |
| 8 | RGB “breathing” effect | Three PWM channels, gamma correction (§8.6) |
| 9 | Copy your work off the Pi and delete it | The lab Pis are shared — see §10 |
2Understanding the GPIO Port
2.1 What a GPIO pin actually is
GPIO stands for general-purpose input/output. These are configurable pins on the processor: the same physical pin can be set up as an input (the processor reads whether the voltage is high or low) or as an output (the processor drives the pin to 3.3 V or 0 V). “General-purpose” means the pin has no fixed job; your program decides.
Internally, an output pin is a pair of transistors acting as a two-way switch. Writing a 1 connects the pin to the 3.3 V rail through a small resistance; writing a 0 connects it to ground. This is called a push-pull output, because it can both source current out of the pin and sink current into it.
An output GPIO pin behaves like a 3.3 V voltage source with a switch and a small internal resistance — not like a current source. Exactly as with the LED driver in Lab 1, a voltage source connected directly to an LED is a recipe for destroying something. The series resistor is not optional.
2.2 Two numbering schemes: BOARD and BCM
Here is the single most common source of confusion in this lab. The Raspberry Pi has two different ways of numbering the same pins, and a program written in one scheme will light the wrong LED — or nothing at all — if you wire it using the other.
| Mode | What the number means | Range and notes |
|---|---|---|
| BOARD | The pin’s physical position on the 40-pin header, counted exactly as printed on the board. | 1 to 40, in order. Pin 1 is the corner nearest the SD-card end; odd numbers run down one row, even numbers down the other. Easy to count with your finger; never changes between Pi models. |
| BCM (Broadcom) |
The processor’s internal name for that signal, e.g. GPIO17. | Not sequential along the header, and there are gaps. This is the name used in datasheets, in pinctrl, and in almost all published example code. |
Why are they different? The processor has far more internal I/O lines than the 40-pin header exposes, and the header layout was chosen for wiring convenience and backward compatibility, not to match the chip’s internal numbering. So the processor pin number and the board header pin number will essentially always disagree. Physical pin 11 is BCM 17; physical pin 12 is BCM 18.
Use BCM numbering, and declare it on the first line of every script with GPIO.setmode(GPIO.BCM). RPi.GPIO will happily accept either scheme, which is exactly why you must state which one you mean — and every datasheet, every pinctrl command and almost every published example uses BCM. Mixing the two schemes inside one program, or across the group members at one bench, is the number-one cause of “my LED doesn’t light and I can’t see why.”
In your lab report, always write pin numbers unambiguously: “BCM 17 (physical pin 11)”, not just “pin 17.”
2.3 The 40-pin header map
Read this table exactly as the header sits on the board: two rows of twenty, physical pin numbers in the dark centre columns, BCM names on the outside. Alternate functions (I²C, SPI, UART, PWM) are shown in small type — those pins still work as ordinary GPIO as long as the corresponding interface is not enabled.
| 3v3 Power | 1 | 2 | 5v Power |
| BCM 2 (SDA) | 3 | 4 | 5v Power |
| BCM 3 (SCL) | 5 | 6 | Ground |
| BCM 4 (GPCLK0) | 7 | 8 | BCM 14 (TXD) |
| Ground | 9 | 10 | BCM 15 (RXD) |
| BCM 17 | 11 | 12 | BCM 18 (PWM0) |
| BCM 27 | 13 | 14 | Ground |
| BCM 22 | 15 | 16 | BCM 23 |
| 3v3 Power | 17 | 18 | BCM 24 |
| BCM 10 (MOSI) | 19 | 20 | Ground |
| BCM 9 (MISO) | 21 | 22 | BCM 25 |
| BCM 11 (SCLK) | 23 | 24 | BCM 8 (CE0) |
| Ground | 25 | 26 | BCM 7 (CE1) |
| BCM 0 (ID_SD) | 27 | 28 | BCM 1 (ID_SC) |
| BCM 5 | 29 | 30 | Ground |
| BCM 6 | 31 | 32 | BCM 12 (PWM0) |
| BCM 13 (PWM1) | 33 | 34 | Ground |
| BCM 19 (PWM1) | 35 | 36 | BCM 16 |
| BCM 26 | 37 | 38 | BCM 20 (MOSI) |
| Ground | 39 | 40 | BCM 21 (SCLK) |
Yellow = the pins recommended for Lab 3. BCM 12, 13, 18 and 19 are the four hardware-PWM-capable pins (§7.5).
Nothing forces these choices, but they keep your wiring tidy, avoid every pin with a special boot-time function, and leave the hardware-PWM pins free for Tasks 7 and 8.
- Task 3 — single LED: BCM 17 (physical 11), ground at physical 9.
- Task 4 — blue on BCM 17 (physical 11), green on BCM 27 (physical 13), ground at physical 9 or 14.
- Task 5 — add red on BCM 22 (physical 15).
- Tasks 7–8 — PWM on BCM 18, 13 and 19 (physical 12, 33 and 35), so hardware PWM stays available.
Avoid BCM 0 and 1 (reserved for HAT identification), and BCM 14/15 if the serial console is enabled.
2.4 Electrical limits: the rules that protect the Pi
The Raspberry Pi’s GPIO has no protection circuitry to speak of. There is no fuse and no current limiting between a GPIO pin and the processor. Exceed these numbers and you damage a £60 computer, not a 3-cent component.
| Limit | Value | What it means for you |
|---|---|---|
| Output high voltage | 3.3 V | Not 5 V. Every logic calculation in this lab uses 3.3 V as the source voltage. |
| Maximum input voltage | 3.3 V | The pins are not 5 V tolerant. Putting 5 V on a GPIO pin can destroy the processor instantly and permanently. The 5 V pins on the header (physical 2 and 4) are power outputs only — never route them back into a GPIO pin. |
| Recommended current, one pin | 16 mA | This is the absolute ceiling. Design your LEDs for roughly 5–10 mA and you have comfortable margin. |
| Total current, all GPIO | ~50 mA | Three LEDs at 8 mA is 24 mA — fine. Eight LEDs at 16 mA would be 128 mA and is not. |
| 3v3 power pin budget | ~50 mA | The 3v3 header pins (physical 1 and 17) are for pull-ups and small sensors, not for powering loads. |
| 5v power pins | do not use | Physical 2 and 4 come straight from the USB-C input. Lab 3 has no need for them, and a stray 5 V wire near the header is the most expensive mistake available at this bench. |
The 16 mA-per-pin limit is precisely why Lab 4 does not drive a motor straight from a GPIO pin: a small DC motor wants hundreds of milliamps. There you will use an IRL510 MOSFET and a separate supply, with the GPIO pin controlling only the gate. Whenever a load needs more current than a pin can give, the GPIO pin commands and a transistor does the work.
2.5 Finding pins at the bench
Never count header pins from memory — the Pi will tell you. Both commands are preinstalled and need no internet connection.
# Print a labelled diagram of the physical header for this exact board pinout # Read the current function and level of a single BCM pin pinctrl get 17 # Force BCM 17 to be an output driven high -- a fast way to test an LED # circuit with no Python at all. Very useful for debugging. pinctrl set 17 op dh pinctrl set 17 op dl # ...and drive it low again
If your LED will not light, run pinctrl set 17 op dh before you debug your Python. If the LED lights, your wiring is good and the bug is in software. If it does not, the bug is in your circuit. This single test splits the problem in half in about five seconds.
(a) You are told to connect an LED to “pin 11.” Which BCM number is that, and which physical pin would you use instead if the instruction had meant BCM 11?
(b) A partner wires an LED to physical pin 12 but the code says GPIO.setup(12, GPIO.OUT) after GPIO.setmode(GPIO.BCM). Which physical pin is the program actually driving, and what will the LED do?
(c) You want to drive five LEDs at once. What is the largest per-LED current you may design for, and why?
3Driving an LED from a GPIO Pin
3.1 Why the series resistor is not optional
This is the same argument you met with the LED driver in Lab 1, and it is worth repeating because now the voltage source is inside a computer you do not want to damage.
An LED is a diode. Above its forward voltage VF, its current rises roughly exponentially with voltage:
A 50 mV change in voltage near the knee can multiply the current several times over. So an LED connected straight across a 3.3 V source has no well-defined operating current: whatever current flows is set by the tiny, uncontrolled series resistances of the pin, the wires and the die. That current is typically far past the LED’s rating and past the pin’s 16 mA ceiling. The LED may survive; the GPIO pin may not.
Adding a series resistor converts an exponential device into a nearly linear one. Because the resistor drops whatever the LED does not, the current is set by Ohm’s law and is insensitive to the exact value of VF.
3.2 The design equation
Apply Kirchhoff’s voltage law around the loop. The pin supplies 3.3 V, the LED drops VF, and the resistor takes the remainder:
Design from the current you want, exactly as you did in Lab 1 — choose the current first, then solve for the resistor:
Then round up to the next standard resistor value. Rounding up lowers the current, which is the safe direction.
470 Ω is the default value for this lab, and 330 Ω also works. Between them they cover every LED in Lab 3. Take a red LED with VF = 2.0 V on a 3.3 V GPIO pin:
Both sit far below the 16 mA pin limit and light the LED clearly. Power in the resistor is I²R = (2.8 mA)² × 470 Ω ≈ 3.7 mW, so any 1/4 W part is fine.
Notice how little current a modern LED actually needs. A few milliamps is plenty — the high-efficiency LEDs in your kit are visible well below 1 mA, which is why one resistor value works across every color. Report which value you used and the current it gives for each LED.
3.3 Forward voltage depends on color — and it matters here
An LED’s forward voltage is set by the band gap of its semiconductor, and the band gap sets the photon energy — that is, the color. Blue photons carry more energy than red ones, so a blue LED needs a noticeably higher forward voltage than a red one.
Work the cases with Equation (1), driving from a 3.3 V GPIO pin:
| LED color | Typical VF | Headroom (3.3 V − VF) | I with 470 Ω | I with 330 Ω |
|---|---|---|---|---|
| Red | 1.8 – 2.0 V | 1.3 – 1.5 V | 2.8 – 3.2 mA | 3.9 – 4.5 mA |
| Yellow / amber | 2.0 – 2.2 V | 1.1 – 1.3 V | 2.3 – 2.8 mA | 3.3 – 3.9 mA |
| Green | 2.0 – 2.4 V | 0.9 – 1.3 V | 1.9 – 2.8 mA | 2.7 – 3.9 mA |
| Blue | 2.7 – 3.0 V | 0.3 – 0.6 V | 0.6 – 1.3 mA | 0.9 – 1.8 mA |
Read down the current columns. With one 470 Ω resistor, the red LED draws about 3 mA and the blue about 1 mA — a factor of three, purely because the blue LED’s higher forward voltage leaves less across the resistor. Nothing is wrong; this is Equation (1) doing exactly what it says.
All four still light clearly, because a modern high-efficiency LED needs very little current to be bright. But if you look carefully at a red and a blue LED side by side on identical resistors, you may notice a brightness difference. That observation belongs in your report, together with the calculation that explains it — it is a small, clean example of a component parameter propagating into observable behaviour.
Measure your actual LEDs’ forward voltages with the DMM’s diode-test function and tabulate them. Then predict each current from Equation (1) and check the prediction by measuring the voltage across the resistor and dividing by R. A predicted-versus-measured table for three or four LEDs is genuinely good lab-report content, and it takes about ten minutes.
3.4 Sourcing and sinking
There are two ways to wire an LED to a GPIO pin, and you will meet both in published examples.
| Arrangement | Wiring | LED is on when | Comment |
|---|---|---|---|
| Sourcing (active high) | GPIO pin → R → LED anode; LED cathode → ground | pin is HIGH | Current flows out of the pin. The intuitive arrangement: GPIO.HIGH means on. Use this throughout Lab 3. |
| Sinking (active low) | 3v3 → R → LED anode; LED cathode → GPIO pin | pin is LOW | Current flows into the pin. Every piece of logic inverts, which catches people out. You will meet it in Task 6 if your RGB LED turns out to be common-anode (§8.1). |
3.5 Wiring checklist before you apply power
- LED long leg (anode) toward the resistor and the GPIO pin; short leg (cathode, also the flat side of the rim) toward ground.
- Exactly one resistor in series with each LED — never one shared resistor for several LEDs (§8.3 explains why).
- Ground return goes to a header ground pin (physical 6, 9, 14, 20, 25, 30, 34 or 39), and to the same ground rail your oscilloscope probe clips to.
- Nothing connected to the 5 V pins (physical 2 and 4) — Lab 3 does not need them.
- Count the header pin twice, from the corner, with the board oriented the same way each time.
- Show the circuit and the code to the instructor or a TA before you run it — Lab 3, Task 3d requires this.
(a) A green LED measures VF = 2.1 V. Find its current on a 3.3 V pin with 470 Ω, and again with 330 Ω. By what factor does the current change, and is it the same factor as the resistor ratio? Explain the discrepancy if there is one.
(b) A student uses 470 Ω with a red LED (VF = 1.8 V). Find the current. Is it within the per-pin limit? Within the total-GPIO limit if four identical LEDs run at once?
(c) Using Equation (2), what resistor would give exactly 5 mA through that red LED? Is that value in your kit, and does it matter?
(d) Explain in one or two sentences why the current through the LED is far less sensitive to the exact value of VF when a 470 Ω resistor is present than when it is absent.
4Talking to the Pins from Python
4.1 Every GPIO program has the same three parts
- Set up — declare which numbering scheme you use and which pins are outputs.
- Drive — write HIGH or LOW, usually inside a loop with delays.
- Clean up — return the pins to a safe default state when the program ends.
4.2 Which library on the lab Pi?
RPi.GPIO is the library for this course. It is installed and working on the lab Pis, it is the library used in the lecture slides, and it is what the overwhelming majority of Raspberry Pi example code on the internet is written in. Everything in these notes uses it unless stated otherwise.
A second library, gpiozero, is also preinstalled. It wraps RPi.GPIO-style operations in higher-level objects (LED, PWMLED, RGBLED) that handle setup and cleanup for you. It is shown alongside the main examples below so you can recognise it, and you are free to use it — just say which library you used in your report, and do not mix the two in one script.
Confirm your environment in the terminal before you start writing:
# Which Python am I using? (Task 2b asks for this) python3 --version # Confirm the GPIO library imports and the board is recognised. # Prints "RPi.GPIO OK" and the library version if all is well. python3 -c "import RPi.GPIO as GPIO; GPIO.setmode(GPIO.BCM); print('RPi.GPIO OK', GPIO.VERSION)"
Older references — including some still online — warn that RPi.GPIO does not work on the Raspberry Pi 5, because the Pi 5 moved GPIO handling onto a separate controller chip called RP1. That was true when the Pi 5 launched. Current Raspberry Pi OS releases ship an updated RPi.GPIO that talks to the new hardware, so the problem is fixed and the code below runs as written.
Mentioning this is not pedantry: it is a good illustration of why you check the date on a source. If you find example code that fails with an error about an unsupported chip, the fix is to update the OS, not to rewrite the program.
4.3 The RPi.GPIO calls you need
This is the whole vocabulary for Lab 3. Everything in Tasks 3 to 8 is built from these calls; what you build is the assignment.
| Call | What it does |
|---|---|
import RPi.GPIO as GPIO | The conventional import. Every call below is then prefixed GPIO. |
GPIO.setmode(GPIO.BCM) | Interpret all pin numbers as BCM names. The alternative is GPIO.BOARD; pick one and never mix (§2.2). |
GPIO.setwarnings(False) | Suppresses the “channel already in use” warning. Convenient, but note that the warning is often telling you something true. |
GPIO.setup(pin, GPIO.OUT) | Configure a pin as an output. pin may be a single number or a list of numbers. |
GPIO.output(pin, GPIO.HIGH) | Drive the pin to 3.3 V. GPIO.LOW drives it to 0 V. Accepts a list here too. |
GPIO.cleanup() | Release every pin the script configured. GPIO.cleanup(pin) releases just one (§4.4). |
GPIO.PWM(pin, freq) | Create a PWM generator on an output pin. See §7.4 for the methods it provides. |
sleep(seconds) | From time. Accepts a float, so sleep(0.001) is one millisecond — approximately (§5). |
4.4 A worked example: blinking one LED
This is the example from the lecture slides, written out in full and commented, and it is the only complete program in these notes. Read every line — each one maps onto one of the three steps in §4.1. Use it as the pattern, not as something to copy into a task.
# Blink a single LED on BCM 17 (physical pin 11). import RPi.GPIO as GPIO from time import sleep # ---- 1. SET UP ---- GPIO.setmode(GPIO.BCM) # interpret pin numbers as BCM names GPIO.setwarnings(False) # suppress "channel already in use" noise GPIO.setup(17, GPIO.OUT) # BCM 17 becomes an output print("Device is all set. Watch the light flash!") # ---- 2. DRIVE ---- try: for i in range(10): GPIO.output(17, GPIO.HIGH) # pin -> 3.3 V sleep(1.0) # ...for about one second GPIO.output(17, GPIO.LOW) # pin -> 0 V sleep(0.5) # ...for about half a second # ---- 3. CLEAN UP ---- finally: GPIO.cleanup() # runs even if you press Ctrl+C
The same thing in gpiozero, for comparison
Shown only so you recognise this style when you meet it. It is the identical circuit and the identical behaviour; the library simply performs the setup and cleanup for you.
from gpiozero import LED from time import sleep led = LED(17) # BCM 17; gpiozero has no BOARD option for i in range(10): led.on() sleep(1.0) led.off() sleep(0.5) led.close()
range(1, 10)runs nine times, not ten.range(10)runs ten. A classic off-by-one — and something an oscilloscope will show you immediately.Printwith a capital P is aNameErrorin Python 3; the function isprint. Likewise, Python 3 requires the parentheses.- Putting
cleanup()in afinally:block means the pins are released even when you stop the program with Ctrl+C — which is how you will stop most of your loops in this lab.
4.5 Why cleanup matters on a shared lab Pi
When a Python program exits without releasing its pins, those pins keep whatever state they were left in. An LED can stay lit after the program ends, and the next program that tries to claim the pin may warn that it is already in use — or, if a previous process crashed while holding it, refuse to claim it at all. GPIO.cleanup() returns every pin your program configured to a safe, high-impedance input state. Call it in a finally: block so it runs no matter how the program ends.
A related habit: GPIO.cleanup() with no arguments releases all channels your script set up. You can also release a single pin with GPIO.cleanup(17), which is worth knowing when one script drives several LEDs and you want to hand just one pin back.
4.6 Task 4 — alternating two LEDs
Task 4 asks for a blue LED and a green LED alternating every 2 seconds. The structure you want is: turn one on and the other off, wait, then swap. Do not write two independent blink loops — they will drift apart.
Before you write anything, work out on paper:
- How many pins do you set up, and what does each
GPIO.setup()line look like? - In a single pass of the loop, how many
GPIO.output()calls are needed, and in what order, so that exactly one LED is lit at any moment? - Where do the
sleep()calls go, and what is the resulting period of the complete cycle? Predict it before you measure it. - How will you stop the program cleanly and release the pins?
One piece of structural advice that will save you time later: put the delay in a named constant at the top of the file rather than typing the number into each sleep(). Task 4d asks you to sweep the rate, and a single constant means changing one line per run instead of four.
Task 4 then asks you to raise the frequency and find the maximum. Because DELAY is a single named constant, you can sweep it without touching the rest of the program. Take the delay through, say, 2 s → 0.5 s → 0.1 s → 10 ms → 1 ms → 100 µs → 10 µs → 0, and record the measured scope frequency at each step in a table. That table is the core result of Task 4 and the thing §5 explains.
5Timing: Why sleep() Is Not a Clock
This is the analytical heart of Lab 3. Everything up to here has been about getting an LED to light. Now we ask a harder question: when exactly does it light, and how well can the Pi be trusted to repeat that?
5.1 Vocabulary
| Quantity | Definition | What it is on the scope |
|---|---|---|
| Period, T | Time for one complete on–off cycle | Rising edge to the next rising edge |
| Frequency, f | f = 1/T | The scope’s Freq measurement |
| On-time, ton | Time spent HIGH in one period | +Width or Pulse Width |
| Duty cycle, D | D = ton/T × 100% | The scope’s Duty measurement |
| Jitter | Cycle-to-cycle variation of the period | Spread of the Period measurement: max − min, or its standard deviation. Visible directly as edge blur in persistence mode. |
| Latency | Delay between asking for an edge and getting one | Not directly visible here; contributes to the overhead in §5.2 |
5.2 The loop period equation
Take the blink loop from §4.4. You asked for 1.0 s on and 0.5 s off, so you might expect T = 1.5 s exactly. What you actually get is
where toverhead covers everything the computer does that is not sleeping: executing the Python bytecode, calling into the GPIO library, the kernel switching to your process and back, and any other work the machine happens to be doing. Two consequences follow immediately, and both are testable at the bench:
- The measured period is always longer than the requested one. Overhead only ever adds. There is no mechanism that makes a loop finish early.
- The error is roughly constant in absolute terms, so it matters more as you go faster. A 2 ms overhead on a 1.5 s period is an error of 0.1% — invisible. The same 2 ms on a requested 1 ms period is a 200% error. This is exactly what you will watch happen in Task 4.
5.3 Where the extra time comes from
Five separate mechanisms contribute, and it is worth being able to name them in your report rather than writing “the Pi is slow.”
- Linux is not a real-time operating system. The kernel runs dozens of processes and shares the CPU by preempting them on a scheduling tick. Your program can be interrupted at any moment, for as long as the scheduler decides. This is the dominant source of large, unpredictable outliers.
sleep()guarantees a minimum, not an exact delay. The call asks the kernel to wake the process no earlier than the requested time. When the wake-up actually happens depends on timer resolution and on whether the CPU is free. For short sleeps the rounding is significant: requesting 100 µs commonly yields something noticeably longer.- Python interpreter overhead. Every
GPIO.output()is a Python function call and a library call before any hardware register is touched — microseconds per operation, not nanoseconds. A higher-level wrapper such asgpiozeroadds further layers and is slower still per toggle, which is one reason this course works at theRPi.GPIOlevel. - Garbage collection. Python periodically pauses to reclaim memory. These pauses are short but arrive unpredictably, adding occasional outliers to an otherwise tidy distribution.
- Everything else on the machine. The desktop environment, network activity, USB polling, and SD-card writes all steal CPU time. A Pi running the full desktop jitters measurably more than one at a bare console — something you can test.
5.4 Finding the maximum toggle frequency (Task 4d)
Setting the delay to zero removes the sleep entirely and leaves a loop that toggles the pin as fast as Python can go. The period is then pure overhead:
— the factor of two because one full period needs two writes, one high and one low.
Do not expect a particular number and do not copy one from these notes: the answer depends on your Pi model, your library, and what else the machine is doing, and measuring your own value is the point of the task. What you should expect qualitatively is a ceiling somewhere in the kilohertz range, with gpiozero slower than a lower-level library, and with the jitter growing dramatically in relative terms as you approach it.
To find the ceiling, remove the delays entirely and time a large, known number of cycles. The tools you need are time.perf_counter() — the monotonic high-resolution clock, not time.time(), which is coarser — called once before and once after the loop. Dividing the cycle count by the elapsed time gives the frequency the software believes it achieved.
The script above prints the frequency the software believes it achieved. The oscilloscope measures what the pin actually did. Put both in your report. If they disagree, that disagreement is itself data: software timing measures average throughput and is blind to individual late cycles, while the scope sees every edge.
5.5 Measuring jitter on the oscilloscope
“Check for timing jitter” in Task 4 means producing a number, not an impression. Three methods, in increasing order of rigour:
- Measurement statistics (best). Turn on the scope’s Period measurement, then enable Statistics. The scope accumulates many measurements and reports mean, minimum, maximum and standard deviation. Then
jitterpk–pk = Tmax − Tmin, jitterRMS = σTReport both, and report the mean period alongside them so the numbers can be interpreted as a fraction.
- Infinite persistence (most vivid). Set the display to infinite persistence and trigger on a rising edge. The first edge after the trigger stays sharp; each subsequent edge smears into a band. The width of that band, read off the timebase, is the accumulated jitter — and it grows the further you look from the trigger. This produces the single most convincing screenshot you can put in the report.
- Eyeball comparison (quickest). Set the timebase so one period fills the screen and watch the falling edge wander relative to the graticule. Fine for a sanity check; not a number.
A table with one row per delay setting and these columns: requested delay, expected frequency (= 1/2tdelay), measured mean frequency, measured mean period, jitter (peak-to-peak), jitter (RMS), and jitter as a percentage of the period. Then a short paragraph interpreting it: at what frequency does the percentage error stop being negligible, and why?
Save a scope screenshot at a slow rate (clean) and one near the maximum rate (jittery) to a USB drive, and include both.
A loop written for a 1 kHz square wave gives these scope statistics over 500 measurements:
Tmean = 1.213 ms, Tmin = 1.140 ms, Tmax = 1.760 ms, σT = 46 µs.
- Mean frequency: 1/1.213 ms = 824 Hz — not 1000 Hz. The overhead is 1.213 − 1.000 = 213 µs per cycle, so the period is 21% too long and the frequency falls 17.6% short. Note that these two percentages are not equal: because f = 1/T, a fractional period error ε gives a fractional frequency error of ε/(1+ε). State which one you are quoting.
- Peak-to-peak jitter: 1.760 − 1.140 = 620 µs, i.e. 51% of the mean period. One cycle in the set took nearly 50% longer than average — almost certainly a scheduler preemption.
- RMS jitter: 46 µs, or 3.8% of the period. The typical cycle is much better behaved than the worst one; the distribution has a tight core and rare large outliers. That asymmetry is the signature of OS scheduling, and saying so is exactly the kind of interpretation the report is looking for.
5.6 Things that will wreck your timing
print()inside the timing loop. Writing to a terminal takes milliseconds and is wildly variable. It is the single most common reason a student’s loop runs a hundred times slower than expected. Collect your data in a list and print after the loop.- Recomputing things every iteration. Anything constant should be computed once before the loop.
- A busy desktop. Close the browser and any other windows before taking timing data. Consider comparing timing with and without the desktop in use — it is a nice extra result.
- Using
time.time()instead oftime.perf_counter().perf_counter()is the monotonic high-resolution clock intended for measuring intervals.
(a) A loop calls sleep(0.001) twice per cycle. The scope reports a mean period of 2.6 ms. What is the per-cycle overhead, and what frequency error does it represent?
(b) The same program run with the delays set to 0.5 s shows a mean period of 1.0016 s. Compute the percentage error and explain, using Equation (3), why it is so much smaller than in part (a).
(c) A scope reports Tmean = 500 µs and σT = 35 µs. Would this signal be acceptable as a clock for a circuit that needs 1% timing accuracy? Justify your answer with a number.
6Flicker Fusion: When Blinking Becomes Brightness
As you sweep the blink rate upward in Task 4, something qualitative happens well before you reach the software limit. Somewhere around 50–90 Hz the LED stops looking like it is flashing and starts looking like it is simply on, but dimmer. This threshold is called the flicker fusion frequency, and it is a property of your eye and brain, not of the circuit.
The retina integrates light over roughly 10–20 ms. Once the flashes arrive faster than that, the visual system averages them and perceives a steady source whose brightness corresponds to the average optical power. Peripheral vision is faster than central vision, which is why a flickering LED at the edge of your field of view can look like it is flashing while the same LED looks steady when you stare at it.
Flicker fusion is what makes brightness control possible without changing any voltage or current. If you switch an LED fully on and fully off faster than the eye can follow, the perceived brightness is set by the fraction of time it spends on. That fraction is the duty cycle, and controlling it is pulse-width modulation.
Note how efficient this is: the LED is only ever fully on or fully off, so the switching transistor dissipates almost no power in either state. That is why PWM, not a variable resistor, is how real products dim LEDs, control motor speed, and regulate power supplies.
In your report for Task 4, note the frequency at which you stopped perceiving flicker, and compare it with the frequency at which the oscilloscope still clearly shows separate pulses. The gap between the two is a good illustration of why an instrument is more trustworthy than an eye.
7Pulse-Width Modulation
7.1 Definition
A PWM signal is a square wave with a fixed period whose on-time is variable. The controlled quantity is the duty cycle:
Changing the duty cycle does not change the frequency, and changing the frequency does not change the duty cycle. They are independent controls, and Task 7b asks you to vary each one and watch that independence on the scope.
7.2 Average voltage and average current
Averaging the waveform over one period, with the low level at 0 V:
with D expressed as a fraction here. Note carefully what is not happening: the LED’s instantaneous current never changes. When the pin is high the LED runs at its full design current from Equation (1); when the pin is low it is completely off. Only the time-average changes, and since both the LED’s light output and your eye respond to the average, the LED appears dimmer.
Equation (6) is a good approximation, not an identity. An LED’s efficiency varies slightly with current, and the switching edges are not infinitely fast. More importantly, perceived brightness is not proportional to average current at all — the eye’s response is strongly compressive. See §8.6.
7.3 Choosing the PWM frequency
| Frequency range | What happens |
|---|---|
| Below ~50 Hz | Visible flicker. Useless for dimming, but very useful for demonstrating what PWM is — start here in Task 7 so you can see the duty cycle changing before you speed it up. |
| ~100 Hz to ~1 kHz | The practical range for LED dimming. Above flicker fusion, easy for software PWM to generate, and slow enough that the scope shows clean edges. Start at 100 Hz. |
| 1 kHz to ~20 kHz | Still fine for LEDs. This is the range used for motor drive (Lab 4), often pushed above 20 kHz so the switching is inaudible. |
| Above ~20 kHz | Software PWM on the Pi breaks down: the duty cycle becomes inaccurate and jittery because the CPU cannot service the edges reliably. Hardware PWM handles it easily. |
7.4 Software PWM
Software PWM is exactly the blink loop from §5, generated by a background thread, with the on and off delays computed from the requested duty cycle. Everything you learned about jitter in §5 applies directly — the edges are placed by the CPU, so the scheduler can move them.
The RPi.GPIO calls you need are these four. Each line below is the syntax, not a program — assembling them into something that performs Task 7 is the assignment.
| Call | What it does |
|---|---|
pwm = GPIO.PWM(pin, freq) | Creates a PWM object on a pin already set up as an output. freq is in hertz. |
pwm.start(duty) | Begins output. duty is a percentage, 0 to 100. |
pwm.ChangeDutyCycle(duty) | Changes the duty cycle while running; the frequency is untouched. |
pwm.ChangeFrequency(freq) | Changes the frequency while running; the duty cycle is untouched. |
pwm.stop() | Stops the output. Call it before GPIO.cleanup(), or the PWM thread lingers. |
The equivalent in gpiozero, should you meet it, is PWMLED(pin, frequency=...) with a .value property and a .frequency property.
RPi.GPIO takes duty cycle as a percentage from 0 to 100. gpiozero takes it as a fraction from 0.0 to 1.0. So pwm.ChangeDutyCycle(0.5) does not give you half brightness — it gives you 0.5%, which looks like “off,” and it is a very easy half-hour to lose. In RPi.GPIO, half brightness is ChangeDutyCycle(50).
7.5 Hardware PWM
The Pi contains dedicated PWM peripherals — small counters in silicon that toggle a pin without any CPU involvement at all. Once configured, the hardware generates every edge on a crystal-derived clock. The jitter is orders of magnitude lower than software PWM, and it does not degrade when the processor is busy.
Hardware PWM is available on four pins: BCM 12, 13, 18 and 19 (physical 32, 33, 12 and 35), arranged as two independent channels. Enabling it requires a device-tree overlay in /boot/firmware/config.txt and a reboot, so check with the instructor or a TA before changing any system configuration on a shared lab Pi. Note that GPIO.PWM() in RPi.GPIO is always software PWM — it never uses the hardware peripheral, whatever pin you give it. That is fine for this lab, as long as you say so in your report rather than claiming a hardware measurement you did not make.
The honest answer has two halves, and a good report gives both:
- Software PWM is better than a hand-written blink loop, but only somewhat. It runs in a dedicated thread with tighter code, so the average frequency is closer to what you asked for. The edges are still placed by the CPU, so scheduler jitter is still there. Compare the scope’s period statistics for your Task-4 loop and your Task-7 software PWM at the same frequency — that comparison is the measurement Task 7d is asking for.
- Hardware PWM is categorically better. Because no software is involved per edge, the jitter drops to the picosecond-to-nanosecond level of the crystal oscillator. This is the real engineering lesson of Lab 3: you do not make a computer keep better time by writing cleverer code, you move the timing into hardware built for it.
7.6 Measuring PWM on the scope (Tasks 7b and 7c)
Task 7c asks you to report the waveform period and on-time. On the scope, turn on three automatic measurements and read them directly: Period, +Width (the on-time), and Duty Cycle. Verify that they are consistent with Equation (5) — +Width divided by Period should equal the duty cycle.
You set 1 kHz and 30% duty cycle. Predict the measurements before you look:
- Period: T = 1/(1000 Hz) = 1.00 ms
- On-time: ton = 0.30 × 1.00 ms = 300 µs
- Off-time: 1.00 ms − 300 µs = 700 µs
- Average voltage: 0.30 × 3.3 V = 0.99 V — and a DMM set to DC volts across the pin should read close to this, which is a nice independent cross-check
- Average LED current with a 330 Ω resistor and a 2.0 V red LED: 0.30 × 3.9 mA = 1.2 mA
Now change the frequency to 500 Hz and keep the duty cycle at 30%. The period doubles to 2.00 ms and the on-time doubles to 600 µs, but the duty cycle, the average voltage, and the apparent brightness are all unchanged. Demonstrating this on the scope is the clearest possible proof that the two controls are independent.
Put a DMM on DC volts across the GPIO pin while PWM runs at a few hundred hertz. The meter averages, so it reads Vavg from Equation (6). Sweep the duty cycle from 0 to 100% in steps and plot DMM reading against commanded duty cycle. A straight line through the origin with slope 3.3 V confirms both your understanding and the PWM generator’s accuracy — and any departure from straightness at the extremes is a real, interesting result about the PWM implementation.
(a) A PWM signal has T = 2.5 ms and ton = 1.75 ms. Find the frequency, the duty cycle, and the average voltage for a 3.3 V logic high.
(b) You need an average of 1.2 V from a 3.3 V PWM signal at 250 Hz. Find the required duty cycle and the resulting on-time in microseconds.
(c) Explain why an LED dimmed to 10% duty cycle at 500 Hz looks steady, while the same LED at 10% duty cycle and 5 Hz looks like it is flashing — even though the average current is identical.
(d) Your scope reports Period = 1.05 ms and +Width = 0.52 ms for a signal you commanded to be 1 kHz at 50%. Comment on both the frequency error and the duty-cycle error, and say which one points to software rather than hardware PWM.
8RGB LEDs and the Breathing Effect
8.1 What is inside the package
An RGB LED is three separate LED dies — red, green and blue — in one plastic body. It has four legs: one for each color, plus one common leg shared by all three. There are two kinds, and wiring one as if it were the other simply will not work.
| Type | Common leg connects to | Color leg connects to | Logic |
|---|---|---|---|
| Common cathode | Ground | Resistor, then a GPIO pin | Pin HIGH = that color on. Sourcing. Easier — prefer this. |
| Common anode | 3v3 (physical pin 1) | Resistor, then a GPIO pin | Pin LOW = that color on. Sinking, so all your logic and all your duty cycles are inverted. |
Everything inverts. A duty cycle of 100% means off, and 0% means fully on, and GPIO.HIGH turns a color off rather than on.
Rather than scattering 100 - duty through your program — where you will certainly forget one — define a single conversion in one place and route every brightness through it, with a constant at the top of the file recording which kind of LED you have. That one piece of structure lets the same program drive either wiring, and it is the difference between Task 8 taking twenty minutes and taking two hours.
8.2 Identifying the legs
- Find the common leg: it is the longest one, usually the second from one end, not on a corner.
- Determine the type: use the DMM’s diode-test function. Put the red (positive) probe on the long leg and touch the black probe to each other leg in turn. If colors light up, it is common anode. If nothing lights, swap the probes: if colors now light with the black probe on the long leg, it is common cathode.
- Map the colors: the same diode test tells you which leg is red, green and blue. Write the mapping down — the standard order is red, common, green, blue, but do not trust it without checking.
8.3 One resistor per color — never one shared
It is tempting to put a single resistor in the common leg and save two components. Do not. Here is why, and it is a genuinely instructive circuit-analysis point:
- With a shared resistor, the three dies are in parallel above it. They have different forward voltages, so the one with the lowest VF — the red — hogs almost all the current, while blue gets almost none.
- Worse, the current through any one die then depends on how many others are on. Turn on green as well as red and the red visibly dims. Your colors become uncontrollable and unrepeatable, which destroys any hope of color mixing in Task 8.
- With an individual resistor in each color leg, each branch’s current is set independently by Equation (1), and turning on one color has no effect on the others.
Start with the same value in all three legs — 470 Ω each, as elsewhere in this lab. Because the three dies have different forward voltages, they will draw different currents from equal resistors (the §3.3 table, now applied inside one package), so the three colors may not look equally bright. For Task 6 that hardly matters. For the color mixing in Task 8 it does, and you have two ways to correct it: change the resistor in the leg that is too bright, or leave the resistors alone and trim that channel’s duty cycle in software. The second is easier, more precise, and adjustable while the program runs — which is itself a small lesson in why PWM is so useful. Say which you did.
8.4 Color mixing with three PWM channels
Three PWM channels driving three dies gives three independent duty cycles, and the eye fuses the result into one perceived color. This is additive mixing, the same principle every display uses.
| Perceived color | R duty | G duty | B duty |
|---|---|---|---|
| Red | 1.0 | 0 | 0 |
| Yellow | 1.0 | 1.0 | 0 |
| Cyan | 0 | 1.0 | 1.0 |
| Magenta | 1.0 | 0 | 1.0 |
| White (approximately) | 1.0 | 1.0 | 1.0 |
| Orange | 1.0 | 0.4 | 0 |
| Purple | 0.6 | 0 | 1.0 |
“White” from a three-die LED at equal duty cycles usually looks tinted, because the three dies do not have equal luminous efficiency and your resistors are not matched to compensate. Correcting it by trimming the duty cycles is a good optional experiment.
8.5 Task 6 — flashing three colors in sequence
Task 6 needs no PWM at all: switch each color fully on for a fixed time, in turn, at a rate slow enough to see (0.3–1 s per color). Treating the three pins as a list keeps the loop short.
Points to settle before you write it:
- Which three BCM pins, and which physical pins do they correspond to? Write both down.
GPIO.setup()accepts a list of pins, and so doesGPIO.output()— worth knowing, because it shortens this task considerably.- What must happen to the other two colors each time you turn one on? A sequence that only ever turns colors on ends with all three lit and looking white.
- For a common-anode LED, what changes?
8.6 Task 8 — the breathing effect, and why a linear ramp looks wrong
“Breathing” means the brightness rises and falls smoothly and continuously. The obvious implementation is to ramp the duty cycle linearly from 0 to 1 and back. Try it — and then notice that it does not look smooth. It appears to brighten very quickly at the start, then hold nearly constant near the top, and it snaps off abruptly at the bottom.
The reason is human, not electrical. The eye’s response to luminance is strongly compressive: doubling the light does not look twice as bright. The standard engineering model treats perceived brightness as roughly a power law,
To make brightness appear to change linearly, you must therefore command a duty cycle that is the inverse of that curve — this is called gamma correction:
So for a perceived half-brightness, command D = 0.52.2 ≈ 0.22, not 0.5. Applying this one line transforms the breathing effect from mediocre to convincing, and it is exactly the kind of detail that distinguishes a thoughtful Task-8 submission.
Implementing this needs three PWM objects, one per color channel, all created at a frequency above flicker fusion — a couple of hundred hertz is ample. Then a loop steps a brightness variable through a smooth path, converts it with Equation (8), and writes the result to ChangeDutyCycle().
The design decisions are yours, and they are what Task 8 is actually assessing:
- What function of time gives a smooth ramp with no jump when it wraps around? A linear ramp reversing at the ends has a discontinuity in slope; a raised cosine, b = ½(1 − cos 2πt/T), does not.
- How many steps per breath, and how long does each step sleep? These two numbers together set the breath period — and §5 tells you the period will come out slightly long.
- How do the three channels relate to each other? Driving them in phase gives a white breath; offsetting them in phase walks around the color wheel.
- Breathe each color in turn, with the next color fading in as the previous one fades out (a cross-fade), so the light never fully extinguishes.
- Sweep smoothly around the color wheel by driving the three channels with sine waves 120° apart in phase — three lines of code, and it looks excellent.
- Breathe brightness and hue at different rates so the pattern does not repeat for a long time.
Whatever you choose, state the mathematical rule you used in the report. “I wrote a loop that looked nice” is not an engineering description; “each channel follows bk = ½[1 + cos(2πt/T − 2πk/3)], gamma-corrected with γ = 2.2” is.
(a) You want an RGB LED to appear at 25% of full perceived brightness in pure green. What duty cycle should you command on the green channel, using γ = 2.2?
(b) A common-anode RGB LED is wired with its common leg to 3v3. What duty cycle on the red channel produces full red, and what produces off?
(c) A student uses one 330 Ω resistor in the common cathode leg. They report that red looks fine alone, but turning on green makes the red dim noticeably. Explain the observation quantitatively, referring to the forward voltages in §3.3.
9Oscilloscope Technique for This Lab
Lab 3 requires oscilloscope screenshots for Task 4 (LED timing) and Task 7 (PWM waveform). These settings will get you there quickly.
| Setting | Use this | Why |
|---|---|---|
| Probe point | Tip on the GPIO pin side of the resistor; ground clip on a header ground pin | You are measuring the logic signal. Probing across the LED instead shows VF switching, which is also interesting but is not the timing signal. |
| Probe attenuation | 10×, and tell the scope it is 10× | Lower capacitive loading and cleaner edges. If the scope’s setting disagrees with the probe switch, every voltage you read is wrong by 10×. |
| Coupling | DC | AC coupling removes the DC level and makes a duty-cycle measurement meaningless. |
| Vertical | 1 V/div, with 0 V about two divisions from the bottom | A 0–3.3 V swing then fills a useful part of the screen. |
| Trigger | Edge, rising, level ≈ 1.65 V, Auto mode | Mid-supply is the most reliable threshold. Switch to Normal mode once triggering is stable. |
| Timebase | So that 2–5 periods fill the screen | Enough cycles to see repetition, few enough to resolve the edges. |
| Measurements | Period, Frequency, +Width, Duty Cycle — then turn on Statistics | Statistics give you the mean, min, max and standard deviation you need for jitter (§5.5). |
| Display | Infinite persistence for the jitter screenshot | Makes jitter directly visible as edge smear. |
The probe ground clip must go to a Pi header ground pin (physical 6, 9, 14, 20, 25, 30, 34 or 39) or to a breadboard rail tied to one. A floating ground clip produces a display full of noise and edges that seem to jitter far more than they do. Before you record a jitter number, confirm that a known-clean signal — the scope’s own calibration output — looks clean on your setup.
Saving screenshots: write them to a USB drive from the scope, then copy them to your own computer. Give each file a name you will still understand next week — task4_1kHz_persistence.png, not scope_07.png.
10Common Mistakes and Bench Safety
10.1 Things that destroy hardware
- 5 V into a GPIO pin. Instantly fatal to the processor. The pins are 3.3 V only and are not 5 V tolerant. Nothing in Lab 3 requires the 5 V pins, so the safest habit is to leave them alone entirely.
- An LED with no series resistor. Exceeds the pin’s current rating immediately.
- Shorting a power pin to ground. Physical pins 1, 2, 4 and 17 are supplies. A stray wire from one to a ground pin can brown out or damage the board.
- Rewiring with the Pi powered. Shut down cleanly (
sudo shutdown -h now), wait for the activity LED to stop, and unplug before changing the circuit. - Connecting a motor, relay or long wire directly to a pin. Inductive loads generate voltage spikes far outside the safe range. That is what Lab 4’s MOSFET is for.
10.2 Things that waste your afternoon
- BOARD/BCM confusion (§2.2). Check this first, always.
- LED in backwards. It will not light and gives no other symptom. Flip it.
- Blue LED with a 330 Ω resistor (§3.3). It is not broken; it has no headroom.
- A breadboard row misread by one hole. Confirm with continuity on the DMM.
range(1, 10)when you meant ten iterations.print()inside a timing loop (§5.6).- A previous script left running and still holding the pin. Check with
ps aux | grep pythonand stop it. - Editing one file and running another. Check the filename in the VS Code title bar and in the terminal command.
10.3 Before you leave (Task 9)
The lab Pis are shared. Copy your whole YourFirstName_python directory to your own computer — via USB drive, or with scp if networking is available — then delete it from the Pi. Verify the copy opens on your machine before you delete the original. Also power down the bench supply and the scope, and return components to their bins.
11Self-Check
Work these before lab. If you can answer all nine, you are ready for the bench.
- State the difference between BOARD and BCM numbering, and give the BCM name of physical pin 11.
- A GPIO pin is at 3.3 V. Find the current through a red LED (VF = 1.9 V) with a 470 Ω series resistor, and confirm it is within the per-pin limit.
- Why can the Raspberry Pi not drive a small DC motor directly from a GPIO pin? Support your answer with a number.
- Write, from memory, the three steps every GPIO program contains, and say what goes wrong if the third is skipped.
- A blink loop requests 0.5 ms high and 0.5 ms low. The scope reads a mean period of 1.31 ms. Compute the overhead and the frequency error, and name two mechanisms that produce it.
- Define jitter, and describe two different ways to measure it with an oscilloscope.
- A PWM signal runs at 200 Hz with a 35% duty cycle. Find the period, the on-time, and the average voltage from a 3.3 V high level.
- Explain why hardware PWM has far lower jitter than software PWM, in terms of what generates each edge.
- Why does a linear duty-cycle ramp not produce a linear-looking brightness ramp, and what should you command instead for a perceived half-brightness?
A GPIO pin is a software-controlled 3.3 V switch with a hard 16 mA limit, so every load needs a resistor and every large load needs a transistor. A Python loop on Linux can place an edge to within milliseconds, not microseconds, and that error is constant — so it becomes fatal as you go faster. When timing matters, move it into hardware. And when you need to control an average rather than an instantaneous value, switch fully on and off faster than the load can follow, and vary the duty cycle.
12Practical Engineering Connections
Every idea in this lecture is load-bearing in real hardware:
- GPIO and logic levels. Every embedded product mixes chips with different supply voltages. Knowing that a pin is 3.3 V and not 5 V tolerant — and reaching for a level shifter when it is not — is a daily task in embedded design.
- Current-limited LED drive. Status indicators, backlights, optical encoders, IR remotes, fiber-optic transmitters and the photodiode sources you used in Lab 1 all rely on a defined LED current.
- PWM dimming. Phone and laptop screen backlights, stage and architectural lighting, automotive tail lamps and every LED dimmer switch work exactly this way, for exactly the efficiency reason in §6.
- PWM as power control. The same waveform driving a MOSFET is how switching regulators, class-D audio amplifiers, induction cooktops and electric-vehicle motor drives control power with almost no loss. Lab 4 is your first version of this.
- PWM as a communication signal. Hobby servos are positioned by pulse width, and PWM feeding an RC filter is a cheap digital-to-analog converter — a technique you may want for your final project.
- Timing and jitter. Jitter budgets determine the maximum data rate of every serial link, the phase noise of every oscillator, and the resolution of time-of-flight measurements in lidar and ultrasound. The measurement you make in Task 4 is a simple version of an analysis done on every high-speed board.
- Real-time versus general-purpose computing. The reason aircraft, medical and automotive controllers use real-time operating systems, dedicated microcontrollers or FPGAs — rather than Linux and Python — is precisely what you will measure in this lab.
- Gamma correction. Every display, camera and image file format encodes brightness with a gamma curve for the same perceptual reason you will apply it in Task 8.
PHYS 351 · Lecture 03 Notes · © Ran Yang, Ph.D. · yangran.org/teaching/phys351/
Companion documents: the Lab 3 handout and the course Linux Commands Guide.