442 lines
12 KiB
C++
442 lines
12 KiB
C++
/*
|
|
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;
|
|
}
|