Electronicsfor Theatre & Live Performance

Class 11 of 16 Unit 6 · Microcontrollers 4 hours · 2 at the bench

Microcontrollers

A chip that does one thing forever, very fast. Pins, the ADC, PWM, timing, and why delay() ruins a show.

Teach mode Safety card

A microcontroller is a chip that does one thing forever, very fast. Everything difficult about them comes from that word "forever".

What a microcontroller actually is#

Not a computer. A computer runs an operating system, schedules many tasks, and has an indeterminate response time. A microcontroller runs one program, from power-on until power-off, with nothing else competing for it.

That is why it is the right thing for show hardware. When you tell a microcontroller to check a switch every millisecond, it checks the switch every millisecond, forever, and nothing decides that a software update is more important.

Inside one chip:

  • CPU, executing instructions at some millions per second
  • Flash memory holding the program, kept when power is removed
  • RAM holding variables, lost when power is removed
  • GPIO pins that can be inputs or outputs
  • ADC, converting an analogue voltage to a number
  • Timers, counting independently of the program, which is what makes PWM possible
  • Serial peripherals — UART, I²C, SPI — for talking to other chips

For this course the chip matters less than the pattern. An ATmega328 at 16 MHz, an ESP32 at 240 MHz, an RP2040: same ideas, different speeds and peripheral counts.

The same chip from another angle. Computer Science for Theatre Class 5 asks a different question about this hardware: not what the chip can do, but whether the job belongs on a chip at all rather than on a computer, and how much work actually fits in one pass of the loop. It is a separate course and it does not require this one; if you want the architecture decision rather than the pins, that is where it is argued.


Pins: input, output, and the third state nobody expects#

A GPIO pin is one of three things:

Output. The pin is driven to the supply voltage or to ground. It can source or sink a limited current, typically about 20 mA and often less in total across the whole chip. This is why a microcontroller never drives a relay, a motor or a metre of LED tape directly — that is what the board from Class 6 is for.

Input. The pin reads high or low, comparing against a threshold. It draws essentially no current.

Input, floating. An input pin with nothing connected is not zero. It is undefined: a tiny capacitance with no path to anywhere, picking up interference from the air, from adjacent pins, from your hand near the board. It reads randomly, and it changes when you move.

A floating input is the single most common beginner fault and it produces a symptom that looks like a software bug or a haunting: a button that presses itself.

Pull-ups and pull-downs#

The cure is a resistor, typically 10 kΩ, that defines the pin's state when nothing else is driving it.

A pull-up ties the pin to the supply, so it reads high when idle, and a switch to ground pulls it low when pressed. This is the standard arrangement, partly because most microcontrollers have internal pull-ups you can enable in software, and partly because a switch that connects to ground is easier to wire and less susceptible to noise on a long run.

The consequence is that the pressed state reads low, which inverts the logic in your code and catches everybody once.

In show hardware, prefer a scheme where the safe state is the de-energised one. A button whose pressed state is a closed circuit to ground will read as "not pressed" if the cable is cut, which is usually what you want. That is the same reasoning as the pull-downs on the Class 6 board.


The loop, and why delay() ruins a show#

The basic structure is setup() once, then loop() forever. The chip executes loop() millions of times a second, and everything the device does is a consequence of what happens on each pass.

Now consider:

void loop() {
  digitalWrite(LED, HIGH);
  delay(1000);
  digitalWrite(LED, LOW);
  delay(1000);
}

delay(1000) does not schedule anything. It sits in a tight loop counting for one second while the processor does nothing else at all. No buttons are read. No DMX is received. No emergency stop is noticed.

For a blinking LED on a bench, fine. In a prop that must respond to a cue, a one-second delay means up to one second of complete deafness, and the operator's experience is "the button did not work sometimes".

The pattern that replaces it#

Instead of stopping, check the clock and decide:

unsigned long lastBlink = 0;
const unsigned long BLINK_INTERVAL = 1000;

void loop() {
  unsigned long now = millis();

  if (now - lastBlink >= BLINK_INTERVAL) {
    lastBlink = now;
    ledState = !ledState;
    digitalWrite(LED, ledState);
  }

  readButtons();      // every pass, a few microseconds
  updateOutputs();    // every pass
  checkSafety();      // every pass, and this is the one that matters
}

The loop now runs thousands of times a second and everything is checked on every pass. That is the difference between a prototype and a device that can be put on a stage.

Note the subtraction. now - lastBlink >= INTERVAL rather than now >= lastBlink + INTERVAL. The counter overflows after about 49 days, and unsigned subtraction handles the wrap correctly while addition does not. A prop in a long-running installation will meet this, and the bug appears once, seven weeks in, and is essentially undiagnosable if you do not know it exists.


The ADC: turning a voltage into a number#

An analogue-to-digital converter compares an input voltage against a reference and reports a number. A 10-bit ADC gives 0 to 1023; a 12-bit one gives 0 to 4095.

The resolution is the reference divided by the number of steps. With a 5 V reference and 10 bits, each step is 4.9 mV. Nothing finer than that exists to the chip, and asking for more precision than the hardware has is a common source of imaginary problems.

Three practical points:

The reference is the accuracy limit. If your reference is the 5 V USB supply, and that supply sags to 4.7 V when the motor runs, every reading changes by six per cent while nothing physical changed. This is a real and common fault in home-built sensor systems.

Source impedance matters, exactly as with the meter in Class 2. The ADC charges a small internal capacitor from your signal. From a high-impedance source it does not finish charging in the sampling window and reads low. Keep the source under about 10 kΩ, or buffer it.

Noise is real and averaging is cheap. Reading a sensor once gives you the value plus whatever noise arrived in that microsecond. Reading it sixteen times and averaging costs nothing and gives a visibly steadier number.


PWM: making an analogue-looking output from a switch#

A digital pin is on or off. To dim an LED or slow a motor you switch it rapidly and vary the proportion of time it is on. That proportion is the duty cycle.

At a high enough frequency the load's own inertia — thermal, mechanical, or your eye's persistence — averages it out.

The frequency matters, and choosing it badly produces specific, recognisable problems:

FrequencyProblem
Under about 100 HzVisible flicker, especially in peripheral vision and on camera
A few hundred Hz to 20 kHzAudible whine from motors and from inductors
Above 20 kHzInaudible, but switching losses rise and gate drive matters more

For stage lighting the constraint that actually decides it is camera. A show being filmed needs PWM well above the camera's shutter rate, or the footage shows rolling bands. This is why professional LED fixtures advertise high PWM frequencies and why a cheap dimmer looks fine to the eye and terrible on broadcast.

And brightness is not linear. Perceived brightness is roughly logarithmic, so 50 per cent duty does not look half as bright. This is why fixtures apply a dimming curve, and why a naive analogWrite(pin, level) fade looks wrong: it rushes the top and crawls at the bottom.


Switch bounce, seen properly#

A mechanical switch does not close once. Its contacts collide and separate several times over 1 to 20 milliseconds before settling.

A human sees one press. A microcontroller polling at 100 kHz sees five to fifty transitions, and a counter counts them all.

You will put a scope on a switch today and see this, and it is one of the most useful things in the class, because bounce is invisible to reasoning and obvious on a screen.

Debouncing, in order of how much you should like them:

  1. Ignore changes for a fixed time after the first one. Twenty to fifty milliseconds. Simple, reliable, and what almost everything uses.
  2. Require the state to be stable for N consecutive reads. Slightly more robust against a noisy line, marginally more code.
  3. An RC filter and a Schmitt trigger input in hardware. The right answer for a long cable run from a switch out in the set, because it also suppresses the interference the cable picks up.

For anything that must not double-fire — a cue trigger, a pyro arm, a scene change — use hardware debouncing and software debouncing. A double-fired cue is a show problem, and the cost of both is two components.


Interrupts: the thing that cannot wait#

Everything so far polls: the loop comes round and asks. That is right for almost everything and wrong for a few things, and knowing which is which is the point of this section.

An interrupt is a hardware arrangement where a pin changing state stops whatever the processor was doing, runs a short function, and returns. The main loop does not have to be looking.

Use one when the event is short, rare and cannot be missed: an encoder pulse at speed, a zero-crossing detector, an emergency input. Do not use one for a button, because a poll every millisecond will never miss a human finger and it is far easier to reason about.

The rules, and they are strict:

  1. Keep it short. Set a flag, store a value, return. No delays, no printing, no long arithmetic. Everything else is blocked while it runs.
  2. Mark shared variables volatile. The compiler otherwise assumes nothing changes them behind its back, caches them in a register, and your main loop reads a stale value forever. This bug is invisible, intermittent, and depends on optimisation level.
  3. A multi-byte variable shared with an interrupt needs care. The main loop can read half of a 32-bit value, be interrupted, and read the other half from a different moment. The result is a number that was never true.
  4. millis() usually stops working inside one, because it is itself driven by an interrupt.

Powering a microcontroller properly#

Most prop faults that look like software are power. Four things worth doing every time:

Decouple every chip. A 100 nF ceramic between the supply pin and ground, physically next to the chip, supplies the current spike each switching edge demands. Without it the local rail sags for nanoseconds and the chip does something undefined. This is the single most common cause of a board that works on the bench and resets in a rack.

Bulk capacitance at the supply entry. 100 µF or more, to hold the rail up through the slow demands: a servo starting, a relay pulling in.

Do not power a motor from the microcontroller's regulator. The board's onboard regulator can supply tens of milliamps beyond the chip. A servo peaks at over an amp. Give the actuator its own supply and join only the grounds.

Join the grounds, once, at one point. Two supplies with separate grounds and a signal between them is the Class 7 ground loop in miniature, and here it shows up as random resets rather than as hum.


Talking to other chips#

Three buses cover almost everything you will connect, and choosing between them is usually decided by what the sensor you want happens to speak.

WiresSpeedDevicesWhere
UART2, plus groundLow to moderateOne, point to pointDMX, GPS, modules, debugging
I²C2, shared100 to 400 kHzMany, each with an addressSensors, displays, real-time clocks
SPI4, shared plus one select eachFast, megahertzMany, one select line eachSD cards, displays, LED drivers

I²C is the one that catches people, in two ways. It needs pull-up resistors on both lines, usually 4.7 kΩ, and exactly one set of them for the whole bus, not one set per device. And two devices with the same fixed address cannot share a bus, which is why sensor breakout boards have an address-select jumper that everybody discovers only after buying the second one.


At the bench#

Two hours.

Confirm every board and toolchain in the room works. This is a checkpoint, not a lesson, and it is scheduled because a class where three people cannot flash a board loses forty minutes.

Block B — Prove the pin limits (30 min)#

Drive an LED from a pin through the correct resistor and measure the current. Then, on the sacrificial board, remove the resistor and measure again. Watch the pin voltage sag as the chip runs out of drive.

Then connect a relay coil directly to a pin, briefly, and watch what happens on the scope when it releases. This is Class 5's flyback, appearing on something you now own.

Block C — Floating inputs and the button that presses itself (25 min)#

Read a bare input pin and print it a thousand times a second. Wave your hand near it. Watch it change.

Now enable the internal pull-up and repeat. Then fit an external 10 kΩ pull-down and a switch to the supply and repeat again.

Write down which arrangement reads as "not pressed" when the wire is cut, and why that is the one you want in a set.

Block D — Bounce, on a scope (20 min)#

Scope on a switch, single-shot trigger. Capture the bounce. Measure how long it lasts and count the transitions.

Then run a counter in software with no debouncing and press the button ten times. Record the count. Then add a 30 ms debounce and repeat.

Block E — delay() against millis() (25 min)#

Write the naive blink with delay(1000) and add a button that should turn something on. Measure how long the button can be pressed and ignored.

Rewrite it with the millis() pattern and measure again. Record both numbers. The first is typically several hundred milliseconds, the second under a millisecond, and that ratio is the lesson.


Numbers from this class#

QuantityValue
Microcontroller pin current, safe maximumabout 20 mA
Typical pull-up or pull-down resistor10 kΩ
10-bit ADC range0–1023
12-bit ADC range0–4095
10-bit ADC step with a 5 V reference4.9 mV
Maximum recommended ADC source impedanceabout 10 kΩ
Switch bounce duration1–20 ms
Typical software debounce time20–50 ms
millis() overflow periodabout 49 days
PWM frequency, visible flicker belowabout 100 Hz
PWM frequency, audible whine band200 Hz – 20 kHz
PWM frequency for broadcast-safe lightingabove about 20 kHz
ATmega328 clock speed16 MHz
Duty cycle for half power50 per cent, but not half perceived brightness

Self test with model answers

Answer it yourself first, out loud or on paper, then open the answer. Reading the answer without attempting the question teaches you almost nothing.

Note. These are model answers, not the only correct ones. If yours differs and you can defend it with the arithmetic and a measurement, that is worth more than matching the wording.

The four hours, accounted for. This is the lecturer's view of the session, and it is here rather than hidden in a pack because a student who can see where the time goes can see what the class is actually for.

Run of the session #

240 minutes · 120 at the bench · 85 on the idea

45402030252025
  1. 10min Open Numbers quiz, and your written guess about what delay() does to the chip
  2. 45min The idea What a microcontroller actually is, pins and the third state, and the loop
  3. 15min Break —
  4. 40min The idea The ADC and why resolution is not accuracy, PWM, and switch bounce
  5. 20min Bench A Everybody blinks: a checkpoint, not a lesson
  6. 30min Bench B Prove the pin limits, including the relay coil on a scope
  7. 25min Bench C Floating inputs, and the button that presses itself
  8. 20min Bench D Bounce, captured single-shot, and counted
  9. 25min Bench E delay() against millis(), with the response time measured both ways
  10. 10min Close The two numbers you measured, and what their ratio means

Blocks are an order of work rather than a timetable: a class that runs long on the bench is a class that is going well. The minutes are here so you know what you are trading against when it does.

Four hours goes fast, and most of it is bench time. What you do before the class is what decides whether you spend those hours learning or catching up.

What you must already be able to do#

Calculate an LED series resistor. Explain a pull-down resistor and why a floating input is not off. Drive a MOSFET. Use a scope with a trigger. Read a schematic.

Three things to do#

  1. Get a board and blink an LED on it before you arrive. Any Arduino-compatible board. Install the toolchain, connect it, load the blink example, change the interval to 200 ms. That is all. If the toolchain fights you, it will fight you for the first hour of class instead, and the toolchain is not what we are here to learn.
  2. Write down what delay(1000) actually does to the chip. One sentence, your guess. Bring it.
  3. Find a microcontroller in a piece of show equipment. Open something dead and find the chip with a crystal next to it. Read its part number and look up what it is.

What to bring#

Your board, a USB cable that carries data (not a charging-only one, which wastes the first ten minutes of every class of this kind), a laptop with the toolchain working, breadboard, meter.

The calculations this class asks for, with the working shown. Use them to check your arithmetic, never to replace it: the exam asks for the method.

PWM duty, frequency and resolution

ADC resolution and sensor scaling

LED series resistor

Frequency, period and wavelength

Retrieval, not rereading. Everything here is self-graded, stored in this browser only, and reported to nobody.

Spot the myth

Numbers drill