Showing posts with label C128. Show all posts
Showing posts with label C128. Show all posts

Saturday, August 15, 2026

Touch points and buttons on M5Core 2" displays, Tab 5" display, and Sunton 7" display for keyboard emulation


I have targeted the following M5 Stack models with my C64/C128/Vic-20/Apple 1 emulator:

(Even though I have targeted other boards, I had only implemented touch on the CoreS3)

The primary reason for targeting these boards was the common 2" 320x240 display perfect for most C64 emulation, and the availability of a wristband that works with Core2 and CoreS3 (it works with the Fire if and only if you never need to charge the battery - not aware of a universe where that works out long term).

[aside... Originally I had supported the M5 Core Basic, but without PSRAM, it was difficult to support the different computer models that currently load into that memory.  So M5 Core BASIC was dropped after its initial development.]

The Basic and Fire have three UI hardware buttons.   The Core2 has three virtual touchscreen buttons (touchscreen expands farther down from the LCD screen to allow for capacitive touch button points, and the M5 library abstracts them similar to physical buttons, so your code doesn't even have to know the difference).  The CoreS3 has no UI buttons, but I've manually implemented virtual touch screen buttons at the bottom of the screen.   From left to right the buttons are known to M5 as A, B, and C.

So button support for Basic, Fire, and Core2 was straightforward.

  • press Button A: Cursor UP
  • press Button B: RETURN
  • press Button C: Cursor Down
With hold functionality as well.
  • hold Button A: toggle computer model (C64, C128, Vic-20, Apple 1, repeats)
  • hold Button B: LOAD"*",8 / RUN for Commodore, snapshot system for Apple 1
  • hold Button C: STOP
Thus, one can demonstrate the emulator loading a menu of programs, scrolling though the listing, and choosing one.  And toggle between the emulators.   The Apple 1 snapshot system allows choosing a snapshot and loading or saving it.

I support other hardware targets, and I have BLE pairing functionality.  So I am thinking of expanding support of the virtual buttons, including more virtual buttons, and providing 8-position joystick inputs (and button) too.  But for now, here is the documentation for the existing buttons.

And then just like that, the CoreS3, Tab5, and Sunton 7" now have more touch points implemented for their capacitive touch screens to allow for more virtual keys.




Sunday, June 7, 2026

Extended Screen for C128




Finally, Commodore 128 can provide the 80 column screen that multiple monitor folks need - extended screen.  Spread those columns across two separate monitors for a wider view.

Instructions

  1. Download disk image, and use as is with modern tools, or image to floppy.
  2. Click the 80 column keyboard button into position to make it the default.
  3. Attach two monitors to your C128.  Composite on the left, digital RGB on the right.
  4. Start up (or reset) C128 with disk attached.  Boot sector will load and run 80DUAL by default.
  5. Enjoy screen extended 80 columns!

It works by reinitializing the VDC 8563/8568 registers to display 40 columns offset by 40 columns, and each line thereafter also offset by 40 columns.  Since the KERNAL hasn't been told any differently, it will continue to treat the VDC as an 80 column display, but the VDC settings are now to display only the right hand 40 columns.  An IRQ routine is installed to copy 5 lines from VDC RAM to VIC-II screen RAM (hardwired to address 1024 currently) and color RAM.  The effect is that as text is typed or displayed on the 80-column virtual and physical display, the hidden left-hand side of the screen will also be displayed on the composite monitor.

This is a proof of concept, and has limitations.  

  • The machine language routines use $1300-$15ff for code and data
  • The code uses self-modification techniques so can't be moved to ROM as is
  • Character set changes are not handled
  • Changing to composite screen is not handled, will continue to be overwritten from VDC RAM
  • No graphics support
  • Slow, less than 12 frames per second operation
  • Not compatible with SNAKE
  • Will interfere with programs that write text to VDC screen using own (not ROM) routines.  It has a fixed list of ROM addresses to avoid interrupting, and doesn't include RAM.
Why?
  • It was a challenge I set myself
  • Because it's ridiculous and outrageous, has little practical purpose.  I like the bizarre possibilities.
  • Related to my rgb64

Monday, May 25, 2026

"VDC Hires," or "If you want to program a Commodore right, do it yourself, because AI is not your friend"

AI: What could go wrong?🤔

Don't get me wrong.  I love AI.  I also hate AI.  It's been great to me for writing C# utilities for work, or SQL queries, reviewing code, writing scripts in languages I don't even know (LUA) -- yeah that looks right, let's put that in production.

But for Commodore?  Spoilers -- it's really horrible.  I'll get back to that later.

My "fun" task for the day?  I want to finally program high resolution graphics on my C128 VDC 80 column screen.  It's well capable of 80x25.  And I bought my C128D with 64K RAM specifically knowing it could do that high resolution graphics well.  The original only came with 16K, and when it arrived COD at my door in 1987, I refused it and sent it back because of 11th hour nearly buyers remorse, delaying my gratification to wait a few more weeks for the newer C128D to arrive with more RAM.

The C64 could only do 320x200 graphics because it's a 40 column machine.  The C128 had 80 columns and thus twice the resolution because it included two graphics chips and had two separate video outputs.  BASIC 7.0 finally supported 320x200, but not graphics specific to the 80 column chip.  I even purchased GEOS 128 with mouse and 1750 memory expansion, and saw the fulfillment of 640x200 graphics.

But was I fulfilled? No.

I am a programmer.  Features don't exist until I code the programs that make them exist.  I enjoy the process of learning APIs and languages, and developing new skills.

Later in 1988 I investigated the VDC (C128's 80 column video display controller) closer, with the guide of some magazine articles, and close scrutiny of the Commodore 128 Programmer's Guide which had a mere 4 pages of a minimalistic datasheet.  I remember wanting to know how to do it.  But as far as I got with changing and programming the VDC registers was a 40 column mode, and an 80x50 interlaced text mode.  The former was released as RGB64, and the later was released as node-m (a unique terminal program).  Graphics abilities unique to the C128 were not accomplished by me.  Not in the 80s.

Now it's nearly 40 years later.  VDC graphics programming is still a challenge I want to conquer.  It's 2026, in the golden age of AI, technical assistants available at our fingertips.

Could AI provide me a working sample?  No.  I went round and round with it for hours.  Gave up, and came back another weekend day for hours.  Did it work?  No.  Was I close?  Maybe.

One web link from AI gave a hint though.  The reference was Commodore 128 Programming Secrets.  The sample program on page 274, Figure 5-4, was short, just 25 lines, and didn't look very complicated.  Obviously this was a joke, you can't achieve such a lofty goal as complicated VDC graphics in just 25 lines.  There weren't even very many VDC register writes.  But I typed it in, and once I figured out some decent parameters to feed it at runtime (10, -10, 1, -1), the results were presented on screen.

Commodore 128 Programming Secrets
640x200 monochrome graphics sample

But how could this be?  All that I learned researching with AI taught me it was so much more complicated than this.  Then I reviewed the code, and my assumptions, and the paths AI had taken me down had to be set aside.

The algorithm in a nutshell is 
  • Initialize raster graphics mode, giving up text screen, attributes, and character images
  • Write to screen memory as bytes going across, then down.  First and high bits are on the left, last and low bits are on the right.  In other words, use math.
  • Follow VDC register rules.  And use block fill feature for speed.
I already knew the rules.  Well, I did in 1988.  And I've learned and relearned them over the years as I've dabbled in VDC programming.  Nothing too special, but enough to know the basics.  But there is the trappings too.  And when you don't use something every day it's easy to forget even the basics. So let's write down the rules here so we all can refer back to them someday.

VDC rules

  1. Access to VDC is through two C128 memory-mapped locations:
    • D600 is generally used to store a register number (and read status)
    • D601 is used to store a register value
  2. VDC is controlled through its registers.
  3. Access to VDC RAM (16K or 64) is only through the registers as well.
  4. Want to write to a register? Store the register index in D600, then wait for the high bit to be set on D600 before storing the desired value in D601.
  5. Want to read a register? store the register index in D600, then wait for the high bit to be set on D600 before reading the value from D601.
  6. Want to write to memory?  At some point you have to set the address using registers 18, 19. After that you can set the value in register 31.
  7. For a block fill, you must write one memory value first, then you can write a count of the remaining slots you also want to fill.  If you want to store 100 bytes total, you store 99 here.
  8. Values in registers 18 (high), 19 (low) auto-increment, so either reading or writing various values to consecutive bytes doesn't require rewriting the address.  But reading from the address, and writing back to it will require rewriting the address, otherwise it has jumped forward by one.
  9. Screen memory is pointed to by registers 12, 13.
  10. Attribute memory is pointed to by registers 20, 21.
  11. Character images is pointed to by register 28 (high 3 bits).  Shift those bits down and multiply by $2000 to get the VDC RAM address, or in reverse shift up.
  12. VDC defaults to 16K RAM operation.  64K mode will scramble how the data is addressed, so would require reinitializing the screen, attributes, and characters.
  13. Graphics mode (bit 7 of register 25) means that the VDC will render to the monitor line and byte by byte or bit by bit from screen memory.
  14. 640x200 monochrome requires 16000 bytes, so not enough left for screen, attributes, characters.
  15. Other graphic modes are possible including using attributes for choosing different colors for cells, and even changing the dimensions of the cell (allows for more color changes).  I'm not versed in this yet.
  16. There are plenty of registers you should not change unless you understand them well, or are willing to take a risk.
  17. C128D includes 8568 chip with the addition of an IRQ line when command completed (not wired to anything), and Sync Polarity options in a new register 37.
  18. Attributes are different than C64 color memory.  These are whole bytes!  The lower 4 bits are RGBI (red/green/blue/intensity).  The upper 4 bits are (character set, reverse, underline, and blink).  The character set bit option lets you have 512 different characters on screen at any one time.  And the reverse bit is useful for game developer, but not necessary for the Commodore character sets as they already have reverse in their latter half of their sets.
  19. Attributes are optional.  See AttrEnable bit 6 in register 25 for turning it off (0).

Tricks

  • BANK 15 (shown as the first F digit below in the monitor listing) is the default bank when C128 starts up.  This memory map has access to I/O and KERNAL ROM.
  • C128 KERNAL already has routines for writing/reading to/from the VDC registers.  These can be leveraged from BASIC as needed, especially useful for prototyping.
    SYS DEC("CDDA"),,R : RREG V : REM Read VDC Register
    SYS DEC("CDCC"),V,R : REM Write VDC register
  • C128 KERNAL has a routine to reset character images in the VDC RAM.  JSR FF62.  (Thanks AI for that tidbit scraped off the web.)
  • SCNCLR is a good way to reset screen and attribute memory.  Similar to GRAPHIC 5,1 and PRINT CHR$(147).
. fcdca  a2 1f    ldx #$1f
. fcdcc  8e 00 d6 stx $d600
. fcdcf  2c 00 d6 bit $d600
. fcdd2  10 fb    bpl $cdcf
. fcdd4  8d 01 d6 sta $d601
. fcdd7  60       rts

. fcdd8  a2 1f    ldx #$1f
. fcdda  8e 00 d6 stx $d600
. fcddd  2c 00 d6 bit $d600
. fcde0  10 fb    bpl $cddd
. fcde2  ad 01 d6 lda $d601
. fcde5  60       rts

Arguing with AI

I got fed up with AI.  Mostly because it didn't work just vibe coding.  I kept delivering results from programs, and they still wouldn't work.  But I also argued.
  • I had to explain I didn't have BASIC 8.0, only 7.0
  • Explaining GRAPHIC 5 doesn't actually have anything to do with graphics
  • No, the processor doesn't have direct access to VDC RAM
  • Variable names cannot have underscores
  • Variable names must be two letters at most to differentiate from each other
  • Spaces are not required between keywords and variable names
  • Just arguing about nonsense that the AI had completely backwards.
  • AI putting in comments REM Completely Fixed Version
  • Explaining the right way of doing things, and AI insisting it was right instead
  • Explaining the right way of doing things, and AI treating me as I had led it astray in the first place, that it was my fault
  • No you can't prompt for values on the same screen trying to draw graphics.  But you can use the 40 column screen simultaneously using GRAPHIC 0
  • No, that order of code is correct, if we change it, it will fail worse
  • Telling me there are two bugs, then listing three bugs
  • Outright hallucinations
So maybe it's a little unfair.  These AIs have been around for a short time, and modern programming is a lot different than Commodore 128 programming.  And there are so many options with the VDC, many not quite documented, more trial and error, through experience, not examples.  With folks talking about the various options, AI can get these intertwined and mixed up.

Now that we know the answer, can we assist AI in providing us a working answer?  I have a collection of AIs available to me.  Let's try a simple prompt, and a complex prompt, and turn up the thinking as high as we can go.

Short prompt

Please provide me a Commodore 128 BASIC 7.0 working example of drawing a circle on the VDC 640x200 display in monochrome bitmapped mode using any facilities in a stock C128 available.   Please provide a BASIC listing output in lowercase text only that will be accepted by a Commodore 128.

LLMs tested:

Qwen2.5-Coder 14B
gpt5-mini (High)
Windows 11 Copilot
Gemini
OpenAI 5.3-Codex (High)
Claude Sonnet 4.6 (Adaptive)

AI results

Out of the gate, Qwen2.5-Coder 14B, running on my own GPU mind you, produced remarks in uppercase, thought that simple pokes to addresses based on the coordinates would hit video memory, but worst of all used bit shifting ala C which is not present in Commodore BASIC 7.0.  Also the while/wend loop should have been do while ... loop.
110 d = 3 - (radius << 1)

120 rem draw circle using Bresenham's algorithm
130 while x <= y

140 rem plot eight points of the circle
150 poke cx + x, cy + y : poke cx - x, cy + y
Actually Codex got the closest... it followed directions, produced a valid program free of syntax errors, it runs, and while it fails to clear the screen, an oval shape does appear.

5.3-Codex (High) result

I should have started and stayed with Codex a bit longer.  I could have worked from this sample, fixing clearing the screen, and changing the aspect ratio of the circle would have been challenges I could have worked through.  Looking back at my history I had started with it, and it had failed for me though, and led me down some more complicated paths.

In contrast, Windows 11 CoPilot produced a version with syntax errors.  It didn't quite understand the syntax of Commodore BASIC 7.0.  That's not how functions work, and you don't assign a value to a sys command.
30 def fnw(v,r):bank15:sys52684=0:rem placeholder
Gemini produced this listing:
10 graphic 5,1
20 color 0,2,1
30 circle 1,320,100,60,60
40 getkey a$
50 graphic 0,1
Resulting in the following, because CIRCLE doesn't work in 80 column mode, because it's a text mode, not a graphics mode.  No GRAPHIC command will put you in a graphics mode using VDC.
?NO GRAPHICS AREA ERROR IN 30
Claude Sonnet 4.6 (Adaptive) thought for 19m 2s and I reached my 5 hour limit before being prompted to upgrade to a paid plan.  That was a lot of thinking!  I'm not upgrading to program an 8-bit computer.  Wake up the next morning, hit continue, and immediately it produces a similar program like Gemini did.  Similar error resulted, this time in line 40.   Nice, line 50 draws on the text screen.  But line 60 thinks inkey$ does something...  it stays empty forever.   Sonnet, if you were taking notes from Gemini, you should have copied it's line 40 instead.  And switching to the 40 column screen was unnecessary.  The other commands are nice though, but no circle produced.
10 graphic 5,1
20 color 0,1
30 color 1,2
40 circle 1,320,100,80,80
50 char 1,1,23,"press any key to exit"
60 do : loop until inkey$<>""
70 graphic 0,1
80 end
gpt5-mini (High) keeps prompting in Visual Studio Code for browsing web pages and is stuck on thinking Commodore has vpeek/vpoke, and because of Graphic command in BASIC 7.0, it must have commands for VDC, but it keeps looking and looking...  after about 8 prompts to browse web pages I'm stopping it, it just keeps looking at general programming html sites (and one PDF).

My solution (disclaimer: Claude did earlier find a logic error in my code that fixed a problem I was having, turned into one of my rules for VDC about register reads auto advancing the memory pointer, and having to reset it to write a changed value).  So reviewing code worked out, but generating it not so much.  My own developed solution is attached:

No AI generated code here, but was AI reviewed

100 rem-----------------------------------------------------------------------
110 rem vdc 640x200 monochrome
120 rem copyright (c) 2026 by david r. van wagner
130 rem mit license
140 rem
150 rem davevw.com
160 rem github.com/davevw
170 rem mit license
180 rem-----------------------------------------------------------------------
190 def fnac(x)=2*atn(sqr(1-x*x)/(1+x))
200 trap 900
210 graphic5,1:fast
220 for i=1 to 21:print chr$(17);:next
230 data 1,2,4,8,16,32,64,128:dimbi(7):fori=0to7:readbi(i):next
240 print"screen";:r=12:gosub 1040:gosub 1050:sc=ad
250 print"attrs ";:r=20:gosub 1040:gosub 1050:at=ad
260 print"chars ";:r=28:gosub 1000:ad=int(v/32)*8192:gosub1050:cr=ad
270 for i=1 to 1000:next:scnclr
280 r=25:gosub 1000:v=v and (255-64) or 128:gosub 1010
290 ad=sc:gosub1060:wait dec("d600"),128:f=0:c=16000:gosub 1070
300 for x=0 to 639:a=fnac((x-320)/321):y=int(99*sin(a)+100):gosub1080
310 y=199-y:gosub 1080:next x
320 getkey a$
900 r=25:gosub 1000:v=v and 127 or 64:gosub 1010:sys dec("ff62"):scnclr
910 if er<>-1 then print err$(err)" at line "el
999 end:rem ******* reusable subroutines *******
1000 sys(dec("cdda")),,r:rreg v:return:rem ** read vdc reg r into v **
1010 sys(dec("cdcc")),v,r:return:rem ** write vdc reg r value v **
1020 sys(dec("cdcf")),v:return:rem ** write next byte **
1030 sys(dec("cddd")):rreg v:return:rem ** read next byte **
1040 gosub 1000:ad=v*256:r=r+1:gosub 1000:ad=ad+v:return:rem ** get address **
1050 printusing" ##";r-1;:printusing" ##";r;:printusing" #####";ad:return:****
1060 r=18:v=ad/256:gosub1010:r=19:v=adand255:gosub1010:return:rem *update addr*
1070 i=0:do while i<c:rem ** block fill (begin) **
1071 r=31:v=f:gosub 1010
1072 v=c-i-1:if v>0 then begin
1073 :ifv>255thenv=255
1074 :r=30:gosub1010
1075 bend
1076 i=i+v+1
1077 wait dec("d600"),128
1078 loop
1079 return:rem ** block fill (end) **
1080 ad=sc+int(y)*80+int(x/8):gosub1060:rem ** plot (begin) **
1081 r=31:gosub1000:n=v or bi(7-(xand7)):gosub1060:r=31:v=n:gosub1010
1082 return:rem ** plot (end) **
Granted, this is more of an oval.  I was going for a large oval on all of the screen: 319 radius on the x axis, and 99 radius on the y axis.  The arc cosine is my attempt at a more intact circle.

Conclusion

I will continue to use AI for certain things, but Commodore vibe coding is not there yet for my needs when I don't have the answers.  AI and myself went down a long path of unknowns, assumptions, inaccuracies, and under/over-complexities.  What ended up working was digging in for myself, trying little things, and lots of credit goes to Commodore 128 Programming Secrets as it produced a sample program that did the job.

The elusive detail was that when turning on bitmap mode in the VDC, screen memory (not character memory) is where the raster data goes.  And with an original 16K RAM attached to VDC, there's no room for attributes, so disable them.  How would an AI know this?  

Noted, I may have tricked the AIs, by not giving them all the detail they needed, especially that VDC graphics primitives are not present in Commodore 128 ROMs and BASIC commands.  And some of my earlier AI prompts dived right into 64K options that overcomplicated the situation.

I could update the prompts to guide AI closer to the answers, now that I know the answers, but what's the point?  I didn't have all the answers, so I was searching.  I should have probably stuck with web searches rather than trust AI to solely come up with the answer.


You have to be pretty smart to use AI effectively.  I may have failed myself this round using AI.  Throwing too much faith into AI is also a mistake.

Remember there are no guarantees.  So try to have fun on the journey, learning, even when some paths lead to failure.    But in the end, don't give up.  There are other paths and you may find them next time.  Don't give up on programming/researching yourself, and don't give up on AI as its capabilities are remarkable.  Learn new things, learn new ways of doing things, and next time might be better and more efficient solution.   

Sunday, May 10, 2026

Palm Portable Keyboard for my custom Commodore Emulators

It's finally here!  After years of anticipation and a weird workaround, the ideal solution is implemented and working.   I can finally use my Palm Portable Keyboard (PPK) with my custom emulators for Commodore 8-bits.   Github: ppk_bluetooth_for_cbm

PPK BLE adapter for wearable/portable emulators of 8-bit CBM systems

History

The Palm Portable Keyboard was released for the Palm Pilot PDA lineup in late 1999.  I remember getting mine right away for US$99 from Circuit City.  The Palm IIIc came out in early 2000 and got one of those too.  To date my longest use was to write up notes on a plane ride.  (Let's just not think too much about me leaving/losing the keyboard in the seatback pocket.  Ouch!  Rushed out and got a replacement at $99.)   Back in the day, it was mostly for the cool factor.   I loved gadgets, and between the Palm III color and the keyboard, and the Kodak color camera, I had some pretty cool gadgets.  I knew I was cool, even if no one else believed.

Fast forward to 2020 when I'm enjoying building emulators for Commodore 64, 128, and Vic-20 on wearable and portable platforms based on ESP32 and similar.  One real need was to input into these devices.  With the STM32F4 I had gotten USB-OTG to work, and mapped a standard USB keyboard to trick the Commodore into thinking a normal matrix keyboard was present.  The Teensy 4.1 target was a direct port of that keyboard code as it had an optional external USB host port.  Also worth a mention of leveraging the same algorithms and data to support keyboard mapping with my Typescript port.

STM32F4 with USG OTG

Teensy 4.1 with keyboard cord off top of photo

Once I had a wearable emulator on my wrist, I also looked for keyboard solutions.  On one July 4th holiday vacation I was able to research and implement a web page helper that translated keystrokes in the keyboard and sent them as Commodore scan codes over USB serial to the emulated system.  And I also found a way to leverage this on my Android phone so it would host the web page and transmit over its USB serial (OTG) to the emulated target host.

Wires and phone required in 2023

More development later, and I had an adapter for a real Commodore keyboard to serial TX line (plus 5V/GND input), that could plug into the Grove connector of the M5Stack targets.  Sure it was fun to plug in a real C128D keyboard into a system with a 2" LCD; quite the show off I am, looking for a good laugh.  But it also worked!

More work (and purchases) later and I had M5Stack's CardKB keyboard, and wrote drivers for that to convert to scan code presses and releases for Commodore.

And then I created a Bluetooth (BLE) adapter to convert serial/I2C connections to a wireless connection.  The keyboard and the wrist emulator could be used with direct wiring.   Much to the delight of portability and more showing off.



External Commodore Keyboard Scan Code Protocol 

Out of necessity, a standardized protocol was born by accident.  Standardized to my implementations only so far, but I was for sure the beneficiary of such technological advance.

The protocol depends on two features:

1. Serial transmission of changes

2. List of Commodore 64/128 scan code values, comma separated, newline (serial only) terminated

Example:

15,7

which means left shift and up/dn key (= cursor up)

The emulators targets (see Unified branch of github.com/davervw/c-simple-emu6502-cbm) support this protocol over USB serial, Grove port (UART RX), and custom BLE service depending on hardware capabilities.   Internally the list of scan codes is used to provide feedback from I/O read/write processing from the 6502 checking to see what lines are connected.  So while I could have implemented a more binary or compact format of transmitting this data, I enjoy the diagnostic capability of seeing the values clearly when necessary.

The keyboard hosts utilizing this protocol include the key scan codes helper for the web (USB serial out), two instantiations of 25-pin matrix to UART TX supporting real Commodore keyboards, and a BLE bridge firmware with a Grove connector (e.g. on M5Stick-C).

The scenarios that work include

1. Type from my Windows keyboard, adapted to scan codes sent over USB serial (any of the targets).  Keyboard and target are both wired via USB.  A web page is making the translation and bridging between the keyboard and USB serial.

2. Connect Commodore keyboard adapter directly to Grove port on one of the various M5Stack devices: M5Core, M5Core2, M5CoreS3, Tab5.   This looks like a Commodore keyboard wired through a mess so tied to the target.

3. BLE adapter takes scenario #2 and cuts the wire between the adapter mess and the target.  The target appears free and clear of any wires (if self powered).  The adapter can be tucked into/under/next to the keyboard.  While this acts as a Commodore BLE keyboard, the keyboard itself has a mess of wires, power supply, and adapters.

The BLE adapter actually accepts THREE different inputs.  USB serial (from PC host), Grove UART RX, and Grove I2C for CardKB).  Note that CardKB isn't using my keyboard standard as an input because it has its own protocol, and I didn't feel like reprogramming it.  CardKB drivers are in both the BLE adapter, and in the emulator itself for supporting direct connection.  Both CardKB uses are converting and outputting the Commodore scan codes internally.

4. USB host adapter takes a standard USB keyboard and converts to UART TX.  This can be cross wired from Grove port to Grove port, or be used in conjunction with #3 (great!! more adapters to the mess) to appear as a USB to BLE wireless adapter.

So I've created an ecosystem of keyboard compatibility built on a custom "standard" so parts can work together and interchange/swap.  It allows for flexibility, and interconnections.  And when a new keyboard input source comes along (like PPK), implementing the current standard brings extra value to the table.

PPK BLE adapter firmware customized for Commodore

The pre-existing firmware for the Bluetooth adapter as developed by pymo took the obvious choice - present as a standard Bluetooth HID keyboard.  That solution works for Windows, Mac, Android, iPhone, and others.  It's a great standard.  But it doesn't directly translate to a great Commodore keyboard experience.  And I don't have a general BLE HID solution integrated into my emulators yet either.

With the way things are with my custom Commodore scan code protocol, it made sense to revise the PPK BLE adapter directly, take advantage of all keys present including Fn and special purpose keys, and map them to what makes sense for Commodore.   And add extra value too!

Palm Portable Keyboard

       1   2   3   4   5   6   7   8   9   0   -   =  Back     Date
    Tab Q   W   E   R   T   Y   U   I   O   P   [   ]    \     Phone
    Caps A   S   D   F   G   H   J   K   L   ;   '   Enter     To Do
    LShf  Z   X   C   V   B   N   M   ,   .   /   RShft Up     Memo
    Ctl Fn Alt Cmd {Space  Bar}Spc2 ` Done{Delete}Lt Dn Rt

Let's compare to the layout of my favorite Commodore system, which is a superset of C64

Commodore 128

    Esc Tab Alt Cap     Help LF 40/80 NoScroll     Up Dn Lt Rt         F1  F3  F5  F7

    ←   1   2   3   4   5   6   7   8   9   0   +   -   £   CH   Dl    7   8   9   +
    Cntl Q   W   E   R   T   Y   U   I   O   P   @   *   ↑   {Rest}    4   5   6   -
    RS SL A   S   D   F   G   H   J   K   L   :   ;   =   {Return }    1   2   3   {Enter}
    C=  Sh  Z  X   C   V   B   N   M   ,   .   /   {Shif} Up/Dn L/R    {0   }  .   {Enter}
               {Space                       Bar}

These are the symbolic key mappings I came up with

PPK C64 Normal

       1   2   3   4   5   6   7   8   9   0   -   =  Back     F1
    Tab q   w   e   r   t   y   u   i   o   p   [   ]    £     F3
    Cap  a   s   d   f   g   h   j   k   l   ;   '   Retrn     F5
    LShf  z   x   c   v   b   n   m   ,   .   /   RShft Up     F7
    Ctr Fn Alt Cbm {Space  Bar}Rest ` Stop{Delete}Lt Dn Rt


PPK C64 Shift

       !   @   #   $   %   ↑   &   *   (   )   ←   +   Ins     F2
    Tab Q   W   E   R   T   Y   U   I   O   P   {   }    |     F4
    Cap  A   S   D   F   G   H   J   K   L   :   "   Retrn     F6
    LShf  Z   X   C   V   B   N   M   <   >   ?   RShft Up     F8
    Ctr Fn Alt Cbm {Space  Bar}Rest ~ Stop{Delete}Lt Dn Rt


PPK C64 Fn (and Numlock)

                               7   8   9   +       =  Home     Help
                                4   5   6   -                  LineFeed
                                 1   2   3   En      Enter     40/80Display
    LShf                          0   0   .   En  RShft Up     NoScroll
                                      Esc {Home } Lt Dn Rt


PPK C64 Caps (C128 only via emulator)

       1   2   3   4   5   6   7   8   9   0   -   =  Back     F1
    Tab Q   W   E   R   T   Y   U   I   O   P   [   ]    £     F3
    Cap  A   S   D   F   G   H   J   K   L   ;   '   Retrn     F5
    LShf  Z   X   C   V   B   N   M   ,   .   /   RShft Up     F7
    Ctr Fn Alt Cbm {Space  Bar}Rest ` Stop{Delete}Lt Dn Rt

Reference

  • Fn Backspace and Fn Del maps to Home key for moving cursor to upper left on Commodore
  • Fn Shift Backspace and Fn Shift Del map to Clear key to erase Commodore screen
  • Fn = toggles NumLock mode, sends unique C128 scan codes for numeric keypad, Enter, and arrow keys.   Hold a key with Fn to send the opposite scan code if necessary.
  • Fn LShift RShift toggles shift lock mode, mimicking the positional switch on Commodore
  • Caps acts as a toggle switch to represent the positional switch on the Commodore 128.  Whether letters are shifted (capitalized), and punctuation is not shifted is the job of the C128 ROM.
  • Fn Done is Esc
  • Alt works only in the C128 mode of the emulator

The end result is a full-featured Commodore 128 keyboard for use with Commodore 128 emulation.  (And some standard ASCII keys [{}~`|] not present with Commodore, so more useful in non-Commodore emulation).  And by the way, the Commodore 64 and Vic-20 emulations map the extra C128 keys to its own matrices automatically -- unlike a real Commodore 128, etc.   For instance, the four arrow keys and numeric keypad don't work in C64 mode on a real C128, but they do in the emulated system -- appearing as if the original 64 key matrix keys were pressed instead.   This is implemented in the emulator itself.  The keyboard is optimistic and reports everything expecting a C128 on the other end.  The emulator maps extra keys down to C64 keys, and if in Vic-20 mode, unscrambles into the Vic-20 scan codes too. 

(C64 and Vic-20 share the same exact keyboard, but the lines were reordered when connected to the I/O chips.  The same keyboard would work in the C128 except for the missing keys - the C128 is a superset.  How do I know this?  I use both a real Vic-20 and real C128D keyboard interchangeably connected to my self designed Commodore to UART Tx [Grove] adapter, and they work interchangeably in my emulator.  How cool would it be to build a C128D replacement keyboard from a Vic-20 keyboard in case?  That would blow some minds.)

Operation

1. Plug in the 3D printed Bluetooth adapter and circuit into the PPK to automatically turn it on and start transmitting that it is in pairing mode (rapid Green flashes).

2. Turn on or reset the Commodore Emulated system (e.g. M5CoreS3 or Sunton)

3. Wait for Commodore to boot

4. Should be paired (occasional Green flashes)

5. Type away!

Sunton 7" tablet (Perler bead frame) and PPK BLE

Conclusion

If you have a Palm Portable Keyboard (PPK), access to a version 2 PPK BLE adapter from pymo / Xinming Chen, and you need your wearable or portable Commodore fix with access to a compatible embedded target, then of course this project is for you!   Enjoy the ultimate in retro portability without wires.  And look cool doing it!

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, July 9, 2024

Disassembler for use with WozMon

 

Disassembler disassembling WozMon

WozMon is a great little monitor program in 256 bytes which Steve Wozniak wrote for his Apple 1 release in 1976.  It was small but powerful allowing for memory inspection, entry of 6502 machine code programs and bytes, and launching of programs in ROM and RAM (jumping to address).

But being small, it is missing features that monitor users have learned to depend on, including a mini-assembler and disassembler.

So here I am today presenting a disassembler I wrote for 6502 which can be integrated with WozMon run command.  Once the disassembler gets control, the Y register contains a pointer to the last character read from the input buffer, and continues parsing for a hex address.  If not found, it simply returns to WozMon.  But if found, it will disassemble 20 statements starting at that address.  (Note: Vic-20 port above, changed to 17 statements due to the smaller screen size).

The syntax may be lengthier than other monitors, but it allows for an integration with the normally ROM based WozMon without modifications.  Plus you get what you pay for.   The disassembler is open source, so feel free to change it.

The disassembler came into being as part of developing a mini-assembler.  First I'd written a prototype in C# .NET. 

The assembler included tables of information for the 6502 instruction set and addressing modes that lent itself to be used as a disassembler (came second) which was prototyped as a 6502 program that disassembles itself.  Third came the disassembler compatible for use with WozMon.

Each prototype and program is one step along the way for the goal of creating a mini-assembler for my 6502 minimal system.  Not there yet, but getting closer with each step!

Also works with original WozMon in Apple 1 style environment

Also works on C64 and C128 systems


Sunday, May 26, 2024

LCD version of 6502 emulators ported to Windows

 

LCDs and Windows too!

Now the c-simple-emu6502-cbm unified branch that works with LCD systems has been ported to Windows!  This is an emulator that includes a feature to switch between various popular Commodore 8-bit models from the BASIC prompt.

You may ask yourself, hey self, wasn't Windows already a supported system?  And you could answer, yes self, it was.  But only in text mode.  If you wanted to run Commodore BASIC from a command prompt, yes you could do that.  If you wanted to run this emulator with the nifty "GO 128" command in a graphical environment you were required to use those smallish LCDs (well the 7" isn't too smallish).

Now, by porting this C++ project back to Windows again, using GUI elements, it can now look more like a Commodore.  The fully resizable window, and keyboard support make it feel like you have a Commodore right in front of you.  You virtually do!

For now, you need to compile the project in Visual Studio 2022 (Community Edition should work just fine).  It probably also works from Visual Code, but I haven't attempted that yet.

Dependencies include a roms folder (see README.md), optional disks/drive8.d64, and optional disks/drive9.d64

The purpose of this is to ease development of new features, utilizing the feature rich Visual Studio IDE including debug support.

The benefits to other users include being able to test drive the project in an environment they already have - their existing Windows desktop or VM.   And if you love it and think you may enjoy a portable version, you can then invest your dollars into an LCD solution via various online retail websites.   There may even be a search engine out there to help you too.

Are you keeping up with Commodore?  Happy computing!  Spend that money wisely.

Wednesday, February 7, 2024

Calculator for Commodore 64 and other CBM BASIC models

 


Introduction

Wow! A new desktop calculator for Commodore.  Programmed all by myself in BASIC.  Wow, so impressed!   Or not.  Doesn't even have decimals.

No, it's not the best thing since sliced bread, but it was fun.  And it's just a start -- there's a bigger goal I'll get to in a while...

This was a puzzle programming exercise I challenged myself with.  How to make the calculator, especially with the operator precedence feature.

Link: LOAD"CALC64",8

Implementation

Let's go through how it was implemented.

User Interface

First was the design of the user interface, by creating PRINT statements.  Used reverse text to make a rectangular box with the keys displayed.

How to click on a key?  I'm so used to a mouse or touch interface.  Oops, Commodore doesn't have that usually, or not so easy.  Ignoring the 1351 mouse for now.   Most Commodore users are used to using the keyboard or joystick.  Forget the joystick, this is not a game!  Keyboard entry one key at a time will do just fine.  Note that ENTER also acts as =, and DEL is equivalent to ←, and X is a synonym for *.

Entry of digits was the first processing, accepting up to the limit of the display.  This stored as a string (V$) with keyed entries appended.

I started with 7 digits, but outgrew that in testing and increased to 9 digits.

At first, only positive numbers were supported, then fixed it to support negative numbers, with the negative sign taking up a position in the display.   The N key toggles a number entry between positive and negative and back again.

Entries allowed by this calculator are interleaved numbers and binary (two argument) operators.  Note that immediate operators like C and N usually take effect immediately, whereas binary operators require multiple arguments and a finalization (another binary operator or =).

There are exceptions allowed in entries to overwrite the prior value or the prior operator.   Examples:

  • 1+1=3 keyed entries will replace the 2 with a 3
  • 2+x3 will replace the addition operator with multiplication operator

Precedence needs a Stack

One design requirement I set for myself was to include operator precedence.  This means that multiplication and division have the same higher precedence over addition and subtraction at the same lower precedence.  Usually math is evaluated left to right, but due to precedence issues, other operators may need to be done first.  In other words the expression 1+2x3 is equivalent to 1 + (2x3) and then 1 + 6 and finally evaluates to 7.   But 1 x 2 + 3 is equivalent to (1x2) + 3, then 2 + 3, and finally 5.

I simply put numbers and operators interchangeably on a stack in order of entry until enough information is present to start evaluating.

Rule 1 - if user presses =, then evaluate

Rule 2 - if first operator is multiplication or division, once the second number is finalized with a second operator, simply evaluate the first expression

Rule 3 - if first operator is addition or subtraction, defer until the second operator and third number is finalized (third operator present!), then figure out what to do - either evaluate first or second expression, reducing the work

The stack is implemented as a count C, and an array of strings S$, which is expected to only grow up to 6 items (0..5), example C=6, S$[0]="1" [1]="+" [2]="2" [3]="*" [4]="3" [5]="+"

This example 1+2*3+ is reduced to 1+6+ and then to 7+, with the display updated to show the 7, but then wiped to disallow appending to it.


And no, I'm not doing RPN.  I didn't grow up with an HP calculator, nor am I implementing one today.  I grew up with Texas Instruments, and that's how my brain works.   Keeping it in entry order keeps it orderly for me.  Sure, there are other was to approach it.   This is BASIC, not Lisp.  And this is MY calculator, so I'm doing it MY way.  Others can follow their own paths.

Expression evaluation includes handling the equals = operation.  In the context of this calculator, that finalizes a calculation, meaning the user wants the result now.

Subroutines

There are several subroutines: 1) Evaluate and reduce stack, 2) Update display, 3) Display stack, 4) Evaluate simple expression involving two numbers and an operator.

That last subroutine is a diagnostic useful most for development, but also shows work in progress compared to a regular simple desk calculator that usually won't show pending work.

Error conditions

Overflow is when the number to be displayed doesn't fit in the 9 digit display, when the string representation of the number is long than 9 characters and/or larger than the maximum whole integer value that can be displayed in that space.   Note that the number is still internally represented and can still be acted on.  If a resulting answer can be displayed it will be (example: divide a ten digit positive number by 10).

Error indicates that division by zero was attempted.

Limitations

Only integers are supported.  No decimal numbers.  No currency.   Workaround required to calculate a percentage -- multiply by one hundred first, then divide by whole percentage.  And division results in a whole number so 1/3= results 0, and 5/2= evaluates to 2.

Non-negative numbers are limited 0 to 999999999 (nine digits).  Negative numbers are limited -1 to -99999999 (note only 8 digits).

Scientific notation is not supported.

No paper tape, no printout, no record of operations.

Limited user feedback.  No keypress indicators.

Stack diagnostic is a little technical, especially with a count, then colon, and the entries.  (Could eliminate the count and colon, and/or show previous and pending operators before and after the display.)

Keyboard entry.

Extras

The program was saved with a starting address $400, to support PET, so you must not load with secondary address 1 on a C64!  And while the program assumes a screen bigger than a VIC-20, a few minor changes to the margin and startup text can remedy that.

Next

What does the future hold?  Who can predict with any accuracy?  I can say anything here, but doesn't mean I'll follow through.  I originally was contemplating near limitless number of digits (considering the 8-bit platform, probably some 16-bit limitation).   Maybe binary and hex?   Maybe a scientific calculator?   Fixed decimal?   Near limitless decimals?   Port to assembly for fun!   We'll see...

Have fun calculating!

Saturday, December 30, 2023

Commodore 128 VDC Reference

 



The VDC8563 and VDC8568 were used in Commodore 128 and Commodore 128D computers for the secondary 80-column text screen.  There were other modes available to developers not exposed to BASIC end users (graphics, interlaced modes for higher resolutions including graphics and text, and lower resolution modes too).

I gathered this information from multiple sources including example data from VICE (sorry, didn't power on the DCR) while implementing support for my own emulator.

Hope you find this useful!

Link: PDF

Saturday, December 9, 2023

Commodore keyboards go wireless for my portable emulators

The c-simple-emu6502-cbm project supports a number of ESP32 platforms to provide a subset of Commodore C64 (and Vic-20, C128) emulation, and many currently include BLE keyboard support.  Originally for CardKB only, now I have added BLE support for Commodore keyboards (20 to 25 pins) to the m5, T-Display-S3, and ESP32-8048S070-7inch ports/branches into the encapsulated BLE_commodore_keyboard_server Arduino sketch.

BLE options: (a) Commodore keyboard (b) CardKB

No expense spared for these awesome graphics, seems retro eh?


While I have wired in the full C128DCR keyboard in the past using a circuit and software sketch with an Adafruit ItsyBitsy, now I trade the wire with another ESP32 and BLE communications.

M5Stick-C with CardKB BLE connected to T-Display-S3

Actually we already had BLE CardKB support, and the protocol for the hard wired keyboard is exactly the same as sent over BLE (string of active C64 and C128 scan codes), it was just a little bit of further coding to make the choice between CardKB and hardwired keyboard.   In fact, that code was already present for CardKB or hardwired keyboard in the M5 branch itself.  The tiny bit of extra work was to duplicate that in the BLE keyboard server project.   And voila!  More options all the way around.

Wired keyboards (a) Commodore (b) CardKB (c) Chrome Browser

There were already three options for wired keyboards.  And three common Commodore keyboards were represented, because they all have compatible pinouts, and because I do have both Vic-20 and C128DCR at home.

Dropping the wire from the wearable (or other ESP32 emulators) adds convenience to mobility, and also defers the need to support wired connections to any ESP32s missing Grove connectors and any that are not 5V tolerant, as both the ItsyBitsy and CardKB use 5V interfacing.   BLE support, and improved BLE support provide more options to the emulators running on hardware such as the T-Display-S3 and the 7"LCD ports without any hardware interventions.  While these latter ports would require extra circuitry for hardwired serial or I2C connections including 5V to 3V3 interfacing, using BLE means that the existing circuit support on the M5Stick-C acting as the BLE server can wire to those keyboards instead.  Going wireless provides the equivalent functionality without requiring a hard-wired circuit to the final display device.

While wireless does have its convenience, it does require careful timing to pair correctly.  Typically if both the BLE client and server are powered or reset at the same moment, they should pair.   A few or more keystrokes may be necessary to confirm pairing is complete.  If it doesn't work, just reset and try again.

Happy C64 computing over BLE!

Monday, December 4, 2023

New! and Improved! C64 text emulator

There has been a steady stream of improvements to my wearable C64/C128 text emulators and related projects since the Summer 2023 debut at VCF West 2023 in Mountain View.  My Twitter/X account includes posts of many of these advancements.

  • used a 6502 test suite to find problems in my emulation
  • Vic-20 emulator
  • Vic-20 upscaled resolution
  • A few more hardware targets supported, including much smaller sizes
    • M5Fire 320x200
    • M5Atom S3 128x128
    • M5Stick-C 160x80
    • LilyGo T-Display-S3 320x170
  • Downscaling resolution as necessary
  • Tilt and pan for 1:1 resolution on tiny screen sizes
  • FATFS partition for files when no SD present, and when PSRAM not present
  • M5Stack CardKb support
  • wireless keyboard (BLE server/client)
And all these changes are stored on GitHub of course!

A fellow attendee at Vintage Computer Festival West 2023 sported a red M5Fire and it looked really good!  So I ordered one soon after, it arrived with some other goodies, and I quickly ported the M5Core2 and M5CoreS3 solution to the M5Fire.   Features are comparable.

Then I added keyboard support to the on-screen buttons.  Left goes up.   Right goes down.   Center is Return.  Left+Center is Shift+Run with a ROM change to make it load the first program from disk.  And Left+Right toggles between the different emulators (C64 -> C128 -> Vic-20)  This made an actual keyboard optional for demos.  I had a boot program to provide a listing of programs selectable by cursor keys and Return key.  

The only downside is that the M5Stack Fire is not watchband compatible in that the recharge circuit is in the detachable base, not in the unit itself.  Otherwise it would make a classy wearable.


Vic-20

The Vic-20 was my original home computer.  It is what I used to deep dive into Commodore, learning BASIC and 6502 Assembly Language inside and out from about 1982 to 1985.  I still have a fondness for this system.

But it has an odd screen resolution.  Text is 22 columns and 23 rows equating to 176x184 pixels.

The M5Core series controllers have an LCD 320x240.   This was just perfect to match the standard text screen of the C64 which is 320x200 pixels. 

Originally I simply increased the border sizes around 176x184 pixels.

Then I revisited my Teensy C64 which has an option for a 480x320 LCD screen.  For that project I researched upscaling, which involved scaling an 8x8 character cell to a 12x12 character cell, using color averaging.

For the M5Core series, I similarly scaled the Vic-20 8x8 character cell to 12x8.   To accomplish this, staring with pixel offset 1, an extra column is interpolated from the previous and next pixels, and repeated a total of four times.  The LCD works in 16-bit color mode with 5 bits for red, 6 bits for green, 5 bits for blue.  The color of the two pixels is broken down into its component red/green/blue parts, the corresponding color parts are averaged (totaled and divided by 2), then recombined into a 16-bit value for the interpolated pixel.

So far only the M5Cores with PSRAM have Vic-20 and C128 support.

M5Atom S3

This is the smallest target hardware I have ported to.  Downscaling was implemented to see what would happen.  The 8x8 character cell is downscaled to 3x4 pixels, so 64 pixels downscaled to 12 pixels which is a large number of pixels to through away.  Priority was given to the center pixels, so it toggles between averaging 6 (3x2) or 4 (2x2) at once.  It does a weighted average between the foreground color, and background color pixel counts, so the resulting downscaled pixel is closer to one or the other.

Surprisingly, the screen is somewhat readable even with this resolution loss.  But to compensate for loss a zoom and tilt to pan feature was implemented.  The whole screen is mounted as a single pushbutton.  Clicking it toggles between zoomed out to the downscaled resolution, and the zoomed in to the pixel perfect 320x200 resolution that is panned, by tilting the device.  Zoomed in, hold the device level with the floor, with the screen pointed to the ceiling, and you will see the top center of the C64 screen.  Tilt to the right and forward to see the top left corner of the emulated screen, and so forth to switch between one of six views of various parts of the emulated screen with every pixel shown.   Click again to zoom out for an overview of the entire screen, but downscaled.  Break out a magnifying loop or such to see the detail of the teeny tiny pixels.

This platform does not include an external storage device such as SD card.  Nor does it have PSRAM normally used by the D64 emulation.   Instead, a FATFS partition was initialized, and individual C64 PRG files were selectively uploaded to the device.   The "$" directory functionality is not present, but LOAD/SAVE/VERIFY are supported.   There is room for about 1.5MB of files in the partition size selected, that's about 9 times larger than the standard single sided C64 floppy, so not too bad!

M5Stick-C

I had forgotten about this hardware device.  It was the first M5Stack device I had purchased, and I had squirreled it away in a project box.   Obviously it was too small for a C64 screen so I didn't give it another thought.   Until I saw a post on Twitter/X showing the solution with CardKB.  Looks like it's using my text-only emulation, using LCD fonts.  Of course I respond!  That would work very well.  I've just been so focused on LCD pixelized solutions simulating the look and feel of Commodore instead of remembering the roots of my text emulation efforts with C64.   Just hook CHARIN and CHAROUT and you're golden.  You don't need a screen editor.  You can just do buffered line input (local edit), and character output.   I was laughing at myself for not pursuing this myself.  CardKB provides ASCII output (from I2C polling) of the alphanumeric characters, and other byte ranges for functions and cursor keys. 

I wanted to do CardKB, but as I have been focused on C64 scan code adapters, it seemed hard.  But here was the challenge presented on the Internet.  I had already implemented an adapter of sorts leveraging a SeeedStudio ATMEGA328P (Uno compatible) board with Grove connectors and an Arduino sketch to translate I2C reads into TTL serial scan code reports.  And it worked, but was very clunky because of all the cords and extra board.   My existing prototype solution was not great for a wearable solution.

The M5Core and such were focused on using the Grove connector as a software serial port, receiving the C64 scan codes.  I had the original C128/C64/Vic-20 keyboard to Grove adapter running on an Adafruit ItsyBitsy and that was my favorite keyboard connection because it was true to the original!  Next best was a web page to USB Serial adapter I had also developed.   The common ground was scan codes, and serial communication.  But the CardKB runs I2C.   So how to do CardKB and serial communication simultaneously on the same Grove port?   Originally I thought of getting the source to CardKB and rewriting, reflashing it to do serial communication instead.  That would make it compatible with my existing solutions.  But I was avoiding the reflashing that would also requiring rigging a programming interface, using an Uno or compatible.   

Instead, I approached the Grove port as an either or, the emulators were updated to check for an I2C response at startup, and if found, regularly read from the I2C port for keyboard presses.  Then adapt those presses from ASCII/function code presses into momentary C64 scan codes, and 1/60th of a second later, respond with no key pressed (key up) scan code response.  This is an out of the box solution that will work for others too!

M5Stick-C also leverages a FATFS partition as there is no built in SD port.  Again 1.5 million bytes.,  Though I did have to revise the partition choices manually via a JSON file as FATFS was not included in the default partition schemes presented.

But the screen resolution is 160x80, with downscaling of one character's 8x8 pixels to 4x3 pixels.  This time the pixels are halved horizontally, and vertically there is an 8 to 3 pixel translation, very similar to 
the downscaling on the AtomS3, but rotated to different axes.

This is by far the worst unreadable display for individual text characters.  More than halving the vertical resolution makes the text completely unreadable (such as the startup screen).  It's not it's fault completely, and is a cute form factor when not pretending to be a C64.

LilyGo T-Display-S3

This is another inexpensive device with a wide yet shorter display 320x170.  Oh so close to the necessary 200 pixels.  So a custom downscaling algorithm favoring the center four vertical pixels of each character cell, while averaging the top two and bottom two pixels.  This results in a very recognizable display of alphanumeric characters with some slight distortion at the top and bottom of each character.

While this device has plenty of flash storage and PSRAM comparable to the best of M5Stack Cores, there is no built in SD card, so the implementation also leverages a FATFS partition successfully.  But with the PSRAM included, D64 support could be included allowing for the floppy drive image feature.

BLE keyboard support has also been included with this one, making it full featured.

The features missing are no Grove connector, no socket for CardKB support, and Vic-20/C128 support.  A bit more effort could transfer these features with software and hardware.

CardKB

     Esc 1! 2@ 3# 4$ 5% 6↑ 7& 8* 9( 0) <x
     Tab Q  W  E[ R] T/ Y£ U| IÏ€ O' P" fn
  Up  Shift A; S: D  F+ G- H← J= K? L  <-
Lt  Rt  Sym Z  X  C  V  B  N  M  ,< .> __
  Dn

fn+1..fn+0 is ctrl+1..ctrl+0
fn+A..fn+Z is commodore+A..commodore+Z
Esc is stop
fn+Esc is stop+restore
tab is load+run
fn+tab is restore
fn+<x is insert
fn+up is clear
fn+right is home
fn+down is toggle case (cbm+shift)

Wireless BLE Keyboard

Taking CardKB one step farther is joining it with a M5 controller such as M5Stick-C to turn it into a Bluetooth Low Energy keyboard.  Currently integrated with M5Fire, M5Core2, M5CoreS3, and T-Display-S3, turn them both on at about the same time and they will automatically pair.  While the keyboard is wired to the M5Stick-C, another ESP32 system can be battery powered and receive keystrokes over the air with the BLE (2.4GHz) radio.  




I used to wire the full size C128 keyboard to my wrist, and it was a hilarious irony in mobile computing.