Build: The Timer Hardware
Purpose
Section titled “Purpose”To build the half of the timer a printer actually touches, and to establish two things about it that cannot be established by looking: that every control can be found and identified by hand in the dark, and that the instrument’s own light does not fog the paper it will stand beside.
Nothing on this page computes an exposure. The firmware page does that, and the switched-output page drives the lamp. What comes out of this session is a working panel, an input-test log showing every control responding, and a processed sheet of paper with a control beside it.
Learning objectives
Section titled “Learning objectives”By the end of this build you should be able to:
- lay out a panel whose controls are distinguished by shape and position rather than by legend, and say why that is a design requirement rather than a nicety in a room with no light in it;
- wire a detented rotary encoder, three buttons and a foot-switch jack to a Pico using its internal pull-ups, and explain what each pull-up is for;
- decode quadrature with a transition table, and read the invalid-transition count as a measurement of contact bounce rather than guessing at it;
- state the current budget for the whole instrument and where it comes from;
- choose an output connector that cannot be confused with the foot-switch connector by touch;
- run the fog test on your own instrument and read it against Kodak’s published criterion.
Prerequisites
Section titled “Prerequisites”Part XIV’s electronics primer is assumed in full and is not repeated here: the Pico, GPIO, PWM, internal pull-ups, the breadboard-first rule, soldering technique and its controls, the current budget and the datasheet discipline all come from that page.
Part XVI’s fog test supplies the method used in Testing and calibration, and this page runs it on a new emitter rather than re-deriving it. The f-stop page explains why the encoder has to move in stops rather than in seconds.
Safety classification
Section titled “Safety classification”Level A, and the criteria that put it there are the rubric’s own: purchased, certified equipment only, with no mains construction or modification anywhere in the build; substances limited to the ordinary print sequence in home-darkroom quantities for the fog test; and no heating of solutions.
What is not a hazard here, and why. There is no mains conductor in this enclosure at any point, so nothing on this page carries the shock or fire hazard that the isolation lesson exists to keep out of the course: the instrument is fed from a certified USB supply or a power bank and its highest potential is 5 V, which is inside the extra-low-voltage band HSE describe. Nor is there a chemical hazard in the electronics half of the session — a Pico, an encoder and a piezo are inert solids, and nothing here produces a vapour, so ventilation is not among this stage’s controls. The two hazards that are real are ordinary and specific: a soldering iron at around 350 °C, and the flux fume it makes. Both belong to Part XIV and its controls apply unchanged.
The fog test at the end brings the print chemistry back, at Level A, in the concentrations the developer and the fixer are supplied at, and nothing in it is stronger than the sequence Part XVI already ran.
Hazards
Section titled “Hazards”Soldering. Burns from the iron and from freshly cut joints; the iron returns to its stand every time it leaves your hand. Rosin-based solder flux fume has a United Kingdom workplace exposure limit of 0.05 mg/m³ over eight hours and 0.15 mg/m³ over fifteen minutes, and it is annotated Sen — capable of causing occupational asthma. Part XIV’s extraction and ventilation controls are followed without modification; do not treat a short session as a reason to skip them, because sensitisation is not a dose-response you can feel accumulating.
Trimmed leads. Component legs fly when cut. Eye protection while cutting, and cut into your hand or into a bin rather than across the bench.
The foot-switch cable is a trip hazard, and this instrument lives in the dark. Route it under the bench, tape it down, and make it long enough to reach without being long enough to loop. A fall in a darkroom lands somebody among trays of chemistry and glass.
The instrument sits in a wet room. It goes on the dry side, away from trays, on a surface that does not get splashed, with its cables dressed so a drip runs away from a connector rather than into it.
Print chemistry for the fog test. Level A: nitrile gloves, eye protection, the usual ventilation for a print sequence, fixer collected for silver recovery.
Required PPE
Section titled “Required PPE”Eye protection whenever leads are being cut and whenever solutions are handled. Nitrile gloves for the fog test’s three baths; they are not worn for soldering, where the hazard is heat rather than splash and a glove is a way of not noticing that you are being burned.
Ventilation is a control for the soldering stage and not for the rest: extraction at the iron under Part XIV’s rules, and the ordinary print-sequence ventilation for the fog test. It is not among the controls for the assembly and testing stages, because nothing there produces a vapour.
| Part | Quantity | What it does | Alternatives and notes |
|---|---|---|---|
| Raspberry Pi Pico | 1 | The instrument | The course standard from Part XIV, so the pinout and firmware are shared |
| Rotary encoder, detented, with push switch | 1 | Sets every number | 20 or 24 detents per turn; a bare potentiometer will not do, because the encoder is read as steps and not as a position |
| Tactile buttons, 12 mm | 3 | START, MODE, SET | Different cap heights, so they differ by touch |
| Piezo transducer, passive | 1 | Metronome and confirmations | Passive, not a self-driving buzzer: the firmware needs to choose the pitch |
| Display, red seven-segment, 4 digits | 1 | The number | Or a monochrome I²C panel behind a red filter — see the display note below |
| Jack socket, 6.35 mm mono, switched | 1 | Foot switch | The size is deliberate: nothing else on the panel accepts this plug |
| Output connector, 2-pin polarised | 1 | The low-voltage channel to the lamp | Must not mate with the foot-switch plug; that is the whole specification |
| Resistors, 10 kΩ | 2 | Encoder pull-ups, if the internal ones prove insufficient | Fitted only if the bounce test says so |
| Capacitors, 10 nF | 2 | Encoder RC filter | Same condition |
| Capacitors, 100 nF and 10 µF | 1 each | Decoupling at the Pico | The RP2040 datasheet asks for 100 nF close to each IOVDD pin |
| Enclosure, about 150 × 100 × 50 mm | 1 | Holds it, and stays put | Diecast aluminium or ABS; see the no-soldering route below |
| USB supply or power bank, certified | 1 | Power | HSE name battery-operated equipment as the safest choice where one will do |
| Stripboard, hook-up wire, heat-shrink | — | The permanent build | Or a breadboard, permanently, if that is the route you take |
The display, and what this page will not tell you. The instrument stands a few tens of centimetres from unexposed paper, so its emission is a safelight question and is decided by the same evidence: Part XVI’s fog test, run on this emitter, with this paper. This course has not tested any particular display module against paper, so this page names no display as passing and none as failing. What it specifies is the design that makes a pass plausible and the test that decides it: red emission rather than white or blue, brightness under firmware control so the test can be run at the setting you will actually use, and a display the firmware can blank entirely. If your panel is a white-emitting one behind a red filter, the filter is part of the device under test and the test result belongs to the pair.
Temperature-controlled soldering iron and its stand, side cutters, wire strippers, tweezers, a helping hand, a multimeter, a small drill and a step drill or tapered reamer for the panel holes, a needle file, a scriber or centre punch, and a computer with a serial terminal. Nothing here is beyond the kit Part XIV’s builds already asked for.
Estimated cost
Section titled “Estimated cost”Cost band ££. One named part costs more than all the rest together — the display — and the enclosure is the second line. Everything else is a handful of pounds of passives and connectors.
This page quotes no price for any of it, because the planner carries no electronics line at all; the parts for Parts XIV to XVII are among the gaps it names, and a figure invented here would be a guess dressed as a quotation. What outlives a quotation is the specification: a detented encoder with a push action, a passive piezo, a red-emitting display whose brightness can be commanded, and a certified low-voltage supply.
The one alternative that changes the total is the bought instrument, and that is priced: a commercial enlarger timer ran £112.20 to £429.00 on 5 September 2026. If your build lands near the bottom of that range, the honest comparison is not cost but what you get — a certificate you wrote yourself, and a log.
Estimated consumables cost
Section titled “Estimated consumables cost”Almost nothing in the electronics half of this session is consumed; every component is capital and goes into an instrument meant to be recalibrated rather than replaced. The consumables are the fog test’s, and they are the same four the fog test page uses, at the smaller quantity a single-emitter test needs.
| Consumed | This session | Sourced price | Cost this session |
|---|---|---|---|
| Photographic paper, variable-contrast RC, 5 × 7 in | 3 sheets — one range-finding, one test, one control | £16.06 per 25 or £44.71 per 100, dated 5 September 2026, so £0.45–£0.64 a sheet | £1.34–£1.93 |
| Paper developer, working strength | 500 mL of 1+9, that is 50 mL of concentrate | £10.52 per 500 mL or £20.03 per 1 L, so £0.020–£0.021 a millilitre | £1.00–£1.05 |
| Stop bath, working strength | 500 mL of 1+19, that is 25 mL of concentrate | £10.66–£12.18 per 500 mL | £0.53–£0.61 |
| Fixer, working strength | 500 mL of 1+4, that is 100 mL of concentrate | £21.05–£25.98 per 1 L | £2.11–£2.60 |
| Lead-free solder, 0.7 mm | about 2 g, or none on the breadboard route | None. The planner carries no electronics line | — |
| Heat-shrink sleeving | about 200 mm | None. The planner carries no electronics line | — |
| Hook-up wire | about 1.5 m | None. The planner carries no electronics line | — |
| Stripboard | one 50 × 100 mm piece, or none | None. The planner carries no electronics line | — |
| Isopropanol, for flux residue | a few millilitres | The planner prices isopropanol only in Part II’s darkroom quantities | — |
| Wash water | a few litres | Not priced by the planner | — |
The priced rows give £4.98 to £6.19 for one run of the fog test. That is a floor and not a total: five rows carry no dated price, nothing on them is free, and the bath figures assume the smallest useful volume is mixed and discarded after the session, which is what a controlled test requires.
Equipment is deliberately out of the table. The iron, the multimeter, the enclosure, the Pico, the display and the dishes are not consumed by a session, and a page that quietly counted them would stop measuring the thing the consumables calculator needs.
Eight stages, four hours. Stage 0 is the one that decides whether the instrument is usable, and it happens before anything is cut.
Stage 0 — Design the panel for a room with no light in it (30 minutes)
Section titled “Stage 0 — Design the panel for a room with no light in it (30 minutes)”Sit at the enlarger, close your eyes, and reach for where the timer will be. That is the design session, and it produces four decisions.
Controls are identified by shape and position, never by a legend. In use you will not see the legends. So: the encoder is the only round thing, it is the largest thing, and it sits where the hand falls. START is the only large button and it is at the near edge, far enough from the others that a mistaken press is a deliberate act. MODE and SET are small, identical in function-class, and are distinguished from each other by height — one with a tall cap and one with a low cap — because the fingertip reads height instantly and reads position poorly.
Legends exist anyway, for the sighted first five minutes and for anybody else who uses the instrument. Engrave, label or print them large enough to read at arm’s length under a safelight, which in practice means about 4 mm of character height and high contrast, not small neat lettering.
The display faces you and nothing else. Recess it, hood it, or angle the enclosure so that its light goes to your eyes and not across the bench to the easel. This costs nothing at the design stage and cannot be retrofitted to a flat panel without a new panel.
Two connectors that cannot be swapped. The foot switch is a 6.35 mm jack; the output is a 2-pin polarised connector of a different family entirely. Not different colours — different shapes, because in the dark colour does not exist. Put them on opposite ends of the enclosure as well.
Draw the panel full size on paper, put your hand on the drawing, and only then mark the enclosure.
Stage 1 — Pico, encoder and buttons on a breadboard (40 minutes)
Section titled “Stage 1 — Pico, encoder and buttons on a breadboard (40 minutes)”Breadboard first, always, and Part XIV’s reason stands: a breadboarded fault takes ten seconds to fix and a soldered one takes ten minutes and a wick.
The pin assignment below is the one the firmware assumes. It leaves GP4 and GP5 free for the display’s
I²C, which is where the RP2 port puts I2C(0) by default, and it puts the output on GP15, which is
the pin Parts XIV and XV already use for a switched load, so that the three instruments share one habit.
| Signal | Pin | Wiring | Why |
|---|---|---|---|
| Encoder A | GP16 | to encoder terminal A, internal pull-up | Quadrature |
| Encoder B | GP17 | to encoder terminal B, internal pull-up | Quadrature |
| Encoder push | GP18 | to encoder switch, internal pull-up | Confirm and long-press |
| START | GP19 | button to 0 V, internal pull-up | Pressed reads 0 |
| MODE | GP20 | button to 0 V, internal pull-up | Pressed reads 0 |
| SET | GP21 | button to 0 V, internal pull-up | Pressed reads 0 |
| Buzzer | GP22 | 100 Ω in series to the piezo, piezo to 0 V | PWM drives it |
| Foot switch | GP14 | jack tip, internal pull-up; sleeve to 0 V | Polarity learned at boot |
| Output | GP15 | to the switched-output page’s gate resistor | Held low by its own pull-down |
| Display SDA / SCL | GP4 / GP5 | I²C, if a panel is fitted | The port’s default assignment |
Every button and every encoder contact goes to ground, with the Pico’s internal pull-up holding the line high when nothing is pressed. That is the arrangement Part XIV established, and the reason is worth restating in one line: a floating input reads as noise, and an input pulled to a defined level reads as a decision.
The interface board, laid out as it is wired
- Pico, GP14 to GP22 — all inputs use the internal pull-up; GP15 is the only output
- Encoder A and B, with optional RC filter — fit the 10 kΩ and 10 nF only if the bounce test asks for them
- Three buttons to ground — START large; MODE tall cap; SET low cap
- Piezo through 100 Ω — bounds the pin current into a capacitive load
- 6.35 mm jack, foot switch — polarity learned at boot, not assumed
- 2-pin polarised output — a different family from the jack, at the other end of the box
Stage 2 — Decode the encoder, and measure its bounce rather than guessing (40 minutes)
Section titled “Stage 2 — Decode the encoder, and measure its bounce rather than guessing (40 minutes)”A quadrature encoder has two contacts that change in a fixed order. Read them as a two-bit number — call it A shifted left, plus B — and every legitimate movement is one of eight transitions: four one way and four the other. Everything else is either no movement or a bounce.
That fact is the decoder and the filter. A sixteen-entry table indexed by the previous state and the current one returns +1, −1 or 0, and a contact that chatters produces repeated same-state entries and occasional invalid ones, both of which return 0 and are therefore rejected by construction rather than by a delay.
Whether you also need hardware filtering is a measurement, not an opinion. The input test below counts the invalid transitions and prints them per detent. Turn the encoder slowly through twenty detents and read the number.
- Zero or near-zero invalid transitions, and exactly four counts per detent: the internal pull-ups and the table are enough. Leave the resistors and capacitors out.
- A scatter of invalid transitions, or counts that jump: fit the RC filter — 10 kΩ from each of A and B to 3V3 and 10 nF from each to ground, which gives a time constant of 100 µs, far longer than a contact bounce and far shorter than the fastest a hand can turn a knob.
- Two counts per detent rather than four: your encoder rests between transitions rather than on them. Change the divisor in the firmware, not the wiring.
This course has measured no particular encoder, which is why the page gives the test and the three readings rather than naming a part and a figure.
Stage 3 — The piezo, and a pitch you have to choose (15 minutes)
Section titled “Stage 3 — The piezo, and a pitch you have to choose (15 minutes)”A passive piezo transducer is, electrically, a small capacitor. Drive it from a GPIO through 100 Ω in series: the resistor bounds the current the pin has to supply while the element charges. The RP2040’s pads can be set to 2, 4, 8 or 12 mA of drive strength, defaulting to the second of those — and the datasheet is careful that those figures are not hard limits, but descriptions of how far the output voltage falls at a stated load. A pin asked for more than it likes does not shut down; it sags, and a sagging rail is a Pico that resets. The series resistor is what keeps the question from arising.
Use PWM rather than a square wave written in software, so the pitch is a number rather than a loop. Set the frequency to your transducer’s resonant frequency, from its datasheet. A piezo element is sharply resonant and its output falls away either side, so a device driven a kilohertz off resonance can be several times quieter for the same drive. The listing’s 2400 Hz is a provisional value, like every other provisional constant in this course, and it is meant to be replaced by yours.
How loud is loud enough is also a measurement. No sound-level figure for a piezo against the noise of running water and an extractor fan appears anywhere in this course. The test is the one that matters and it takes two minutes: run the metronome, stand where you print, turn the tap on, and see whether you can count by ear with your eyes shut.
Stage 4 — The foot switch, and the polarity you must not assume (25 minutes)
Section titled “Stage 4 — The foot switch, and the polarity you must not assume (25 minutes)”A bought sustain pedal is the cheapest good foot switch there is, and it has one trap: pedals are made both normally-open and normally-closed, and many carry a polarity switch on the body that the previous owner may have left either way. Firmware that assumes “pressed reads 0” will treat a normally-closed pedal as permanently pressed.
The fix costs four lines of code and no hardware. At power-up the firmware samples the jack for a fifth of a second, takes whatever it finds as the released state, and treats any change from it as a press. The input test prints which polarity it inferred, so you can see that it inferred the right one by standing off the pedal while it boots.
Wire the tip to GP14 and the sleeve to 0 V. Use a switched jack socket if you have one, so the firmware can also tell whether a pedal is plugged in at all: with nothing inserted, the switch contact shorts the input to a known state and the instrument reports “no pedal” rather than “pedal held”.
The cable is the hazard, not the switch. Route it under the bench, tape it at two points, and leave no loop on the floor.
Stage 5 — Power, and the current budget (20 minutes)
Section titled “Stage 5 — Power, and the current budget (20 minutes)”The whole instrument runs from USB: a certified supply with a moulded lead, or a power bank. HSE name battery-operated equipment as the safest choice where powered equipment is used, and a power bank is a lithium cell in a certified enclosure with its own protection circuit, which is precisely the difference between it and a bare cell.
The budget is short and worth writing down before you fit anything:
| Load | Typical draw | Note |
|---|---|---|
| Pico, running | tens of milliamps | From the board datasheet’s own tables |
| Display, at full brightness | the largest single line | Read it from your module’s datasheet; a four-digit LED display multiplexed at full brightness can exceed the Pico itself |
| Piezo | small and intermittent | A capacitive load, not a continuous one |
| Encoder, buttons, foot switch | negligible | Pull-up currents only |
Two rules follow. The Pico’s 3V3 pin is an output, and the board datasheet asks that its external load be kept below 300 mA — so a display drawing more than that gets its own feed from the 5 V rail and its own regulator, never the Pico’s. And whatever the total is, measure it: put the multimeter in series with the USB lead, run the instrument at full display brightness with the metronome going, and write the number on the inside of the lid.
Stage 6 — The panel (40 minutes)
Section titled “Stage 6 — The panel (40 minutes)”Mark from the full-size drawing, centre-punch every hole, drill small and open out with a step drill or a reamer, deburr with a file. Fit the encoder last, because it is the part whose shaft length decides how the knob sits.
Two details that matter more than they look. The encoder knob wants to be big — 25 mm or more — because a large knob is easier to turn by a precise amount than a small one, and because it is the control your hand finds first. And the display’s window wants a bezel or a recess, not a flush sticker, so that its light is directed rather than scattered.
Stage 7 — Commit it, or do not (30 minutes)
Section titled “Stage 7 — Commit it, or do not (30 minutes)”Two honest routes.
Solder it. Transfer to stripboard, cut the tracks, strain-relieve every wire that leaves the board with a cable tie or a hot-melt anchor, and label the connectors on the outside of the enclosure — the labels are for whoever opens it in two years, which is you.
Do not solder it. A breadboard screwed into the enclosure with the wiring dressed and the connectors brought out to the panel is a legitimate finished instrument, and it is the accessibility route for anybody who cannot solder. Part XIV said the same about its own builds. What you give up is reliability under vibration, which a bench instrument in a dark room does not experience much of. What you must not give up is strain relief: a wire pulled out of a breadboard in the middle of an exposure is the same fault as a wire pulled off a pad.
The input-test firmware
Section titled “The input-test firmware”Save this as timer_inputs.py. It is not the timer — the firmware
page has that — it is the smallest
program that proves every control is wired correctly, and it is the only thing you should run until it
passes.
"""timer_inputs.py - Pure Silver, Part XVII: input test for the timer hardware.
Target Raspberry Pi Pico (RP2040), MicroPython v1.24.1 for RPI_PICO.Install copy to the board as timer_inputs.py, then at the REPL: import timer_inputs as t t.run()Licence CC BY-SA 4.0, as the rest of this course's content.
Everything printed is comma-separated, with '#' beginning any line that is acomment, so a captured terminal session is already the build log this pageasks you to keep.
This program never asserts the output pin. It is an input test, and the lampchannel stays off throughout - which is why it is safe to run with the headconnected."""
import machineimport micropythonimport sysimport time
micropython.alloc_emergency_exception_buf(100)
VERSION = "1.0"
# --- Wiring. These nine numbers are the whole hardware interface. -----------PIN_ENC_A = 16PIN_ENC_B = 17PIN_ENC_SW = 18PIN_START = 19PIN_MODE = 20PIN_SET = 21PIN_BUZZER = 22PIN_FOOT = 14PIN_OUT = 15
DEBOUNCE_MS = 25 # further edges on a button ignored for this longFOOT_SETTLE_MS = 200 # the jack is sampled for this long at bootPIEZO_HZ = 2400 # provisional: use your transducer's resonant frequencyPIEZO_DUTY = 32768 # half of 65535, a square waveCOUNTS_PER_DETENT = 4 # 2 on encoders that rest between transitions
# Quadrature transition table, indexed by (previous << 2) | current, where a# state is (A << 1) | B. The eight legal transitions give +1 or -1; the four# impossible ones and the four no-change ones give 0, so a bouncing contact is# rejected by the table itself rather than by a delay._STEP = (0, 1, -1, 0, -1, 0, 0, 1, 1, 0, 0, -1, 0, -1, 1, 0)
enc_a = machine.Pin(PIN_ENC_A, machine.Pin.IN, machine.Pin.PULL_UP)enc_b = machine.Pin(PIN_ENC_B, machine.Pin.IN, machine.Pin.PULL_UP)enc_sw = machine.Pin(PIN_ENC_SW, machine.Pin.IN, machine.Pin.PULL_UP)btn_start = machine.Pin(PIN_START, machine.Pin.IN, machine.Pin.PULL_UP)btn_mode = machine.Pin(PIN_MODE, machine.Pin.IN, machine.Pin.PULL_UP)btn_set = machine.Pin(PIN_SET, machine.Pin.IN, machine.Pin.PULL_UP)foot = machine.Pin(PIN_FOOT, machine.Pin.IN, machine.Pin.PULL_UP)out = machine.Pin(PIN_OUT, machine.Pin.OUT, value=0)buzzer = machine.PWM(machine.Pin(PIN_BUZZER))buzzer.duty_u16(0)
_counts = 0 # quadrature counts, four per detent on most encoders_bad = 0 # transitions the table calls impossible: bounce_state = (enc_a.value() << 1) | enc_b.value()_foot_released = 1 # replaced at boot by whatever the jack actually reads
def _on_quadrature(pin): """Hard-IRQ safe: no allocation, no printing, table lookup only.""" global _state, _counts, _bad now = (enc_a.value() << 1) | enc_b.value() step = _STEP[(_state << 2) | now] if step: _counts += step elif now != _state: _bad += 1 _state = now
enc_a.irq(trigger=machine.Pin.IRQ_RISING | machine.Pin.IRQ_FALLING, handler=_on_quadrature, hard=True)enc_b.irq(trigger=machine.Pin.IRQ_RISING | machine.Pin.IRQ_FALLING, handler=_on_quadrature, hard=True)
def beep(ms=40, hz=PIEZO_HZ): """One confirmation tone. Blocking, and short enough not to matter here.""" buzzer.freq(hz) buzzer.duty_u16(PIEZO_DUTY) time.sleep_ms(ms) buzzer.duty_u16(0)
def learn_foot_polarity(): """Take whatever the pedal reads at boot as its released state.
Sustain pedals are made both normally open and normally closed and many carry a polarity switch, so 'pressed reads 0' is an assumption this course will not make. Stand off the pedal while this runs. """ global _foot_released samples = [] end = time.ticks_add(time.ticks_ms(), FOOT_SETTLE_MS) while time.ticks_diff(end, time.ticks_ms()) > 0: samples.append(foot.value()) time.sleep_ms(5) _foot_released = 1 if sum(samples) * 2 > len(samples) else 0 return _foot_released
def foot_pressed(): return foot.value() != _foot_released
def detents(): """Encoder position in detents, signed.""" return _counts // COUNTS_PER_DETENT
def selftest(): """Report the resting state of every input. Nothing here moves the output.""" print("# Pure Silver timer input test v{}".format(VERSION)) print("# {}".format(sys.implementation)) released = learn_foot_polarity() print("# foot switch released state learned as {}".format(released)) print("# resting: enc_a {} enc_b {} enc_sw {} start {} mode {} set {}".format( enc_a.value(), enc_b.value(), enc_sw.value(), btn_start.value(), btn_mode.value(), btn_set.value())) if 0 in (btn_start.value(), btn_mode.value(), btn_set.value()): print("# FAIL: a button reads pressed with nothing touching it.") print("# Check for a short to ground, or a button wired across 3V3.") return False if out.value(): print("# FAIL: the output pin is high at rest.") return False beep() print("# PASS: every input rests high and the output rests low.") print("# If you heard nothing, the piezo, its resistor or PIEZO_HZ is wrong.") return True
def run(seconds=120): """Log every input event for `seconds`, then print the bounce figure.
Work through the panel with your eyes shut: turn the encoder twenty detents each way, press every button, press the encoder, and use the pedal. Every event should appear once, in the order you made it. """ global _bad if not selftest(): return _bad = 0 start_counts = _counts print("t_ms,event,detail") buttons = ((btn_start, "START"), (btn_mode, "MODE"), (btn_set, "SET"), (enc_sw, "ENC_PUSH")) last = {name: 1 for _, name in buttons} last_edge = {name: 0 for _, name in buttons} last_foot = False shown = detents() end = time.ticks_add(time.ticks_ms(), seconds * 1000) while time.ticks_diff(end, time.ticks_ms()) > 0: now = time.ticks_ms() here = detents() if here != shown: print("{},ENCODER,{:+d}".format(now, here - shown)) shown = here beep(8) for pin, name in buttons: v = pin.value() if v != last[name]: if time.ticks_diff(now, last_edge[name]) < DEBOUNCE_MS: continue last_edge[name] = now last[name] = v print("{},{},{}".format(now, name, "down" if v == 0 else "up")) if v == 0: beep() f = foot_pressed() if f != last_foot: last_foot = f print("{},FOOT,{}".format(now, "down" if f else "up")) if f: beep() time.sleep_ms(2) turned = abs(_counts - start_counts) print("# {} quadrature counts, {} rejected as impossible".format(turned, _bad)) if turned: print("# {:.2f} bad transitions per detent".format( _bad / (turned / COUNTS_PER_DETENT))) print("# a figure near zero means the table is filter enough;") print("# a scatter means fit the 10k and 10n on A and B and run this again.") buzzer.duty_u16(0)Three things in that listing are worth a second reading. The interrupt handler is registered with
hard=True and allocates nothing, because a hard interrupt handler that allocates will fail at the
worst possible moment; everything it does is a table lookup and two integer operations. The foot
switch’s polarity is learned rather than assumed. And the output pin is created with value=0 and
never touched again, so this program can be run with the lamp channel connected.
Testing and calibration
Section titled “Testing and calibration”Three tests, in this order. The first two are done in the light; the third is the reason the page declares a darkroom.
T1 — Every control responds, once, in the order you pressed it
Section titled “T1 — Every control responds, once, in the order you pressed it”Run t.run() and work the panel with your eyes shut. Read the log afterwards and check four things:
every event you made appears; no event appears that you did not make; a single press produces a single
down and a single up; and the encoder’s sign matches the direction you turned it. If it counts
backwards, swap A and B — in the wiring or in the two pin constants, whichever is easier.
Then read the bounce figure at the end of the log and act on it as stage 2 says. Record the number in your build log: it is a property of the encoder you bought, and it is the first line of this instrument’s certificate.
T2 — Legible and findable at the working position
Section titled “T2 — Legible and findable at the working position”In the dark, at the enlarger, with your hands where they will be:
- Can you find START without hunting, and are you confident you have not found MODE?
- Can you read the display from the working position at the brightness you intend to use?
- Does every accepted action produce a sound you can hear over the extractor and the tap?
- Can you turn the encoder by exactly three detents by feel?
A “no” to any of these is a panel problem and it is cheaper to fix now than after the fog test.
T3 — The fog test, on this instrument
Section titled “T3 — The fog test, on this instrument”The instrument is an emitter standing next to unexposed paper, and Kodak’s own guide makes the point that matters: virtually all exposures are cumulative, so the display’s contribution adds to the safelight’s rather than being separate from it. An instrument that passes on its own and pushes the room’s total over the line has still failed.
Run Part XVI’s fog test procedure unchanged, with one substitution: the light under test is the timer, running, at the brightness and distance you will actually use, in the position it will actually occupy. Follow that page for the grey-tone pre-exposure, the stepped exposure, the After and Before patches and the processing in total darkness, and read the result against its criterion — a safe time not more than half the time at which a change first becomes detectable.
Then run it again with the safelight on as well, because that is the condition you print in, and the cumulative rule means the pair has to be tested as a pair.
Three outcomes and what each means.
- No detectable change at the longest step, alone and with the safelight: record the time, the brightness setting and the distance, and treat the instrument as tested at those settings only.
- A change appears: turn the brightness down, move the instrument further away or hood it, and retest. Distance is the cheapest of the three because it costs nothing to try.
- A change appears at the lowest usable brightness: the display is not suitable and the instrument goes outside the room, or behind a shutter that the firmware closes during an exposure. Write that down as a result rather than as a failure — it is exactly the finding this test exists to produce.
Enter all three tests on the instrument’s certificate, with the date. The calibration page adds the timing figures to the same sheet later.
Alternative route
Section titled “Alternative route”Without a darkroom. The build itself needs none: stages 0 to 7 and tests T1 and T2 are bench work in daylight. The fog test is not substitutable, because the quantity it measures is light in a room. What a reader without a dark room should do is small and honest: keep paper in a changing bag until the moment of exposure, use a daylight tank for film, and record on the certificate that the instrument’s emission is untested — which is a different statement from “acceptable”, and the difference is the point. Part XVI’s room page lists the temporary blackouts that turn “no dark room” into “a dark room for two hours”, and two hours is more than this test needs.
Without soldering. Stage 7’s breadboard route is a complete build, and every test on this page applies to it unchanged.
Without buying an enclosure. A wooden box, a rigid plastic tool case, a biscuit tin — anything rigid, opaque and stable will do, provided the panel can be drilled accurately and the box does not slide on a dry bench. Opaque matters more than pretty: a translucent enclosure with an LED inside it is a lamp.
Without building at all. A bought enlarger timer, £112.20 to £429.00 on 5 September 2026, is a legitimate route through this whole part, and every test on this page still applies to it. A commercial instrument’s display has not been fog-tested in your room either.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Likely cause | Test that separates it |
|---|---|---|
| A button reads pressed at rest | Wired to 3V3 rather than to ground, or a solder bridge | selftest() names the pin; a meter across the button reads 0 Ω at rest |
| The encoder counts in the wrong direction | A and B swapped | Swap the two constants; nothing else changes |
| The encoder counts two per detent, or eight | The detent rests between transitions, or the table is being entered twice | Set COUNTS_PER_DETENT; the count per detent is printed by the test |
| Many “impossible” transitions | Contact bounce beyond what the table absorbs | Fit the 10 kΩ and 10 nF filter and rerun; the figure should fall to near zero |
| The pedal reads permanently pressed | Normally-closed pedal, or your foot was on it at boot | Reboot with your foot off it; selftest() prints the polarity it learned |
| No sound at all | Wrong pitch for the transducer, an active buzzer fitted instead of a passive one, or the 100 Ω open | Sweep the frequency by hand from 1 to 5 kHz and listen for the peak |
| Sound too faint at the easel | Driven off resonance, or the piezo is buried inside the enclosure | Drill a hole over the disc; that alone is often several times louder |
| The display flickers or the Pico resets | Display drawing more than the 3V3 pin’s 300 mA budget | Meter the supply current; give the display its own regulated feed |
| The instrument slides on the bench when you press START | Nothing wrong electrically | Feet, or a heavier enclosure; a control you have to hold still is a control you will mis-press |
Questions
Section titled “Questions”- Your input test reports four counts per detent and 0.02 impossible transitions per detent. Say what you fit, and what you would have fitted at 1.5 per detent, and why the second number is not simply “worse wiring”.
- A friend proposes labelling the three buttons with coloured caps instead of different heights. Explain, from the conditions this instrument is used in, why that fails, and name the one situation in which colour would still be worth adding.
- The foot switch reads permanently pressed when a particular pedal is plugged in, and correctly with another. Give the mechanism, and say why the firmware’s answer is better than a solder-blob fix.
- Your display module’s datasheet gives a maximum current of 340 mA at full brightness. State exactly what you may not do, quoting the number from the board datasheet that decides it, and give two arrangements that would be acceptable.
- The fog test passes with the timer alone and fails with the timer and the safelight together. Interpret that result, and say which of the two you would change first and why.
- Explain why the input-test firmware creates the output pin with
value=0and then never touches it, and what would go wrong if it toggled the pin to prove that it worked. - You cannot solder. Set out the route through this page that gets you a working instrument, and name the one thing you must not compromise on.
Further experiments
Section titled “Further experiments”Measure your encoder’s bounce properly. Put a second Pico on the A line, timestamp every edge with
ticks_us, and turn the knob at three speeds. The distribution of inter-edge intervals is the bounce
figure that this page could only estimate, and it tells you whether the RC filter’s 100 µs is generous
or marginal for your part.
Find the piezo’s resonance by measurement rather than datasheet. Sweep the PWM frequency from 1 to 6 kHz in 100 Hz steps, hold each for a second, and record the loudest by ear at the working position — then repeat with the enclosure closed. The difference between the two is what a hole over the disc is worth.
Test the display’s emission against the safelight’s. Both are emitters in the same room, and Part XVI’s spectra page gives the framework. Running the fog test on the display alone, the safelight alone and the two together produces three numbers whose relationship is the cumulative-exposure rule made visible.
Try the panel on somebody else. Hand the instrument, in the dark, to a person who did not build it, and ask them to set 12.4 seconds and start an exposure. Everything you learn in the next two minutes is about the design and not about them.
Check your understanding
Sources for this page
6 cited · checked 2026-09-05
- 01Raspberry Pi Pico Datasheet: An RP2040-based microcontroller boardRaspberry Pi Ltd§ Section 1, key features - 26 multi-function 3.3 V GPIO of which 0 to 22 are digital only and 26 to 28 may also be ADC inputs, 16 PWM channels; section 2.3 and the pin descriptions - VBUS is the 5 V from the USB connector, VSYS accepts 1.8 to 5.5 V, and the 3V3 pin is an output whose external load is to be kept below 300 mA; section 4.2, general purpose IO, powered from the on-board 3.3 V rail and fixed at 3.3 Vdatasheets.raspberrypi.com/pico/pico-datasheet.pdftier 1, primary2026-09-05
- 02RP2040 Datasheet: A microcontroller by Raspberry PiRaspberry Pi Ltd§ Section 2.19, GPIO - output drive strength can be set to 2 mA, 4 mA, 8 mA or 12 mA, with the pad control register's DRIVE field defaulting to 0x1; section 5.5.3.5, interpreting GPIO output voltage specifications, where those figures are stated not to be hard limits but to describe how far the output voltage falls at a stated load; section 2.9.1, digital IO supply, which asks for a 100 nF decoupling capacitor close to each IOVDD pindatasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdftier 1, primary2026-09-05
- 03Quick reference for the RP2, MicroPython documentationDamien P. George, Paul Sokolovsky and contributors§ Pins and GPIO - the internal pull-up on an input, and Pin.irq with its trigger constants; PWM - eight slices of two channels each, sixteen channels in total; hardware I2C, where machine.I2C(0) takes the port's default assignment of scl on Pin 5 and sda on Pin 4docs.micropython.org/en/latest/rp2/quickref.htmltier 1, primary2026-09-05
- 04How Safe Is Your Safelight? A Guide to Darkroom Illumination, publication K-4Eastman Kodak Company, 2006§ Test for Black-and-White Papers - the grey-tone pre-exposure targeted at a reflection density of 0.25 to 0.50, the stepped exposure made with a moving card, the After and Before enlarger exposures at opposite ends of the sheet, processing in total darkness, the term super-additive exposure with the statement that virtually all exposures are cumulative, and the definition of safe time as an exposure less than or equal to one half of the time required to produce a detectable changekodak.com/content/products-brochures/Film/KODAK-A-Guide-to-Darkroom-Illumination-K-4.pdftier 1, primary2026-09-05
- 05EH40/2005 Workplace exposure limits, containing the list of workplace exposure limits for use with COSHHHealth and Safety Executive, 2005§ Table 1 - rosin-based solder flux fume, CAS 8050-09-7, at 0.05 mg/m3 over eight hours and 0.15 mg/m3 over fifteen minutes; Annotations - Sen, capable of causing occupational asthmahse.gov.uk/pubns/priced/eh40.pdftier 1, primary2026-09-05
- 06Electrical safety and you: A brief guide, INDG231(rev1)Health and Safety Executive, 2012§ Reducing the risk, Reduce the voltage - limit the supply voltage to the lowest needed to get the job done, with battery-operated equipment named as the safest choicehse.gov.uk/pubns/indg231.pdftier 1, primary2026-09-05
Formulas, hazard statements, historical dates and process descriptions on this page were checked against the sources above on the date shown. Safety data changes: obtain the current safety data sheet for the product you actually buy before you open it.