Seyed Masoud Hosseini · Overview · Study log · Ideas · Transcript · RSS feed

Embedded Systems, 6502 breadboard computer · Lecture 17 of 29 · 31:17

Lecture 17: How Hardware Timers Work

How do hardware timers work? on YouTube

Study guide

What this lecture covers

The lecture answers a practical question for the breadboard 6502 project: how do you make a program wait a precise amount of time without wasting the processor on an unpredictable busy-wait loop? It builds on the earlier videos about the 6522 versatile interface adapter's I/O ports, now covering the chip's built-in timers.

You start with software delay loops using NOP and decrement instructions, see why they are imprecise and wasteful, then move to the 6522's Timer 1 in one-shot mode, and finally to free-run mode with interrupts, which lets the processor keep counting time in the background while doing other work.

Key ideas

  • Busy-wait delay loop: using NOP or a decrementing counter (in the X or Y register) to burn clock cycles is simple but imprecise and blocks the processor from doing anything else.
  • Timer 1 one-shot mode: set both control bits in the auxiliary control register (ACR) to 00; the timer counts down a 16-bit value loaded into the counter registers and sets an interrupt flag bit when it reaches zero.
  • Loading the counter starts it: writing the high byte of Timer 1's counter both loads the value and starts the countdown on the next clock cycle; the low byte must be written first.
  • Interrupt flag register (IFR): bit 6 signals a Timer 1 timeout; you can poll it with the BIT instruction and BVC, or read the low counter byte to clear it.
  • Free-run mode: setting the ACR control bits to 01 makes the timer automatically reload from its latch and restart after each timeout, producing continuous, evenly spaced interrupts.
  • Interrupt enable register (IER): setting its enable bit and the Timer 1 bit lets the 6522 raise a real IRQ, handled by a small interrupt routine instead of a polling loop.
  • Tick counter: an interrupt handler that increments a multi-byte counter (ticks) on every timer interrupt gives the rest of the program a free-running clock it can compare against to schedule periodic actions.
  • Reading a multi-byte counter safely: interrupts must be disabled briefly while reading a counter that the interrupt handler also updates, to avoid reading a value that changes mid-read.

Walkthrough

Delaying with NOP loops (0:00)

The lecture opens with a flashing-LED program and shows the timing problem directly: without a delay, the LED toggles far too fast to see. It first tries NOP instructions, then a decrementing loop using the X register (255 iterations), and finally a nested loop with the Y register to slow things down enough to see the LED blink on an oscilloscope. This section establishes why counting instruction cycles by hand is fragile and clock-speed dependent, and why the processor is stuck doing nothing useful while it waits.

Timer 1 one-shot mode (6:02)

The video introduces the 6522's registers for Timer 1 (counter low/high, latch low/high) and the auxiliary control register that selects timer mode. Setting the ACR's top two bits to zero puts Timer 1 into one-shot (countdown) mode. Loading the low counter byte then the high byte sets a 16-bit count and starts the timer; writing the high byte is the trigger. A worked example loads a value for a 50-millisecond delay ($C350 at 1 MHz) and rewrites the delay subroutine to poll the interrupt flag register's bit 6, using BIT and BVC, until the timer expires.

Free-run mode and continuous interrupts (14:52)

Because one-shot mode caps delays at 65,535 cycles and still blocks the processor while polling, the lecture switches to free-run mode (ACR bits set to 01). In this mode the timer automatically reloads from the latch and restarts after each expiration, generating a steady stream of interrupts. The timer is configured to fire roughly every 10 milliseconds, accounting for the datasheet's "n+2 cycles" detail when choosing the count value.

Building an interrupt-driven tick counter (19:08)

The interrupt enable register is configured so Timer 1 timeouts trigger a real IRQ. A minimal interrupt handler clears the interrupt flag and increments a 32-bit ticks variable spread across four bytes in zero page, handling carry between bytes. With ticks incrementing every 10 milliseconds in the background, the main program can compare the current tick count against a saved value to decide when 250 milliseconds have passed, toggling the LED without ever blocking in a wait loop.

Reusable update routines and a second periodic task (26:11)

The delay logic is refactored into an update_led subroutine that checks elapsed ticks and toggles the LED only when due, then returns immediately. A second update_lcd subroutine reuses the same pattern to print the current tick count to an LCD once per second. The main loop simply calls both subroutines repeatedly, showing that several independently timed tasks can share the same background tick counter. The lecture also flags a subtlety: reading the multi-byte ticks value must briefly disable interrupts so an interrupt handler cannot change it mid-read.

Before you watch

  • Familiarity with the 6522 VIA's I/O ports (port A/B, data direction registers) from earlier videos in this series.
  • Basic 6502 assembly: registers, branches, and subroutines (JSR/RTS).
  • Comfort with hexadecimal and reading a datasheet's bit-level register descriptions.

Check your understanding

  1. Why does writing to Timer 1's high counter byte, rather than the low byte, start the countdown?
  2. What is the practical limit on delay length in one-shot mode, and how does free-run mode get around it?
  3. Why must interrupts be disabled briefly when the main program reads the multi-byte ticks counter?
  4. How does the update_led and update_lcd pattern let two differently timed tasks share one interrupt-driven clock?

Chapters

From the YouTube description

In this video, learn how a CPU uses hardware timers to control execution timing.
More 6502 stuff: https://eater.net/6502

Support these videos on Patreon: https://www.patreon.com/beneater or https://eater.net/support for other ways to support.

------------------

Social media:
Website: https://www.eater.net
Twitter: https://x.com/beneater
Patreon: https://patreon.com/beneater
Reddit: https://www.reddit.com/r/beneater

Special thanks to these supporters for making this video possible:
Aleksey Smolenchuk, Anders Carlsson, Andrew C. Young, Anson VanDoren, Anthanasius, anula, Armin Brauns, Ben, Ben Cochran, Ben Kamens, Ben Williams, Benny Olsson, Bill Cooksey, Binh Tran, Bouke Groenescheij, Bradley Pirtle, Bradley Stach, Bryan Brickman, Burt Humburg, Carlos Ambrozak, Chris, Christian Carter, Christopher Blackmon, Dale Andrew Darling, Daniel Jeppsson, Daniel Tang, Dave Burley, Dave Walter, David Clark, David Cox, David Dawkins, David House, David Sastre Medina, David Turner, David Worsham, Dean Bevan, Dean Winger, Dilip Gowda, Dissy, dko, Dmitry Guyvoronsky, Dušan Dželebdžić, Dzevad Trumic, Emilio Mendoza, Eric Dynowski, Erik Broeders, Eugene Bulkin, Evan Thayer, Eveli László, fisksvett, fxshlein, George Miroshnykov, Harry McDow, hotwire33, Ingo Eble, Ivan Sorokin, James Capuder, james schaefer, Jared Dziedzic, Jason DeStefano, JavaXP, Jaxon Ketterman, Jay Binks, Jayne Gabriele, Jeremy, Jeremy Cole, Jesse Miller, Jim Kelly, Jim Knowler, Jim Van Meggelen, Joe Beda, Joe OConnor, Joe Pregracke, Joel Miller, John Fenwick, John Hamberger jn., John Meade, Jon Dugan, Jonn Miller, Joseph Portaro, Joshua King, Jurģis Brigmanis, Kai Wells, Kefen, Kenneth Christensen, Kitick, Koreo, Lambda GPU Workstations, Larry, László Bácsi, Lucky Resistor, Lukasz Pacholik, Marcos Fujisawa, Marcus Classon, Mark Day, Marko Clemente, Martin Noble, Martin Roth, Mats Fredriksson, Matt Krueger, Matthäus Pawelczyk, Matthew Duphily, Max Gawletta, Maxim Hansen, melvin2001, Michael Tedder, Michael Timbrook, Michael Weitman, Miguel Ríos, mikebad, Mikel Lindsaar, Miles Macchiaroli, Muqeet Mujahid, My Yiddishe Mama, Nicholas Counts, Nicholas Moresco, Nick Chapman, Not Yet Wise, Örn Arnarson, Paul Pluzhnikov, Paul Randal, Pete Dietl, Phil Dennis, Philip Hofstetter, Phillip Glau, PixelSergey, Porus, ProgrammerDor, Randal Masutani, Randy True, raoulvp, Renaldas Zioma, Ric King, Richard Butler-Malone, Richard Ertel, Rick Hennigan, Robert Comyn, Robert Diaz, Robey Pointer, §çīŤøşHï ŇåĶąýŐbŕÔ, Scott Holmes, Sean Patrick O’Brien, Sergey Kruk, SonOfSofaman, Stefan Nesinger, Stefanus Du Toit, Stephen, Stephen Kovalcik, Stephen Riley, Stephen Smithstone, Steve Jones, Tayler Porter, TheWebMachine, Thomas Bruggink, Thomas Eriksen, Thomas Rolfs, Tim Walkowski, Tom, Tom Yedwab, Tommaso Palmieri, Tyler Latham, Vincent Bernat, Walter Montalvo, Warren Miller, William, Wim Coekaerts, Wraithan McCarroll, xisente, Yee Lam Wan

← SPI: The Serial Peripheral Interface · Lecture 18: The RS-232 Protocol →