Files
2023-03-13 09:05:51 +00:00

181 lines
5.5 KiB
C++

/* Arduino sketch that emulates a Commodore 64
Copyright (C) 2021 Doctor Volt
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.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "keyboard.h"
#include "cpu.h"
#include "char_rom.h"
// Change pins here, if necessary
#if defined ARDUINO_AVR_MEGA2560
#include "bitmaps.h"
#define SYN_DD_REG DDRE // Bit in PORT that is connected for sync
#define SYNCPIN PE4 // Sync pulse (Pin 2)
#define BLNKPIN PE5 // Blanking / Black level (Pin 3)
#else
#define SYN_DD_REG DDRD // Data Direction register
#define SYNCPIN PD2 // Sync pulse (Pin 2)
#define BLNKPIN PD3 // Blanking / Black level (Pin3)
#endif
//#define NTSC //uncomment this, if your (CRT) TV shows never the same color (NTSC). However with PAL the thing has better performance
#ifdef NTSC
#define HSYNC 126 // Hsync frequency (divided from Fcpu)
#define LINES 262 // Lines per field
#define START_L 41 // First computer line (Margin from top)
#else
#define HSYNC 127 // Hsync frequency (divided from Fcpu)
#define LINES 311 // Lines per field
#define START_L 69
#endif
#define CPL 40 // Characters per line
#define END_L START_L + 199
#define VSYNCLEN 8 // Length of VSYNC at start of frame
volatile byte VBE = 0; // Set this to 1 to let the CPU sleep while shifting out characters.
// This smoothens the image
#define DELAY500 asm("nop\n nop\n nop\n nop\n nop\n nop\n nop\n nop\n"); // Every NOP delays for 62.5ns
uint16_t scanline = 0;
uint32_t frame_nr = 0;
void cursor() // Emulate Cursor here for better performance
{
uint8_t &phase = sysram[0xCF]; // 0 if reversed, 1 if not reversed
uint8_t &cuc = sysram[0xCE]; // Character under cursor
uint16_t pos = ((sysram[0xD2] << 8) + sysram[0xD1] - VIDEOADDR) + sysram[0xD3];
phase ^= 1;
if (phase)
cuc = videomem[pos];
videomem[pos] = cuc | (phase << 7); // Invert it
}
ISR(TIMER0_COMPA_vect) // Raster interrupt. This is called for every scanline (64ms)
{
bitSet(SYN_DD_REG, BLNKPIN); // Start blanking interval (Front porch)
DELAY500;
DELAY500;
DELAY500; // Front porch
bitSet(SYN_DD_REG, SYNCPIN); // Start sync
scanline >= LINES ? scanline = 0 : scanline++;
if (scanline >= VSYNCLEN) // Hor. Sync interval
{
// Draw visible line
delayMicroseconds(4); // Length of Sync Pulse
bitClear(SYN_DD_REG, SYNCPIN); // End of sync / Start back Porch
delayMicroseconds(7); // Length of back Porch
bitClear(SYN_DD_REG, BLNKPIN); // End of back porch / Start visible part of line
if ((scanline >= START_L) && (scanline <= END_L)) // Screen area with characters
{
uint16_t line = scanline - START_L;
delayMicroseconds(2); // H-align
DELAY500;
#if defined ARDUINO_AVR_MEGA2560
uint16_t start = (line >> 3) * 320 + (line & 0x07);
uint8_t *p_bitmap = (uint8_t *)bitmaps[bnr] + start;
if (vic2.scr1 & 32)
{ // Hires bitmap mode
for (uint16_t p = 0; p < 320; p += 8)
{
UDR0 = pgm_read_byte(p_bitmap + p);
asm("nop\n");
}
sei();
asm("sleep\n");
}
else
#endif
{ // Text mode
uint16_t firstchr = (line >> 3) * CPL; // Index of first character
uint8_t register *p_vidmem = &videomem[firstchr]; // Pointer to first character in current line
for (uint16_t p = 0; p < CPL; p++)
{
UDR0 = pgm_read_byte_near(&charROM[line & 0x07][*(p_vidmem++)]);
// while (!(UCSR0A & _BV(UDRE0)));
}
}
//return;
}
else if (scanline == START_L - 1) // Last frame line before output
{
VBE = 1; // Sleep CPU
KBDISABLE();
}
else if (scanline == END_L + 1) // First frame line after output
{
VBE = 0;
KBENABLE();
}
}
else if (scanline == 1 && !sysram[0xCC]) // Cursor enabled
{
if (!sysram[0xCD]--)
{
cursor();
sysram[0xCD] = 20; // Cursor blink phase
}
}
}
void on_keypressed(uint8_t code) // Called when key pressed or released
{
if (code)
{
if (code == 3) // Stop (Esc) key
{
stop();
return;
}
sysram[0x277] = code;
sysram[0xC6] = 1;
scanline += 16; // Reduces flickering*/
}
else // Key released
scanline += 58; // Reduces flickering*/
}
void setup()
{
pinMode(13, OUTPUT); // Debug pin
TCCR0A = 0;
TCCR0B = 0;
OCR0A = HSYNC; // = (16MHz) / (15625*8) - 1
bitSet(TCCR0A, WGM01); // CTC mode
bitSet(TCCR0B, CS01); // clk/8
set_sleep_mode(SLEEP_MODE_IDLE);
UCSR0C = _BV(UMSEL00) | _BV(UMSEL01); // Set USART to SPI Mode
UBRR0 = 0; // 8 MHz pixel clock
bitSet(UCSR0B, TXEN0);
resetCPU();
while (videomem[0] != 32) // Boot the C64
execCPU();
//vic2.scr1 = 32; // Start with slide show (Mega only)
kb_init();
bitSet(TIMSK0, OCIE0A); // Start screen output
}
void loop()
{
while (VBE == 1) // Let CPU sleep between the scanlines
asm("sleep\n");
execCPU();
}