Wie versprochen, das Projekt...
Kernelswitch+ Rev.3a
Ein kleiner Mod für die meisten C64 Modelle, ausprobiert habe ich Assy 250407, Assy 250425, Assy 260469
und mein Ultimate 64 Elite.
Mit diesem Mod kann das Kernal umgeschaltet werden, ohne Löcher ins Gehäuse zu bohren, zudem stehen
weitere Ein-, Ausgänge für andere Anwendungen zur Verfügung (daher das "+" im Namen).
Möglich wären z.B. eine Adressumschaltung für ein internes SDIEC oder das wechseln der Konfiguration bei
Einbauten wie dem FPGASID oder dem MixSID.
Ich verwende z.B. die zusätzlichen Ausgänge für eine LED-Beleuchtung bei meinem 250425 und dem U64.
Funktionsweise
Der Kernalswitch wird, je nach C64 Modell, direkt auf den Keyboard-Connector des Mainboards aufgesteckt
oder über ein 20 poliges Kabel angeschlossen. (Econ Connect PS20, bei Conrad ca. 10€, bei ELV ca. 2,50€)
Darüber wird der Kernalswitch sowohl versorgt, als auch diverse Tastendrücke erkannt. Allerdings wird nicht
die komplette Tastatur abgefragt, sondern nur 2 Reihen/5 Spalten.
Damit können die Tasten F1, F3, F5, F7, Space, Q, 2 und CBM erkannt werden, zusätzlich noch Restore, es
sollte aber möglich sein weitere Tasten zu erkennen, dazu müsste die Interrupt-Routine überarbeitet werden
und sicherlich einiges am Timing gemacht werden.
Ich verwende nur einge Tastenkombinationen: Restore, Restore-F1, Restore-F3, Restore-F5, Restore-F7 und
CBM.
Die Schaltung
Die gesamte Steuerung erfolgt über einen Arduino Nano, die Ausgänge für die Adressumschaltung (A13, A14)
und der Reset-Ausgang werden über einen 75LS07 gepuffert. (30V, 40mA).
Ein weiteres Gatter des 74LS07 übernimmt die Ansteuerung der Power-LED, die zusätzlich auf vorhanden ist,
das erleichtert den Einbau alter Boards (250407, 250425 etc) in das flache C64 Gehäuse.
Gatter Nr.5 löst einen Reset am C64 aus wenn der PRG-Jumper gesteckt ist.
Der 4066 dient im wesentlichen dazu die Taste F1, F3, F5 und F7 zu unterdrücken wenn Restore gedrückt ist,
zudem trennt ein weiteres Gate des 4066 Restore vom Arduino wenn der PRG-Jumper gesteckt ist.
Der PRG-Jumper dient dazu den Kernalswitch+ über den ISP-Header zu flashen ohne den Arduino entnehmen
zu müssen.
Die Software
Das ist mein allererstes Arduino Projekt, sicherlich gibt es noch Einiges zu verbessern, aber es läuft ganz gut.
Im Moment habe ich 2 Versionen die sich ein wenig unterscheiden:
Eine einfache Kernalumschaltung über die Tasten Restore-F1, Restore-F3, Restore-F5 und Restore-F7, ein
langer Druck auf Restore (3 sek.) löst einen Reset des C64 aus.
Getestet habe ich das Ganze mit einem 2364 Adapter und mit einen ReProm64, ich konnte keine Probleme
finden.
Für die Version mit der Kernalumschaltung sollte der Arduino mittels externem Programmer geflasht werden
um den Bootloader zu umgehen, sonst startet der Arduino zu langsam und es muss ein zusätzlicher Reset
beim Starten in den Programmcode eingefügt werden.
Wenn für diesen externen Programmer ein ISP-Header auf dem Arduino verlötet wird MUSS eine gewinkelte
Variante verwendet werden (siehe Bilder), andernfalls schliesst der Deckel des flachen C64 nicht mehr richtig!
Zusätzlich sind noch die Routinen zur Beleuchtungssteuerung enthalten, hier kann über Double-Tap auf CBM
die Lichtfarbe gewechelt werden.
Verwendet werden hier WS2812b LED Stripes mit 21 LED's, verwendet werden aber nur 20 davon, ferner ist
die Helligkeit der LED's begrenzt um die Stromversorgung nicht zu überlasten.
#include <CRC32.h> //https://github.com/bakercp/CRC32
#include <EEPROM.h>
#include <FastLED.h> //https://github.com/FastLED/FastLED
#include <TimerOne.h> //https://github.com/PaulStoffregen/TimerOne
#include <EasyButton.h> //https://github.com/evert-arias/EasyButton
//#include <U8x8lib.h> //https://github.com/olikraus/u8g2
//macros for fast pin access (https://www.best-microcontroller-projects.com)
//fixed tstPin macro and renamed to getPin
#define setPin(b) ((b)<8 ? PORTD |=(1<<(b)) : PORTB |=(1<<(b-8)))
#define clrPin(b) ((b)<8 ? PORTD &=~(1<<(b)) : PORTB &=~(1<<(b-8)))
#define getPin(b) ((b)<8 ? (PIND &(1<<(b)))!=0 : (PINB &(1<<(b-8)))!=0)
//Constants
#define NUM_LEDS 21 //Const - Number of LED's
#define DISABLE_LED 20 //Const - switch off last LED
#define WAIT_TIME 100 //Const - Delay for Reset
#define MAX_LEVEL 100 //Const - max. LED Level
#define MIN_LEVEL 1 //Const - min. LED Level
#define EEPROM_ADDR 0 //Memory - Configuration.
#define LED_PIN 13 //Output - Onboard LED/Power LED
#define ROW_PIN 10 //Output - Umschaltung Row 0/7
#define DATA_PIN 14 //Output - Pin for LED-Stripe
#define RESET_PIN 11 //Output - connected to C64 Reset
#define ADDR0_PIN 9 //Output - Addr. 0 Kernelswitch
#define ADDR1_PIN 8 //Output - Addr. 1 Kernelswitch
#define RESTORE_PIN 12 //Input - connected to Restore Key
//some volatile variables (interupt routines)
volatile byte scanCode = 0; //Scan Result
volatile byte maxLevel = MAX_LEVEL; //LED-Stripe, max. led level
volatile byte ledLevel = MIN_LEVEL; //LED-Stripe, min. led level
volatile CRGB ledColor = CRGB::Lime; //LED-Stripe, current Color (RGB)
byte myKernal = 0; //current Kernal
byte ledIndex = 0; //LED-Stripe, Colorindex
CRGB leds[NUM_LEDS]; //LED-Stripe, number of LED's
EasyButton Restore(RESTORE_PIN, 35, false, true);
#ifdef U8X8_HAVE_HW_I2C
U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(/* reset=*/ U8X8_PIN_NONE);
#endif
void setup() {
PORTB |= B00001100; //LED = LOW : RESTORE = NOPULLUP : RESET, ROW = HIGH : ADDR0, ADDR1 = LOW
DDRB |= B00101111; //LED = OUTPUT : RESTORE = INPUT : RESET, ROW, ADDR0, ADDR1 = OUTPUT
DDRD &= B00000011; //PA0, PA7, PB3, PB4, PB5, PB6 = INPUT : leave TxD, RxD alone
cli(); //Interrupts sperren
PCICR |= (1 << PCIE2);
PCMSK2 |= B00001100;
sei(); //Interrupts freigeben;
SetKernal(EEPROM.read(EEPROM_ADDR), false); //Set Kernal
Restore.begin(); Restore.onPressedFor(3000, onRestore);
FastLED.addLeds<WS2812B, DATA_PIN, RGB>(leds, NUM_LEDS);
Timer1.initialize(10000); Timer1.attachInterrupt(isrTimer);
SetColor(EEPROM.read(EEPROM_ADDR+1));
#ifdef U8X8_HAVE_HW_I2C
u8x8.begin();
u8x8.setPowerSave(0);
u8x8.setFont(u8x8_font_amstrad_cpc_extended_r);
UpdateLCD(myKernal);---
#endif
//Serial.begin(9600);
}
//asm inline utility (swap nibbles)
inline byte SWAP(byte val) {
__asm__ __volatile__ ( "swap %0 ": "+r"(val));
return val;
}
ISR(PCINT2_vect){
byte scan, cols;
static byte scantemp = 0;
scan = PIND & B11111100;
switch (scan & B00001100) {
case B00000000: //init scan sequence
setPin(ROW_PIN);
if (scantemp == 0) {
scanCode = 0;
} else (scantemp = 0);
break;
case B00001000: //Row0 (F7, F1, F3, F5)
//if (!(getPin(RESTORE_PIN))) {
if (Restore.isPressed()) {
clrPin(ROW_PIN);
}
cols = SWAP(scan & 0xF0);
if (cols != 0x0F) {
(scantemp = cols);
} else (scantemp = 0);
break;
case B00000100: //Row7 (2, Spc, CBM, Q)
cols = (scan & 0xF0);
if (cols != 0xF0) {
scantemp = (scantemp & 0x0F) | cols;
} else scantemp = (scantemp & 0x0F);
scanCode = scantemp;
break;
default: //other Row
break;
}
}
void isrTimer() {
static uint32_t ledCRC32 = 0;
ledLevel = min(ledLevel, maxLevel);
if (ledLevel < maxLevel) {
ledLevel++;
}
CRC32 crc;
for (int i = 0; i < NUM_LEDS; i++) {
if (i != DISABLE_LED) {
//swap green and red (GRB order)
leds[i] = CRGB(ledColor.green, ledColor.red, ledColor.blue);
} else leds[i] = CRGB::Black;
leds[i].maximizeBrightness(map(ledLevel, 0, 100, 0, 255));
crc.update(leds[i].red);
crc.update(leds[i].green);
crc.update(leds[i].blue);
}
uint32_t checksum = crc.finalize();
if (checksum != ledCRC32) {
ledCRC32 = checksum;
FastLED.show();
}
}
void loop() {
static byte lastCode = 1;
static uint32_t cbmtime = 0;
Restore.read();
noInterrupts();
byte keyCode = scanCode;
interrupts();
setPin(RESET_PIN);
if (lastCode != keyCode) {
switch (keyCode) {
case 0x0D: //F1
if (Restore.isPressed()) {
SetKernal(0, true);
}
break;
case 0x0B: //F3
if (Restore.isPressed()) {
SetKernal(1, true);
}
break;
case 0x07: //F5
if (Restore.isPressed()) {
SetKernal(2, true);
}
break;
case 0x0E: //F7
if (Restore.isPressed()) {
SetKernal(3, true);
}
break;
case 0xB0: //CBM
if (lastCode == 0) {
if ((millis() - cbmtime) < 500) {
ledIndex++;
if (ledIndex > 3) {
ledIndex = 0;
}
SetColor(ledIndex);
}
}
cbmtime = millis();
break;
}
lastCode = keyCode;
}
}
void wait(uint32_t period) {
uint32_t time_now = millis();
while(millis() < time_now + period){
//wait approx. [period] ms
}
}
void onRestore() {
SetKernal(myKernal, true);
}
#ifdef U8X8_HAVE_HW_I2C
void UpdateLCD(byte Kernal) {
u8x8.clearDisplay();
u8x8.setInverseFont(Kernal == 0);
u8x8.drawString(0, 0, "F1 CBM Kernal ");
u8x8.setInverseFont(Kernal == 1);
u8x8.drawString(0, 10, "F3 Jiffy DOS ");
u8x8.setInverseFont(Kernal == 2);
u8x8.drawString(0, 20, "F5 Dolphin DOS");
u8x8.setInverseFont(Kernal == 3);
u8x8.drawString(0, 30, "F7 Speed DOS+ ");
}
#endif
void SetKernal(byte Kernal, boolean Restart) {
if ((Kernal >= 0) && (Kernal <= 3)) {
myKernal = Kernal;
} else myKernal = 0;
if (Restart) {
clrPin(RESET_PIN);
wait(WAIT_TIME);
#ifdef U8X8_HAVE_HW_I2C
UpdateLCD(myKernal);
#endif
}
switch (myKernal) {
case 0:
clrPin(ADDR0_PIN);
clrPin(ADDR1_PIN);
break;
case 1:
setPin(ADDR0_PIN);
clrPin(ADDR1_PIN);
break;
case 2:
clrPin(ADDR0_PIN);
setPin(ADDR1_PIN);
break;
case 3:
setPin(ADDR0_PIN);
setPin(ADDR1_PIN);
break;
}
EEPROM.update(EEPROM_ADDR, myKernal);
}
void SetColor(byte Index) {
noInterrupts();
ledIndex = Index;
switch (ledIndex) {
case 0:
ledColor = CRGB::Lime;
maxLevel = 36; //max. Brightness (I < 100mA)
ledLevel = MIN_LEVEL;
break;
case 1:
ledColor = CRGB::Red;
maxLevel = 36; //max. Brightness (I < 100mA)
ledLevel = MIN_LEVEL;
break;
case 2:
ledColor = CRGB::DeepSkyBlue;
maxLevel = 21; //max. Brightness (I < 100mA)
ledLevel = MIN_LEVEL;
break;
case 3:
ledColor = CRGB::Black;
ledLevel = MAX_LEVEL;
break;
default:
ledIndex = 0;
break;
}
interrupts();
EEPROM.update(EEPROM_ADDR+1, ledIndex);
}
Alles anzeigen
Die zweite Version ist für die Verwendung mit einen Superkernal gedacht, die 3-Sek. Reset-Funktion ist auch
hier enthalten, die Kernalumschaltung über Restore F1..F7 entfällt natürlich.
Dafür wird über einen Double-Tab auf Restore das Superkernal Menü aufgerufen, dazu wird ein Doppel-Reset
ausgeführt.
Die Beleuchtungsteuerung bleibt wie oben.
#include <CRC32.h> //https://github.com/bakercp/CRC32
#include <EEPROM.h>
#include <FastLED.h> //https://github.com/FastLED/FastLED
#include <TimerOne.h> //https://github.com/PaulStoffregen/TimerOne
#include <EasyButton.h> //https://github.com/evert-arias/EasyButton
//macros for fast pin access (https://www.best-microcontroller-projects.com)
//fixed tstPin macro and renamed to getPin
#define setPin(b) ((b)<8 ? PORTD |=(1<<(b)) : PORTB |=(1<<(b-8)))
#define clrPin(b) ((b)<8 ? PORTD &=~(1<<(b)) : PORTB &=~(1<<(b-8)))
#define getPin(b) ((b)<8 ? (PIND &(1<<(b)))!=0 : (PINB &(1<<(b-8)))!=0)
//Constants
#define NUM_LEDS 21 //Const - Number of LED's
#define DISABLE_LED 20 //Const - switch off last LED
#define WAIT_TIME 80 //Const - Delay for Reset
#define MAX_LEVEL 100 //Const - max. LED Level
#define MIN_LEVEL 1 //Const - min. LED Level
#define EEPROM_ADDR 0 //Memory - Configuration.
#define LED_PIN 13 //Output - Onboard LED/Power LED
#define ROW_PIN 10 //Output - Umschaltung Row 0/7
#define DATA_PIN 14 //Output - Pin for LED-Stripe
#define RESET_PIN 11 //Output - connected to C64 Reset
#define ADDR0_PIN 9 //Output - Addr. 0 Kernelswitch
#define ADDR1_PIN 8 //Output - Addr. 1 Kernelswitch
#define RESTORE_PIN 12 //Input - connected to Restore Key
//some volatile variables (interupt routines)
volatile byte scanCode = 0; //Scan Result
volatile byte maxLevel = MAX_LEVEL; //LED-Stripe, max. led level
volatile byte ledLevel = MIN_LEVEL; //LED-Stripe, min. led level
volatile CRGB ledColor = CRGB::Lime; //LED-Stripe, current Color (RGB)
byte ledIndex = 0; //LED-Stripe, Colorindex
CRGB leds[NUM_LEDS]; //LED-Stripe, number of LED's
EasyButton Restore(RESTORE_PIN, 35, false, true);
void setup() {
PORTB |= B00001100; //LED = LOW : RESTORE = NOPULLUP : RESET, ROW = HIGH : ADDR0, ADDR1 = LOW
DDRB |= B00101111; //LED = OUTPUT : RESTORE = INPUT : RESET, ROW, ADDR0, ADDR1 = OUTPUT
DDRD &= B00000011; //PA0, PA7, PB3, PB4, PB5, PB6 = INPUT : leave TxD, RxD alone
cli(); //Interrupts sperren
PCICR |= (1 << PCIE2);
PCMSK2 |= B00001100;
sei(); //Interrupts freigeben;
Restore.begin();
Restore.onPressedFor(3000, onRestore);
Restore.onSequence(2, 500, onSuperkernal);
FastLED.addLeds<WS2812B, DATA_PIN, RGB>(leds, NUM_LEDS);
Timer1.initialize(50000); Timer1.attachInterrupt(isrTimer);
SetColor(EEPROM.read(EEPROM_ADDR+1));
//Serial.begin(9600);
}
//asm inline utility (swap nibbles)
inline byte SWAP(byte val) {
__asm__ __volatile__ ( "swap %0 ": "+r"(val));
return val;
}
ISR(PCINT2_vect){
byte scan, cols;
static byte scantemp = 0;
scan = PIND & B11111100;
switch (scan & B00001100) {
case B00000000: //init scan sequence
setPin(ROW_PIN);
if (scantemp == 0) {
scanCode = 0;
} else (scantemp = 0);
break;
case B00001000: //Row0 (F7, F1, F3, F5)
if (!(getPin(RESTORE_PIN))) {
clrPin(ROW_PIN);
}
cols = SWAP(scan & 0xF0);
if (cols != 0x0F) {
(scantemp = cols);
} else (scantemp = 0);
break;
case B00000100: //Row7 (2, Spc, CBM, Q)
cols = (scan & 0xF0);
if (cols != 0xF0) {
scantemp = (scantemp & 0x0F) | cols;
} else scantemp = (scantemp & 0x0F);
scanCode = scantemp;
break;
default: //other Row
break;
}
}
void isrTimer() {
static uint32_t ledCRC32 = 0;
ledLevel = min(ledLevel, maxLevel);
if (ledLevel < maxLevel) {
ledLevel++;
}
CRC32 crc;
for (int i = 0; i < NUM_LEDS; i++) {
if (i != DISABLE_LED) {
//swap green and red (GRB order)
leds[i] = CRGB(ledColor.green, ledColor.red, ledColor.blue);
} else leds[i] = CRGB::Black;
leds[i].maximizeBrightness(map(ledLevel, 0, 100, 0, 255));
crc.update(leds[i].red);
crc.update(leds[i].green);
crc.update(leds[i].blue);
}
uint32_t checksum = crc.finalize();
if (checksum != ledCRC32) {
ledCRC32 = checksum;
FastLED.show();
}
}
void loop() {
static byte lastCode = 1;
static uint32_t cbmtime = 0;
Restore.read();
noInterrupts();
byte keyCode = scanCode;
interrupts();
setPin(RESET_PIN);
if (lastCode != keyCode) {
switch (keyCode) {
case 0xB0: //CBM
if (lastCode == 0) {
if ((millis() - cbmtime) < 500) {
ledIndex++;
if (ledIndex > 3) {
ledIndex = 0;
}
SetColor(ledIndex);
}
}
cbmtime = millis();
break;
}
lastCode = keyCode;
}
}
void wait(uint32_t period) {
uint32_t time_now = millis();
while(millis() < time_now + period){
//wait approx. [period] ms
}
}
void onRestore() {
clrPin(RESET_PIN);
wait(WAIT_TIME);
}
void onSuperkernal() {
for (int i = 0; i < 2; i++) {
onRestore();
setPin(RESET_PIN);
wait(WAIT_TIME);
}
}
void SetColor(byte Index) {
noInterrupts();
ledIndex = Index;
switch (ledIndex) {
case 0:
ledColor = CRGB::Lime;
maxLevel = 36; //max. Brightness (I < 100mA)
ledLevel = MIN_LEVEL;
break;
case 1:
ledColor = CRGB::Red;
maxLevel = 36; //max. Brightness (I < 100mA)
ledLevel = MIN_LEVEL;
break;
case 2:
ledColor = CRGB::DeepSkyBlue;
maxLevel = 21; //max. Brightness (I < 100mA)
ledLevel = MIN_LEVEL;
break;
case 3:
ledColor = CRGB::Black;
ledLevel = MAX_LEVEL;
break;
default:
ledIndex = 0;
break;
}
interrupts();
EEPROM.update(EEPROM_ADDR+1, ledIndex);
}
Alles anzeigen
Bild 1, Assy 250407 mit Kernalswitch+ Rev. 3
Bitte melde dich an, um diesen Anhang zu sehen.
Bild 2, Assy 250425 mit Kernalswitch+ Rev. 3a
Bitte melde dich an, um diesen Anhang zu sehen.
Bild 3, Assy 250469 mit Kernalswitch+ Rev. 3a
Bitte melde dich an, um diesen Anhang zu sehen.
Kernalswitch+ Rev. 3 und Rev. 3a im Vergleich, elektrisch sind beide Revisionen identisch, lediglich die Position
des LED-Connectors und die Grösse der Platine unterscheiden sich.
Das war notwendig weil die ältere Version Rev. 3 nur schlecht in ein Assy 250469 passt.
Bitte melde dich an, um diesen Anhang zu sehen.
So, das war nun ein halbes Buch und ich hoffe ich habe nichts vergessen, ich habe alle benötigten Dateien
incl. der Gerber-Files in den Anhang gepackt.
Viel Spaß beim Nachbauen.
Mfg Jood