M5Cardputer-C64-Emulator/src/t-hmi-c64/C64.cpp

443 lines
12 KiB
C++
Raw Normal View History

2025-02-15 12:45:04 +01:00
/*
Copyright (C) 2024 iEle <melephas@gmail.com>
Copyright (C) 2024 retroelec <retroelec42@gmail.com>
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 3 of the License, or (at your
option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
For the complete text of the GNU General Public License see
http://www.gnu.org/licenses/.
*/
#include "C64.h"
#include "CPUC64.h"
#include "VIC.h"
#include "roms/charset.h"
#include <cstdint>
#include <esp_log.h>
#include "SDCard.h"
#include "loadactions.h"
#include "Keyboard.h"
#include <algorithm>
#include "LiteLED.h"
#define LED_TYPE LED_STRIP_SK6812
#define LED_TYPE_IS_RGBW 1
#define LED_BRIGHT 245
static const char *TAG = "C64";
static const uint16_t INTERRUPTSYSTEMRESOLUTION = 1000;
SDCard sdcard;
LiteLED led(LED_TYPE, LED_TYPE_IS_RGBW);
uint8_t *ram;
VIC vic;
CPUC64 cpu;
Keyboard keyboard;
SID sid;
SidRegPlayer player(&sid);
SidRegPlayerConfig sid_cfg;
const int BUFFER_SIZE = 4 * 882;
uint8_t audiobuffer[3][BUFFER_SIZE];
uint16_t checkForKeyboardCnt = 0;
uint8_t throttlingCnt = 0;
uint32_t numofburnedcyclespersecond = 0;
static const uint16_t kBasicPrgStart = 0x0801;
static const uint16_t kBasicTxtTab = 0x002b;
static const uint16_t kBasicVarTab = 0x002d;
static const uint16_t kBasicAryTab = 0x002f;
static const uint16_t kBasicStrEnd = 0x0031;
hw_timer_t *interruptProfiling = NULL;
hw_timer_t *interruptTOD = NULL;
hw_timer_t *interruptSystem = NULL;
TaskHandle_t cpuTask;
TaskHandle_t vicTask;
TaskHandle_t loadTask;
TaskHandle_t keyboardTask;
TaskHandle_t soundTask;
void IRAM_ATTR interruptProfilingFunc()
{
if (vic.cntRefreshs != 0)
{
ESP_LOGI(TAG, "fps: %d", vic.cntRefreshs);
}
vic.cntRefreshs = 0;
ESP_LOGI(TAG, "noc: %d, nbc: %d", cpu.numofcyclespersecond,
numofburnedcyclespersecond);
cpu.numofcyclespersecond = 0;
numofburnedcyclespersecond = 0;
}
bool updateTOD(CIA &cia)
{
uint8_t dc08 = cia.latchrundc08.load(std::memory_order_acquire);
dc08++;
if (dc08 > 9)
{
dc08 = 0;
uint8_t dc09 = cia.latchrundc09.load(std::memory_order_acquire);
uint8_t dc09one = dc09 & 15;
uint8_t dc09ten = dc09 >> 4;
dc09one++;
if (dc09one > 9)
{
dc09one = 0;
dc09ten++;
if (dc09ten > 5)
{
dc09ten = 0;
uint8_t dc0a = cia.latchrundc0a.load(std::memory_order_acquire);
uint8_t dc0aone = dc0a & 15;
uint8_t dc0aten = dc0a >> 4;
dc0aone++;
if (dc0aone > 9)
{
dc0aone = 0;
dc0aten++;
if (dc0aten > 5)
{
dc0aten = 0;
uint8_t dc0b = cia.latchrundc0b.load(std::memory_order_acquire);
uint8_t dc0bone = dc0b & 15;
uint8_t dc0bten = dc0b >> 4;
bool pm = dc0b & 128;
dc0bone++;
if (((dc0bten == 0) && (dc0bone > 9)) ||
(dc0bten && (dc0bone > 1)))
{
dc0bone = 0;
dc0bten++;
if (dc0bten > 1)
{
dc0bten = 0;
pm = !pm;
}
}
cia.latchrundc0b.store(dc0bone | (dc0bten << 4) | (pm ? 127 : 0),
std::memory_order_release);
}
}
cia.latchrundc0a.store(dc0aone | (dc0aten << 4),
std::memory_order_release);
}
}
cia.latchrundc09.store(dc09one | (dc09ten << 4), std::memory_order_release);
}
cia.latchrundc08.store(dc08, std::memory_order_release);
uint8_t alarmdc08 = cia.latchalarmdc08.load(std::memory_order_acquire);
if (dc08 == alarmdc08)
{
uint8_t dc09 = cia.latchrundc09.load(std::memory_order_acquire);
uint8_t alarmdc09 = cia.latchalarmdc09.load(std::memory_order_acquire);
if (dc09 == alarmdc09)
{
uint8_t dc0a = cia.latchrundc0a.load(std::memory_order_acquire);
uint8_t alarmdc0a = cia.latchalarmdc0a.load(std::memory_order_acquire);
if (dc0a == alarmdc0a)
{
uint8_t dc0b = cia.latchrundc0b.load(std::memory_order_acquire);
uint8_t alarmdc0b = cia.latchalarmdc0b.load(std::memory_order_acquire);
if (dc0b == alarmdc0b)
{
return true;
}
}
}
}
return false;
}
void IRAM_ATTR interruptTODFunc()
{
if (cpu.cia1.isTODRunning.load(std::memory_order_acquire))
{
if (updateTOD(cpu.cia1))
{
cpu.cia1.isAlarm.store(true, std::memory_order_release);
}
}
if (cpu.cia2.isTODRunning.load(std::memory_order_acquire))
{
if (updateTOD(cpu.cia2))
{
cpu.cia2.isAlarm.store(true, std::memory_order_release);
}
}
}
void IRAM_ATTR interruptSystemFunc()
{
checkForKeyboardCnt++;
if (checkForKeyboardCnt > 50)
{
checkForKeyboardCnt = 0;
}
// throttle 6502 CPU
throttlingCnt++;
uint16_t measuredcyclestmp =
cpu.measuredcycles.load(std::memory_order_acquire);
if (measuredcyclestmp > throttlingCnt * INTERRUPTSYSTEMRESOLUTION)
{
uint16_t adjustcycles =
measuredcyclestmp - throttlingCnt * INTERRUPTSYSTEMRESOLUTION;
cpu.adjustcycles.store(adjustcycles, std::memory_order_release);
numofburnedcyclespersecond += adjustcycles;
}
if (throttlingCnt == 50)
{
throttlingCnt = 0;
cpu.measuredcycles.store(0, std::memory_order_release);
}
}
void cpuCode(void *parameter)
{
cpu.run();
}
void vicRefresh(void *parameter)
{
while (true)
{
vic.refresh(cpu.refreshframecolor);
vTaskDelay(portTICK_PERIOD_MS);
}
}
void handleSound(void *parameter)
{
long micro = micros();
int i = 0;
while (true)
{
i++;
if (micros() - micro < player.getFramePeriod())
continue;
micro = micros();
size_t l = player.read(audiobuffer[i%3], player.getSamplesPerFrame());
M5Cardputer.Speaker.playRaw((int16_t*)audiobuffer[i%3], l*2, sid_cfg.samplerate, true, 1, -1, false);
}
}
void handleKeyboard(void *parameter)
{
while (true)
{
keyboard.handleKeyboard();
switch (keyboard.joystickMode())
{
case 0:
{
// keyboard
led.fill(rgb_from_values(255, 0, 0), true);
break;
}
case 1:
{
// j1
led.fill(rgb_from_values(0, 0, 255), true);
break;
}
case 2:
{
// j2
led.fill(rgb_from_values(0, 255, 0), true);
break;
}
case 3:
{
// keyboard + j1
led.fill(rgb_from_values(255, 0, 255), true);
break;
}
case 4:
{
// keyboard + j2
led.fill(rgb_from_values(255, 255, 0), true);
break;
}
}
vTaskDelay(50 * portTICK_PERIOD_MS);
}
}
void loadFile(void *parameter)
{
vTaskDelay(3000 * portTICK_PERIOD_MS);
std::string path = (const char *)parameter;
if (!path.empty())
{
ESP_LOGI(TAG, "load from sdcard... %s", path.c_str());
cpu.cpuhalted = true;
size_t pos = path.find_last_of('.');
std::string ext = path.substr(pos);
if (ext.compare(".bas") == 0)
{
cpu.cpuhalted = false;
std::string bas = sdcard.loadBas(SD_MMC, path.c_str());
for (char c : bas)
{
keyboard.typeCharacter(c);
vTaskDelay(20);
}
vTaskDelete(NULL);
return;
}
else if (ext.compare(".prg") == 0)
{
uint16_t addr = sdcard.loadFile(SD_MMC, path.c_str(), ram);
if (sdcard.loadAddr == 0)
{
ESP_LOGI(TAG, "error loading file");
}
else if (sdcard.loadAddr == kBasicPrgStart)
{
ram[kBasicTxtTab] = kBasicPrgStart;
ram[kBasicVarTab] = addr;
ram[kBasicAryTab] = addr;
ram[kBasicStrEnd] = addr;
for (char &c : std::string("RUN\n"))
keyboard.typeCharacter(c);
}
else
{
cpu.setPC(addr);
}
cpu.cpuhalted = false;
vTaskDelete(NULL);
return;
}
else
{
ESP_LOGI(TAG, "error init sdcard");
}
cpu.initMemAndRegs();
cpu.cpuhalted = false;
}
vTaskDelete(NULL);
return;
}
void C64::run(const std::string &path)
{
// allocate ram
ram = new uint8_t[1 << 16];
// init VIC
vic.init(ram, charset_rom);
player.setDefaultConfig(&sid_cfg);
player.begin(&sid_cfg);
keyboard.init();
// init CPU
cpu.init(ram, charset_rom, &vic, &keyboard, &player);
led.begin(21, 1);
led.brightness(64, true);
// start cpu task
xTaskCreatePinnedToCore(cpuCode, // Function to implement the task
"CPU", // Name of the task
10000, // Stack size in words
NULL, // Task input parameter
10, // Priority of the task
&cpuTask, // Task handle
0); // Core where the task should run
// interrupt each 1000 us to get keyboard codes and throttle 6502 CPU
interruptSystem = timerBegin(0, 80, true);
timerAttachInterrupt(interruptSystem, &interruptSystemFunc, false);
timerAlarmWrite(interruptSystem, INTERRUPTSYSTEMRESOLUTION, true);
timerAlarmEnable(interruptSystem);
// // profiling: interrupt each second
interruptProfiling = timerBegin(1, 80, true);
timerAttachInterrupt(interruptProfiling, &interruptProfilingFunc, false);
timerAlarmWrite(interruptProfiling, 1000000, true);
timerAlarmEnable(interruptProfiling);
// interrupt each 100 ms to increment CIA real time clock (TOD)
interruptTOD = timerBegin(2, 80, true);
timerAttachInterrupt(interruptTOD, &interruptTODFunc, false);
timerAlarmWrite(interruptTOD, 100000, true);
timerAlarmEnable(interruptTOD);
xTaskCreatePinnedToCore(vicRefresh, // Function to implement the task
"vicRefresh", // Name of the task
10000, // Stack size in words
NULL, // Task input parameter
10, // Priority of the task
&vicTask, // Task handle
1); // Core where the task should run
xTaskCreatePinnedToCore(loadFile, // Function to implement the task
"loadFile", // Name of the task
10000, // Stack size in words
(void *)path.c_str(), // Task input parameter
10, // Priority of the task
&loadTask, // Task handle
1); // Core where the task should run
xTaskCreatePinnedToCore(handleKeyboard, // Function to implement the task
"keyboard", // Name of the task
10000, // Stack size in words
NULL, // Task input parameter
10, // Priority of the task
&keyboardTask, // Task handle
0); // Core where the task should run
//xTaskCreatePinnedToCore(handleSound, // Function to implement the task
// "sound", // Name of the task
// 10000, // Stack size in words
// NULL, // Task input parameter
// 10, // Priority of the task
// &soundTask, // Task handle
// 1); // Core where the task should run
while (!keyboard.reset())
{
vTaskDelay(1000 * portTICK_PERIOD_MS);
}
vTaskDelete(cpuTask);
vTaskDelete(vicTask);
vTaskDelete(keyboardTask);
vTaskDelete(soundTask);
timerAlarmDisable(interruptProfiling);
timerAlarmDisable(interruptTOD);
timerAlarmDisable(interruptSystem);
timerDetachInterrupt(interruptProfiling);
timerDetachInterrupt(interruptTOD);
timerDetachInterrupt(interruptSystem);
timerEnd(interruptProfiling);
timerEnd(interruptTOD);
timerEnd(interruptSystem);
led.brightness(0, true);
player.stop();
delete ram;
}