Friday, June 20, 2025

Keyboard Editor/Driver for Commodore 64

Keymap Editor for C64

This utility (available from github) allows the user to specify how to remap physical key locations on the Commodore 64 keyboard to different PETSCII keystrokes.

The original intent is to put punctuation keys where they are supposed to go compared to modern keyboard layouts.  If one is using an authentic keyboard or equivalent then you may reach for the wrong key if trying to type an @, quotes, or colon.

Control keys revised with numeric keypad

One scenario you may not have thought of is to create a temporary palette of Commodore graphics characters in a custom configuration for programming a game or screen, where the graphics characters are not presently mapped to the keyboard, or not in intuitive locations.  You can always restore to the default keyboard mapping, or load another preferred one.

The popular scenario for non-US users is for international keyboard layouts: to match regional differences in layouts, and to support characters not present in the US character map, including corresponding character font image differences.  There are existing ROMs that include modified layouts and character images and are available at zimmers.net.  Some have slight differences, and some have great differences; changes may also be focused on the upper/lowercase character set in comparison with the upper/graphics set.

Danish ROMs example

Alternatively to having different ROMs, one could modify in RAM including the character set with a font editor, and manually revise the keyboard layouts with the utility I am describing here.

Font editor is also available

Remapping punctuation example

Here are some step by step instructions for remapping the punctuation

1. Start your Commodore 64

2. Insert c-keymaps disk (or 64keymaps.prg)

3. LOAD "64KEYMAPS 49152",8,1

4. SYS 49152

5. Navigate to full colon key with cursor keys

6. Type new key to use: single quote ', and it will appear in its place

7. Press cursor left to return to that key

8. Press home to switch to mapping when shift key is pressed

9. Type new key to use: double quote ", and it will appear in its place

10. Navigate to the punctuation in the numeric key row (still in shift key map), and update them according to your preferences, e.g. !"#$%^&*()

11. When changes are to your liking, press F7 to Save

12. The program exits to BASIC showing you a driver program entry and has typed SAVE" for you.  Complete the filename, end the quotes (probably using the original keystroke if not active yet) and add comma 8 for the disk drive, and press RETURN.

13. Once saved, you are welcome to VERIFY as well, and/or make sure your drive is not blinking red

14. To activate the driver, one need only RUN this program in memory.   Validate the keys are mapped as expected.

15. When your computer is restarted, you need only load and run the driver you saved to apply the key mapping.   The editor is the 49152 program.

16. Whenever the editor is in upper memory, you can return to it with SYS 49152.  Whatever active keyboard mapping is in effect will be displayed for editing, whether that be from ROM or RAM.

17. To return to the ROM keyboard mapping, press STOP+RESTORE and verify normal operation (but if the keyboard is not working, or system is locked up, you may have to reset the C64 computer).

18. Whenever the RAM driver is running, editing will use the new mapping, but changes are not applied until you press F7 for Save.  When the program for the driver is displayed be very careful not to LOAD over it (like a directory), or your system will lock up because the system was relying on the keyboard driver between addresses $0800 and $09FF.  To avoid this, press RUN or STOP+RESTORE first before loading a new program.   RUN will move the start of BASIC programs up to $0A00.

Advanced uses

Some keys are not typeable, or you may want to choose them visually.  Also, any navigation controls cannot immediately be typed for mapping as they will move the cursor around, etc.

So to get to the character selection screen, press RETURN on a key to edit.

Character selection "picker"

To choose a character, navigate to it and press RETURN, and the display will return to the main editor screen with that character replacing the previous one.

Alternatively, you can select most characters by typing them, and the current keyboard mapping will be displayed as navigation will move to the typed character. 

If you need to find a keystroke that is a navigation control (e.g. cursor key, RETURN), then press F3 first, and then any key, and that corresponding key will be selected.  You have the option to continue navigating, or press RETURN or STOP to return to editing.

Conclusion

While the editor is MIT LICENSE (do most anything with it, but give me credit if you republish, distribute), the driver portion is considered Public Domain as it is trivial, do with the bytes $0800-$09FF any way you see fit, though many bytes $08FC-$09FF are the mapping bytes and will be mostly copies from Commodore ROMs, so tread carefully with redistribution, or just use for personal use.

Hopefully you find this pair of utilities (editor and driver) useful and/or entertaining.  Enjoy!

Sunday, June 1, 2025

Programming Secrets for Creating Snake Game

How do you write a snake game?  Very carefully. ;-)

Open source at GitHub

For me, it started as an idea to make a featured game for my handheld Commodore emulator platform, most importantly the ones I can easily transport on my person.  I've written a simple but not complete Commodore emulator that behaves well on ESP32 and other available microcontrollers.  One example hardware platform is the M5Stack Fire that I can stick in my pocket and has physical buttons for inputs.

I was away on vacation with the family when I came up with the idea of replicating the snake game.  I was yearning for a good coding puzzle, and this was it!

So I started brainstorming on how one would accomplish it.  

First, I decided to just use BASIC.  It comes with the Commodores, and is relatively easy to work with.  I only have 40+ years experience so I should be able to get by.  So no machine language this time.

What system should I develop for?  Vic-20 is good for games as the text screen is small (22x23) and the characters are big, easy to see.

The key to snake is that each time it eats food it gets longer, so the snake is bigger and bigger as the game progresses, becoming an obstacle to your own movements.  As the snake moves, it inches along, the head advancing in one direction, and the tail catching up to where its next to last segment was.  So constantly the head and tail are both moving, and we have to keep track of where they are.  The answer is a circular buffer!  Segment locations will be stored in a circular buffer.  

What data structures do we have with BASIC?  Just arrays.  Can we implement a circular buffer with an array? Certainly, the array must be pre-allocated (DIM) with a fixed size, and keep track of the head and tail of the snake for the extents of the segments.  This is how you would do it in Pascal or C with an array, so BASIC is not much different.

How big should the array be?  Let's go big and say the snake can be as large as the screen.  But we want a title and score on the screen too, so let's say the bottom line is reserved for those.  That leaves 22x22 locations for the segments = 484.  Let's plan on keeping an offset into screen memory in each array element.

How should game inputs work?  Since my target platform has three buttons pre-configured for Up/Enter/Down, I want to use those, specifically Up/Down since I don't have access to four direction buttons.  I intend to use those buttons to rotate the direction left or right relative to the snake's current direction.

How shall we display the snake?  I bring up a PETSCII chart and look at the graphics characters.  I pick a circle outline as the head, a solid circle as body segments, and a graphic X as the death character.  Later in the process I chose the diamond as the food character.

So first step I implement the screen layout, place a single segment in the center of the screen, and implement moving around every second.  With some fine-tuning I get it working well, including implementing dying if hit the edge of the screen.  I am tracking horizontal position in X (0 to 21) and vertical position in Y (0 to 21) with DX and DY being -1, 0, or 1 each.  The D stands for difference or direction.  Moving is as easy as adding DX to X, and DY to Y, then making sure it is still within bounds, otherwise dead.

Quickly I found that allocating floating point values for the snake segments caused the original Vic-20 to run out of memory, so the array was changed to DIM S%(SZ) to use 2-byte integers instead of 5-byte floats.

The basics are in place, but the snake is not growing yet.  Next the circular buffer is implemented, and I implement computing the length of the snake for the score.  It took a few tries to get right, and once finished, I realized I could just track the score/length separately without all that fancy work, but it's done, so I let it be.

If the next position is another character or out of bounds, the snake dies.  If the next position is not food, the snake's tail is shortened so the snake appears to move.  But if the next position is food, then the snake appears to grow because the tail remains in place.  The head is drawn at the next position.

Between moves there is a delay of a fraction of seconds.  And as the snake gets longer, the delay gets smaller with a minimum delay to speed up the game, but keep it reasonable.

It took many incremental tries to get everything just right, took the opportunity of beta testing and feedback from my son, and addressed stuff including an opening information screen, the title/copyright/score bar, timing, and fixing bugs.  The bugs included running into your own tail that was about to move -- that is allowed, testable by spinning in a tight loop when 4 segments long.  

For inputs on a standard Vic-20, I remembered that cursor keys use shift to go the opposite direction.  That would be unnatural to require on a real Commodore, so I added Z and / (slash) as alternate inputs for rotating the head.  These keys are at opposite sides of the keyboard, so easy to indicate turn left or turn right.  (If someone doesn't like the key layout, it's in BASIC, easy enough to change.)

During the process of refinement I added random colors to the food, adjusted the randomizer to pick different colors than the head/tail of the snake so it appears to change, and the snake picks up the color of the food for an interesting effect.  Also the background color is avoided so the snake doesn't become invisible.

Next added Vic-20 joystick support.  Joystick support is implemented to direct the snake the literal direction of the joystick instead of rotating.  And avoids the direction opposite that the snake is already going so it impossible to reverse onto itself (otherwise the snake would die more often).  Also because of how the joystick and the keyboard share some input lines, it is necessary to disable some keyboard lines to read the joystick successfully.  Pressing STOP will break the program, but not reset the keyboard.  This renders some keys unusable until STOP+RESTORE resets the keyboard.  

Once the Vic-20 version was stable, source was pushed to GitHub.

Then the game was ported to Commodore 64 with 40 column screen.

And after that, Commodore Plus/4 and 16 port. initially with a machine language routine for reading the joystick until realizing that support is already in BASIC.

Next came a Commodore PET port designed to support most if not various models all running Commodore BASIC, including 40/80 column models, and actively switching to the uppercase/graphics character set in case a model defaults to lowercase.  Since the PET doesn't normally have a visible border, inverse spaces are placed around the edges to contain the snake a bit more.  Some screen real estate is lost but playability is gained.

I fell in love with the green on black look of the PET game, mostly for nostalgia, but to me it just looks perfect!  And that's when I adjusted the PET version to also run successfully on Vic-20 and C64.  It happens to work on C128 and Plus/4, 16 as well.  The PET version doesn't support joystick though, is all keyboard.  Detection of C64 turns colors to green on black, and Vic-20 does white on black.

Then I created Snake targeting my cbmish-script for the web...

And I'm still playing this game often daily...





Sunday, April 13, 2025

Snake for Commodore PET, Vic-20, 64, 16, Plus/4, etc.

Here is a single player version of the classic snake game that I have recreated in BASIC for the Commodore Vic-20.  Some may remember snake from the Nokia classic phones (1998) or from Atari VCS 2600: Surround (1977).

I have a Commodore watch platform (M5Stack with watch wristband running my Commodore emulators) which I can run some simple programs.  The purpose of developing this game was to create something I could play on my watch with just two buttons.


Apparently my high score is 73.  Try to beat that!

Open source (MIT License) and binaries including disk image are available at github.

Update 2025-04-16: Also ported to C64 for those so inclined.

Update 2025-04-19: Ported to Commodore PET and TED systems: Plus/4, 16, etc.



Tuesday, December 24, 2024

Avoid conflicts between C64 keyboard and joystick #1

 



The Commodore 64 keyboard may display unexpected characters when joystick #1 is used.  There is a solution!  Avoid the keyboard when scanning the joystick, and exclude the joystick data when scanning the keyboard.  But somewhat easier said than done.

Tracing the source of the issue is that PORTB of CIA1 is connected to the keyboard and first joystick simultaneously.  Normally the C64 KERNAL IRQ will write zero bits to PORTA to select a row, and read from PORTB to read the columns.  And reading the joystick also is read from PORTB.  So with two devices both read from PORTB there's bound to be conflicts.

Reading from the joystick without conflict from the keyboard is the easiest solution.  Write 0xFF (all ones) to PORTA to turn off reading any of the column lines from the keyboard, and read PORTB.  The result will only be the lines from the joystick.  Any of the lower 5 bits which are zeros will mean that the corresponding joystick line is pulled low (switch is connected), meaning a direction is held and/or the fire button is pressed.

Accommodating keyboard reads ignoring the joystick is not as easy because the joystick cannot be logically disconnected in the same way, it is always connected.  The approach I took was to read the joystick, and then read the keyboard ignoring any lines active on the joystick.  This solution gives the joystick priority (as it is always connected), and skips supporting some keystrokes which would otherwise conflict with the joystick.

As inputs can change suddenly, there is debounce logic consistent with how the Commodore KERNAL already handles the keyboard.  Any reads of PORTB are followed by a comparison with PORTB, reading it twice, and making sure the values are identical.  If there is a change in values, the process is repeated without limit.  Both the joystick and keyboard in this solution both have this debounce logic.  And additionally, the joystick is read before and after the keyboard is read, so if there is a change in the joystick values, the process as a whole is repeated for that keyboard row.  As the joystick is repeatedly read, only the last value for the last keyboard row will remain until the next scan.

To implement a general fix for the BASIC Editor (which accepts keystrokes and draws characters on the screen), the IRQ scan key handler would ideally be updated to incorporate these changes.  But the challenge is that the IRQ does other things as well, and the scan key routine is not vectored.  But the key log routine is vectored, which is called when any key is pressed, so the IRQ can be intercepted to scan the joysticks, and the key log routine is intercepted.

The key log routine is responsible for interpreting raw key matrix presses, handle character set swapping, keyboard repeating, and putting keys into the keyboard input buffer.  The new code vectored here effectively throws away the old scan key handler by doing its own new scan key routine avoiding conflict with joystick #1, writing its own results to the current key scan matrix value and shift flag memory locations, and then returning to the existing key log routine to handle these filtered key presses.

The effect is a cleaner keyboard input without conflict from joysticks, and joystick values stored in memory locations 253 and 254.  The joystick values are inverted so they are 0..31 based on positioning (1=up, 2=down, 4=left, 8=right) and fire (16).

Instructions: load the machine code at absolute address, and SYS 49152 to activate it.  Only titles, copyright, and links will be displayed.  While active, keyboard and joystick #1 will avoid conflict, and joystick readings every jiffy will be available to peek from memory locations 253 and 254.  Press STOP+RESTORE to deactivate it.  When deactivated, normal operation including possible conflicts will occur.

Links: open source and d64



Sunday, September 15, 2024

No name IPS 240*240 ST7789 TFT display with Arduino GFX Library and ESP32-C3 (RISC-V) development board

 

This is an alternative take on the previous article Adafruit ST7789 TFT display with Arduino GFX Library and M5StampS3.   This time with a generic display, and a different MCU (ESP32-C3).  This one has a single RISC-V core (instead of dual core Xtensa).

So excuse the repeated text, the content has been changed only slightly for the different hardware...


Attaching a display to a circuit can provide a lot of detailed info and graphical value.  While "a picture is worth a thousand words," an animated display can be entertainment value, textual information can be informative, and add an input device, and the system can be interactive with billions of possibilities.

The tricks in embedded development is choosing the right display, having the right library that supports the display, and figuring out to use it all together with your target embedded system.

Shown above is a minimal footprint ESP32-C3 development board (I purchased one from AliExpress for around two US dollars, very cheap!), with a generic IPS 240x240 TFT display (ST7789 based) I had purchased years ago on eBay.  An SPI interface is utilized for communications using the GFX Library for Arduino.  I am using a solderless breadboard to prototype the circuit.  Later I will implement as a more permanent circuit.

Besides power and ground, there are only 4 connections from the ESP32: SCLK, MOSI, DC, and Reset.  In this sample, the TFT select line is permanently selected by connecting to ground.

#include <Arduino.h>
#include <Arduino_GFX_Library.h>

Arduino_DataBus *bus = new Arduino_HWSPI(6/*dc*/, 7/*cs*/,
   2/*sclk*/, 3/*mosi*/, 10/*miso*/, &SPI,
   true/*is_shared_interface*/);
Arduino_GFX *gfx = new Arduino_ST7789(bus, 11/*rst*/, 1/*r*/,
   true/*ips*/, 240, 240);

void setup() {
  gfx->begin();
  gfx->fillScreen(BLACK);
  gfx->setTextColor(WHITE);
}

void loop() {
  int x = (int)random(240);
  int w = (int)random(240 - x);
  int y = (int)random(240);
  int h = (int)random(240 - y);  
  int color = (int)random(65536);
  gfx->fillRect(x, y, w, h, color);
  if (random(20)==13)
    delay(100);
}

The wiring corresponds to the code of the databus and gfx initialization parameters, specifically lines GPIO02 (SPI CLK), GPIO03 (SPI MOSI), GPOI06, GPIO11 of the ESP32-C3 connected to SCL, SDA, DC, and RES (Reset) of the display respectively.  Also both the VCC and BLK lines of the display are connected to 3V3, and GND is connected commonly between the display and ESP32-C3 board.  MISO and CS lines are defined for use in the code, but wiring them was unnecessary and not possible.

TFT   ESP32-C3
GND   Ground
VCC   3V3
SCL   SPI Clock (2)
SDA   SPI MOSI (3)
RES   (11)
DC    (6)
BLK   3V3

Not sure why triangles are sometimes displayed (maybe out of range data?).  That is why there is a 5% chance of a tenth of a second delay to pause for the viewer.

This is just a demo.  You should be able to find much better uses for the display that these random rectangles.

Build details:
  • Arduino IDE 2.3.2
  • esp32 boards 3.0.4
  • GFX Library for Arduino 1.4.7
Important!  The board I have will only run its flash chip in DIO mode, so be sure to check all the board options in Tools menu of Arduino IDE before flashing, or if you run into trouble.  Also, updating board libraries may reset your options to default so if flashing after a time, check these settings again, and again.

Some models of the ESP32-C3 development board have a serial chip, and some utilize the C3's built in USB support.  Be sure to set USB CDC on Boot settings accordingly, and use the BOOT/RST buttons to put into bootloader mode for flashing if necessary.

I found it necessary to unplug and replug the ESP32-C3 board to get the graphic demo to work.  Your mileage may vary.

Sunday, September 8, 2024

Adafruit ST7789 TFT display with Arduino GFX Library and M5StampS3

 


Attaching a display to a circuit can provide a lot of detailed info and graphical value.  While "a picture is worth a thousand words," an animated display can be entertainment value, textual information can be informative, and add an input device, and the system can be interactive with billions of possibilities.

The tricks in embedded development is choosing the right display, having the right library that supports the display, and figuring out to use it all together with your target embedded system.

Shown above is a minimal footprint ESP32S3 in the form of an M5StampS3 from M5Stack connected to 2.54mm pins, with a rounded corner ST7789 based 280x240 display from Adafruit.  An SPI interface is utilized for communications using the GFX Library for Arduino.  I am using a solderless breadboard to prototype the circuit.  Later I will implement as a more permanent circuit.

Besides power and ground, there are only 4 connections from the ESP32: SCLK, MOSI, DC, and Reset.  In this sample, the TFT select line is permanently selected by connecting to ground.

(The Adafruit display used in this example also includes a MicroSD connector and SPI connections for that as well.  Support for SD is beyond the scope of this article, and would require additional changes to the circuit and code.)

#include <Arduino.h>
#include <Arduino_GFX_Library.h>
Arduino_DataBus *bus = new Arduino_HWSPI(1/*dc*/,
  GFX_NOT_DEFINED/*cs*/, 7/*sclk*/, 5/*mosi*/,
  GFX_NOT_DEFINED/*miso*/, &SPI, true/*is_shared_interface*/);
Arduino_GFX *gfx = new Arduino_ST7789(bus, 3/*rst*/, 1/*r*/,
  true/*ips*/, 240, 320);

void setup() {
  gfx->begin();
  gfx->fillScreen(BLACK);
  gfx->setTextColor(WHITE);
}

void loop() {
  int x = 20 + (int)random(280);
  int w = (int)random(300 - x);
  int y = (int)random(240);
  int h = (int)random(240 - y);  
  int color = (int)random(65536);
  gfx->fillRect(x, y, w, h, color);
  if (random(20)==13)
    delay(100);
}

The wiring corresponds to the code of the databus and gfx initialization parameters, specifically lines 1, 3, 5, 7 of the M5StampS3 connected to DC, RT (reset), SI (serial in), and CK (clock) of the display.  Also the displays V+ line is connected to 5V, and both TC and G (Ground) are connected to ground common to the ESP32 S3.

TFT   StampS3
V+    5V
3V    NC
G     Ground
CK    SPI Clock (7)
SO    NC
SI    SPI MOSI (5)
TC    Ground
RT    (3)
DC    (1)
CC    No Connection
BL    No Connection

It appears there is an overscan issue, beyond the rounded corners.  The gfx object is defining 320x240 display instead of 280x240, and the position and sizes of the rectangles are also interesting here.

Not sure why triangles are sometimes displayed (maybe out of range data?).  That is why there is a 5% chance of a tenth of a second delay to pause for the viewer.

This is just a demo.  You should be able to find much better uses for the display that these random rectangles.

Build details:
  • Arduino IDE 2.3.2
  • esp32 boards 3.0.4
  • GFX Library for Arduino 1.4.7

Sunday, August 25, 2024

Wireless controller adapter for Commodore

 


It's August 2024.  I've just bought a new (to me) Vic-20 for US$50 to replace my childhood one.  I already have the PenUltimate Vic-20 Cartridge with a ton of games.  I also bought a Hyperkin Trooper joystick.  But I want to game wirelessly!

I've already had great success with a BlueRetro Nintendo GameCube controllers wireless adapter with my original Wii game console.  I purchased it on ebay for a good price and it worked great.

But I wanted to go back further to replacing an Atari joystick on my Commodore Vic-20 that has a DB-9 connector.  Searching the Internet, it looks like BlueRetro internally supports Atari 7800 with a 2-player mode, and there is a single player version available for sale on Amazon for about $25.  Also the ESP 32 firmware source code is available as open source.

Having experience developing with ESP32, and a plethora of electronic parts already at my disposal, I did what any enterprising consumer would do -- I purchased additional parts from AliExpress.  I mean, you can't have enough electronic parts, can you?  Okay, I could've done it completely from scratch but my time is valuable, I have only so much energy at the end of the day, and cheating my way to success is legal here.   So we're doing it halfway.

First I bought the BlueRetro Core.  It provides the base functionality with a DB-25 connector for all its pins.  It is designed to allow connector adapters to be wired to various console systems.  It is a very creative and functional design.  And there are adapters available for many of the common systems.  But I didn't have any luck finding Atari connectors.

But no problem!  I can build my own!  So I found a DIY screw terminal DB25 male connector with the correct optional mating screws, and the counterpart DB9 female connector with screw terminals.  The only trick left is to wire it up.

Steps

  1. Connect BlueRetro Core to USB power adapter (don't require direct connection to computer)
  2. Download latest firmware ZIP via link provided on BlueRetro support site
  3. Unzip, and look for the parallel 2P firmware (1P should work fine too, skip the 3V3 versions.
  4. Follow flashing firmware instructions from BlueRetro support site
  5. Pair a supported wireless controller (I'm using an Xbox One Bluetooth compatible controller)
  6. Plug BlueRetro Core into the DB25 breakout
  7. Connect a voltage meter to ground on the breakout, and then test the voltage at each remaining pin with and without pressing a button (e.g. DPAD up/down/left/right, and A)
  8. Write down the DB25 pin numbers and the names of the controller functions
  9. Unplug the core, and power off for now
  10. Search the internet for the DB9 pin numbers for Atari joystick connector pinout so you have their functions handy
  11. Cut and strip ends of 8 wires of different colors long enough to connect the DB9 and DB25 breakouts
  12. Be sure to first feed the wires through the included rings, and confirm their orientation.
  13. Then connect the appropriate functions: up, down, left, right, fire, framing ground, signal ground, and voltage in  (VIN) to +5V supply from Commodore/Atari
  14. Minimally connect, and test the functionality first with meter, then with Commodore/Atari console
  15. Once verified working, finish connecting wiring stress-relief, bolt the enclosures closed, and otherwise button-up and tidy up the solution.
  16. Play games!
DB25-1   to DB9-4   (right)
DB25-4   to DB9-3   (left)
DB25-GND to DB9-GND (frame ground)
DB25-7   to DB9-7   (VIN to +5V)
DB25-8   to DB9-8   (signal ground)
DB25-13  to DB9-6   (fire)
DB25-15  to DB9-1   (up)
DB25-17  to DB9-2   (down)

Another link to support this wiring is the BlueRetro Hardware Design documented over at hackaday.  There are schematics showing signal ground and VIN with the DB25 pinout.

I had fun with this effort, and should be able to expand this effort to support a second controller simultaneously connected to the same core, or wire up adapters for other systems myself.

The cost of the solution for me was about $22 with shipping, and as I was careful to choose items shipped directly from AliExpress and live on the west coast of USA, received the items quickly in about a week.  While this didn't save me the labor and only saved $3 from the Amazon solution, I learned a lot and feel comfortable leveraging the result to make solutions for other consoles and a second player.

(Any of you worried about 5V tolerance on the ESP32 or the buffer chip included with BlueRetro Core?  The Commodore is at 5V, and while the ESP32 is powered by 5V its IO is exclusively 3.3V.  It works for me without any additional consideration, but your mileage or concerns may vary.  You can usually get away with it for ESP32 as many of the IO are designed to survive 5V, and a 3.3V high signal does read as a high signal with most 5V systems.  So am I just getting away with it?  Maybe.)

But wait! Some keyboard lines are not working. I'm not done yet!

So with some prototyping, it turned out that the circuit to the Vic-20 needed to be open drain instead of push-pull. Pulling down was fine, but something was amiss with pushing 3.3V (or even 5V with other attempts) from the ESP32 BlueRetro Core circuit.

How does an Atari style joystick work anyways? There are 4 directions and one extra button. When any of them are active, the associated line is connected by a switch to ground. The BlueRetro Core simulates this by pulling to ground just fine, but when idle, it is pushing 3.3V. And the Vic-20 has its own 5V pull ups. That does sound messy. Somehow (without looking at the various schematics) this appears to cause a conflict so the 2, 4, etc. and F7 keys for example don't work anymore, they can't pull down to ground when this joystick circuit is attached.

So let's think how to correct this.  We want the connection to ground when the joystick is active, but we want high impedance (appear disconnected) when the no direction or selection is made.

The solution is a circuit that can perform this.  At first I'm thinking I don't have any FETs left over from previous projects, but finally after using one of my go-to buffer chips in breadboard circuits trying to solve this thing, I finally remembered that instead of being annoyed there are enable inputs for each buffer, the enable inputs can function as the inputs from the BlueRetro Core.   They are enable low, so when the joystick selection is made, the circuit will connect, and can take a grounded low input and buffer that to the Vic-20 joystick port.   So when the button or movement is made, the circuit will be grounded low, but otherwise that individual circuit is disabled (high impedance) as if the switch is not connected.  And this separates the 5V Vic-20 circuit from the 3.3V BlueRetro Core (ESP32) circuit.  And best of all, testing on breadboard, it works great!   The keyboard is now functioning normally with the joystick circuit powered and connected, and is still self powered from the Vic-20.

The revision to the above wiring is to connect DB25 joystick lines to OE/ lines of a SN74AHC125N, connect ground to the A lines, and connect the Y lines to the appropriate DB9 joystick lines. Also connect 5V power (14) and ground (7) to the appropriate IC pins.  And since there are five joystick lines, and this is only a quad (4) buffer part, we're gonna need two of them (and three if extending to two joysticks for dual player such as with a C64 or Atari 2600 VCS).  I recommend setting the unused enable lines for the extra buffers high (5V) to disable their outputs.

Again, these buffers are only for up/down/left/right/fire(select).   The voltage and ground wires should be wired between the DB25 and DB9 without buffering.