- The ‘Working’ Commodore 64/128 Diorama and Raspberry Pi VICE Emulator
- Installing the VICE Commodore Emulator for Console Mode on a fresh Raspberry Pi 4
- Mirroring Raspberry Pi HDMI Video to a ST7789 1.3 inch LCD Display
- -> Patching the VICE emulator to light up floppy drive LEDs
Last Updated on March 17, 2023.
In a previous article of the Diorama 64 series, we installed VICE 3.4, unmodified, on our Raspberry Pi 4. I didn’t cover any customization of VICE in that article because I wanted to ‘walk before I run’. However, our goal is to take a miniature floppy drive model, shove a LED into it, and have our VICE emulator control when the LED blinks on or off, mirroring the Commodore 1541 disk drive activity LED. This article won’t cover the circuit itself (that’s a later article), but it is focused on modifying the VICE source code, and using some Python to transfer data from VICE and drive the Raspberry PI’s GPIO pins, which would later drive an LED light.
While the circuit details will come in a later article, here’s a little visual of the circuit setup – a Raspberry Pi 4 with GPIO pins attached to a breakout board, attached to a breadboard. The top breadboard is used to drive a separate power source for the LEDs, but you can see the green ‘power’ LEDs and one of the red ‘activity’ LEDs light up there, that’s all driven by the Raspberry PI and VICE.
High Level Design
Since VICE is an open source project (thank goodness!), I figured I’d need to get in that code and see if I can detect when the virtual floppy drives are being used. If I could do that, I’d want to send that data to a different program that would be responsible for driving the Raspberry Pi GPIO pins, which then drive the LEDs. I purposefully did not want to drive the LEDs directly from VICE itself – I wanted to affect the actual emulator process as little as possible. Just enough to get the data out of VICE. After that, some other process takes the data and controls the circuit. The secondary program would be written in Python, primarily because it’s very easy to interface with the Raspberry PI with the Python GPIO libraries. VICE, of course, is written in C.
So I’d have a C program that gathers data, and a Python program that does something with it. We typically call that a Publisher/Subscriber scenario. VICE is the Publisher as it is sourcing data, and making it available (publishing) for other processes to use, and VICE isn’t getting any data back – this is a one-sided conversation. Our Python program will be one of the Subscribers as it will poll for new ‘messages’ or changes in state. The Python program will then, in turn, send signals via the GPIO library and Raspberry PI GPIO pins to the Darlington Array, which controls individual LEDs, namely our Commodore 1541 Floppy Disk Drive Activity Lights. Here’s a high level flow:
The first real question I pondered was how I’d get a C program and a Python program on a Raspberry PI to share data, even if the conversation is one-sided. I figured I had four options off the top of my head:
- Datafile access – Vice writes to a file that the Python program constantly reads from to determine drive status. While this is definitely feasible, I wanted to avoid constant file-system access, OS caching or not. The LED is meant to blink on and off rapidly, that’s a lot of file updates to apply on a SD card.
- Networking Channel – UDP or TCP, some sort of client/server or packet broadcast. This would be necessary if the diorama was remote (an interesting idea), but ultimately the emulator and the consumer are on the same machine, so I wanted to keep the communications channel a little tighter.
- UNIX Sockets – Since the Raspberry Pi 4 is running Raspbian, it supports UNIX style sockets for a simple IPC (Inter-process Communication) channel between the VICE and Python processes. However, it’s still a lot of code to inject into VICE.
- The Winner – Shared Memory – I finally decided on a shared memory solution to avoid a lot of unnecessary and potentially harmful patching to VICE with any networking related code (the less I’m in VICE the less I affect its processing), along with Python recently getting support for shared memory between disparate processes with Python 3.8 and above.
While we are calling this a ‘Publisher/Subscriber’ model, it’s a very loose fit – there’s no middle-ware here handling message flow – it’s a bit bucket we poll for state changes every few milliseconds. Therefore, I hesitate to apply the terminology of ‘Publisher/Subscriber’ without a few grains of salt. Thy key point: VICE is decoupled from the consuming process(es).
Patching VICE
Remember where we compiled VICE on the command line? Even if a binary was available, the reason why I wanted to compile VICE was because I knew I’d be tinkering with the source code. To that end, I downloaded their SVN source and started searching through the code for key strings like ‘1541’, ‘drive’, ‘floppy’, etc. until I found drive.c and drive.h. I won’t include the whole pieces here, but this part is what I found particularly interesting – I wanted to find some place to initialize the shared memory, and some place to write to it so we can ‘publish’ our floppy drive status updates.
/* Initialize the hardware-level drive emulation (should be called at least
once before anything else). Return 0 on success, -1 on error. */
int drive_init(void)
{
unsigned int dnr;
drive_t *drive;
if (rom_loaded) {
return 0;
}
//..... More
}
Great! drive_init sounds like a good hook method – we don’t want to constantly be creating shared memory segments, create it once, but write to it many times. Here’s where I figured I’d place the ‘publishing’ code:
static void drive_led_update(drive_t *drive, drive_t *drive0)
{
int my_led_status = 0;
CLOCK led_period;
unsigned int led_pwm;
/* Actually update the LED status only if the `trap idle'
idling method is being used, as the LED status could be
incorrect otherwise. */
if (drive0->idling_method != DRIVE_IDLE_SKIP_CYCLES) {
my_led_status = drive->led_status;
}
//More.....
}
So now, we know the two spots that sound like they’d be great for initialization and continuous updates of our shared memory with drive status. Let’s look at the modifications necessary, then. Our goal is to allocate some shared memory (just enough to hold drive status for drive devices 8 through 11), and when VICE is updating it’s UI LED, we want to update shared memory with that status, a simple 1 or 0 toggle will suffice. That’s a pretty straightforward hook!
Modifications for drive.c
#include <fcntl.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <unistd.h>
const char *led_shm_name = "vice-drive-led-shm";
void *viceDriveLedShmPointer;
void init_drive_led_shm(void) {
int shm_fd;
const int SIZE = 16;
/* create the shared memory object */
shm_fd = shm_open(led_shm_name, O_CREAT | O_RDWR, 0666);
/* configure the size of the shared memory object */
ftruncate(shm_fd, SIZE);
/* memory map the shared memory object */
viceDriveLedShmPointer = mmap(0, SIZE, PROT_WRITE, MAP_SHARED, shm_fd, 0);
sprintf(viceDriveLedShmPointer, "%s", "80900010");
}
void update_shm_drive_status(unsigned int driveNumber,
int driveLedStatus) {
// Uncomment for some simple and extreme debugging
// log_warning(drive_log, "Hey! Drive %d Status : %d", driveNumber,
// driveLedStatus);
if (drive_init_was_called) {
((char*) viceDriveLedShmPointer)[(driveNumber * 2) + 1] = (
driveLedStatus == 0 ? '0' : '1');
}
}
// Call our initialization method from drive_init
init_drive_led_shm();
// Add this to update our status in the existing function drive_led_update
update_shm_drive_status(drive->mynumber, my_led_status);
Modifications to drive.h
extern void init_drive_led_shm(void);
extern void update_shm_drive_status(unsigned int driveNumber, int driveLedStatus);
Modifications for compilation
There’s not much else to this – all I needed to add (and this was a quite a bit of stumbling around on my part as I’m not comfortable or familiar with automake/autoconf) was adding the system libraries to work with shared memory to the configuration file configure.proto, around line 3316, before references to GFXOUTPUT_DRIVERS
dnl Patch to add -lrt for shared memory IPC work
echo "Patch to add -lrt for shared memory IPC work"
old_LIBS="$LIBS"
LIBS="$LIBS -lrt"
Github repo
Of course, you don’t have to copy these and paste them yourselves. I put a GitHub repo for my modifications here:
https://github.com/erkrystof/vice
You’ll want to check the tags for ‘pure VICE’ and tags for my modifications to see deltas or pull the version you desire.
When I wrote this – there was one tag that represented my modifications and VICE 3.4 combined:
git clone --single-branch --branch 3.4-Modded-1.0 https://github.com/erkrystof/vice
Running VICE with our patch
After running the same build commands as I did when installing basic VICE, executing the binary should have no discernible difference (unless you un-comment that log statement) in the logs for the way VICE handles.
However, you should now notice there’s a piece of shared memory you can see, and that you can read, given that everything in *nix is a file.
pi@vice-pi:/dev/shm $ ls -l
total 4
-rw-r--r-- 1 pi pi 16 Feb 9 09:16 vice-drive-led-shm
pi@vice-pi:/dev/shm $ cat vice-drive-led-shm
80900010
pi@vice-pi:/dev/shm $
Excellent, we now know it’s working! If I loop over that in an SSH console, just to see, and run VICE and load a floppy, you should see the second byte (0/1 – the drive 8 status LED) flip between 1 and 0.
for i in {1..10000}; do cat vice-drive-led-shm; echo ... Counter: $i; done
#Sample output:
80900010... Counter: 43
80900010... Counter: 44
80900010... Counter: 45
81900010... Counter: 46 <----- We have activity on drive 8
81900010... Counter: 47
81900010... Counter: 48
81900010... Counter: 49
Looking good so far. So VICE is essentially done – it’s ‘publishing’ drive status to an area in shared memory that we’ll now want to read from and drive the LEDs connected to the Raspberry Pi GPIO pins.
Reading the 1541 drive status with Python
We’re halfway there – the next step is to work on a separate process – our subscriber that reads the VICE drive status and sends commands to the Raspberry Pi’s GPIO pins to drive our LEDs. I could have done this in C, matching how we read shared memory that we’re writing to in VICE, but I wasn’t familiar with driving a Raspberry Pi’s GPIO pins from C. I am a bit more comfortable with the GPIO libraries available in Python, however, though the question of reading shared memory needed an answer.
Granted, I could probably just read from /dev/shm/vice-drive-led-shm
, but I really wanted to keep this semi-portable in the sense that I wanted to use the available shared-memory libraries instead of a known *nix-specific file behavior. After doing some research on Python and shared memory, it turns out we need a more recent version of Python than may come with your default Raspbian install – Python 3.8 or greater.
Python 3.8 or greater and RPi.GPIO libraries
For that installation, I’m going to divert away from this article to a separate one that’s not necessarily part of the series as reference if you need it – How to install Python 3.8 on the Raspberry Pi.
If you already have Python 3.8 or greater, you’ll just need to make sure you have the RPi GPIO library installed:
sudo python3.8 -m pip install RPi.GPIO
A simple Python client
The latest source code for the Python piece is available on GitHub, although I’ll go over this roughly below.
Initialization
Let’s look at the first chunk, primarily initialization and some structure:
powerLedsPin = 23
drive8ActivityPin = 27
drive9ActivityPin = 22
DRIVE_8_ACTIVITY_INDEX = 1
DRIVE_9_ACTIVITY_INDEX = 3
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.setup([powerLedsPin,drive8ActivityPin,drive9ActivityPin],
GPIO.OUT, initial=GPIO.HIGH)
My diorama has four ‘devices’ – two 1541 floppy drives, a Commodore keyboard, and a Commodore monitor. We use one pin for all green ‘power’ LEDs, since I didn’t feel like controlling power LEDs separately, it’s all the same output. For the floppy drive activity LEDs, however, I needed them controlled separately, so I created a drive 8 and drive 9 pin and reserved them to pins 27 and 22, respectively.
After that I’m setting up the state to be HIGH – initialize all LEDs as on.
Load the shared memory reference
Our primary code must first initialize the reference to shared memory:
try:
print ("Consumix Python version starting up.")
while (True):
try:
shm_vice = shared_memory.SharedMemory(name="vice-drive-led-shm", create=False, size=16)
break;
except Exception as e:
print (e)
print ("Error Trying to Open Shared Memory... Trying again in a bit.")
time.sleep(.5)
continue
print ("Shared memory found, let's read it.")
We’re using the same shared memory name we used in VICE, and we say ‘create=False’ since VICE is responsible for creating the shared memory object – we are just reading 16 bytes worth. In the case that the Python program starts before VICE, we catch the exception and try again in a little bit. I had to add that slightly fault tolerant try/catch loop as this Python client ends up staring before VICE when I startup my Raspberry Pi.
Read Shared, Write LED
Now that we’ve loaded our shared memory reference, we read from the shared memory, place it into a character array, and update the LEDs with a loop and a function:
while (True):
driveData = array.array('b',shm_vice.buf[:8])
driveDataString = driveData.tostring().decode("utf-8")
updateLEDLighting(driveDataString)
time.sleep(0.025)
#... more stuff after....
#update function for reference:
def updateLEDLighting(driveDataString):
if (driveDataString[DRIVE_8_ACTIVITY_INDEX] == '1'):
GPIO.output(drive8ActivityPin,GPIO.HIGH)
else:
GPIO.output(drive8ActivityPin,GPIO.LOW)
if (driveDataString[DRIVE_9_ACTIVITY_INDEX] == '1'):
GPIO.output(drive9ActivityPin,GPIO.HIGH)
else:
GPIO.output(drive9ActivityPin,GPIO.LOW)
It’s a pretty simple program overall – but it does the job. Since I only ended up wiring up two drives, I only read the drive 8 and 9 data, and ignore 10 and 11.
A note on Python shared memory
This isn’t as fault tolerant as I’d like though – if you quit your Python client, the shared memory reference seems to be destroyed – which completely baffles me. The creator of a resource is in charge of closing it, not some other process. So if VICE is creating the shared memory, I could understand if my Python client needs to open and close a reference, but not the shared memory itself.
Regardless, when I close my Python client, it destroys the shared memory object completely, even if I try to ‘close’ the reference ahead of time. Perhaps I’ve wired something up wrong here – but i think, based on some of these links referencing the shared-memory usage in Python 3.8, that I’m not entirely crazy.
https://bugs.python.org/issue38119
Now, I’m not really worried about this myself – but it could be an interesting read for others – as far as my Python client goes, I don’t need to restart it, so it shouldn’t start closing the shared memory VICE created on me. What I may need to do now and then is restart VICE, however, and this setup handles that just fine (my patched-VICE simply uses the existing shared-memory object and the Python client keeps reading happily).
The outcome
Load up VICE and the Python client, and watch your GPIO pins toggle high and low with drive activity. You can debug by tailing the shared memory object as we did above, you could use an oscilloscope or multimeter to watch some voltage changes on the GPIO pins, or you could do what I did – have some LEDs on the pins and watch them blink on and off – which is exactly what I’m looking for.