Seyed Masoud Hosseini · Overview · Study log · Weekly summaries · Ideas · Search · Transcript · RSS feed

Embedded Systems, 6502 breadboard computer · Lecture 27 of 29 · 20:25

27 of 29: How input buffering works

How input buffering works on YouTube

Study guide

What this lecture covers

Pasting a program into the breadboard computer's terminal garbles the text: the CPU is busy processing one character when the next arrives, so characters get dropped. This lecture fixes that by moving character reception into an interrupt handler backed by a circular buffer, so the UART can receive input as fast as it arrives instead of at a rate slow enough for the CPU to keep up.

It follows directly from the earlier video that got Microsoft BASIC running on the breadboard 6502. By the end, both WozMon and BASIC read characters from the new buffer instead of polling the UART directly, and typed or pasted input no longer loses characters at normal speed.

Key ideas

  • Interrupt on receive: the 6551 UART can pull the IRQ line low when its receiver data register is full, letting the CPU handle a new character immediately instead of polling.
  • Circular buffer: a fixed-size buffer with a write pointer and a read pointer; the write pointer advances as characters arrive, the read pointer advances as software consumes them.
  • Wraparound: using one-byte pointers into a 256-byte buffer means a pointer naturally wraps from 255 back to 0, which is why the buffer is sized at exactly 256 bytes.
  • Buffer subroutines: separate routines to initialize the buffer, write a character, read a character, and compute how many unread bytes are buffered (write pointer minus read pointer).
  • Register discipline: an interrupt handler must save and restore any register it touches (here, A and X) so it doesn't disturb whatever code was interrupted.
  • Acknowledging the interrupt: reading the UART's status register clears the interrupt condition; without this the IRQ line stays low and the handler runs forever.
  • Spurious interrupts: leaving unused UART pins (DCD, DSR) floating can trigger extra interrupts; grounding them stops the false triggers.
  • Overrun: the circular buffer can still lose data if input arrives faster than it's read and the write pointer laps the read pointer.

Walkthrough

Unbuffered input (0:00)

Pasting a program directly into the terminal garbles it because the computer can't process characters as fast as they arrive; a 20ms pacing delay in the terminal program works around it but isn't a real fix.

Interrupts (1:04)

The 6551 UART's data sheet shows it can assert IRQ when its receiver data register is full. Clearing bit 1 of the command register (changing it from 8B to 89) enables that interrupt. The UART's interrupt output pin is wired to the 6502's IRQ pin, with a pull-up resistor added since the UART's interrupt line is open-drain.

Circular buffer (3:20)

A write pointer tracks where the next received character goes; a read pointer tracks where the next character to consume comes from. When the pointers are equal, the buffer is empty. Using one-byte pointers makes the buffer wrap automatically, which is why it's called circular.

Setting up variables (5:01)

The read and write pointers go in zero page (shifting BASIC's existing zero-page layout by two bytes to make room), and the 256-byte input buffer gets its own memory segment defined in the linker config, loaded read-write with no ROM image.

Buffer subroutines (7:25)

Four routines are written: init buffer sets both pointers equal, write buffer stores the character in A at the write pointer's offset and increments it, read buffer does the reverse for reading, and a size routine subtracts the read pointer from the write pointer to report how many bytes are queued.

Interrupt handler (10:45)

init buffer is called once at reset, before interrupts are enabled. The interrupt handler itself pushes A and X, reads the received character, writes it to the buffer, restores the registers, reads the status register to acknowledge the interrupt, and returns. The whole handler takes roughly 60 clock cycles, well under the time it takes to receive one character at 19.2 kbps.

New input routines (14:03)

The BASIC input routine (character in) is changed to check the buffer's size instead of the UART's status register, and to call read buffer instead of reading the UART directly. WozMon's input code is simplified similarly, removing its direct hardware access and manual character echo.

Testing it out (16:44)

After flashing the updated ROM, typed characters repeat unpredictably, pointing to spurious interrupts.

Fixing a bug (17:50)

The UART's status register shows that floating DCD and DSR pins can also trigger interrupts. Grounding both unused pins eliminates the spurious interrupts.

Success! (19:05)

With pacing removed, pasting the same program works instantly and correctly. Pasting a much larger block still overruns the 256-byte buffer, which the next video addresses.

Before you watch

  • Know how the 6551 UART and the WozMon/BASIC input routines work from the earlier breadboard 6502 videos in this course.
  • Be comfortable with 6502 assembly basics: registers, the stack, and subroutine calls.
  • Understand memory-mapped I/O and how zero page addressing works on the 6502.

Check your understanding

  1. Why does a one-byte read/write pointer pair make a 256-byte buffer naturally "circular"?
  2. What does the interrupt handler have to do before returning, and why?
  3. Why did grounding the DCD and DSR pins fix the repeated-character bug?
  4. What limitation remains in the buffered input scheme by the end of the lecture?

Vocabulary

garble (verb)
To distort or scramble information so it becomes unreadable.
Pasting text too fast can garble the input.
circular buffer (noun)
A fixed-size storage area that wraps back to the start once it's full.
A circular buffer stores incoming characters temporarily.
write pointer (noun)
A tracked position marking where new data should be stored next.
The write pointer advances as characters arrive.
read pointer (noun)
A tracked position marking where the next data should be read from.
The read pointer advances as the program consumes characters.
wraparound (noun)
A value looping back to zero after reaching its maximum.
Wraparound makes the buffer behave in a circular way.
acknowledge (verb)
To confirm receipt of a signal so it can be cleared or reset.
Reading the status register acknowledges the interrupt.
spurious interrupt (noun)
An unwanted interrupt caused by noise or an unintended signal.
A spurious interrupt caused repeated characters to appear.
open-drain (adjective)
Describing an output that can only pull a signal low, needing a resistor to bring it high.
The UART's interrupt output is open-drain.
overrun (noun)
A situation where new data arrives faster than it can be processed, causing loss.
A large paste can still cause an overrun of the buffer.
register discipline (noun)
The careful practice of saving and restoring registers a routine uses.
Good register discipline prevents the handler from corrupting other code.
paste (verb)
To insert previously copied text all at once.
Pasting a program too fast can garble the text.
pacing (noun)
The rate at which something happens or is delivered over time.
A pacing delay slows the terminal enough for the CPU to keep up.
ground (verb)
To connect a wire directly to the common reference voltage of zero.
Grounding the unused pins stops the spurious interrupts.
consume (verb)
To read and use up data that was produced elsewhere.
The main loop consumes characters from the buffer.
queue (verb)
To wait in line to be processed.
Unread characters queue in the buffer until they're read.
assert (verb)
To actively set a signal to its active state.
The UART can assert IRQ when its receiver is full.
loss (noun)
Data that disappears or is dropped instead of being processed.
Overrun can cause data loss if the buffer fills up.
instant (adjective)
Happening immediately, without any delay.
Pasting now works instantly without pacing.
unreliable (adjective)
Not consistently correct or trustworthy.
Unreliable timing caused characters to be dropped.
repeat unpredictably (phrase)
To happen again in a way that isn't consistent or expected.
Typed characters repeat unpredictably due to spurious interrupts.

Chapters

From the YouTube description

Code from this video is at: https://github.com/beneater/msbasic
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:
19day, Adrien Friggeri, Aleksey Smolenchuk, Anthony Weems, anula, Ben, Ben Cochran, Benjamin D. Williams, Benjamin Elder, Benjamin Keil, Benji Bromberg, Bill Cooksey, Binh Tran, Богдан Федоров, Bradley Stach, Brian Haug, Burt Humburg, Carl Fooks, Carsten Schwender, Chad Fertig, Chai, Chris Anders, Chris Lajoie, criis, Cristi Cobzarenco, Daniel Tang, Dave Walter, Dave Westwood, David Clark, David Cox, David Dawkins, David House, David Klassen, David Sastre Medina, David Turner, Dean Winger, Deep Kalra, DemoniacDeath, Dennis Henderson, Dilip Gowda, Dirk Sperling, Dmitry Guyvoronsky, Dustin Campbell, Dzevad Trumic, Emilio Mendoza, Eric Dynowski, Erik Broeders, Erik Chancy, Erik Granlund, Ethan Sifferman, Eugene Bulkin, Evan Serrano, Evan Thayer, Eveli László, Florian Bürgi, George Harris, George Miroshnykov, ghostdunk, Glen Jarvis, Gregory Burns, GusGold, Hailey, Hovis Biddle, Ingo Eble, Ingram Leedy, Isaac Parker, Jack McKinney, Jacob Ford, James Capuder, James Chacon, James Will, Jason DeStefano, Jason Grim, Jason Thorpe, JavaXP, Jaxon Ketterman, jemmons, Jeremy Cole, Jesse Miller, Jim Kelly, Jim Kelly, Jim Knowler, Joe Beda, Joe Pregracke, Joe Rork, Joel, John Henning, Jon Dugan, Jonn Miller, Josh Smith, Justin Graziani, Justin Williams, Kai Wells, Kefen, Ken Paul, Kennard Smith, Kenneth Christensen, Kevin McQuown, Kristian Høy Horsberg, Kyle Kellogg, Lambda GPU Workstations, Larry Scherr, László Bácsi, Lithou, Marcos Fujisawa, Marcus Classon, Mariano Uvalle, Mark Day, Martin Noble, Matthew Clifford, melvin2001, Michael Cartwright, MICHAEL SLASS, Michael Tedder, Michael Timbrook, Michael Weitman, Miguel Ríos, mikebad, Miles Macchiaroli, Muqeet Mujahid, Nate Welch, Nicholas Counts, Nicholas Moresco, Nick Chapman, Olivier HUBER, Örn Arnarson, Owen Arnett, Paul Heller, Paul Pluzhnikov, Pete Dietl, Phil Dennis, Philip Hofstetter, Ponytail Bob, ProgrammerDor, Ralph Irons, Randal Masutani, Randy True, raoulvp, real_huitz, Ric King, Richard Wagoner, Rick Hennigan, Rob Bruno, Robert Brown, Robert Diaz, Robey Pointer, Roland Munsil, Ryan Morrison, Sagnik Bhattacharya, Sam Sturgis, Scott Gorlick, Scott Holmes, Sean Patrick O’Brien, Sergey Kruk, snc, SonOfSofaman, sorek.uk, Spencer Ruport, spookybassoon, Stefan Nesinger, Stephen Kovalcik, Stephen Riley, Steve Jones, Thomas Eriksen, Tim Oriol, Tim Sanders, Tim Walkowski, Tom, Tom Smith, Trevor Johnston, Trey Webb, tryonlinux, Tyler Latham, Vaida N, Vitali Filinkou, Warren Miller, Wraithan McCarroll

0:00 - Unbuffered input
1:04 - Interrupts
3:20 - Circular buffer
5:01 - Setting up variables
7:25 - Buffer subroutines
10:45 - Interrupt handler
14:03 - New input routines
16:44 - Testing it out
17:50 - Fixing a bug
19:05 - Success!

← Lecture 26: Running MSBASIC on My Breadboard 6502 Computer · 28 of 29: Computer noises →