Tag Archives: C64

Retro stuff

A new “old” book, printed via Lulu

Plexiglass case for my collection of motherboards (8088,8086,386 and 486)
See this post:

A Ben Eater 6502 add-on, A bus monitor using ULN2803A.

Not all green/yellow wires are drawn. You can figure out where to add them.

I made these on a breadboard long ago, these will be made on permboards. (Like my Clock module on steroids.

ULN2803 will draw nearly no power from your bus, but they invert the signal, that’s why I rotated the ledbars and attached them to VCC.

UPDATE: Added other modules

Little Oled audio monitors.

UPDATE : Better waveform display

I wanted to make a SID monitor to display the 3 Channels of a C64 SID chip. This requires 3 SID chips, so this is a work-in-progress.

I bought several display I2C 128*32 in China, for cheap .. to have some stock .. So much for having stock.

But monitoring works! I’ve made 3 setups using a Wemos D1

Monitoring, scrolling monitoring and FFT analysis.

When mode selector using a button works, I’ll post the code.

UPDATE : New waveform display, better centered.

Meanwhile, practice SMD soldering.

Cynthcart (c64) midi control

I bought a teensyrom a while ago.

UPDATE 20250712 : Display
UPDATE 20250904 : Added PitchBend and stabilized potmeters.

Tyrone and I wanted to control the settings using potmeters.
So I grabbed a Teensy 4.1 controller and some 10K potmeters, and worte some code

Code for 12 pots, pitch bend and display, don’t forget to set your USB mode to midi!
Schematic soon, also all tweaks and note sending.

Todo: Nice 3D printed Pitch Bend wheel, rest of display code and extra buttons!

Let’s use an old box to hold the pots!

3D printed wooden knobs (Yes wood filament)
Unstable release pot due too bad wires. Cynthcart has no decay and hold (hold is how long you are pressing key)

Knob 1 = volume now, and left of box has centering Pitch Bend.


CODE

#include <MIDIUSB.h>
#include <ResponsiveAnalogRead.h>

const int pitchbend = A14;
const int filterresonance = A0;
const int filtercutoff = A1;
const int voicemode = A2;
const int modulationmode = A3;
const int attack = A4;
const int release = A5;
const int pulsewidth = A6;
const int tremolodepth = A7;
const int tremolospeed = A8;
const int oscwave = A9;
const int oscvoice2 = A10;
const int oscvoice3 = A11;
const int volume = A12;

ResponsiveAnalogRead analogpitchbend(pitchbend, true);

ResponsiveAnalogRead analogfilterresonance(filterresonance, true);
ResponsiveAnalogRead analogfiltercutoff(filtercutoff, true);
ResponsiveAnalogRead analogvoicemode(voicemode, true);
ResponsiveAnalogRead analogmodulationmode(modulationmode, true);
ResponsiveAnalogRead analogattack(attack, true);
ResponsiveAnalogRead analogrelease(release, true);
ResponsiveAnalogRead analogpulsewidth(pulsewidth, true);
ResponsiveAnalogRead analogtremolodepth(tremolodepth, true);
ResponsiveAnalogRead analogtremolospeed(tremolospeed, true);
ResponsiveAnalogRead analogoscwave(oscwave, true);
ResponsiveAnalogRead analogoscvoice2(oscvoice2, true);
ResponsiveAnalogRead analogoscvoice3(oscvoice3, true);
ResponsiveAnalogRead analogvolume(volume, true);


// A0 -> A3, A6 -> A13
const int numPots = 12;


int lastfilterresonance = -1;
int lastfiltercutoff = -1;
int lastvoicemode = -1;
int lastmodulationmode = -1;
int lastattack = -1;
int lastrelease = -1;
int lastpulsewidth = -1;
int lasttremolodepth = -1;
int lasttremolospeed = -1;
int lastoscwave = -1;
int lastoscvoice2 = -1;
int lastoscvoice3 = -1;
int lastvolume = -1;


// CC 7 ff niet

// Custom mappings:
const int potPins[numPots]     = {A0, A1, A2, A3, A6, A7, A8, A9, A10, A11, A12, A13};  // Analog pins
const int ccNumbers[numPots]   = {0,1,2,3,4,5,6,8,9,13,14,15};             // CC numbers
const int midiChannels[numPots]= {1,1,1,1,1,1,1,1,1,1,1,1};                // MIDI channels (1–16)

int lastValues[numPots];  // Store last values to reduce redundant MIDI messages


void setup() {
  Serial.begin(9600);
  for (int i = 0; i < numPots; i++) {
    pinMode(potPins[i], INPUT);
    lastValues[i] = -1;
  }
  pinMode(pitchbend, INPUT);
}

void loop() {
  analogfilterresonance.update();
  if(analogfilterresonance.hasChanged()) {
    int analogValue = analogfilterresonance.getValue();
    int midiValue = analogValue / 8;  // Scale 0–1023 to 0–127
    if (abs(midiValue - lastfilterresonance) > 0){
    usbMIDI.sendControlChange(7, midiValue, 1);
    lastfilterresonance = midiValue;
    }
  }

  analogfiltercutoff.update();
  if(analogfiltercutoff.hasChanged()) {
    int analogValue = analogfiltercutoff.getValue();
    int midiValue = analogValue / 8;  // Scale 0–1023 to 0–127
    if (abs(midiValue - lastvoicemode) > 0){
    usbMIDI.sendControlChange(1, midiValue, 1);
    lastvoicemode = midiValue;
    }  
  }

  analogvoicemode.update();
  if(analogvoicemode.hasChanged()) {
    int analogValue = analogvoicemode.getValue();
    int midiValue = analogValue / 8;  // Scale 0–1023 to 0–127
    if (abs(midiValue - lastvoicemode) > 0){
    usbMIDI.sendControlChange(2, midiValue, 1);
    lastvoicemode = midiValue;
    }
  }

  analogmodulationmode.update();
  if(analogmodulationmode.hasChanged()) {
    int analogValue = analogmodulationmode.getValue();
    int midiValue = analogValue / 8;  // Scale 0–1023 to 0–127
    if (abs(midiValue - lastmodulationmode) > 0){
    usbMIDI.sendControlChange(3, midiValue, 1);
    lastmodulationmode = midiValue;
    }
  }


  analogattack.update();
  if(analogattack.hasChanged()) {
    int analogValue = analogattack.getValue();
    int midiValue = analogValue / 8;  // Scale 0–1023 to 0–127
    if (abs(midiValue - lastattack) > 0){
    usbMIDI.sendControlChange(4, midiValue, 1);
    lastattack = midiValue;
    }
  }


  analogrelease.update();
  if(analogrelease.hasChanged()) {
    int analogValue = analogrelease.getValue();
    int midiValue = analogValue / 8;  // Scale 0–1023 to 0–127
    if (abs(midiValue - lastrelease) > 0){
    usbMIDI.sendControlChange(5, midiValue, 1);
    lastrelease = midiValue;
    }
  }


  analogpulsewidth.update();
  if(analogpulsewidth.hasChanged()) {
    int analogValue = analogpulsewidth.getValue();
    int midiValue = analogValue / 8;  // Scale 0–1023 to 0–127
    if (abs(midiValue - lastpulsewidth) > 0){
    usbMIDI.sendControlChange(6, midiValue, 1);
    lastpulsewidth = midiValue;
    }
  }


  analogtremolodepth.update();
  if(analogtremolodepth.hasChanged()) {
    int analogValue = analogtremolodepth.getValue();
    int midiValue = analogValue / 8;  // Scale 0–1023 to 0–127
    if (abs(midiValue - lasttremolodepth) > 0){
    usbMIDI.sendControlChange(8, midiValue, 1);
    lasttremolodepth = midiValue;
    }
  }


  analogtremolospeed.update();
  if(analogtremolospeed.hasChanged()) {
    int analogValue = analogtremolospeed.getValue();
    int midiValue = analogValue / 8;  // Scale 0–1023 to 0–127
    if (abs(midiValue - lasttremolospeed) > 0){
    usbMIDI.sendControlChange(9, midiValue, 1);
    lasttremolospeed = midiValue;
    }
  }


  analogoscwave.update();
  if(analogoscwave.hasChanged()) {
    int analogValue = analogoscwave.getValue();
    int midiValue = analogValue / 8;  // Scale 0–1023 to 0–127
    if (abs(midiValue - lastoscwave) > 0){
    usbMIDI.sendControlChange(13, midiValue, 1);
    lastoscwave = midiValue;
    }
  }


  analogoscvoice2.update();
  if(analogoscvoice2.hasChanged()) {
    int analogValue = analogoscvoice2.getValue();
    int midiValue = analogValue / 8;  // Scale 0–1023 to 0–127
    if (abs(midiValue - lastoscvoice2) > 0){
    usbMIDI.sendControlChange(14, midiValue, 1);
    lastoscvoice2 = midiValue;
    }
  }


  analogoscvoice3.update();
  if(analogoscvoice3.hasChanged()) {
    int analogValue = analogoscvoice3.getValue();
    int midiValue = analogValue / 8;  // Scale 0–1023 to 0–127
    if (abs(midiValue - lastoscvoice3) > 0){
    usbMIDI.sendControlChange(15, midiValue, 1);
    lastoscvoice3 = midiValue;
    }
  }







// PITCHBEND!
  analogpitchbend.update();
  if(analogpitchbend.hasChanged()) {
    int pitchBend = map(analogpitchbend.getValue(), 0, 1023, 0, 16383); // Map to MIDI Pitch Bend range
    sendPitchBend(pitchBend, 0);
  }
  /*
  int potValue = analogRead(A14);         // Read pot (0–1023)
  int pitchBend = map(potValue, 0, 1023, 0, 16383); // Map to MIDI Pitch Bend range
  //pitchBend = pitchBend + 8192;


  if (abs(pitchBend - lastPitch) > 250) { // Send only on significant change
    sendPitchBend(pitchBend, 0); 
        Serial.println(pitchBend);

    lastPitch = pitchBend;
  }
  */


  delay(5);  // CPU-friendly update rate
}

void sendPitchBend(int value, byte channel) {
  byte lsb = value & 0x7F;
  byte msb = (value >> 7) & 0x7F;

  midiEventPacket_t pitchBendPacket = {0x0E, 0xE0 | (channel & 0x0F), lsb, msb};
  MidiUSB.sendMIDI(pitchBendPacket);
  // Needed? 
  MidiUSB.flush();
}

CODE for display

#include <MIDIUSB.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET 4
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// A0 -> A3, A6 -> A13
const int numPots = 12;

// A14 - Pitch bend!
int lastPitch = -1;

//A4 A5 I2C display


// Custom mappings:
const int potPins[numPots]     = {A0, A1, A2, A3, A6, A7, A8, A9, A10, A11, A12, A13};  // Analog pins
const int ccNumbers[numPots]   = {0,1,2,3,4,5,19,7,8,9,13,14};             // CC numbers
const int midiChannels[numPots]= {1,1,1,1,1,1,1,1,1,1,1,1};                // MIDI channels (1–16)

int lastValues[numPots];  // Store last values to reduce redundant MIDI messages

void setup() {
  for (int i = 0; i < numPots; i++) {
    pinMode(potPins[i], INPUT);
    lastValues[i] = -1;
  }
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.clearDisplay();
  display.display();
}

void loop() {
  for (int i = 0; i < numPots; i++) {
    int analogValue = analogRead(potPins[i]);
    int midiValue = analogValue / 8;  // Scale 0–1023 to 0–127

    if (abs(midiValue - lastValues[i]) > 1) {
      usbMIDI.sendControlChange(ccNumbers[i], midiValue, midiChannels[i]);
      lastValues[i] = midiValue;
    }
  }
  int potValue = analogRead(A12);         // Read pot (0–1023)
  int pitchBend = map(potValue, 0, 1023, 0, 16383); // Map to MIDI Pitch Bend range

  if (abs(pitchBend - lastPitch) > 5) { // Send only on significant change
    sendPitchBend(pitchBend, 0); // Channel 1
    lastPitch = pitchBend;
  }

  displayInfo();


  delay(5);  // CPU-friendly update rate
}

void sendPitchBend(int value, byte channel) {
  byte lsb = value & 0x7F;
  byte msb = (value >> 7) & 0x7F;

  midiEventPacket_t pitchBendPacket = {0x0E, 0xE0 | (channel & 0x0F), lsb, msb};
  MidiUSB.sendMIDI(pitchBendPacket);
  // Needed? 
  MidiUSB.flush();
}

void displayInfo(){
 byte x0, y0, x1, y1;     // start/end coordinates for drawing lines on OLED
  display.clearDisplay();
  display.setCursor(0, 0);
  display.print("Attack / Release");

  // draw attack line
  x0 = 0;
  y0 = 63;
  x1 = map(attackParam, 0, 127, 0, ((SCREEN_WIDTH / 4) - 1));
  y1 = 20;
  display.drawLine(x0, y0,  x1,  y1, SH110X_WHITE);

   // draw release line
  x0 = x1;  // start line from previous line's final x,y location
  y0 = y1;
  x1 = x0 + map(releaseParam, 0, 127, 0, ((SCREEN_WIDTH / 4) - 1));
  y1 = 63;
  display.drawLine(x0, y0,  x1,  y1, SH110X_WHITE);

  display.display();

}

New C64 Cartridges

New cartridges stuff.
I’ve bought some in the past, and was gifted some.

Made a case for an own made 8085 cartridge.
In this case, I used a gifted PCB from Bigred (thanks).
This was designed by Venneker.

With a gap for a 40 pins socket to piggyback signals.

https://media.henriaanstoot.nl/c64-cartridge-gap.tgz

Then a new Nordic Replay, this is a new version of the Retro Replay.

Today the TeensyRom, this came without case, so I printed one in fancy dual silk color.

Demos and hacks soon

Lilygo PC, 68000 progress and C64 hacks

Made a micro PC using Fabgl library and a ESP32 LilyGo Vga

More info about this device

68000 Progress

My address decoder seems to work (using an ATF22v10C) See previous posts.

Also new Rom and Ram chips. These are 8 bits, but the 68000’s data bus we need two (Odd and Even Addresses)

C64 Hacks

I made a proof of concept for a Rom switcher.
8 Different Roms can be selected using the dip switches.
(Dipswitches are being replaces bij something smarter in the future, like an Arduino Nano (like Adrian Black’s solution)

My other C64 stopped working, I need to make my PLA replacement.
https://www.henriaanstoot.nl/2024/05/27/notes-for-next-projects-i-made-using-our-short-holiday-in-madeira/
But beside the PLA not working, I noticed the glass fuse was blown.
I didn’t have a large fuse (32mm) but a small one.
(Fast 1.6A and 230V)
So some fancy soldering and fixed.

Raster line with open borders to draw a flag

A fun experiment using opening the C64 border and changing colors on certain rasterlines.

Screenshot (only a little artefact on the lefthand side)

Code (acme)

acme borderflag.asm
x64 +drive8truedrive borderflag.prg

!cpu 650rasterline
!to "borderflag.prg",cbm


* = $0801
    !byte $0d,$08,$dc,$07,$9e,$20,$34
    !byte $39,$31,$35,$32,$00,$00,$00

* = $c000
        sei                     ; turn off interrupts

        ldx #1                  ; enable raster interrupts
        stx $d01a

        lda #<irq       	; set raster interrupt vector
        ldx #>irq
        sta $0314
        stx $0315

        ldy #$f0                ; set first interrupt rasterline
        sty $d012
        lda $d011               ; reset rasterline hi bit
        and #%01111111
        sta $d011

        asl $d019               ; ack VIC interrupts
        cli

loop_until_doomsday
        jmp loop_until_doomsday

irq
	asl $d019       	; ack irq

	lda #$01		; set screenframe and background
	sta $d020
	lda #$02
	sta $d021

	lda #$38        	; wait for line $38
	cmp $d012       	
	bne *-3

	lda #$02		; set screenframe and background
	sta $d020
	lda #$01
	sta $d021

	lda #$f9        	; wait for line $f9C
	cmp $d012       	; just below border in 25 row mode
	bne *-3

	lda $d011       	; switch to 24 row mode ($d011 bit 3 = 0)
	and #$f7        	; %11110111
	sta $d011

	lda #$fd        	; wait for line $fd
	cmp $d012       	; just below border in 25 row mode
	bne *-3

	lda $d011       	; switch back to 25 row mode ($d011 bit 3 = 1)
	ora #$08        	; %00001000
	sta $d011

	jmp $ea31		; exit irq

Writing C64 machinecode using Linux With Visual Studio, Kickass and C64Debugger

While still having a love-hate relationship with Visual Studio (I’m a Vim guy), here is my C64 coding setup. (for now)

Needed:
Vice (C64 emulator (and more))
C64 Debugger (embeds above in an awesome debugger)
Visual Studio
Kickass C64 assembler (you need java for this)
(http://theweb.dk/KickAssembler/Main.html)

Visual Studio click extensions and add kickass by Captain Jinx

https://sourceforge.net/projects/c64-debugger/
This is Commodore 64, Atari XL/XE and NES code and memory debugger that works in real time. It is quick prototyping tool where you can play with Commodore 64 machine and its internals.

I’ve got mostly installed in /data
Change accordingly

Create new file, press ctrl-shift-p and invoke kickass debug!

JiffyDos notes (work in progress)

JiffyDOS is an enhanced DOS for the C64. The software is programmed onto ROM chips that replace the Kernal ROM chip on the motherboard and the DOS ROM chip in the disk drive. JiffyDOS is intended to provide greater speed, commands and convenience than on stock systems.

The 1541 drive is a computer on its own, using a 6502 and VIA chips.
(See other pages) (C64 uses a 6510, that is the same slightly modified version of the 6502)
A cool example of the drive being an OS/computer on its own:
https://www.youtube.com/watch?v=zprSxCMlECA

Some notes:

  • I want to use a larger rom and using the higher address lines as kernal selector. Address line A13 and A14 can be used as selector
  • There is a schematic out there using runstop at boottime to do de selection of the rom part

Did I misspell kernel? NO
(Below from Wikipedia)

The KERNAL was known as kernel inside of Commodore since the PET days, but in 1980 Robert Russell misspelled the word as kernal in his notebooks. When Commodore technical writers Neil Harris and Andy Finkel collected Russell’s notes and used them as the basis for the VIC-20 programmer’s manual, the misspelling followed them along and stuck.

Original Kernal: 901227-03
8-kilobyte 2364 ROM 4K * 8 bits PROM

28C265 = 32K * 8bits

Diffference in ROM size AND there are some other pin placements.
V0.1

Romselect should be /(a15 * a14 * a13) depending on ram/rom switch.


SEL0SEL1
00rom0
01rom1
10rom2
11rom3

$E000-$FFFF – ROM
57344-65535

KERNAL ROM or RAM area (8192 bytes); depends on the value of bits #0-#2 of the processor port at memory address $0001
$FFFA-$FFFF – hardware vectors

%x0x: RAM area.

%x1x: KERNAL ROM.