Seyed Masoud Hosseini · Overview · Study log · Ideas · 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?

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 →