Links:
- https://www.sx64.net/info/disk_commands.html
- https://www.scribd.com/document/1055555189/Twin-Cities-128-Issue-08
Links:
BUT!!! On the C128, the SYS command was extended to allow ease of passing registers. This is fine if you want to transfer data in 0..255 chunks. But coordinates on the screen are 320x200, so that goes past a byte, and addresses are 0..65535, they definitely go past a byte. And strings are another animal.
On the C128 the statement SYS 4864,1,2,3 will call the routine with .A, .X, .Y registers populated with 1, 2, and 3 respectively. That's fine and dandy, but if you want to do something more, you have to do it another way.
Here comes the new syntax:
SYS 4864 : REM 300, 65535, "Hello C128"
By using the token getter, you can check for colon, REM token, then proceed to parse values and variables as before on C64. (With the exception that I couldn't get expressions like 2+3 to work, maybe something to do with REM. But we can work around that.)
Check out the github repository for program listings including commented assembly for more details, and to grab a copy of the D64 containing the PRG.
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.
![]() |
| Teensy in green, ESP32-C3 in blue |
I have a Teensy 4.1 operating as a Commodore 64 (and Vic-20, and 128, and Apple 1) emulator. It already supports USB Host (via 5 pin header on top) and web page helper (via USB CDC).
The Teensy doesn't have wireless capability, but I wanted to add BLE keyboard support. So enter the ESP32-C3 as a coprocessor. It is running a custom firmware using the NimBLE library to enumerate BLE HID devices in pairing mode, and connects as soon as it finds one. If everything works successfully, it sends HID reports as ASCII over the UART channel (received at Teensy RX pin 0).
While the Teensy is busy driving the LCD with a bunch of connections, the ESP32-C3 is only wired at 5V input, ground, and a TX pin. The ESP firmware is built without USB CDC support so that the Serial line defaults to GPIO.
Shown is a USB only ESP32-C3 board with the advantage of being slightly cheaper. When I purchased it years ago when ESP32-C3 was relatively new, it was about US$2 on AliExpress. Looking today, there's even more choices available in all sorts of price ranges and sizes.
So the Teensy is doing what it is good at, and the ESP32-C3 is filling in a wireless connection gap. The red jumper is provided to be able to pull power. Though the power connection does have its advantage that the ESP32-C3 has a USB-C port, while the Teensy has a USB-Micro connector.
![]() |
| Look Ma -- no wires! |
Mouse, Trackpad, and Gamepad/Joystick are supported as well. Gamepads and Joysticks will require HID maps which are loaded but not interpreted yet, nor passed on the interface (work to be done). Joystick support is not implemented yet in my emulators, but stay tuned!
Update (2026-07-03): coprocessor firmware updated to support my custom BLE CBM keyboard sources, and optionally convert standard HID reports to Commodore key scan codes. By focusing on Commodore scan codes, the integration with the emulators is now simpler, and seamlessly supports more of my keyboards -- while also as being an option, the coprocessor can be used by those that expect standard HID bytes. (Note: no conversion from Commodore to keyboard HID, only the other way around.)
![]() |
| Tab5 Keyboard is a convenience add-on |
I added native keyboard support to my emulators already running on M5Stack's Tab5.
There were already 64/Vic-20/128 original keyboards, CardKB, web page adapter, Palm Portable Keyboard, and custom BLE support.
Now there is also attached keyboard accessory support. The Tab5 is a 5" ESP32-P4 handheld tablet with high resolution 1280x720 LCD. The keyboard plugs into the bottom of the tablet, extending the size, and the capabilities.
The implementation scans all 70 keys for presses and translates to a list of C128 key scan codes into ASCII comma separated and newline terminated form, which the emulators already know how to translate into Commodore model specific keyboard scan codes. As these are pressed scan codes, the press is persisted in the emulated system until the physical key is released. These are symbolic mappings, not positional mappings because that is my preference. Want different? This is open source!
All visibly labeled keys are supported mostly as is. Keys not Commodore specific are mapped to a Commodore PETSCII character already mapped to the proper ASCII character for the minimal environment (left arrow, underscore, caret, curly braces, pipe, backslash, backquote, tilde). In addition, shift [Aa] supports letters, and numbers and punctuation to do the normal shifted characters. Both [sym] and shift [Aa] do the symbols. Additional keys worthy of documentation are:
As a reminder the emulators included are:
Links:
https://github.com/davervw/c-simple-emu6502-cbm/tree/unified-pio
Instructions
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.
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.
. 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 rtsPlease 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.
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
30 def fnw(v,r):bank15:sys52684=0:rem placeholder10 graphic 5,1
20 color 0,2,1
30 circle 1,320,100,60,60
40 getkey a$
50 graphic 0,1Resulting 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
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 end100 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) **
Auto binds to pairing device
gampad activity shows here
and see serial monitoring
for more devices & infoUp, Left, Right, Down, Fire (M5) and second button (click joystick). Not just a d-pad. Is also mirrored as xpot, ypot, so can do paddle games, or analog joystick games.
Go play some games!
Link: github.com/davervw/m5_minijoystickc_gamepad
Also posted on M5Stack M5Burner
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 |
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 |
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.
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.
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!
Let's compare to the layout of my favorite Commodore system, which is a superset of C64
These are the symbolic key mappings I came up with
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.)
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 |