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

Embedded Systems, 6502 breadboard computer · Lecture 15 of 29 · 20:03

Keyboard Interface Software: From Scan Codes to Characters

Keyboard interface software on YouTube

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 F0 before 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 bitwise AND/ORA operations on the 6502, used throughout for buffer indexing and flag manipulation.

Check your understanding

  1. Why does the main loop disable interrupts briefly while comparing the read and write pointers of the keyboard buffer?
  2. What problem does the circular buffer solve that simply reading and printing each scan code immediately would not?
  3. How does the program distinguish a key release event from a genuine keypress, and why is that necessary?
  4. Why does the shift key need two lookup tables instead of, say, adding an offset to the character code?
  5. 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?

Vocabulary

circular buffer (noun)
A fixed-size storage area where writing wraps back to the beginning after reaching the end.
A circular buffer holds scan codes until the program reads them.
write pointer (noun)
A tracked position showing where the next new item should be stored.
The interrupt handler advances the write pointer after each key.
read pointer (noun)
A tracked position showing where the next item should be read from.
The main loop advances the read pointer as it prints characters.
wraparound (noun)
The behavior of a value looping back to the start after reaching its maximum.
Wraparound is handled naturally since both pointers are 8-bit.
lookup table (noun)
A stored list used to quickly find a matching value for a given input.
A lookup table converts scan codes into ASCII characters.
shared state (noun)
Data that both the main program and an interrupt handler can access and change.
The buffer pointers are shared state between the main loop and the interrupt.
flag bit (noun)
A single bit used to record whether some condition is true or false.
A flag bit marks that the next scan code is a key release.
decouple (verb)
To separate two parts so one doesn't have to wait directly on the other.
The buffer decouples fast typing from slower screen printing.
state (noun)
The current condition or settings a system is remembering.
The keyboard flags byte tracks ongoing state like shift being held.
offset (noun)
A position measured relative to a starting point.
The scan code is stored at the offset given by the write pointer.
discard (verb)
To throw away or ignore a piece of data.
The release scan code is discarded instead of printed.
interpret (verb)
To assign meaning to raw data.
The goal is to interpret scan codes into real characters.
unmapped (adjective)
Not assigned any corresponding value in a lookup table.
Unmapped codes are shown as a question mark.
pending (adjective)
Waiting to happen or be resolved.
A release-pending flag tracks an incoming key release.
extension (noun)
An addition that builds on existing functionality.
Handling Enter or Escape is suggested as a further extension.
redesign (noun)
A change to how something is structured or built.
The program redesign turns raw codes into real text.
consistent (adjective)
Staying the same and not contradicting itself.
Interrupts must be disabled to keep the pointers consistent.
advance (verb)
To move something forward by one step.
The read pointer advances after each character is consumed.
confirm (verb)
To prove that something behaves as expected.
Testing confirms the buffer and shift logic work together.
distinguish (verb)
To recognize the difference between two similar things.
The program must distinguish a key press from a release.

Chapters

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 →