Seyed Masoud Hosseini · Overview · Study log · Ideas · Transcript · RSS feed
Embedded Systems, 6502 breadboard computer · Lecture 15 of 29 · 20:03
Keyboard Interface Software: From Scan Codes to Characters
Study guide
What this lecture covers
The previous two videos built the hardware to capture PS/2 keyboard scan codes and interrupt the CPU on each one, but the running program just printed the raw scan code as a number. This lecture asks how to turn that raw interrupt-driven scan code stream into readable, correctly-cased text on the LCD, which requires buffering keystrokes, mapping scan codes to ASCII, and tracking key state like releases and the shift key.
By the end, you can explain how a circular buffer decouples fast keyboard input from slower program reads, how a scan-code lookup table converts hardware codes into characters, and how a simple state flag distinguishes a "key released" event from a genuine keypress so the display shows only intended characters.
Key ideas
- Circular keyboard buffer: a 256-byte buffer in RAM with independent write and read pointers; the interrupt handler writes new scan codes, and the main loop reads them, with wraparound handled naturally because both pointers are 8-bit.
- Interrupts modify shared state: because the buffer pointers are updated inside the interrupt handler, the main loop briefly disables interrupts while comparing them to avoid reading inconsistent values.
- Scan-code-to-ASCII mapping table: a 256-byte lookup table in ROM, indexed directly by scan code, converts hardware codes into printable characters, with unmapped codes shown as
?. - Handling the release code: the keyboard sends
F0before the scan code of a released key; a flag bit records "the next scan code is a release" so that byte can be discarded instead of being printed. - Keyboard flags byte: a single byte in RAM holds multiple state bits (release-pending, shift-held), letting the interrupt handler track ongoing keyboard state across interrupts.
- Shift key tracking: separate scan codes for left and right shift are checked on press (to set the shift flag) and release (to clear it), using the same release-detection logic as any other key.
- Two lookup tables: an unshifted and a shifted key map exist side by side, and the handler picks which one to use based on the shift flag before writing a character into the buffer.
Walkthrough
Recap and program redesign (0:00)
The lecture reviews the previous video's behavior (printing raw scan codes as numbers) and states the goal: interpret those codes into actual typed characters.
Designing a circular buffer (0:45)
A 256-byte keyboard buffer is allocated in RAM with separate write and read pointers; when they're equal, nothing new has been typed, and when they differ, unread keystrokes are waiting.
Implementing the main loop (2:20)
The main loop compares the read and write pointers (with interrupts briefly disabled for the comparison), and when they differ, reads the next character from the buffer, prints it with the existing print_char routine, and advances the read pointer.
Updating the interrupt handler (4:02)
The interrupt handler is renamed to a keyboard interrupt handler: it reads the scan code from port A and stores it into the buffer at the write-pointer offset, then increments that pointer, saving and restoring the X register around the work.
Scan code to ASCII mapping (5:08)
A 256-byte key map table is built in ROM, with each byte holding the ASCII character for that scan code position (or ? for unmapped codes), and the interrupt handler is updated to look up the character before storing it in the buffer.
Testing the basic mapping (7:21)
Pressing keys now prints correct letters, but each keypress is followed by an unwanted ? from the F0 release scan code appearing in the output.
Handling key release codes (8:10)
A keyboard_flags byte and a release-tracking bit are introduced: when the handler sees the F0 release scan code, it sets the flag and skips buffering that byte; on the next interrupt, if the flag is set, that scan code (the key being released) is also discarded and the flag is cleared.
Detecting shift key state (11:01)
The handler checks incoming scan codes against the left- and right-shift scan codes, setting a shift bit in keyboard_flags on press and clearing it on the matching release sequence, reusing the same release-detection pattern already built for ordinary keys.
Implementing shift functionality (12:26)
A second lookup table, the shifted key map, is added as a copy of the original with capital letters and shifted symbols. The handler checks the shift flag before mapping a scan code, choosing the shifted or unshifted table accordingly.
Final testing and conclusion (17:04)
After reprogramming the EEPROM, typing produces correct lowercase letters, and holding shift correctly produces capital letters, confirming the buffer, release handling, and shift logic all work together. The lecture closes by suggesting further extensions like handling Enter or Escape.
Before you watch
- Watch "So how does a PS/2 keyboard interface work?" and "Keyboard interface hardware" first, since this lecture builds directly on the scan code format and the interrupt-triggering circuit from those videos.
- Be comfortable with indexed addressing modes (
LDA table,X) and bitwiseAND/ORAoperations on the 6502, used throughout for buffer indexing and flag manipulation.
Check your understanding
- Why does the main loop disable interrupts briefly while comparing the read and write pointers of the keyboard buffer?
- What problem does the circular buffer solve that simply reading and printing each scan code immediately would not?
- How does the program distinguish a key release event from a genuine keypress, and why is that necessary?
- Why does the shift key need two lookup tables instead of, say, adding an offset to the character code?
- What would happen if the keyboard buffer's write pointer wrapped around and caught up to the read pointer while the user was still typing quickly?
Chapters
- 0:00 Keyboard interfacing review
- 0:45 Designing a circular buffer
- 2:20 Implementing the main loop
- 4:02 Updating the interrupt handler
- 5:08 Scan code to ASCII mapping
- 7:21 Testing the basic mapping
- 8:10 Handling key release codes
- 11:01 Detecting shift key state
- 12:26 Implementing shift functionality
- 17:04 Final testing and conclusion
From the YouTube description
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:
Aaron Todd, Aleksey Smolenchuk, Alexander Wendland, Andrew C. Young, Anson VanDoren, Anthanasius, anula, Armin Brauns, Asherah Connor, Ben Cochran, Ben Dyson, Ben Kamens, Ben Williams, Benny Olsson, Bill Cooksey, Binh Tran, Bouke Groenescheij, Bradley Pirtle, Bradley Stach, Brent Reusing, Brian T Hoover, Bryan Brickman, Bryan Glezerson, Carlos Ambrozak, Christopher Blackmon, Dale Andrew Darling, Daniel Jeppsson, Daniel Tang, dans, Dave Burley, Dave Walter, David Brown, David Clark, David Cox, David House, David Sastre Medina, David Turner, David Worsham, Dean Winger, Dissy, dko, Dmitry Guyvoronsky, Dušan Dželebdžić, Dzevad Trumic, Emilio Mendoza, Eric Dynowski, Erik Broeders, Eugene Bulkin, George Miroshnykov, Harry McDow, Ian Tait, Ingo Eble, Ivan Sorokin, James Capuder, james schaefer, Jared Dziedzic, Jason DeStefano, Jason Specland, JavaXP, Jaxon Ketterman, Jay Binks, Jayne Gabriele, Jeremy A., Jim Kelly, Jim Knowler, Jim Van Meggelen, Joe Beda, Joe OConnor, Joe Pregracke, Joel Miller, John Fenwick, John Meade, Jon Dugan, Joseph Portaro, Joshua King, Jurģis Brigmanis, Kai Wells, Kefen, Kenneth Christensen, Kent Collins, Kitick, Koreo, Lambda GPU Workstations, Larry, László Bácsi, Lucky Resistor, Lukasz Pacholik, Marcos Fujisawa, Marcus Classon, Mark Day, Martin Roth, Mats Fredriksson, Matt Krueger, Matthäus Pawelczyk, Matthew Duphily, melvin2001, Michael Tedder, Michael Timbrook, Michael Weitman, Miguel Ríos, mikebad, Mikel Lindsaar, Miles Macchiaroli, moi n, Nicholas Counts, Nicholas Moresco, Nick, Not Yet Wise, Örn Arnarson, Paul Pluzhnikov, Paul Randal, Pete Dietl, Phil Dennis, Philip Hofstetter, Phillip Glau, PixelSergey, ProgrammerDor, Randal Masutani, Randy True, raoulvp, Renaldas Zioma, Ric King, Rob Bruno, Robert Comyn, Robert Diaz, Roland Bobek, sam raza, Scott Holmes, Sergey Kruk, SonOfSofaman, Stefan Nesinger, Stefanus Du Toit, Stephen, Stephen Riley, Stephen Smithstone, Steve Jones, Steven Zilberberg, Tayler Porter, TheWebMachine, Thomas Bruggink, Tii, Tom, Tommaso Palmieri, Tyler Latham, Vincent Bernat, Vladimir Solomatin, Walter Montalvo, Warren Miller, Wim Coekaerts, Wraithan McCarroll, xisente, Yee Lam Wan
← Keyboard Interface Hardware: Triggering Interrupts · SPI: The Serial Peripheral Interface →
