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!

Tuesday, December 5, 2023

7" LCD (ESP32) with C64 Text Emulator

 

7" is giant compared to this much smaller screen

So this ESP32-8048S070 showed up from AliExpress today.  It is a 7" IPS with touchscreen with ESP32-S3-WROOM-1 module, 16MB flash, 8MB PSRAM, microSD card, USB-C serial (CH3400 serial), Speaker connector, and other IO connectors.   The large LCD is 800x480 resolution.

The ordering page had a ZIP download of the examples, so I made sure I could build their Arduino Hello World TFT sample (I downgraded to Arduino_GFX library 1.3.1), and then ported my c-simple-emu6502-cbm project including upscaling from one to four pixels to cover the screen with C64 goodness.  Without upscaling, the C64 was using less than a quarter of the screen.   I skipped the SD support, and went straight to creating a FATFS partition and uploading a D64 image.  Saves me from pulling an SD, but moving files up and down will be a bit more cumbersome.  Included in the project already was custom wireless BLE keyboard support, so I was typing away and running my programs right away.

The largest IPS I had on hand was 3.5" 480x320, so this is a definite upgrade.  

The only downsides to this board that I can tell are the lack of an enclosure (ships in a nice plastic storage box though - I hear there is a 3D printable one on github somewhere though), and the lack of direct connection to the native ESP32-S3 USB port.  Then again, a permanent serial connection is more convenient for flashing Arduino sketches otherwise you have to keep selecting the COM port on other solutions.

Even though I've worked with it only a few hours, I highly recommend this board!

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.

Friday, November 3, 2023

When Emulation goes wrong

My name is Dave and I'm a software developer.

I both create and fix defects in code that I've written.   Hopefully I find the bugs quickly, but sometimes they linger for weeks, months, years, before being discovered or reproduced.

My story is not unique.  It is difficult or impossible to write software without introducing defects.  Computers are complex systems, but very obedient.   They do exactly as they are told if you know how to speak their language, precisely, without misunderstanding or any inaccuracies.  Problems can crop up if the developer didn't understand the system requirements in the first place (design/requirements issues), didn't thoroughly test all inputs and code paths, didn't understand concurrency issues, made assumptions, or makes the simplest of mistakes.   As humans, we learn to overlook some inconsistencies, mistakes of others, so as to not disrupt the social norm.  But with most of the languages we use with computers (Assembly, C, C++, C#, Java, etc.) many of these don't allow for the slightest of change. Some scripting languages (HTML, JavaScript, CSS, etc.) allow for some variance, but still are expecting a valid input.

Am I making an excuse for my mistakes?  No, but explaining that they will exist.  I wrote a limited Commodore/6502 emulator a while back and have ported it to various platforms with demonstrable results.  I can run Commodore 64 and related systems at the BASIC prompt and it appears to mostly function normally.

Then I ported to Vic-20 including adding full-screen editing support and conversion from Commodore 64 key scans to Vic-20 key scans.   And it did not work.   But I swore up and down that I did everything correctly (ha!).  Then dove in deeper to see what was different.   Turned out the ROL ABS and ASL ABS instructions were doing the value shift but not storing the result in memory.  A normal 6502 knows how to do this just fine, but the emulator needed to accurately replicate this behavior to be correct.  The fix was to store the result at the absolute 16-bit address.

My first mistake was that I never performed a formal test of the instruction set of the emulated 6502.  I knew they existed, but when bringing up the C64 I only spot checked a few instructions, and even compared traces between Vice and my emulator to note differences, and correct the emulation in a few places.  (There's a reason my trace looks exactly like that from Vice -- so the comparison would be easy!)  But once the system appeared to function correctly, I stopped testing.

Fast forward over 3 years later, and I know that some of my systems are not 100% correct.  The effort I had brushed away came back to nag me.  I really should test better.  I have a saying that if it's not tested, it's not working.  And I had ignored that saying.  But I buckled down and did the effort of running a 6502 test suite against my emulator.  And sure enough there were problems.

And since I have multiple emulator instances, the problems were slightly different, as I hadn't applied fixes consistently between all of them (still have missed a few at date of this article!  STM32, mbed, TypeScript still have known bugs). [update 2023-11-05: TypeScript emulator updated with fixes]

Here are the instructions and features of the 6502 that required fixes in my emulator:

ROL (absolute address)

LSR (absolute address)

PHP (B flag)

PLP (B flag)

IRQ (push processor state without B flag) 

(zp,X) indirect zero page indexed by X addressing not truncated to byte address when overlap page, affecting both load value and store value

SBC resulting in a negative result when in decimal mode

Many changes are now checked in for C++, C#, Arduino, M5, Teensy and related projects.

There are remaining bugs in emulation, probably closer to individual system systems outside the 6502 emulation.  A few issues can be reproduced on demand, so I can tackle those next.

But the 6502 emulation should be good, at least as much as it has been tested.

Thanks to Klaus Dormann for these 6502 tests.

Reference: 6502_65C02_functional_tests

Friday, June 30, 2023

Extremely small emulated C64 and C128

 


This "portable" Commodore 64 and 128 emulator (m5 source code branch) is my work in progress, one in a series of minimalist emulators ported to different hardware targets. Only text (on LCD) with background, foreground, border colors, keyboard entry via USB serial tethered web browser, and general 6502/6510 and C64 memory management emulation is present (no, won't play games, make sound, or do bitmapped graphics) with some D64 emulation [added 7/2/2023].

(Update 7/28/2023) Now with GO 128 command.

GO 128 command


Even my son asked, "Why do you need to do that?"  Well, he has a point.  I wanted a C64 that fit in my pocket or even on my wrist.  And targeting new hardware platforms with my emulator is part of my hobby.

How does it work?  Check out my highly technical drawing.

Now I already here you asking why I didn't connect Bluetooth to the M5Core, because certainly it has Bluetooth as well, and why didn't I use a USB keyboard connected to CoreS3, because it includes USB Host.  But I've had trouble tracking down examples of HID Host examples for M5; client examples are prevalent, but host?

Pictured here is a phone is running Chrome with a custom copy of the html/javascript keyboard adapter including web-serial-polyfill because mobile Chrome doesn't directly include Serial API support.  A Palm Pilot foldable keyboard has a Bluetooth adapter, paired with the phone.   HID keystrokes are captured by the web page, converted to C64 key scan codes, and a list of the active key scan codes (or 64 when keys released) is sent over USB Serial to the M5Core device which is running the C64 ROMs which are tricked into thinking a real keyboard is attached; keystrokes are processed by the C64 KERNAL IRQ as normal.

The M5Core is being powered by the phone.   Why M5Core?  Because it's a polished packaged solution.

Yeah, we could just run a Commodore 64 emulator on the phone, but this way, I could have complete control over the keyboard emulation, what keys are present, how CTRL and Commodore keys work, etc.  And it's just because I can, not because I should.

Why the Palm Keyboard?  Because it folds in my pocket!  And because I had one from back in the day.  Any keyboard you can attach to a phone or computer should work.  And this Bluetooth adapter just makes it so cool, and easier than a tethered keyboard.

The next step is to merge this solution with my Commodore 128 keyboard adapter to completely reject the portability feature.   That would look really cool hooked up to my phone!  Update (7/31/2023): check out YouTube for connection from ItsyBitsy/keyboard to Core Port.A.


C128 Keyboard Adapter Breadboarded Prototype


7/23/2023: PCB prototype keyboard adapter w/ ItsyBitsy

I am excited about my nonsense crazy adventures. Even if only I enjoy them.

=====

Update (7/2/2023): D64 support is currently working with Core2 only (Basic Core doesn't usually have the additional SPI RAM, but not yet sure why CoreS3 is failing to attach SD).

Update (7/3/2023): Got CoreS3 working with SD switching header to M5Unified.h for that target (was M5Cores3.h) and adding special definition, override logic for SD_CS to use GPIO_NUM_4 instead of default.  See updates to M5Core.h.

Update (7/28/2023): Commodore 128D extended keyboard working with UART connection to Port.A of Core, and Commodore 128 emulation is ported as well.

=====