Digital I/O, PWM, and the MicroPython Workflow¶
Summary¶
This chapter completes the circuits foundation by covering logic-level concepts (3.3-volt logic, short and open circuits, series and parallel circuits) and diagnostic tools like the multimeter and continuity test. It introduces digital and analog input/output, pulse-width modulation (PWM) and duty cycle, and button debouncing, along with the schematic and circuit diagram conventions used to document a build. The chapter closes with the MicroPython language and Thonny IDE workflow -- writing, uploading, and running code on the Pico -- culminating in the canonical first program: blinking an LED. Students finishing this chapter will be able to write and upload a MicroPython program that reads a digital input and drives a PWM output.
Concepts Covered¶
This chapter covers the following 22 concepts from the learning graph:
- 3.3 Volt Logic
- Short Circuit
- Open Circuit
- Series Circuit
- Parallel Circuit
- Multimeter
- Continuity Test
- Digital Output
- Digital Input
- Analog To Digital Converter
- PWM
- Duty Cycle
- Debounce
- Schematic Diagram
- Circuit Diagram
- Wire Color Convention
- Power Rail
- Component Datasheet
- Static Discharge
- MicroPython
- Thonny IDE
- Blink An LED
Prerequisites¶
This chapter builds on concepts from:
- Chapter 2: Choosing the Right Raspberry Pi Product
- Chapter 3: Breadboard Wiring and Electrical Fundamentals
Let's Make It Move
Wiring is only half the job — now we make it do something. This chapter covers how to tell whether a circuit is actually working, how to control it with real code, and finally, the moment every maker remembers: writing and running your very first program. Let's build something!
Chapter 3 gave you the vocabulary to wire a safe circuit. This chapter gives you two more things every project needs: a way to check whether a circuit is actually healthy before you power it on, and the software workflow that turns a wired breadboard into a program you control. By the end, you'll have written, uploaded, and run your first real MicroPython program.
Logic Levels and Circuit Health¶
Every digital signal on a Pico is built from just two voltage levels. 3.3 volt logic is the standard used by the Raspberry Pi Pico's GPIO pins, where a signal near 3.3 volts represents a logical "high" (on, or 1) and a signal near 0 volts represents a logical "low" (off, or 0) — unlike some older microcontroller families that use 5 volts for the same job. This matters in practice: connecting a 5-volt component directly to a Pico's 3.3-volt pin can send more voltage into the pin than it's designed to handle, damaging it permanently.
Before you power on any new circuit, it helps to be able to name what's wrong when something doesn't work. A short circuit is an unintended, low-resistance connection between two points in a circuit that should not be directly connected — often a stray wire or solder bridge — which lets current flow far higher than intended, generating heat and potentially damaging components or the power source. An open circuit is a break somewhere in a circuit's intended path, such as a loose wire or a component that failed, which stops current from flowing at all, even though every other part of the circuit looks correctly wired.
Diagnosing which one you're dealing with is easier with the right tool. A multimeter is a handheld electronic instrument that measures multiple electrical quantities — typically voltage, current, and resistance — selectable with a dial or button, making it the single most useful diagnostic tool for any electronics project. One of its most common uses for a beginner is the continuity test: a multimeter mode that checks whether two points in a circuit are electrically connected, usually indicated by a beep, letting you confirm a wire or connection is intact without measuring an exact resistance value.
Berry's Tip
When an LED refuses to light and rubber duck debugging from Chapter 1 hasn't cracked it yet, reach for the continuity test before you reach for anything else. It answers the single most common question in troubleshooting: "is this wire actually connected, or does it just look connected?"
Now that short circuits, open circuits, and the continuity test are all defined, here's a quick reference for what each symptom usually means and how to confirm it with a multimeter.
| Symptom | Likely Cause | How to Confirm |
|---|---|---|
| Component won't turn on at all | Open circuit somewhere in the path | Continuity test each wire segment until one doesn't beep |
| Component gets hot or a fuse/power supply trips | Short circuit bypassing intended resistance | Power off, then continuity test between VCC and ground directly |
| Component works but reads an unexpected value | Wiring is intact but connected to the wrong pin | Measure voltage at the pin directly and compare to the expected 3.3 volt logic level |
Series and Parallel Circuits¶
How components are arranged relative to each other changes how current and voltage behave across them, and there are two basic arrangements every circuit is built from. A series circuit connects components one after another along a single path, so the same current flows through every component in turn, and the total resistance is the sum of each component's resistance. A parallel circuit connects components along multiple separate paths between the same two points, so each path can carry its own current independently, and the same voltage is applied across every parallel branch. Most of the circuits you built in Chapter 3 — a single LED and resistor in a row — were series circuits; wiring several LEDs so each one has its own resistor between the same VCC and ground rails would make them parallel.
Let's explore how current and voltage actually redistribute between these two arrangements.
Diagram: Series vs. Parallel Circuit Explorer¶
Run the Series vs. Parallel Circuit Explorer MicroSim fullscreen
Series vs. Parallel Circuit Explorer (reused MicroSim)
Type: interactive-diagram
sim-id: series-parallel
Library: p5.js
Status: Reused
Source: https://dmccreary.github.io/intro-to-physics-course/sims/series-parallel/
Source Repo: https://github.com/dmccreary/intro-to-physics-course/tree/main/docs/sims/series-parallel
Reused from the MicroSim catalog (WHAT match score 0.7606). Learning objective: Students will compare (Bloom L4: Analyze) how current and voltage distribute differently across components wired in series versus components wired in parallel.
Digital and Analog Input/Output¶
With circuit topology and diagnosis covered, it's time to connect these ideas back to the GPIO pins from Chapter 3 and see exactly how software controls them. A digital output is a GPIO pin configured by software to actively drive a voltage — either 3.3 volts (high) or 0 volts (low) — onto a wire, which is how a program turns an LED on or off. A digital input is a GPIO pin configured by software to read whatever voltage is currently present on a wire and report it back as high or low, which is how a program detects whether a button is pressed.
Not every real-world signal fits neatly into "high" or "low," though. Recall from Chapter 3 that an analog pin can read a continuously varying voltage. The circuitry that actually makes that possible is called an analog to digital converter (ADC) — a component built into the Pico's chip that measures a continuously varying input voltage and converts it into a discrete numeric value your program can work with, such as a number from 0 to 65535 representing a voltage anywhere between 0 and 3.3 volts. Without an ADC, a microcontroller could only ever perceive the world in stark on/off terms, which is far too limited for reading something like gradually changing light levels.
The poster below lays digital and analog signals side by side and shows PWM and ADC as the two bridges connecting them.
Diagram: Digital vs. Analog Signals¶
Run the Digital vs. Analog Signals poster fullscreen
Digital vs. Analog Signals (reused poster)
Type: interactive-infographic-poster
sim-id: digital-vs-analog
Status: Reused
Source: https://dmccreary.github.io/learning-micropython/posters/digital-vs-analog/
Source Repo: https://github.com/dmccreary/learning-micropython/tree/main/docs/posters/digital-vs-analog
Reused from the companion book Learning MicroPython's poster catalog. Frames this section's digital output/input and ADC concepts as one comparison, previewing PWM as the bridge covered next.
Pulse-Width Modulation: Faking an Analog Output¶
Digital outputs can only be fully on or fully off, so how does a program dim an LED gradually instead of just switching it on and off? The answer is a clever trick rather than a special new pin type. PWM (pulse-width modulation) is a technique that simulates a variable analog output using only a digital pin, by rapidly switching the pin on and off many times per second and varying the fraction of time it spends on. Because the switching happens fast enough, an LED's apparent brightness — or a motor's apparent speed — averages out to something in between fully on and fully off, even though the pin itself is never anything but high or low at any single instant.
That fraction of "on" time has a specific name. Duty cycle is the percentage of each PWM cycle that a signal spends in the "on" (high) state — a 100% duty cycle is indistinguishable from a plain digital output stuck high, a 0% duty cycle is indistinguishable from stuck low, and a 50% duty cycle spends exactly half of each cycle on and half off, producing roughly half brightness to the eye.
Before exploring duty cycle interactively, note that PWM is exactly the mechanism this book will use to control LED brightness, and later, servo motor position — one technique, many applications.
Diagram: PWM Duty Cycle Visualizer¶
Run the PWM Duty Cycle Visualizer MicroSim fullscreen
PWM Duty Cycle Visualizer (reused MicroSim)
Type: microsim
sim-id: pwm
Library: p5.js
Status: Reused
Source: https://dmccreary.github.io/microsims/sims/pwm/
Source Repo: https://github.com/dmccreary/microsims/tree/main/docs/sims/pwm
Reused from the MicroSim catalog (WHAT match score 0.7898). Learning objective: Students will apply (Bloom L3: Apply) duty cycle adjustments to a PWM signal and observe the resulting change in simulated LED brightness or motor speed.
Taming a Bouncy Button¶
Digital inputs bring their own surprise: a mechanical button doesn't produce the single, clean voltage transition you might expect when pressed. Debounce is the process of filtering out the rapid, unintended on-off signal fluctuations a mechanical button or switch produces in the first few milliseconds after being pressed or released, before its metal contacts settle into a stable state. Without debouncing, a program reading a button as a digital input might register five or ten presses from a single real press, because the raw electrical signal is genuinely bouncing between high and low several times before settling.
Berry's Key Insight
Debouncing is usually solved in software, not hardware — a program simply ignores any additional button transitions for a short delay (often 20-50 milliseconds) after the first one it detects. It's a small trick, but it's the difference between a button that works reliably and one that seems to have a mind of its own.
Let's look at the bounce itself before trusting a program to filter it out.
Diagram: Button Debounce Simulator¶
Run the Button Debounce Simulator MicroSim fullscreen
Button Debounce Simulator (MicroSim)
Type: microsim
sim-id: button-debounce-simulator
Library: p5.js
Status: Specified
Learning objective: Students will analyze (Bloom L4: Analyze) why a mechanical button's raw electrical signal bounces between high and low immediately after a press, and evaluate (Bloom L5: Evaluate) how a debounce delay filters that bounce into a single clean transition.
Canvas: 700x420px, responsive — recompute the oscilloscope-style trace width from windowWidth inside windowResized().
Layout: an oscilloscope-style horizontal trace along the top of the canvas, showing signal voltage over time as a stepped line (high near the top, low near the bottom), with time in milliseconds along the x-axis. A second trace directly beneath shows the same time window after debounce filtering is applied, initially hidden until the student enables it.
Controls: a createButton() labeled "Press Button" that, when clicked, generates a randomized bounce pattern (3-6 rapid high/low transitions within the first 15 milliseconds, based on random()) followed by a stable high signal; a createSlider() labeled "Debounce Delay (ms)" (range 0-100, default 30) controlling the filtered trace; a createButton() labeled "Reset" clearing both traces.
Interaction: after clicking "Press Button," the raw trace animates left to right showing the bouncy signal, and a counter beneath it displays "Raw transitions detected: N" updating live as the trace draws, dramatizing how many false presses an undebounced program would register. The filtered trace beneath it shows only one clean transition, and a second counter reads "Debounced transitions detected: 1." Changing the debounce-delay slider to 0 removes filtering entirely, making the filtered counter match the raw counter, so students can directly see the delay's effect by dragging it up and down.
Implementation: p5.js. Generate the raw bounce pattern as an array of {time, state} transition events at mousePressed() time; the debounce filter walks that same array and discards any transition occurring within debounceDelay milliseconds of the previous accepted one. Draw both traces with line() segments computed from the transition array, redrawing on every animation frame during the trace-in animation and on slider change afterward.
Reading and Drawing Circuit Documentation¶
Before you build increasingly complex circuits, you need a way to plan and communicate them that doesn't rely on a photograph of your actual breadboard. A schematic diagram is a standardized drawing of a circuit that represents each component with an abstract symbol (a zigzag or rectangle for a resistor, a triangle with a line for an LED, and so on) connected by lines representing wires, showing the circuit's electrical structure without showing its physical layout. A circuit diagram is closely related but sometimes shows physical layout more literally — for this book's purposes, treat the two terms as describing the same kind of planning drawing, with "schematic" emphasizing the abstract symbol-based version.
Two more conventions make circuit documentation easier to read at a glance. A wire color convention is an informal but widely followed standard where wire color hints at its electrical role — red for VCC, black for ground, and other colors used consistently for signal wires — making a circuit's wiring easier to trace visually without testing every connection with a multimeter. A power rail, which you met on the breadboard in Chapter 3, appears in schematics too, typically drawn as a horizontal line labeled with its voltage (such as "3V3" or "GND") that every component connecting to power or ground attaches to, rather than drawing a separate wire back to the Pico for every single component.
One more resource belongs in every maker's toolkit before wiring an unfamiliar component. A component datasheet is a manufacturer-published technical document describing a component's electrical specifications, pin functions, and safe operating limits — voltage range, current rating, and sometimes example circuits — which is the authoritative source to check before assuming how a new part behaves.
Now that schematic symbols, wire color convention, power rails, and datasheets are all defined, let's practice recognizing schematic symbols directly.
Diagram: Schematic Symbol Matcher¶
Run the Schematic Symbol Matcher MicroSim fullscreen
Schematic Symbol Matcher (interactive infographic)
Type: infographic
sim-id: schematic-symbol-matcher
Library: p5.js
Template: https://github.com/dmccreary/circuits/tree/main/docs/sims/circuit-symbol-flashcards
Status: Specified
Learning objective: Students will identify (Bloom L1: Remember) standard schematic symbols for a resistor, LED, capacitor, battery/VCC source, and ground, and match each symbol to its component name.
Canvas: 700x400px, responsive — recompute a card grid's column count from windowWidth inside windowResized() (3 columns above 600px, 2 columns between 380-600px, 1 column below 380px).
Layout: a grid of small cards, each rendering one schematic symbol (drawn with basic p5.js line/shape primitives: zigzag for resistor, triangle-plus-line for LED, two parallel lines for capacitor, stacked long/short lines for a battery, downward hatching for ground) beside a blank label field.
Controls: a createSelect() dropdown per card, pre-populated with all five component names, defaulting to "Choose..."; a createButton() labeled "Check Answers" that grades every card at once.
Interaction: selecting a component name from a card's dropdown and clicking "Check Answers" colors that card's border green if correct or red if incorrect, and a small text link beneath an incorrect card reveals the correct name plus a one-sentence description drawn from this chapter's definitions. A "Shuffle" button (createButton()) re-randomizes which symbol appears on which card and clears all previous answers, letting a student re-attempt the activity.
Implementation: p5.js. Store the five symbol/name/description triples as an array of objects; render each symbol procedurally with line()/triangle()/rect() primitives rather than image files so the diagram stays crisp at any canvas size. Use createSelect() for every dropdown per project convention, positioned relative to each card's canvas coordinates and repositioned inside windowResized().
Handling Components Safely¶
One more hazard belongs in this chapter alongside short circuits and reversed polarity: the components themselves can be damaged by something you can't even see. Static discharge is a sudden, brief flow of electricity between two objects at different electrical charge levels — the same phenomenon behind a shock from touching a doorknob after walking across carpet — which can silently destroy a microcontroller's internal circuitry even at a voltage too low for a person to feel.
Berry's Gentle Warning
Treat static discharge seriously, even though you can't see or feel most of it. Before handling a Pico or any bare circuit board, especially in a dry room or after walking across carpet, touch a grounded metal object first to discharge any static buildup from your body. A chip damaged by static often doesn't fail obviously — it just behaves strangely in ways that are very hard to debug later.
Writing Your First Program: MicroPython and Thonny¶
Every concept in this chapter, and every circuit from Chapter 3, becomes useful only once code is actually controlling it. MicroPython is a compact implementation of the Python programming language designed to run directly on microcontrollers like the Pico, supporting most of the syntax you already know from standard Python while adding hardware-specific modules for controlling pins, PWM, and sensors. Thonny IDE is a beginner-friendly code editor that connects to a Pico over a USB cable, letting you write MicroPython code on your computer, upload it to the board, and watch its output — including error messages — appear in a console window, all without leaving one application.
If you've written standard Python before, the poster below shows exactly what carries over and what doesn't once your code moves from a desktop computer to a microcontroller.
Diagram: Python vs. MicroPython¶
Run the Python vs. MicroPython poster fullscreen
Python vs. MicroPython (reused poster)
Type: interactive-infographic-poster
sim-id: python-vs-micropython
Status: Reused
Source: https://dmccreary.github.io/learning-micropython/posters/python-vs-micropython/
Source Repo: https://github.com/dmccreary/learning-micropython/tree/main/docs/posters/python-vs-micropython
Reused from the companion book Learning MicroPython's poster catalog. Grounds this chapter's opening definition of MicroPython with a concrete platform, library, and hardware-access comparison against the desktop Python some students already know.
The Thonny workflow itself always follows the same four steps: plug the Pico into your computer with a USB cable, open Thonny and confirm it shows the Pico as the connected device in the bottom-right corner, write or open a MicroPython file, and click the green "Run" button (or save the file directly onto the Pico as main.py so it runs automatically every time the board powers on).
You've Got This!
If this is the first time you've ever uploaded code to a physical device, that mix of nervous and excited is completely normal. The worst that typically happens is an error message — and you already have rubber duck debugging and root cause analysis from Chapter 1 ready to go.
Now you're ready for the exercise every physical computing course starts with. Blink an LED is the canonical first program for any new microcontroller platform: a short script that turns an LED on, waits, turns it off, waits, and repeats forever, serving the same role a "Hello, World" text program plays in general software courses — proof that your code, your upload process, and your circuit are all working together correctly. Before looking at the code, it helps to know what each piece does: the machine module gives MicroPython access to GPIO pins, Pin(15, Pin.OUT) configures GPIO pin 15 as a digital output (matching the current-limiting-resistor LED circuit from Chapter 3), .value(1) and .value(0) drive that pin high and low, and sleep(0.5) pauses execution for half a second so the blink is visible to the human eye instead of flickering too fast to see.
from machine import Pin
from time import sleep
led = Pin(15, Pin.OUT)
while True:
led.value(1) # turn the LED on
sleep(0.5) # wait half a second
led.value(0) # turn the LED off
sleep(0.5) # wait half a second
First Light!
That's berry exciting — an LED blinking on command is a small circuit doing exactly what your code told it to do, and that's the whole game from here on out. Every project in this book, no matter how advanced, is still just this same loop: change a pin, wait, check a condition, repeat.
Let's step through that program's execution one line at a time, matching each pin change to the resulting GPIO state.
Diagram: Blink an LED Code Tracer¶
Run the Blink an LED Code Tracer MicroSim fullscreen
Blink an LED Code Tracer (MicroSim)
Type: microsim
sim-id: blink-led-code-tracer
Library: p5.js
Template: https://github.com/dmccreary/learning-micropython/tree/main/docs/sims/pico-pinout-explorer
Status: Specified
Learning objective: Students will apply (Bloom L3: Apply) knowledge of MicroPython syntax to trace the blink-an-LED program line by line and predict the resulting GPIO pin state and LED behavior at each step.
Canvas: 700x440px, responsive — recompute the two-panel split (code panel and circuit panel) from windowWidth inside windowResized(), stacking vertically below 550px.
Data Visibility Requirements:
Stage 1: Show the full blink-an-LED code listing in the left panel with line numbers, and a simplified Pico-plus-LED circuit diagram in the right panel with pin 15 labeled.
Stage 2: As the student steps forward, highlight the currently executing line in the code panel and simultaneously update the right panel showing the LED as lit (yellow glow) or unlit, plus a text readout "Pin 15: HIGH" or "Pin 15: LOW."
Stage 3: Show an elapsed-time counter that increments only when a sleep() line executes, so students connect the sleep(0.5) argument to actual visible pause duration.
Controls: a createButton() labeled "Step Forward" advancing execution by one line (looping back to the while True: line after the last one); a createButton() labeled "Run Continuously" that auto-steps on a timer matching the real sleep() durations; a createButton() labeled "Reset."
Interaction: stepping onto a led.value(1) or led.value(0) line immediately updates the LED's lit state and the pin-state readout in the right panel. Stepping onto a sleep() line animates the elapsed-time counter counting up to that line's argument value before allowing the next step. Hovering any line of code shows a one-sentence tooltip explaining that line, matching the plain-language explanation given in the surrounding chapter text.
Instructional Rationale: A step-through code tracer with explicit pin-state and timing visibility is appropriate for this Apply-level objective because it lets students connect each line of MicroPython syntax directly to an observable hardware effect, rather than treating the program as a black box that "just blinks."
Implementation: p5.js. Represent the program as an ordered array of line objects with a type field (assign, pinwrite, sleep, loop) driving what the step handler does; maintain a currentLine index and an elapsedMs counter in the sketch's state. Use createButton() for all three controls per project convention, and redraw both panels every frame from state rather than mutating previously drawn pixels.
Your first program won't always run cleanly on the first try, and Thonny's console is where MicroPython reports exactly why. The reference below decodes the most common error messages you'll see there.
Diagram: Common MicroPython Errors¶
Run the Common MicroPython Errors poster fullscreen
Common MicroPython Errors (reused poster)
Type: interactive-infographic-poster
sim-id: micropython-errors
Status: Reused
Source: https://dmccreary.github.io/learning-micropython/posters/micropython-errors/
Source Repo: https://github.com/dmccreary/learning-micropython/tree/main/docs/posters/micropython-errors
Reused from the companion book Learning MicroPython's poster catalog. Gives students a debugging reference the moment they start seeing Thonny console error messages, applying Chapter 1's root-cause-analysis mindset to real MicroPython tracebacks.
Bringing It Together¶
You now have a complete diagnostic and software toolkit: you can identify a short or open circuit, use a multimeter's continuity test to confirm a wire is intact, read a schematic before you build, and reach for PWM whenever a project needs to fade rather than snap on and off. Most importantly, you've completed the full MicroPython workflow end to end — writing code in Thonny, uploading it to a Pico, and watching a real LED blink because of it. Every remaining hardware project in this book, from a rainbow of NeoPixels to a full sense-think-act robot, is built from exactly these same pieces: a digital or PWM pin, a debounced input, and a MicroPython program running the show.
You Unlocked a Superpower!
That's berry impressive work — you can now diagnose a circuit, document it properly, and control it entirely with your own code. STEM is our superpower, and you just leveled yours up considerably. Let's build something — see you in Chapter 5, where we string dozens of LEDs together and make them dance!