Added 1581 mode.
I tried to get burst mode working but couldn't.
This commit is contained in:
parent
6912ae1e80
commit
481a887e7c
25 changed files with 4767 additions and 446 deletions
6
Makefile
6
Makefile
|
@ -1,9 +1,9 @@
|
|||
OBJS = armc-start.o armc-cstartup.o armc-cstubs.o armc-cppstubs.o \
|
||||
exception.o main.o rpi-aux.o rpi-i2c.o rpi-mailbox-interface.o rpi-mailbox.o \
|
||||
rpi-gpio.o rpi-interrupts.o cache.o ff.o interrupt.o Keyboard.o performance.o \
|
||||
Pi1541.o DiskImage.o iec_bus.o iec_commands.o m6502.o m6522.o \
|
||||
Drive.o gcr.o prot.o lz.o emmc.o diskio.o options.o Screen.o SSD1306.o ScreenLCD.o \
|
||||
Timer.o FileBrowser.o DiskCaddy.o ROMs.o InputMappings.o xga_font_data.o
|
||||
Drive.o Pi1541.o DiskImage.o iec_bus.o iec_commands.o m6502.o m6522.o \
|
||||
gcr.o prot.o lz.o emmc.o diskio.o options.o Screen.o SSD1306.o ScreenLCD.o \
|
||||
Timer.o FileBrowser.o DiskCaddy.o ROMs.o InputMappings.o xga_font_data.o m8520.o wd177x.o Pi1581.o
|
||||
|
||||
SRCDIR = src
|
||||
OBJS := $(addprefix $(SRCDIR)/, $(OBJS))
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Pi1541
|
||||
|
||||
Commodore 1541 emulator for the Raspberry Pi
|
||||
Commodore 1541/1581 emulator for the Raspberry Pi
|
||||
|
||||
Pi1541 is a real-time, cycle exact, Commodore 1541 disk drive emulator that can run on a Raspberry Pi 3B (or 3B+). The software is free and I have endeavored to make the hardware as simple and inexpensive as possible.
|
||||
|
||||
|
|
|
@ -159,6 +159,9 @@ bool DiskCaddy::Insert(const FILINFO* fileInfo, bool readOnly)
|
|||
case DiskImage::NBZ:
|
||||
success = InsertNBZ(fileInfo, (unsigned char*)DiskImage::readBuffer, bytesRead, readOnly);
|
||||
break;
|
||||
case DiskImage::D81:
|
||||
success = InsertD81(fileInfo, (unsigned char*)DiskImage::readBuffer, bytesRead, readOnly);
|
||||
break;
|
||||
default:
|
||||
success = false;
|
||||
break;
|
||||
|
@ -234,6 +237,19 @@ bool DiskCaddy::InsertNBZ(const FILINFO* fileInfo, unsigned char* diskImageData,
|
|||
return false;
|
||||
}
|
||||
|
||||
bool DiskCaddy::InsertD81(const FILINFO* fileInfo, unsigned char* diskImageData, unsigned size, bool readOnly)
|
||||
{
|
||||
DiskImage diskImage;
|
||||
if (diskImage.OpenD81(fileInfo, diskImageData, size))
|
||||
{
|
||||
diskImage.SetReadOnly(readOnly);
|
||||
disks.push_back(diskImage);
|
||||
selectedIndex = disks.size() - 1;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void DiskCaddy::Display()
|
||||
{
|
||||
unsigned numberOfImages = GetNumberOfImages();
|
||||
|
@ -297,7 +313,7 @@ void DiskCaddy::ShowSelectedImage(u32 index)
|
|||
, numberOfImages
|
||||
, GetImage(index)->GetReadOnly() ? 'R' : ' '
|
||||
);
|
||||
screenLCD->PrintText(false, x, y, buffer, RGBA(0xff, 0xff, 0xff, 0xff), RGBA(0xff, 0xff, 0xff, 0xff));
|
||||
screenLCD->PrintText(false, x, y, buffer, 0, RGBA(0xff, 0xff, 0xff, 0xff));
|
||||
y += LCDFONTHEIGHT;
|
||||
|
||||
if (numberOfImages > numberOfDisplayedImages && index > numberOfDisplayedImages-1)
|
||||
|
@ -321,7 +337,7 @@ void DiskCaddy::ShowSelectedImage(u32 index)
|
|||
memset(buffer, ' ', screenLCD->Width()/screenLCD->GetFontWidth());
|
||||
screenLCD->PrintText(false, x, y, buffer, BkColour, BkColour);
|
||||
snprintf(buffer, 256, "%d %s", caddyIndex + 1, name);
|
||||
screenLCD->PrintText(false, x, y, buffer, RGBA(0xff, 0xff, 0xff, 0xff), caddyIndex == index ? RGBA(0xff, 0xff, 0xff, 0xff) : BkColour);
|
||||
screenLCD->PrintText(false, x, y, buffer, 0, caddyIndex == index ? RGBA(0xff, 0xff, 0xff, 0xff) : BkColour);
|
||||
y += LCDFONTHEIGHT;
|
||||
}
|
||||
if (y >= screenLCD->Height())
|
||||
|
|
|
@ -92,6 +92,7 @@ private:
|
|||
bool InsertG64(const FILINFO* fileInfo, unsigned char* diskImageData, unsigned size, bool readOnly);
|
||||
bool InsertNIB(const FILINFO* fileInfo, unsigned char* diskImageData, unsigned size, bool readOnly);
|
||||
bool InsertNBZ(const FILINFO* fileInfo, unsigned char* diskImageData, unsigned size, bool readOnly);
|
||||
bool InsertD81(const FILINFO* fileInfo, unsigned char* diskImageData, unsigned size, bool readOnly);
|
||||
|
||||
void ShowSelectedImage(u32 index);
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ extern "C"
|
|||
|
||||
unsigned char DiskImage::readBuffer[READBUFFER_SIZE];
|
||||
|
||||
static unsigned char compressionBuffer[HALF_TRACK_COUNT * NIB_TRACK_LENGTH];
|
||||
static unsigned char compressionBuffer[HALF_TRACK_COUNT * MAX_TRACK_LENGTH];
|
||||
|
||||
static const unsigned short SECTOR_LENGTH = 256;
|
||||
static const unsigned short SECTOR_LENGTH_WITH_CHECKSUM = 260;
|
||||
|
@ -40,6 +40,52 @@ static const unsigned char GCR_SYNC_BYTE = 0xff;
|
|||
static const unsigned char GCR_GAP_BYTE = 0x55;
|
||||
static const int SECTOR_HEADER_LENGTH = 8;
|
||||
static const unsigned MAX_D64_SIZE = 0x30000;
|
||||
static const unsigned MAX_D71_SIZE = 0x55600 + 1366;
|
||||
static const unsigned MAX_D81_SIZE = 822400;
|
||||
|
||||
// CRC-16-CCITT
|
||||
// CRC(x) = x^16 + x^12 + x^5 + x^0
|
||||
unsigned short DiskImage::CRC1021[256] =
|
||||
{
|
||||
0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7,0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef,
|
||||
0x1231,0x0210,0x3273,0x2252,0x52b5,0x4294,0x72f7,0x62d6,0x9339,0x8318,0xb37b,0xa35a,0xd3bd,0xc39c,0xf3ff,0xe3de,
|
||||
0x2462,0x3443,0x0420,0x1401,0x64e6,0x74c7,0x44a4,0x5485,0xa56a,0xb54b,0x8528,0x9509,0xe5ee,0xf5cf,0xc5ac,0xd58d,
|
||||
0x3653,0x2672,0x1611,0x0630,0x76d7,0x66f6,0x5695,0x46b4,0xb75b,0xa77a,0x9719,0x8738,0xf7df,0xe7fe,0xd79d,0xc7bc,
|
||||
0x48c4,0x58e5,0x6886,0x78a7,0x0840,0x1861,0x2802,0x3823,0xc9cc,0xd9ed,0xe98e,0xf9af,0x8948,0x9969,0xa90a,0xb92b,
|
||||
0x5af5,0x4ad4,0x7ab7,0x6a96,0x1a71,0x0a50,0x3a33,0x2a12,0xdbfd,0xcbdc,0xfbbf,0xeb9e,0x9b79,0x8b58,0xbb3b,0xab1a,
|
||||
0x6ca6,0x7c87,0x4ce4,0x5cc5,0x2c22,0x3c03,0x0c60,0x1c41,0xedae,0xfd8f,0xcdec,0xddcd,0xad2a,0xbd0b,0x8d68,0x9d49,
|
||||
0x7e97,0x6eb6,0x5ed5,0x4ef4,0x3e13,0x2e32,0x1e51,0x0e70,0xff9f,0xefbe,0xdfdd,0xcffc,0xbf1b,0xaf3a,0x9f59,0x8f78,
|
||||
0x9188,0x81a9,0xb1ca,0xa1eb,0xd10c,0xc12d,0xf14e,0xe16f,0x1080,0x00a1,0x30c2,0x20e3,0x5004,0x4025,0x7046,0x6067,
|
||||
0x83b9,0x9398,0xa3fb,0xb3da,0xc33d,0xd31c,0xe37f,0xf35e,0x02b1,0x1290,0x22f3,0x32d2,0x4235,0x5214,0x6277,0x7256,
|
||||
0xb5ea,0xa5cb,0x95a8,0x8589,0xf56e,0xe54f,0xd52c,0xc50d,0x34e2,0x24c3,0x14a0,0x0481,0x7466,0x6447,0x5424,0x4405,
|
||||
0xa7db,0xb7fa,0x8799,0x97b8,0xe75f,0xf77e,0xc71d,0xd73c,0x26d3,0x36f2,0x0691,0x16b0,0x6657,0x7676,0x4615,0x5634,
|
||||
0xd94c,0xc96d,0xf90e,0xe92f,0x99c8,0x89e9,0xb98a,0xa9ab,0x5844,0x4865,0x7806,0x6827,0x18c0,0x08e1,0x3882,0x28a3,
|
||||
0xcb7d,0xdb5c,0xeb3f,0xfb1e,0x8bf9,0x9bd8,0xabbb,0xbb9a,0x4a75,0x5a54,0x6a37,0x7a16,0x0af1,0x1ad0,0x2ab3,0x3a92,
|
||||
0xfd2e,0xed0f,0xdd6c,0xcd4d,0xbdaa,0xad8b,0x9de8,0x8dc9,0x7c26,0x6c07,0x5c64,0x4c45,0x3ca2,0x2c83,0x1ce0,0x0cc1,
|
||||
0xef1f,0xff3e,0xcf5d,0xdf7c,0xaf9b,0xbfba,0x8fd9,0x9ff8,0x6e17,0x7e36,0x4e55,0x5e74,0x2e93,0x3eb2,0x0ed1,0x1ef0
|
||||
};
|
||||
|
||||
void DiskImage::CRC(unsigned short& runningCRC, unsigned char data)
|
||||
{
|
||||
runningCRC = CRC1021[(runningCRC >> 8) ^ data] ^ (runningCRC << 8);
|
||||
}
|
||||
|
||||
void DiskImage::OutputD81HeaderByte(unsigned char*& dest, unsigned char data)
|
||||
{
|
||||
*dest++ = data;
|
||||
//crc = CRC1021[(crc >> 8) ^ byte] ^ (crc << 8);
|
||||
CRC(crc, data);
|
||||
}
|
||||
|
||||
void DiskImage::OutputD81DataByte(unsigned char*& src, unsigned char*& dest)
|
||||
{
|
||||
unsigned char data;
|
||||
data = *src++;
|
||||
*dest++ = data;
|
||||
//crc = CRC1021[(crc >> 8) ^ data] ^ (crc << 8);
|
||||
CRC(crc, data);
|
||||
}
|
||||
|
||||
|
||||
#define NIB_HEADER_SIZE 0xFF
|
||||
|
||||
|
@ -70,20 +116,31 @@ void DiskImage::Close()
|
|||
{
|
||||
case D64:
|
||||
CloseD64();
|
||||
memset(tracks, 0x55, sizeof(tracks));
|
||||
break;
|
||||
case G64:
|
||||
CloseG64();
|
||||
memset(tracks, 0x55, sizeof(tracks));
|
||||
break;
|
||||
case NIB:
|
||||
CloseNIB();
|
||||
memset(tracks, 0x55, sizeof(tracks));
|
||||
break;
|
||||
case NBZ:
|
||||
CloseNBZ();
|
||||
memset(tracks, 0x55, sizeof(tracks));
|
||||
break;
|
||||
case D71:
|
||||
CloseD71();
|
||||
memset(tracksD81, 0x55, sizeof(tracksD81));
|
||||
break;
|
||||
case D81:
|
||||
CloseD81();
|
||||
memset(tracksD81, 0, sizeof(tracksD81));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
memset(tracks, 0x55, sizeof(tracks));
|
||||
memset(trackLengths, 0, sizeof(trackLengths));
|
||||
diskType = NONE;
|
||||
fileInfo = 0;
|
||||
|
@ -205,7 +262,7 @@ bool DiskImage::WriteD64()
|
|||
|
||||
f_close(&fp);
|
||||
|
||||
f_utime(fileInfo->fname, fileInfo);
|
||||
//f_utime(fileInfo->fname, fileInfo);
|
||||
SetACTLed(false);
|
||||
|
||||
DEBUG_LOG("Converted %d blocks into D64 file\r\n", blocks_to_save);
|
||||
|
@ -229,6 +286,441 @@ void DiskImage::CloseD64()
|
|||
attachedImageSize = 0;
|
||||
}
|
||||
|
||||
bool DiskImage::OpenD71(const FILINFO* fileInfo, unsigned char* diskImage, unsigned size)
|
||||
{
|
||||
Close();
|
||||
|
||||
this->fileInfo = fileInfo;
|
||||
|
||||
unsigned offset = 0;
|
||||
|
||||
if (size > MAX_D71_SIZE)
|
||||
size = MAX_D71_SIZE;
|
||||
|
||||
attachedImageSize = size;
|
||||
|
||||
for (unsigned headIndex = 0; headIndex < 2; ++headIndex)
|
||||
{
|
||||
for (unsigned halfTrackIndex = 0; halfTrackIndex < D71_HALF_TRACK_COUNT; ++halfTrackIndex)
|
||||
{
|
||||
unsigned char track = (halfTrackIndex >> 1);
|
||||
unsigned char* dest = tracksD81[halfTrackIndex][headIndex];
|
||||
|
||||
trackLengths[halfTrackIndex] = SectorsPerTrack[track] * GCR_SECTOR_LENGTH;
|
||||
|
||||
if ((halfTrackIndex & 1) == 0)
|
||||
{
|
||||
if (offset < size) // This will allow for >35 tracks.
|
||||
{
|
||||
trackUsed[halfTrackIndex] = true;
|
||||
//DEBUG_LOG("Track %d used\r\n", halfTrackIndex);
|
||||
for (unsigned sectorNo = 0; sectorNo < SectorsPerTrack[track]; ++sectorNo)
|
||||
{
|
||||
convert_sector_to_GCR(diskImage + offset, dest, track + 1, sectorNo, diskImage + 0x165A2, 0);
|
||||
dest += 361;
|
||||
|
||||
offset += SECTOR_LENGTH;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
trackUsed[halfTrackIndex] = false;
|
||||
//DEBUG_LOG("Track %d not used\r\n", halfTrackIndex);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
trackUsed[halfTrackIndex] = false;
|
||||
//DEBUG_LOG("Track %d not used\r\n", halfTrackIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
diskType = D71;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DiskImage::WriteD71()
|
||||
{
|
||||
if (readOnly)
|
||||
return true;
|
||||
|
||||
//FIL fp;
|
||||
//FRESULT res = f_open(&fp, fileInfo->fname, FA_CREATE_ALWAYS | FA_WRITE);
|
||||
//if (res == FR_OK)
|
||||
//{
|
||||
// u32 bytesToWrite;
|
||||
// u32 bytesWritten;
|
||||
|
||||
// int track, sector;
|
||||
// BYTE id[3];
|
||||
// BYTE d71data[MAXBLOCKSONDISK * 256 * 2], *d71ptr;
|
||||
// int blocks_to_save = 0;
|
||||
|
||||
// DEBUG_LOG("Writing D71 file...\r\n");
|
||||
|
||||
// memset(d71data, 0, sizeof(d71data));
|
||||
|
||||
// if (!GetID(34, id, tracksD81[34][0]))
|
||||
// {
|
||||
// DEBUG_LOG("Cannot find directory sector.\r\n");
|
||||
// return false;
|
||||
// }
|
||||
// d71ptr = d71data;
|
||||
// //for (track = 0; track <= 40 * 2; track += 2)
|
||||
// for (track = 0; track <= 35 * 2; track += 2)
|
||||
// {
|
||||
// if (trackUsed[track])
|
||||
// {
|
||||
// //printf("Track %d\n", track);
|
||||
|
||||
// for (sector = 0; sector < SectorsPerTrack[track / 2]; sector++)
|
||||
// {
|
||||
// ConvertSector(track, sector, d71ptr, tracksD81[track][0]);
|
||||
// d71ptr += 256;
|
||||
// blocks_to_save++;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// bytesToWrite = blocks_to_save * 256;
|
||||
// SetACTLed(true);
|
||||
// if (f_write(&fp, d71data, bytesToWrite, &bytesWritten) != FR_OK || bytesToWrite != bytesWritten)
|
||||
// {
|
||||
// SetACTLed(false);
|
||||
// DEBUG_LOG("Cannot write d71 data.\r\n");
|
||||
// f_close(&fp);
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// f_close(&fp);
|
||||
|
||||
// //f_utime(fileInfo->fname, fileInfo);
|
||||
// SetACTLed(false);
|
||||
|
||||
// DEBUG_LOG("Converted %d blocks into D71 file\r\n", blocks_to_save);
|
||||
|
||||
// return true;
|
||||
//}
|
||||
//else
|
||||
{
|
||||
DEBUG_LOG("Failed to open %s for write\r\n", fileInfo->fname);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void DiskImage::CloseD71()
|
||||
{
|
||||
if (dirty)
|
||||
{
|
||||
WriteD71();
|
||||
dirty = false;
|
||||
}
|
||||
attachedImageSize = 0;
|
||||
}
|
||||
|
||||
bool DiskImage::OpenD81(const FILINFO* fileInfo, unsigned char* diskImage, unsigned size)
|
||||
{
|
||||
const unsigned physicalSectors = 10;
|
||||
unsigned char headIndex;
|
||||
unsigned headPos;
|
||||
|
||||
Close();
|
||||
|
||||
this->fileInfo = fileInfo;
|
||||
|
||||
unsigned offsetSource = 0;
|
||||
|
||||
if (size > MAX_D81_SIZE)
|
||||
size = MAX_D81_SIZE;
|
||||
|
||||
attachedImageSize = size;
|
||||
|
||||
unsigned char* src = diskImage;
|
||||
|
||||
for (unsigned trackIndex = 0; trackIndex < D81_TRACK_COUNT; ++trackIndex)
|
||||
{
|
||||
unsigned offsetDest = 0;
|
||||
unsigned index;
|
||||
|
||||
trackUsed[trackIndex] = true;
|
||||
memset(trackD81SyncBits[trackIndex][0], 0, MAX_TRACK_LENGTH >> 3);
|
||||
memset(trackD81SyncBits[trackIndex][1], 0, MAX_TRACK_LENGTH >> 3);
|
||||
//32x 4e
|
||||
// For 10 sectors
|
||||
// 12x 00 // SYNC
|
||||
// *3x a1
|
||||
// 1x fe // ID
|
||||
// 1x TrackNo (0 indexed)
|
||||
// 1x Head (0 = 1 1 = 0)
|
||||
// 1x sector (1 indexed)
|
||||
// 1x 02 (sector size)
|
||||
// 1x crc high byte
|
||||
// 1x crc low byte
|
||||
// 22x 4e (gap2)
|
||||
// For 2 loops (logical sectors to physical sectors)
|
||||
// if first loop
|
||||
// 12x 00 // SYNC
|
||||
// *3x a1
|
||||
// 1x fb // Data mark
|
||||
// endif
|
||||
// 256x data (indexed sequentailly through D81)
|
||||
// end for
|
||||
// 1x crc high byte
|
||||
// 1x crc low byte
|
||||
// 35x 4e (gap3)
|
||||
// end for
|
||||
|
||||
// 38/9/0 0x26(38) s9 h1 = 0x60000 27 11 - 27 16d
|
||||
// 38/10/0 0x26(38) s10 h1 = 0x60200 27 13 - 27 18d
|
||||
// 38/1/1 0x26(38) s1 h0 = 0x60400 27 15 - 27 20d
|
||||
// 38/2/2 0x26(38) s2 h0 = 0x60600 27 17 - 27 22d
|
||||
// 3, 4, 5, 6, 7, 8, 9, 10
|
||||
// 37/6/0 0x25(37) s6 h1 = 0x5d200 26 0b - 26 10d
|
||||
// 37/7/0 0x25(37) s6 h1 = 0x5d400 26 0d - 26 12d
|
||||
|
||||
// sectors 0-19 H1
|
||||
// sectors 20-39 H0
|
||||
|
||||
// 4d800 20 01
|
||||
// 4d900 20 02
|
||||
// 4da00 20 03
|
||||
// 4db00 20 04
|
||||
// 4dc00 20 05
|
||||
// 4dd00 20 06
|
||||
// 4de00 20 07
|
||||
|
||||
// 50000 21 01
|
||||
// 50100 21 02
|
||||
// 50200 21 03
|
||||
// 50300 21 04
|
||||
// 50400 21 05
|
||||
// 50500 21 06
|
||||
// 50600 21 07
|
||||
// 50700 21 08
|
||||
// 50800 21 09
|
||||
// 50900 21 0a
|
||||
// 50a00 21 0b
|
||||
// 50b00 21 0c
|
||||
// 50c00 21 0d
|
||||
// 50d00 21 0e
|
||||
// 50e00 21 0f
|
||||
// 50f00 21 10
|
||||
// 51000 21 11
|
||||
// 51100 21 12
|
||||
// 51200 21 13
|
||||
// 51300 21 14
|
||||
// 51400 21 15
|
||||
// 51500 21 16
|
||||
// 51600 21 17
|
||||
// 51700 21 18
|
||||
// 51800 21 19
|
||||
// 51900 21 1a
|
||||
// 51a00 21 1b
|
||||
// 51b00 21 1c
|
||||
// 51c00 21 1d
|
||||
// 51d00 21 1e
|
||||
// 51e00 21 1f
|
||||
// 51f00 21 20
|
||||
// 52000 21 21
|
||||
// 52100 21 22
|
||||
// 52200 21 23
|
||||
// 52300 21 24
|
||||
// 52400 21 25
|
||||
// 52500 21 26
|
||||
// 52600 21 27
|
||||
// 52700 20 00
|
||||
// 52800 22 01
|
||||
// 52900 22 02
|
||||
// 52a00 22 03
|
||||
// 52b00 22 04
|
||||
// 52c00 22 05
|
||||
// 52d00 22 06
|
||||
// 52e00 22 07
|
||||
// 52f00 22 08
|
||||
// ..
|
||||
// 54200 22 1b
|
||||
// ..
|
||||
// 54d00 22 26
|
||||
// 54e00 22 27 - 22 38d
|
||||
// 54f00 21 00 - 22 39d
|
||||
// 55000 23 01
|
||||
|
||||
unsigned int physicalSectorIndex;
|
||||
|
||||
// (sectors 20 - 39 are on physical side 2)
|
||||
for (headIndex = 0; headIndex < 2; ++headIndex)
|
||||
{
|
||||
unsigned char* dest = tracksD81[trackIndex][headIndex];
|
||||
memset(dest, 0x4e, 32); dest += 32;
|
||||
for (physicalSectorIndex = 0; physicalSectorIndex < physicalSectors; ++physicalSectorIndex)
|
||||
{
|
||||
// If a sequence of zeros followed by a sequence of three Sync Bytes is found, then the PLL(phase locked loop) and data separator are synchronized and data bytes can be read.
|
||||
|
||||
memset(dest, 0, 12); dest += 12; // SYNC - This sequence provides to the DPLL enough time to adjust the frequency and center the inspection window.
|
||||
|
||||
headPos = dest - tracksD81[trackIndex][headIndex];
|
||||
SetD81SyncBit(trackIndex, headIndex, headPos++, true);
|
||||
SetD81SyncBit(trackIndex, headIndex, headPos++, true);
|
||||
SetD81SyncBit(trackIndex, headIndex, headPos++, true);
|
||||
|
||||
// The CRC includes all information starting with the address mark and up to the CRC characters.
|
||||
// The CRC Register is preset to ones.
|
||||
crc = 0xffff;
|
||||
|
||||
OutputD81HeaderByte(dest, 0xa1); // Special bytes are encoded that violates the MFM encoding rules with a missing clock in one of the sequential zero bits.
|
||||
OutputD81HeaderByte(dest, 0xa1);
|
||||
OutputD81HeaderByte(dest, 0xa1);
|
||||
OutputD81HeaderByte(dest, 0xfe); // Header ID
|
||||
OutputD81HeaderByte(dest, (unsigned char)trackIndex); // 0 indexed
|
||||
OutputD81HeaderByte(dest, headIndex);
|
||||
OutputD81HeaderByte(dest, (unsigned char)physicalSectorIndex + 1); // 1 indexed
|
||||
OutputD81HeaderByte(dest, 2); // sector length code (0=128, 1=256, 2=512, 3=1024)
|
||||
*dest++ = (unsigned char)(crc >> 8);
|
||||
*dest++ = (unsigned char)(crc & 0xff);
|
||||
memset(dest, 0x4e, 22); dest += 22;
|
||||
|
||||
memset(dest, 0, 12); dest += 12; // SYNC
|
||||
|
||||
headPos = dest - tracksD81[trackIndex][headIndex];
|
||||
SetD81SyncBit(trackIndex, headIndex, headPos++, true);
|
||||
SetD81SyncBit(trackIndex, headIndex, headPos++, true);
|
||||
SetD81SyncBit(trackIndex, headIndex, headPos++, true);
|
||||
|
||||
// The CRC Register is preset to ones.
|
||||
crc = 0xffff;
|
||||
OutputD81HeaderByte(dest, 0xa1);
|
||||
OutputD81HeaderByte(dest, 0xa1);
|
||||
OutputD81HeaderByte(dest, 0xa1);
|
||||
OutputD81HeaderByte(dest, 0xfb); // Data ID
|
||||
|
||||
for (index = 0; index < D81_SECTOR_LENGTH; ++index)
|
||||
{
|
||||
OutputD81DataByte(src, dest);
|
||||
}
|
||||
|
||||
*dest++ = (unsigned char)(crc >> 8);
|
||||
*dest++ = (unsigned char)(crc & 0xff);
|
||||
|
||||
memset(dest, 0x4e, 35); dest += 35;
|
||||
}
|
||||
|
||||
trackLengths[trackIndex] = dest - tracksD81[trackIndex][headIndex];
|
||||
}
|
||||
}
|
||||
|
||||
diskType = D81;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DiskImage::WriteD81()
|
||||
{
|
||||
const unsigned physicalSectors = 10;
|
||||
|
||||
if (readOnly)
|
||||
return true;
|
||||
|
||||
FIL fp;
|
||||
FRESULT res = f_open(&fp, fileInfo->fname, FA_CREATE_ALWAYS | FA_WRITE);
|
||||
if (res == FR_OK)
|
||||
{
|
||||
u32 bytesToWrite;
|
||||
u32 bytesWritten;
|
||||
|
||||
for (unsigned trackIndex = 0; trackIndex < D81_TRACK_COUNT; ++trackIndex)
|
||||
{
|
||||
unsigned offsetDest = 0;
|
||||
unsigned index;
|
||||
|
||||
if (trackLengths[trackIndex] != 0 && trackUsed[trackIndex])
|
||||
{
|
||||
unsigned int physicalSectorIndex;
|
||||
|
||||
// (sectors 20 - 39 are on physical side 2)
|
||||
for (unsigned headIndex = 0; headIndex < 2; ++headIndex)
|
||||
{
|
||||
unsigned char* src = tracksD81[trackIndex][headIndex];
|
||||
src += 32;
|
||||
for (physicalSectorIndex = 0; physicalSectorIndex < physicalSectors; ++physicalSectorIndex)
|
||||
{
|
||||
// If a sequence of zeros followed by a sequence of three Sync Bytes is found, then the PLL(phase locked loop) and data separator are synchronized and data bytes can be read.
|
||||
|
||||
src += 12; // 12x00 SYNC - This sequence provides to the DPLL enough time to adjust the frequency and center the inspection window.
|
||||
src += 3; // 3xA1
|
||||
src += 1; // 1xFE header ID
|
||||
src += 1; // 1x track index
|
||||
src += 1; // 1x head index
|
||||
src += 1; // 1x physical sector index
|
||||
src += 1; // 1x sector length code
|
||||
src += 1; // 1x crc high
|
||||
src += 1; // 1x crc low
|
||||
src += 22; // 22x4e
|
||||
|
||||
src += 12; // 12x00 SYNC
|
||||
src += 3; // 3xA1
|
||||
src += 1; // 1xFB header ID
|
||||
|
||||
SetACTLed(true);
|
||||
bytesToWrite = D81_SECTOR_LENGTH;
|
||||
if (f_write(&fp, src, bytesToWrite, &bytesWritten) != FR_OK || bytesToWrite != bytesWritten)
|
||||
{
|
||||
SetACTLed(false);
|
||||
f_close(&fp);
|
||||
return false;
|
||||
}
|
||||
src += D81_SECTOR_LENGTH;
|
||||
SetACTLed(false);
|
||||
|
||||
src += 1; // 1x crc high
|
||||
src += 1; // 1x crc low
|
||||
src += 35; // 35x4e
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const unsigned trackLength = physicalSectors * 2 * D81_SECTOR_LENGTH;
|
||||
|
||||
SetACTLed(true);
|
||||
for (index = 0; index < trackLength; ++index)
|
||||
{
|
||||
unsigned char zero = 0;
|
||||
bytesToWrite = 1;
|
||||
if (f_write(&fp, &zero, bytesToWrite, &bytesWritten) != FR_OK || bytesToWrite != bytesWritten)
|
||||
{
|
||||
SetACTLed(false);
|
||||
f_close(&fp);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
SetACTLed(false);
|
||||
}
|
||||
}
|
||||
|
||||
f_close(&fp);
|
||||
|
||||
//f_utime(fileInfo->fname, fileInfo);
|
||||
SetACTLed(false);
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_LOG("Failed to open %s for write\r\n", fileInfo->fname);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void DiskImage::CloseD81()
|
||||
{
|
||||
if (dirty)
|
||||
{
|
||||
WriteD81();
|
||||
dirty = false;
|
||||
}
|
||||
attachedImageSize = 0;
|
||||
}
|
||||
|
||||
bool DiskImage::OpenG64(const FILINFO* fileInfo, unsigned char* diskImage, unsigned size)
|
||||
{
|
||||
Close();
|
||||
|
@ -314,10 +806,10 @@ bool DiskImage::WriteG64()
|
|||
BYTE header[12];
|
||||
DWORD gcr_track_p[MAX_HALFTRACKS_1541] = { 0 };
|
||||
DWORD gcr_speed_p[MAX_HALFTRACKS_1541] = { 0 };
|
||||
BYTE gcr_track[NIB_TRACK_LENGTH + 2];
|
||||
BYTE gcr_track[MAX_TRACK_LENGTH + 2];
|
||||
size_t track_len;
|
||||
int index = 0, track;
|
||||
BYTE buffer[NIB_TRACK_LENGTH], tempfillbyte;
|
||||
BYTE buffer[MAX_TRACK_LENGTH], tempfillbyte;
|
||||
|
||||
DEBUG_LOG("Writing G64 file...\r\n");
|
||||
//DEBUG_LOG("G64 Track Length = %d", G64_TRACK_MAXLEN);
|
||||
|
@ -499,7 +991,7 @@ bool DiskImage::WriteNIB()
|
|||
else
|
||||
{
|
||||
bytesToWrite = NIB_TRACK_LENGTH;
|
||||
for (track = 0; track < (MAX_TRACKS_1541 * 2); ++track)
|
||||
for (track = 0; track < HALF_TRACK_COUNT; ++track)
|
||||
{
|
||||
if (trackUsed[track])
|
||||
{
|
||||
|
@ -635,6 +1127,8 @@ DiskImage::DiskType DiskImage::GetDiskImageTypeViaExtention(const char* diskImag
|
|||
return D64;
|
||||
else if (toupper((char)ext[1]) == 'L' && toupper((char)ext[2]) == 'S' && toupper((char)ext[3]) == 'T')
|
||||
return LST;
|
||||
else if (toupper((char)ext[1]) == 'D' && ext[2] == '8' && ext[3] == '1')
|
||||
return D81;
|
||||
}
|
||||
return NONE;
|
||||
}
|
||||
|
@ -644,6 +1138,30 @@ bool DiskImage::IsDiskImageExtention(const char* diskImageName)
|
|||
return GetDiskImageTypeViaExtention(diskImageName) != NONE;
|
||||
}
|
||||
|
||||
bool DiskImage::IsDiskImageD81Extention(const char* diskImageName)
|
||||
{
|
||||
char* ext = strrchr((char*)diskImageName, '.');
|
||||
|
||||
if (ext)
|
||||
{
|
||||
if (toupper((char)ext[1]) == 'D' && ext[2] == '8' && ext[3] == '1')
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DiskImage::IsDiskImageD71Extention(const char* diskImageName)
|
||||
{
|
||||
char* ext = strrchr((char*)diskImageName, '.');
|
||||
|
||||
if (ext)
|
||||
{
|
||||
if (toupper((char)ext[1]) == 'D' && ext[2] == '7' && ext[3] == '1')
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DiskImage::IsLSTExtention(const char* diskImageName)
|
||||
{
|
||||
char* ext = strrchr((char*)diskImageName, '.');
|
||||
|
@ -751,7 +1269,7 @@ int DiskImage::FindSync(unsigned track, int bitIndex, int maxBits, int* syncStar
|
|||
else
|
||||
{
|
||||
bitIndex++;
|
||||
if (bitIndex >= NIB_TRACK_LENGTH * 8)
|
||||
if (bitIndex >= MAX_TRACK_LENGTH * 8)
|
||||
bitIndex = 0;
|
||||
byte = tracks[track][bitIndex >> 3];
|
||||
}
|
||||
|
|
|
@ -21,8 +21,9 @@
|
|||
#include "types.h"
|
||||
#include "ff.h"
|
||||
|
||||
#define READBUFFER_SIZE 1024 * 512
|
||||
#define READBUFFER_SIZE 1024 * 512 * 2 // Now need over 800K for D81s
|
||||
|
||||
#define MAX_TRACK_LENGTH 0x2000
|
||||
#define NIB_TRACK_LENGTH 0x2000
|
||||
|
||||
#define BAM_OFFSET 4
|
||||
|
@ -35,6 +36,8 @@
|
|||
#define DIR_ENTRY_NAME_LENGTH 18-2
|
||||
|
||||
static const unsigned char HALF_TRACK_COUNT = 84;
|
||||
static const unsigned char D71_HALF_TRACK_COUNT = 70;
|
||||
static const unsigned char D81_TRACK_COUNT = 80;
|
||||
static const unsigned short GCR_SYNC_LENGTH = 5;
|
||||
static const unsigned short GCR_HEADER_LENGTH = 10;
|
||||
static const unsigned short GCR_HEADER_GAP_LENGTH = 8;
|
||||
|
@ -44,6 +47,8 @@ static const unsigned short GCR_SECTOR_LENGTH = GCR_SYNC_LENGTH + GCR_HEADER_LEN
|
|||
|
||||
static const unsigned short G64_MAX_TRACK_LENGTH = 7928;
|
||||
|
||||
static const unsigned short D81_SECTOR_LENGTH = 512;
|
||||
|
||||
class DiskImage
|
||||
{
|
||||
public:
|
||||
|
@ -55,6 +60,8 @@ public:
|
|||
NIB,
|
||||
NBZ,
|
||||
LST,
|
||||
D71,
|
||||
D81,
|
||||
RAW
|
||||
};
|
||||
|
||||
|
@ -64,6 +71,8 @@ public:
|
|||
bool OpenG64(const FILINFO* fileInfo, unsigned char* diskImage, unsigned size);
|
||||
bool OpenNIB(const FILINFO* fileInfo, unsigned char* diskImage, unsigned size);
|
||||
bool OpenNBZ(const FILINFO* fileInfo, unsigned char* diskImage, unsigned size);
|
||||
bool OpenD71(const FILINFO* fileInfo, unsigned char* diskImage, unsigned size);
|
||||
bool OpenD81(const FILINFO* fileInfo, unsigned char* diskImage, unsigned size);
|
||||
|
||||
void Close();
|
||||
|
||||
|
@ -71,8 +80,8 @@ public:
|
|||
|
||||
inline bool GetNextBit(u32 track, u32 byte, u32 bit)
|
||||
{
|
||||
if (attachedImageSize == 0)
|
||||
return 0;
|
||||
//if (attachedImageSize == 0)
|
||||
// return 0;
|
||||
|
||||
return ((tracks[track][byte] >> bit) & 1) != 0;
|
||||
}
|
||||
|
@ -103,9 +112,65 @@ public:
|
|||
const char* GetName() { return fileInfo->fname; }
|
||||
|
||||
inline unsigned BitsInTrack(unsigned track) const { return trackLengths[track] << 3; }
|
||||
inline unsigned TrackLength(unsigned track) const { return trackLengths[track]; }
|
||||
|
||||
inline bool IsD81() const { return diskType == D81; }
|
||||
inline unsigned char GetD81Byte(unsigned track, unsigned headIndex, unsigned headPos) const { return tracksD81[track][headIndex][headPos]; }
|
||||
inline void SetD81Byte(unsigned track, unsigned headIndex, unsigned headPos, unsigned char data)
|
||||
{
|
||||
//unsigned headDataOffset;
|
||||
//if (headPos > 0)
|
||||
// headDataOffset = headPos - 1;
|
||||
//else
|
||||
// headDataOffset = trackLengths[track] - 1;
|
||||
//if (tracksD81[track][headIndex][headDataOffset] != data)
|
||||
//{
|
||||
// tracksD81[track][headIndex][headDataOffset] = data;
|
||||
// trackDirty[track] = true;
|
||||
// trackUsed[track] = true;
|
||||
// dirty = true;
|
||||
//}
|
||||
|
||||
if (tracksD81[track][headIndex][headPos] != data)
|
||||
{
|
||||
tracksD81[track][headIndex][headPos] = data;
|
||||
trackDirty[track] = true;
|
||||
trackUsed[track] = true;
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
//unsigned headDataOffset;
|
||||
//if (headPos < trackLengths[track])
|
||||
// headDataOffset = headPos + 1;
|
||||
//else
|
||||
// headDataOffset = 0;
|
||||
//if (tracksD81[track][headIndex][headDataOffset] != data)
|
||||
//{
|
||||
// tracksD81[track][headIndex][headDataOffset] = data;
|
||||
// trackDirty[track] = true;
|
||||
// trackUsed[track] = true;
|
||||
// dirty = true;
|
||||
//}
|
||||
|
||||
}
|
||||
|
||||
inline bool IsD81ByteASync(unsigned track, unsigned headIndex, unsigned headPos) const
|
||||
{
|
||||
return (trackD81SyncBits[track][headIndex][headPos >> 3] & (1 << (headPos & 7))) != 0;
|
||||
}
|
||||
inline void SetD81SyncBit(unsigned track, unsigned headIndex, unsigned headPos, bool sync)
|
||||
{
|
||||
if (sync)
|
||||
trackD81SyncBits[track][headIndex][headPos >> 3] |= 1 << (headPos & 7);
|
||||
else
|
||||
trackD81SyncBits[track][headIndex][headPos >> 3] &= ~(1 << (headPos & 7));
|
||||
|
||||
}
|
||||
|
||||
static DiskType GetDiskImageTypeViaExtention(const char* diskImageName);
|
||||
static bool IsDiskImageExtention(const char* diskImageName);
|
||||
static bool IsDiskImageD81Extention(const char* diskImageName);
|
||||
static bool IsDiskImageD71Extention(const char* diskImageName);
|
||||
static bool IsLSTExtention(const char* diskImageName);
|
||||
|
||||
bool GetReadOnly() const { return readOnly; }
|
||||
|
@ -117,16 +182,28 @@ public:
|
|||
|
||||
static unsigned char readBuffer[READBUFFER_SIZE];
|
||||
|
||||
static void CRC(unsigned short& runningCRC, unsigned char data);
|
||||
|
||||
union
|
||||
{
|
||||
unsigned char tracks[HALF_TRACK_COUNT][MAX_TRACK_LENGTH];
|
||||
unsigned char tracksD81[HALF_TRACK_COUNT][2][MAX_TRACK_LENGTH];
|
||||
};
|
||||
|
||||
private:
|
||||
void CloseD64();
|
||||
void CloseG64();
|
||||
void CloseNIB();
|
||||
void CloseNBZ();
|
||||
void CloseD71();
|
||||
void CloseD81();
|
||||
|
||||
bool WriteD64();
|
||||
bool WriteG64();
|
||||
bool WriteNIB();
|
||||
bool WriteNBZ();
|
||||
bool WriteD71();
|
||||
bool WriteD81();
|
||||
|
||||
inline void TestDirty(u32 track, bool isDirty)
|
||||
{
|
||||
|
@ -144,17 +221,26 @@ private:
|
|||
int FindSectorHeader(unsigned track, unsigned sector, unsigned char* id);
|
||||
int FindSync(unsigned track, int bitIndex, int maxBits, int* syncStartIndex = 0);
|
||||
|
||||
void OutputD81HeaderByte(unsigned char*& dest, unsigned char byte);
|
||||
void OutputD81DataByte(unsigned char*& src, unsigned char*& dest);
|
||||
|
||||
bool readOnly;
|
||||
bool dirty;
|
||||
unsigned attachedImageSize;
|
||||
DiskType diskType;
|
||||
const FILINFO* fileInfo;
|
||||
|
||||
unsigned char tracks[HALF_TRACK_COUNT][NIB_TRACK_LENGTH];
|
||||
unsigned short trackLengths[HALF_TRACK_COUNT];
|
||||
union
|
||||
{
|
||||
unsigned char trackDensity[HALF_TRACK_COUNT];
|
||||
unsigned char trackD81SyncBits[HALF_TRACK_COUNT][2][MAX_TRACK_LENGTH >> 3];
|
||||
};
|
||||
bool trackDirty[HALF_TRACK_COUNT];
|
||||
bool trackUsed[HALF_TRACK_COUNT];
|
||||
|
||||
unsigned short crc;
|
||||
static unsigned short CRC1021[256];
|
||||
};
|
||||
|
||||
#endif
|
|
@ -254,7 +254,6 @@ void FileBrowser::BrowsableListView::RefreshHighlightScroll()
|
|||
|
||||
bool FileBrowser::BrowsableListView::CheckBrowseNavigation(bool pageOnly)
|
||||
{
|
||||
InputMappings* inputMappings = InputMappings::Instance();
|
||||
bool dirty = false;
|
||||
u32 numberOfEntriesMinus1 = list->entries.size() - 1;
|
||||
|
||||
|
@ -334,7 +333,8 @@ bool FileBrowser::BrowsableListView::CheckBrowseNavigation(bool pageOnly)
|
|||
}
|
||||
|
||||
FileBrowser::BrowsableList::BrowsableList()
|
||||
: current(0)
|
||||
: inputMappings(0)
|
||||
, current(0)
|
||||
, currentIndex(0)
|
||||
, currentHighlightTime(0)
|
||||
, scrollHighlightRate(0)
|
||||
|
@ -375,7 +375,6 @@ void FileBrowser::BrowsableList::RefreshViewsHighlightScroll()
|
|||
|
||||
bool FileBrowser::BrowsableList::CheckBrowseNavigation()
|
||||
{
|
||||
InputMappings* inputMappings = InputMappings::Instance();
|
||||
u32 numberOfEntriesMinus1 = entries.size() - 1;
|
||||
|
||||
bool dirty = false;
|
||||
|
@ -488,8 +487,9 @@ FileBrowser::BrowsableList::Entry* FileBrowser::BrowsableList::FindEntry(const c
|
|||
return 0;
|
||||
}
|
||||
|
||||
FileBrowser::FileBrowser(DiskCaddy* diskCaddy, ROMs* roms, u8* deviceID, bool displayPNGIcons, ScreenBase* screenMain, ScreenBase* screenLCD, float scrollHighlightRate)
|
||||
: state(State_Folders)
|
||||
FileBrowser::FileBrowser(InputMappings* inputMappings, DiskCaddy* diskCaddy, ROMs* roms, u8* deviceID, bool displayPNGIcons, ScreenBase* screenMain, ScreenBase* screenLCD, float scrollHighlightRate)
|
||||
: inputMappings(inputMappings)
|
||||
, state(State_Folders)
|
||||
, diskCaddy(diskCaddy)
|
||||
, selectionsMade(false)
|
||||
, roms(roms)
|
||||
|
@ -508,11 +508,11 @@ FileBrowser::FileBrowser(DiskCaddy* diskCaddy, ROMs* roms, u8* deviceID, bool di
|
|||
rows = 1;
|
||||
|
||||
folder.scrollHighlightRate = scrollHighlightRate;
|
||||
folder.AddView(screenMain, columns, rows, positionX, positionY, false);
|
||||
folder.AddView(screenMain, inputMappings, columns, rows, positionX, positionY, false);
|
||||
|
||||
positionX = screenMain->ScaleX(1024 - 320);
|
||||
columns = screenMain->ScaleX(40);
|
||||
caddySelections.AddView(screenMain, columns, rows, positionX, positionY, false);
|
||||
caddySelections.AddView(screenMain, inputMappings, columns, rows, positionX, positionY, false);
|
||||
|
||||
|
||||
|
||||
|
@ -523,7 +523,7 @@ FileBrowser::FileBrowser(DiskCaddy* diskCaddy, ROMs* roms, u8* deviceID, bool di
|
|||
positionX = 0;
|
||||
positionY = 0;
|
||||
|
||||
folder.AddView(screenLCD, columns, rows, positionX, positionY, true);
|
||||
folder.AddView(screenLCD, inputMappings, columns, rows, positionX, positionY, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -894,8 +894,6 @@ void FileBrowser::UpdateCurrentHighlight()
|
|||
|
||||
void FileBrowser::Update()
|
||||
{
|
||||
InputMappings* inputMappings = InputMappings::Instance();
|
||||
|
||||
if ( inputMappings->CheckKeyboardBrowseMode() || inputMappings->CheckButtonsBrowseMode() || (folder.searchPrefixIndex != 0) )
|
||||
UpdateInputFolders();
|
||||
|
||||
|
@ -977,7 +975,6 @@ bool FileBrowser::AddImageToCaddy(FileBrowser::BrowsableList::Entry* current)
|
|||
|
||||
void FileBrowser::UpdateInputFolders()
|
||||
{
|
||||
InputMappings* inputMappings = InputMappings::Instance();
|
||||
bool dirty = false;
|
||||
|
||||
if (inputMappings->BrowseFunction())
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#include "DiskCaddy.h"
|
||||
#include "ROMs.h"
|
||||
#include "ScreenBase.h"
|
||||
#include "InputMappings.h"
|
||||
|
||||
#define VIC2_COLOUR_INDEX_BLACK 0
|
||||
#define VIC2_COLOUR_INDEX_WHITE 1
|
||||
|
@ -59,8 +60,9 @@ public:
|
|||
class BrowsableListView
|
||||
{
|
||||
public:
|
||||
BrowsableListView(BrowsableList* list, ScreenBase* screen, u32 columns, u32 rows, u32 positionX, u32 positionY, bool lcdPgUpDown)
|
||||
BrowsableListView(BrowsableList* list, InputMappings* inputMappings, ScreenBase* screen, u32 columns, u32 rows, u32 positionX, u32 positionY, bool lcdPgUpDown)
|
||||
: list(list)
|
||||
, inputMappings(inputMappings)
|
||||
, screen(screen)
|
||||
, columns(columns)
|
||||
, rows(rows)
|
||||
|
@ -81,6 +83,7 @@ public:
|
|||
|
||||
BrowsableList* list;
|
||||
u32 offset;
|
||||
InputMappings* inputMappings;
|
||||
|
||||
ScreenBase* screen;
|
||||
u32 columns;
|
||||
|
@ -111,9 +114,10 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
void AddView(ScreenBase* screen, u32 columns, u32 rows, u32 positionX, u32 positionY, bool lcdPgUpDown)
|
||||
void AddView(ScreenBase* screen, InputMappings* inputMappings, u32 columns, u32 rows, u32 positionX, u32 positionY, bool lcdPgUpDown)
|
||||
{
|
||||
BrowsableListView view(this, screen, columns, rows, positionX, positionY, lcdPgUpDown);
|
||||
this->inputMappings = inputMappings;
|
||||
BrowsableListView view(this, inputMappings, screen, columns, rows, positionX, positionY, lcdPgUpDown);
|
||||
views.push_back(view);
|
||||
}
|
||||
|
||||
|
@ -153,6 +157,7 @@ public:
|
|||
void RefreshViewsHighlightScroll();
|
||||
bool CheckBrowseNavigation();
|
||||
|
||||
InputMappings* inputMappings;
|
||||
std::vector<Entry> entries;
|
||||
Entry* current;
|
||||
u32 currentIndex;
|
||||
|
@ -166,7 +171,7 @@ public:
|
|||
std::vector<BrowsableListView> views;
|
||||
};
|
||||
|
||||
FileBrowser(DiskCaddy* diskCaddy, ROMs* roms, u8* deviceID, bool displayPNGIcons, ScreenBase* screenMain, ScreenBase* screenLCD, float scrollHighlightRate);
|
||||
FileBrowser(InputMappings* inputMappings, DiskCaddy* diskCaddy, ROMs* roms, u8* deviceID, bool displayPNGIcons, ScreenBase* screenMain, ScreenBase* screenLCD, float scrollHighlightRate);
|
||||
|
||||
void SelectAutoMountImage(const char* image);
|
||||
void DisplayRoot();
|
||||
|
@ -219,6 +224,8 @@ private:
|
|||
|
||||
bool SelectROMOrDevice(u32 index);
|
||||
|
||||
InputMappings* inputMappings;
|
||||
|
||||
enum State
|
||||
{
|
||||
State_Folders,
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
|
||||
#ifndef InputMappings_H
|
||||
#define InputMappings_H
|
||||
#include "Singleton.h"
|
||||
#include "Keyboard.h"
|
||||
|
||||
#define ESC_FLAG (1 << 0)
|
||||
|
@ -50,10 +49,10 @@
|
|||
// dont exceed 32!!
|
||||
|
||||
|
||||
class InputMappings : public Singleton<InputMappings>
|
||||
class InputMappings //: public Singleton<InputMappings>
|
||||
{
|
||||
protected:
|
||||
friend Singleton<InputMappings>;
|
||||
// friend Singleton<InputMappings>;
|
||||
|
||||
unsigned keyboardFlags;
|
||||
unsigned buttonFlags;
|
||||
|
|
|
@ -29,6 +29,8 @@ extern "C"
|
|||
#define REPEAT_RATE 8
|
||||
#define REPEAT_DELAY 3
|
||||
|
||||
Keyboard* Keyboard::instance;
|
||||
|
||||
void Keyboard::KeyPressedHandlerRaw(TUSBKeyboardDevice* device, unsigned char modifiers, const unsigned char RawKeys[6])
|
||||
{
|
||||
// byte 0 - modifires
|
||||
|
@ -147,6 +149,7 @@ Keyboard::Keyboard()
|
|||
, updateCount(0)
|
||||
, updateCountLastRead(-1)
|
||||
{
|
||||
instance = this;
|
||||
keyStatus[0] = 0;
|
||||
keyStatus[1] = 0;
|
||||
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
|
||||
#ifndef Keyboard_H
|
||||
#define Keyboard_H
|
||||
#include "Singleton.h"
|
||||
#include <stdio.h>
|
||||
|
||||
extern "C"
|
||||
|
@ -284,18 +283,18 @@ extern "C"
|
|||
#define KEY_MEDIA_REFRESH 0xfa
|
||||
#define KEY_MEDIA_CALC 0xfb
|
||||
|
||||
class Keyboard : public Singleton<Keyboard>
|
||||
class Keyboard //: public Singleton<Keyboard>
|
||||
{
|
||||
protected:
|
||||
friend Singleton<Keyboard>;
|
||||
//friend Singleton<Keyboard>;
|
||||
|
||||
u8 modifier;
|
||||
volatile u64 keyStatus[2];
|
||||
volatile u64 keyStatusPrev[2];
|
||||
/*volatile*/ u64 keyStatus[2];
|
||||
/*volatile*/ u64 keyStatusPrev[2];
|
||||
u32 keyRepeatCount[MAX_KEYS];
|
||||
u32 timer;
|
||||
//volatile bool dirty;
|
||||
volatile u32 updateCount;
|
||||
/*volatile*/ u32 updateCount;
|
||||
u32 updateCountLastRead;
|
||||
|
||||
static void KeyPressedHandlerRaw(TUSBKeyboardDevice* device, unsigned char modifiers, const unsigned char RawKeys[6]);
|
||||
|
@ -304,6 +303,8 @@ protected:
|
|||
public:
|
||||
Keyboard();
|
||||
|
||||
static Keyboard* Instance() { return instance; }
|
||||
|
||||
//inline u32 UpdateCount() const { return updateCount; }
|
||||
|
||||
inline bool CheckChanged()
|
||||
|
@ -342,12 +343,16 @@ public:
|
|||
}
|
||||
|
||||
inline bool KeyAnyHeld()
|
||||
{ return (keyStatus[0] | keyStatus[1]); }
|
||||
{
|
||||
return (keyStatus[0] | keyStatus[1]);
|
||||
}
|
||||
|
||||
inline bool KeyEitherAlt() { return (modifier & (KEY_MOD_LALT | KEY_MOD_RALT) ); }
|
||||
|
||||
inline bool KeyNoModifiers() { return (!modifier ); }
|
||||
|
||||
inline bool KeyLCtrlAlt() { return (modifier == (KEY_MOD_LALT | KEY_MOD_LCTRL) ); }
|
||||
|
||||
static Keyboard* instance;
|
||||
};
|
||||
#endif
|
||||
|
|
141
src/Pi1541.cpp
141
src/Pi1541.cpp
|
@ -18,6 +18,147 @@
|
|||
|
||||
#include "Pi1541.h"
|
||||
#include "debug.h"
|
||||
#include "options.h"
|
||||
#include "ROMs.h"
|
||||
|
||||
extern Options options;
|
||||
extern Pi1541 pi1541;
|
||||
extern u8 s_u8Memory[0xc000];
|
||||
extern ROMs roms;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// 6502 Address bus functions.
|
||||
// Move here out of Pi1541 to increase performance.
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// In a 1541 address decoding and chip selects are performed by a 74LS42 ONE-OF-TEN DECODER
|
||||
// 74LS42 Ouputs a low to the !CS based on the four inputs provided by address bits 10-13
|
||||
// 1800 !cs2 on pin 9
|
||||
// 1c00 !cs2 on pin 7
|
||||
u8 read6502(u16 address)
|
||||
{
|
||||
u8 value = 0;
|
||||
if (address & 0x8000)
|
||||
{
|
||||
switch (address & 0xe000) // keep bits 15,14,13
|
||||
{
|
||||
case 0x8000: // 0x8000-0x9fff
|
||||
if (options.GetRAMBOard()) {
|
||||
value = s_u8Memory[address]; // 74LS42 outputs low on pin 1 or pin 2
|
||||
break;
|
||||
}
|
||||
case 0xa000: // 0xa000-0xbfff
|
||||
case 0xc000: // 0xc000-0xdfff
|
||||
case 0xe000: // 0xe000-0xffff
|
||||
value = roms.Read(address);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Address lines 15, 12, 11 and 10 are fed into a 74LS42 for decoding
|
||||
u16 addressLines12_11_10 = (address & 0x1c00) >> 10;
|
||||
switch (addressLines12_11_10)
|
||||
{
|
||||
case 0:
|
||||
case 1:
|
||||
value = s_u8Memory[address & 0x7ff]; // 74LS42 outputs low on pin 1 or pin 2
|
||||
break;
|
||||
case 6:
|
||||
value = pi1541.VIA[0].Read(address); // 74LS42 outputs low on pin 7
|
||||
break;
|
||||
case 7:
|
||||
value = pi1541.VIA[1].Read(address); // 74LS42 outputs low on pin 9
|
||||
break;
|
||||
default:
|
||||
value = address >> 8; // Empty address bus
|
||||
break;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
// Allows a mode where we have RAM at all addresses other than the ROM and the VIAs. (Maybe useful to someone?)
|
||||
u8 read6502ExtraRAM(u16 address)
|
||||
{
|
||||
if (address & 0x8000)
|
||||
{
|
||||
return roms.Read(address);
|
||||
}
|
||||
else
|
||||
{
|
||||
u16 addressLines11And12 = address & 0x1800;
|
||||
if (addressLines11And12 == 0x1800) return pi1541.VIA[(address & 0x400) != 0].Read(address); // address line 10 indicates what VIA to index
|
||||
return s_u8Memory[address & 0x7fff];
|
||||
}
|
||||
}
|
||||
|
||||
// Use for debugging (Reads VIA registers without the regular VIA read side effects)
|
||||
u8 peek6502(u16 address)
|
||||
{
|
||||
u8 value;
|
||||
if (address & 0x8000) // address line 15 selects the ROM
|
||||
{
|
||||
value = roms.Read(address);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Address lines 15, 12, 11 and 10 are fed into a 74LS42 for decoding
|
||||
u16 addressLines15_12_11_10 = (address & 0x1c00) >> 10;
|
||||
addressLines15_12_11_10 |= (address & 0x8000) >> (15 - 3);
|
||||
if (addressLines15_12_11_10 == 0 || addressLines15_12_11_10 == 1) value = s_u8Memory[address & 0x7ff]; // 74LS42 outputs low on pin 1 or pin 2
|
||||
else if (addressLines15_12_11_10 == 6) value = pi1541.VIA[0].Peek(address); // 74LS42 outputs low on pin 7
|
||||
else if (addressLines15_12_11_10 == 7) value = pi1541.VIA[1].Peek(address); // 74LS42 outputs low on pin 9
|
||||
else value = address >> 8; // Empty address bus
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
void write6502(u16 address, const u8 value)
|
||||
{
|
||||
if (address & 0x8000)
|
||||
{
|
||||
switch (address & 0xe000) // keep bits 15,14,13
|
||||
{
|
||||
case 0x8000: // 0x8000-0x9fff
|
||||
if (options.GetRAMBOard()) {
|
||||
s_u8Memory[address] = value; // 74LS42 outputs low on pin 1 or pin 2
|
||||
break;
|
||||
}
|
||||
case 0xa000: // 0xa000-0xbfff
|
||||
case 0xc000: // 0xc000-0xdfff
|
||||
case 0xe000: // 0xe000-0xffff
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Address lines 15, 12, 11 and 10 are fed into a 74LS42 for decoding
|
||||
u16 addressLines12_11_10 = (address & 0x1c00) >> 10;
|
||||
switch (addressLines12_11_10)
|
||||
{
|
||||
case 0:
|
||||
case 1:
|
||||
s_u8Memory[address & 0x7ff] = value; // 74LS42 outputs low on pin 1 or pin 2
|
||||
break;
|
||||
case 6:
|
||||
pi1541.VIA[0].Write(address, value); // 74LS42 outputs low on pin 7
|
||||
break;
|
||||
case 7:
|
||||
pi1541.VIA[1].Write(address, value); // 74LS42 outputs low on pin 9
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void write6502ExtraRAM(u16 address, const u8 value)
|
||||
{
|
||||
if (address & 0x8000) return; // address line 15 selects the ROM
|
||||
u16 addressLines11And12 = address & 0x1800;
|
||||
if (addressLines11And12 == 0) s_u8Memory[address & 0x7fff] = value;
|
||||
else if (addressLines11And12 == 0x1800) pi1541.VIA[(address & 0x400) != 0].Write(address, value); // address line 10 indicates what VIA to index
|
||||
}
|
||||
|
||||
Pi1541::Pi1541()
|
||||
{
|
||||
|
|
12
src/Pi1541.h
12
src/Pi1541.h
|
@ -42,6 +42,18 @@ public:
|
|||
|
||||
M6502 m6502;
|
||||
|
||||
enum PortPins
|
||||
{
|
||||
VIAPORTPINS_DEVSEL0 = 0x20, //pb5
|
||||
VIAPORTPINS_DEVSEL1 = 0x40, //pb6
|
||||
};
|
||||
|
||||
inline void SetDeviceID(u8 id)
|
||||
{
|
||||
VIA[0].GetPortB()->SetInput(VIAPORTPINS_DEVSEL0, id & 1);
|
||||
VIA[0].GetPortB()->SetInput(VIAPORTPINS_DEVSEL1, id & 2);
|
||||
}
|
||||
|
||||
private:
|
||||
//u8 Memory[0xc000];
|
||||
|
||||
|
|
299
src/Pi1581.cpp
Normal file
299
src/Pi1581.cpp
Normal file
|
@ -0,0 +1,299 @@
|
|||
// Pi1541 - A Commodore 1541 disk drive emulator
|
||||
// Copyright(C) 2018 Stephen White
|
||||
//
|
||||
// This file is part of Pi1541.
|
||||
//
|
||||
// Pi1541 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.
|
||||
//
|
||||
// Pi1541 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 Pi1541. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#include "Pi1581.h"
|
||||
#include "iec_bus.h"
|
||||
#include "options.h"
|
||||
#include "ROMs.h"
|
||||
#include "debug.h"
|
||||
|
||||
extern Pi1581 pi1581;
|
||||
extern u8 s_u8Memory[0xc000];
|
||||
extern ROMs roms;
|
||||
|
||||
// PA0 SIDE0
|
||||
// PA1 !RDY
|
||||
// PA2 !MOTOR
|
||||
// PA3 ID 1
|
||||
// PA4 ID 2
|
||||
// PA5 POWER LED
|
||||
// PA6 ACT LED
|
||||
// PA7 !DISK_CHNG
|
||||
// PB0 DATA IN
|
||||
// PB1 DATA OUT
|
||||
// PB2 CLK IN
|
||||
// PB3 CLK OUT
|
||||
// PB4 ATNA
|
||||
// PB5 FAST SER DIR
|
||||
// PB6 /WPAT
|
||||
// PB7 ATN IN
|
||||
|
||||
// FAST SER DIR sets the direction of U13 (74ls241)
|
||||
// When 1
|
||||
// - SP is sent to DATA
|
||||
// - Fast Clock (SRQ) is sent to CNT
|
||||
// When 0
|
||||
// - DATA is sent to SP
|
||||
// - CNT is sent to Fast Clock (SRQ)
|
||||
|
||||
enum PortPins
|
||||
{
|
||||
// PORT A
|
||||
PORTA_PINS_SIDE0 = 0x01, //pa0
|
||||
PORTA_PINS_RDY = 0x20, //pa1
|
||||
PORTA_PINS_MOTOR = 0x04, //pa2
|
||||
|
||||
PORTA_PINS_DEVSEL0 = 0x08, //pa3
|
||||
PORTA_PINS_DEVSEL1 = 0x10, //pa4
|
||||
|
||||
PORTA_PINS_ACT_LED = 0x40, //pa6
|
||||
PORTA_PINS_DISKCHNG = 0x80, //pa7
|
||||
|
||||
// PORT B
|
||||
PORTB_PINS_FAST_SER_DIR = 0x20, //pb5
|
||||
PORTB_PINS_WPAT = 0x40, //pb6
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
FAST_SERIAL_DIR_IN,
|
||||
FAST_SERIAL_DIR_OUT
|
||||
};
|
||||
|
||||
extern u16 pc;
|
||||
|
||||
// CS
|
||||
// 8520
|
||||
// $4000
|
||||
//
|
||||
// 1770
|
||||
// $6000
|
||||
//
|
||||
// ROM
|
||||
// $8000
|
||||
//
|
||||
// RAM
|
||||
// 0-$1fff
|
||||
|
||||
u8 read6502_1581(u16 address)
|
||||
{
|
||||
u8 value = 0;
|
||||
if (address & 0x8000)
|
||||
{
|
||||
value = roms.Read1581(address);
|
||||
}
|
||||
else if (address >= 0x6000)
|
||||
{
|
||||
value = pi1581.wd177x.Read(address);
|
||||
//DEBUG_LOG("177x r %04x %02x %04x\r\n", address, value, pc);
|
||||
}
|
||||
else if (address >= 0x4000)
|
||||
{
|
||||
value = pi1581.CIA.Read(address);
|
||||
//DEBUG_LOG("CIA r %04x %02x %04x\r\n", address, value, pc);
|
||||
}
|
||||
else if (address < 0x2000)
|
||||
{
|
||||
value = s_u8Memory[address & 0x1fff];
|
||||
}
|
||||
else
|
||||
{
|
||||
value = address >> 8; // Empty address bus
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
// Use for debugging (Reads VIA registers without the regular VIA read side effects)
|
||||
u8 peek6502_1581(u16 address)
|
||||
{
|
||||
u8 value = 0;
|
||||
return value;
|
||||
}
|
||||
|
||||
void write6502_1581(u16 address, const u8 value)
|
||||
{
|
||||
if (address & 0x8000)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if (address >= 0x6000)
|
||||
{
|
||||
//DEBUG_LOG("177x w %04x %02x %04x\r\n", address, value, pc);
|
||||
pi1581.wd177x.Write(address, value);
|
||||
}
|
||||
else if (address >= 0x4000)
|
||||
{
|
||||
//DEBUG_LOG("CIA w %04x %02x %04x\r\n", address, value, pc);
|
||||
pi1581.CIA.Write(address, value);
|
||||
}
|
||||
else if (address < 0x2000)
|
||||
{
|
||||
s_u8Memory[address & 0x1fff] = value;
|
||||
}
|
||||
}
|
||||
|
||||
static void CIAPortA_OnPortOut(void* pUserData, unsigned char status)
|
||||
{
|
||||
Pi1581* pi1581 = (Pi1581*)pUserData;
|
||||
|
||||
pi1581->wd177x.SetSide(status & PORTA_PINS_SIDE0);
|
||||
|
||||
bool motorAsserted = (status & PORTA_PINS_MOTOR) == 0;
|
||||
|
||||
if (motorAsserted)
|
||||
{
|
||||
if (!pi1581->wd177x.IsExternalMotorAsserted())
|
||||
{
|
||||
pi1581->CIA.GetPortA()->SetInput(PORTA_PINS_RDY, true);
|
||||
|
||||
pi1581->RDYDelayCount = 250000;
|
||||
pi1581->wd177x.AssertExternalMotor(motorAsserted); // !MOTOR
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pi1581->RDYDelayCount = 0;
|
||||
pi1581->CIA.GetPortA()->SetInput(PORTA_PINS_RDY, true);
|
||||
if (pi1581->wd177x.IsExternalMotorAsserted())
|
||||
{
|
||||
//DEBUG_LOG("pc=%04x\r\n", pc);
|
||||
pi1581->wd177x.AssertExternalMotor(motorAsserted); // !MOTOR
|
||||
}
|
||||
}
|
||||
|
||||
pi1581->SetLED((status & PORTA_PINS_ACT_LED) != 0);
|
||||
}
|
||||
|
||||
static void CIAPortB_OnPortOut(void* pUserData, unsigned char status)
|
||||
{
|
||||
Pi1581* pi1581 = (Pi1581*)pUserData;
|
||||
|
||||
pi1581->wd177x.SetWPRTPin(status & PORTB_PINS_WPAT); // !WPAT
|
||||
|
||||
if (status & PORTB_PINS_FAST_SER_DIR)
|
||||
pi1581->fastSerialDirection = FAST_SERIAL_DIR_OUT;
|
||||
else
|
||||
pi1581->fastSerialDirection = FAST_SERIAL_DIR_IN;
|
||||
|
||||
|
||||
IEC_Bus::PortB_OnPortOut(0, status);
|
||||
}
|
||||
|
||||
Pi1581::Pi1581()
|
||||
{
|
||||
Initialise();
|
||||
}
|
||||
|
||||
void Pi1581::Initialise()
|
||||
{
|
||||
LED = false;
|
||||
|
||||
CIA.ConnectIRQ(&m6502.IRQ);
|
||||
// IRQ is not connected on a 1581
|
||||
//wd177x.ConnectIRQ(&m6502.IRQ);
|
||||
|
||||
CIA.GetPortA()->SetPortOut(this, CIAPortA_OnPortOut);
|
||||
CIA.GetPortB()->SetPortOut(this, CIAPortB_OnPortOut);
|
||||
|
||||
// For now disk is writable
|
||||
CIA.GetPortB()->SetInput(PORTB_PINS_WPAT, true);
|
||||
|
||||
CIA.GetPortA()->SetInput(PORTA_PINS_DISKCHNG, false);
|
||||
CIA.GetPortA()->SetInput(PORTA_PINS_RDY, true);
|
||||
|
||||
RDYDelayCount = 0;
|
||||
}
|
||||
|
||||
void Pi1581::Update()
|
||||
{
|
||||
//CIA.GetPortA()->SetInput(PORTA_PINS_DISKCHNG, 1);
|
||||
//CIA.GetPortA()->SetInput(PORTA_PINS_RDY, false);
|
||||
|
||||
if (RDYDelayCount)
|
||||
{
|
||||
RDYDelayCount--;
|
||||
if (RDYDelayCount == 0)
|
||||
{
|
||||
CIA.GetPortA()->SetInput(PORTA_PINS_RDY, false);
|
||||
}
|
||||
}
|
||||
|
||||
CIA.Execute();
|
||||
|
||||
// SRQ is pulled high by the c128
|
||||
|
||||
// When U13 (74LS241) is set to fast serial IN
|
||||
// - R20 pulls SP high
|
||||
// - R19 pulls CNT high
|
||||
// - R26 pulls Fast Clock (SRQ) in high
|
||||
// When U13 (74LS241) is set to fast serial OUT
|
||||
// - R25 pulls DATA high
|
||||
// - R28 pulls Fast Clock (SRQ) out high
|
||||
|
||||
if (fastSerialDirection == FAST_SERIAL_DIR_OUT)
|
||||
{
|
||||
// When 1
|
||||
// - SP is sent to DATA
|
||||
// - Fast Clock (SRQ) is sent to CNT
|
||||
IEC_Bus::SetFastSerialData(!CIA.GetPinSP()); // Communication on fast serial is done after the inverter.
|
||||
IEC_Bus::SetFastSerialSRQ(CIA.GetPinCNT());
|
||||
//trace lines and see it this needs to set other lines
|
||||
}
|
||||
else
|
||||
{
|
||||
// When 0
|
||||
// - DATA is sent to SP
|
||||
// - CNT is sent to Fast Clock (SRQ)
|
||||
CIA.SetPinSP(!IEC_Bus::GetPI_Data()); // Communication on fast serial is done before the inverter.
|
||||
CIA.SetPinCNT(IEC_Bus::GetPI_SRQ());
|
||||
}
|
||||
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
wd177x.Execute();
|
||||
}
|
||||
}
|
||||
|
||||
void Pi1581::Reset()
|
||||
{
|
||||
IOPort* CIABPortB;
|
||||
|
||||
fastSerialDirection = FAST_SERIAL_DIR_IN;
|
||||
CIA.Reset();
|
||||
wd177x.Reset();
|
||||
IEC_Bus::Reset();
|
||||
// On a real drive the outputs look like they are being pulled high (when set to inputs) (Taking an input from the front end of an inverter)
|
||||
CIABPortB = CIA.GetPortB();
|
||||
CIABPortB->SetInput(VIAPORTPINS_DATAOUT, true);
|
||||
CIABPortB->SetInput(VIAPORTPINS_CLOCKOUT, true);
|
||||
CIABPortB->SetInput(VIAPORTPINS_ATNAOUT, true);
|
||||
}
|
||||
|
||||
void Pi1581::SetDeviceID(u8 id)
|
||||
{
|
||||
CIA.GetPortA()->SetInput(PORTA_PINS_DEVSEL0, id & 1);
|
||||
CIA.GetPortA()->SetInput(PORTA_PINS_DEVSEL1, id & 2);
|
||||
}
|
||||
|
||||
void Pi1581::Insert(DiskImage* diskImage)
|
||||
{
|
||||
// CIA.GetPortB()->SetInput(PORTB_PINS_WPAT, !diskImage->GetReadOnly());
|
||||
CIA.GetPortA()->SetInput(PORTA_PINS_DISKCHNG, true);
|
||||
wd177x.Insert(diskImage);
|
||||
}
|
||||
|
68
src/Pi1581.h
Normal file
68
src/Pi1581.h
Normal file
|
@ -0,0 +1,68 @@
|
|||
// Pi1541 - A Commodore 1541 disk drive emulator
|
||||
// Copyright(C) 2018 Stephen White
|
||||
//
|
||||
// This file is part of Pi1541.
|
||||
//
|
||||
// Pi1541 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.
|
||||
//
|
||||
// Pi1541 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 Pi1541. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#ifndef PI1581_H
|
||||
#define PI1581_H
|
||||
|
||||
#include "Drive.h"
|
||||
#include "m6502.h"
|
||||
#include "iec_bus.h"
|
||||
#include "wd177x.h"
|
||||
#include "m8520.h"
|
||||
|
||||
class Pi1581
|
||||
{
|
||||
|
||||
public:
|
||||
Pi1581();
|
||||
|
||||
void Initialise();
|
||||
|
||||
void Update();
|
||||
|
||||
void Reset();
|
||||
|
||||
void SetDeviceID(u8 id);
|
||||
|
||||
void Insert(DiskImage* diskImage);
|
||||
|
||||
inline bool IsLEDOn() const { return LED; }
|
||||
inline bool IsMotorOn() const { return wd177x.IsExternalMotorAsserted(); }
|
||||
inline void SetLED(bool value) { LED = value; }
|
||||
//Drive drive;
|
||||
WD177x wd177x;
|
||||
m8520 CIA;
|
||||
|
||||
M6502 m6502;
|
||||
|
||||
unsigned fastSerialDirection;
|
||||
unsigned int RDYDelayCount;
|
||||
|
||||
private:
|
||||
bool LED;
|
||||
|
||||
//u8 Memory[0xc000];
|
||||
|
||||
//static u8 Read6502(u16 address, void* data);
|
||||
//static u8 Read6502ExtraRAM(u16 address, void* data);
|
||||
//static u8 Peek6502(u16 address, void* data);
|
||||
//static void Write6502(u16 address, const u8 value, void* data);
|
||||
//static void Write6502ExtraRAM(u16 address, const u8 value, void* data);
|
||||
};
|
||||
|
||||
#endif
|
|
@ -30,13 +30,20 @@ public:
|
|||
{
|
||||
return ROMImages[currentROMIndex][address & 0x3fff];
|
||||
}
|
||||
inline u8 Read1581(u16 address)
|
||||
{
|
||||
return ROMImage1581[address & 0x7fff];
|
||||
}
|
||||
|
||||
void ResetCurrentROMIndex();
|
||||
|
||||
static const int ROM_SIZE = 16384;
|
||||
static const int ROM1581_SIZE = 16384 * 2;
|
||||
static const int MAX_ROMS = 7;
|
||||
|
||||
unsigned char ROMImages[MAX_ROMS][ROM_SIZE];
|
||||
unsigned char ROMImage1581[ROM1581_SIZE];
|
||||
char ROMName1581[256];
|
||||
char ROMNames[MAX_ROMS][256];
|
||||
bool ROMValid[MAX_ROMS];
|
||||
|
||||
|
|
101
src/iec_bus.cpp
101
src/iec_bus.cpp
|
@ -29,6 +29,7 @@ u32 IEC_Bus::PIGPIO_MASK_IN_RESET = 1 << PIGPIO_RESET;
|
|||
bool IEC_Bus::PI_Atn = false;
|
||||
bool IEC_Bus::PI_Data = false;
|
||||
bool IEC_Bus::PI_Clock = false;
|
||||
bool IEC_Bus::PI_SRQ = false;
|
||||
bool IEC_Bus::PI_Reset = false;
|
||||
|
||||
bool IEC_Bus::VIA_Atna = false;
|
||||
|
@ -38,6 +39,11 @@ bool IEC_Bus::VIA_Clock = false;
|
|||
bool IEC_Bus::DataSetToOut = false;
|
||||
bool IEC_Bus::AtnaDataSetToOut = false;
|
||||
bool IEC_Bus::ClockSetToOut = false;
|
||||
bool IEC_Bus::SRQSetToOut = false;
|
||||
|
||||
m6522* IEC_Bus::VIA = 0;
|
||||
m8520* IEC_Bus::CIA = 0;
|
||||
IOPort* IEC_Bus::port = 0;
|
||||
|
||||
bool IEC_Bus::OutputLED = false;
|
||||
bool IEC_Bus::OutputSound = false;
|
||||
|
@ -58,7 +64,6 @@ u32 IEC_Bus::inputRepeatThreshold[5];
|
|||
u32 IEC_Bus::inputRepeat[5] = { 0 };
|
||||
u32 IEC_Bus::inputRepeatPrev[5] = { 0 };
|
||||
|
||||
m6522* IEC_Bus::VIA = 0;
|
||||
|
||||
u32 IEC_Bus::emulationModeCheckButtonIndex = 0;
|
||||
|
||||
|
@ -111,7 +116,7 @@ void IEC_Bus::ReadBrowseMode(void)
|
|||
Resetting = !ignoreReset && ((gplev0 & PIGPIO_MASK_IN_RESET) == (invertIECInputs ? PIGPIO_MASK_IN_RESET : 0));
|
||||
}
|
||||
|
||||
void IEC_Bus::ReadEmulationMode(void)
|
||||
void IEC_Bus::ReadEmulationMode1541(void)
|
||||
{
|
||||
IOPort* portB = 0;
|
||||
unsigned gplev0 = read32(ARM_GPIO_GPLEV0);
|
||||
|
@ -126,14 +131,15 @@ void IEC_Bus::ReadEmulationMode(void)
|
|||
//emulationModeCheckButtonIndex++;
|
||||
//emulationModeCheckButtonIndex %= buttonCount;
|
||||
|
||||
portB = VIA->GetPortB();
|
||||
portB = port;
|
||||
|
||||
bool ATNIn = (gplev0 & PIGPIO_MASK_IN_ATN) == (invertIECInputs ? PIGPIO_MASK_IN_ATN : 0);
|
||||
if (PI_Atn != ATNIn)
|
||||
{
|
||||
PI_Atn = ATNIn;
|
||||
|
||||
//if (VIA)
|
||||
//DEBUG_LOG("A%d\r\n", PI_Atn);
|
||||
//if (port)
|
||||
{
|
||||
if ((portB->GetDirection() & 0x10) != 0)
|
||||
{
|
||||
|
@ -182,3 +188,90 @@ void IEC_Bus::ReadEmulationMode(void)
|
|||
|
||||
Resetting = !ignoreReset && ((gplev0 & PIGPIO_MASK_IN_RESET) == (invertIECInputs ? PIGPIO_MASK_IN_RESET : 0));
|
||||
}
|
||||
|
||||
void IEC_Bus::ReadEmulationMode1581(void)
|
||||
{
|
||||
IOPort* portB = 0;
|
||||
unsigned gplev0 = read32(ARM_GPIO_GPLEV0);
|
||||
|
||||
int buttonIndex;
|
||||
for (buttonIndex = 0; buttonIndex < 3; ++buttonIndex)
|
||||
{
|
||||
UpdateButton(buttonIndex, gplev0);
|
||||
}
|
||||
// Doing it this way screws with the debounce counters.
|
||||
//UpdateButton(emulationModeCheckButtonIndex, gplev0);
|
||||
//emulationModeCheckButtonIndex++;
|
||||
//emulationModeCheckButtonIndex %= buttonCount;
|
||||
|
||||
portB = port;
|
||||
|
||||
bool ATNIn = (gplev0 & PIGPIO_MASK_IN_ATN) == (invertIECInputs ? PIGPIO_MASK_IN_ATN : 0);
|
||||
if (PI_Atn != ATNIn)
|
||||
{
|
||||
PI_Atn = ATNIn;
|
||||
|
||||
//DEBUG_LOG("A%d\r\n", PI_Atn);
|
||||
//if (port)
|
||||
{
|
||||
if ((portB->GetDirection() & 0x10) != 0)
|
||||
{
|
||||
// Emulate the XOR gate UD3
|
||||
// We only need to do this when fully emulating, iec commands do this internally
|
||||
AtnaDataSetToOut = (VIA_Atna & PI_Atn);
|
||||
}
|
||||
|
||||
portB->SetInput(VIAPORTPINS_ATNIN, ATNIn); //is inverted and then connected to pb7 and ca1
|
||||
CIA->SetPinFLAG(!ATNIn);
|
||||
}
|
||||
}
|
||||
|
||||
if (portB && (portB->GetDirection() & 0x10) == 0)
|
||||
AtnaDataSetToOut = false; // If the ATNA PB4 gets set to an input then we can't be pulling data low. (Maniac Mansion does this)
|
||||
|
||||
if (!AtnaDataSetToOut && !DataSetToOut) // only sense if we have not brought the line low (because we can't as we have the pin set to output but we can simulate in software)
|
||||
{
|
||||
bool DATAIn = (gplev0 & PIGPIO_MASK_IN_DATA) == (invertIECInputs ? PIGPIO_MASK_IN_DATA : 0);
|
||||
if (PI_Data != DATAIn)
|
||||
{
|
||||
PI_Data = DATAIn;
|
||||
portB->SetInput(VIAPORTPINS_DATAIN, DATAIn); // VIA DATAin pb0 output from inverted DIN 5 DATA
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PI_Data = true;
|
||||
portB->SetInput(VIAPORTPINS_DATAIN, true); // simulate the read in software
|
||||
}
|
||||
|
||||
if (!ClockSetToOut) // only sense if we have not brought the line low (because we can't as we have the pin set to output but we can simulate in software)
|
||||
{
|
||||
bool CLOCKIn = (gplev0 & PIGPIO_MASK_IN_CLOCK) == (invertIECInputs ? PIGPIO_MASK_IN_CLOCK : 0);
|
||||
if (PI_Clock != CLOCKIn)
|
||||
{
|
||||
PI_Clock = CLOCKIn;
|
||||
portB->SetInput(VIAPORTPINS_CLOCKIN, CLOCKIn); // VIA CLKin pb2 output from inverted DIN 4 CLK
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PI_Clock = true;
|
||||
portB->SetInput(VIAPORTPINS_CLOCKIN, true); // simulate the read in software
|
||||
}
|
||||
|
||||
if (!SRQSetToOut) // only sense if we have not brought the line low (because we can't as we have the pin set to output but we can simulate in software)
|
||||
{
|
||||
bool SRQIn = (gplev0 & PIGPIO_MASK_IN_SRQ) == (invertIECInputs ? PIGPIO_MASK_IN_SRQ : 0);
|
||||
if (PI_SRQ != SRQIn)
|
||||
{
|
||||
PI_SRQ = SRQIn;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PI_SRQ = false;
|
||||
}
|
||||
|
||||
Resetting = !ignoreReset && ((gplev0 & PIGPIO_MASK_IN_RESET) == (invertIECInputs ? PIGPIO_MASK_IN_RESET : 0));
|
||||
}
|
||||
|
||||
|
|
119
src/iec_bus.h
119
src/iec_bus.h
|
@ -21,6 +21,7 @@
|
|||
|
||||
#include "debug.h"
|
||||
#include "m6522.h"
|
||||
#include "m8520.h"
|
||||
|
||||
#include "rpi-gpio.h"
|
||||
#include "rpiHardware.h"
|
||||
|
@ -66,6 +67,11 @@
|
|||
// Therefore in the same vein if PB7 is set to output it could cause the input of the XOR to be pulled low
|
||||
//
|
||||
|
||||
// NOTE ABOUT SRQ
|
||||
// SRQ is a little bit different.
|
||||
// The 1581 does not pull it high. Only the 128 pulls it high.
|
||||
//
|
||||
|
||||
enum PIGPIO
|
||||
{
|
||||
// Original Non-split lines
|
||||
|
@ -83,7 +89,8 @@ enum PIGPIO
|
|||
// 3 I2C_CLK //5
|
||||
PIGPIO_IN_BUTTON4 = 4, // 07 Common
|
||||
PIGPIO_IN_BUTTON5 = 5, // 29 Common
|
||||
PIGPIO_OUT_RESET = 6, // 31
|
||||
//PIGPIO_OUT_RESET = 6, // 31
|
||||
PIGPIO_OUT_SPI0_RS = 6, // 31
|
||||
// 7 SPI0_CS1 //26
|
||||
// 8 SPI0_CS0 //24
|
||||
// 9 SPI0_MISO //21
|
||||
|
@ -218,8 +225,6 @@ enum VIAPortPins
|
|||
VIAPORTPINS_CLOCKIN = 0x04, //pb2
|
||||
VIAPORTPINS_CLOCKOUT = 0x08,//pb3
|
||||
VIAPORTPINS_ATNAOUT = 0x10, //pb4
|
||||
VIAPORTPINS_DEVSEL0 = 0x20, //pb5
|
||||
VIAPORTPINS_DEVSEL1 = 0x40, //pb5
|
||||
VIAPORTPINS_ATNIN = 0x80 //bp7
|
||||
};
|
||||
|
||||
|
@ -260,7 +265,9 @@ public:
|
|||
RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_IN_BUTTON1, FS_INPUT);
|
||||
|
||||
|
||||
RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_OUT_RESET, FS_OUTPUT);
|
||||
//RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_OUT_RESET, FS_OUTPUT);
|
||||
RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_OUT_SPI0_RS, FS_OUTPUT);
|
||||
|
||||
RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_OUT_ATN, FS_OUTPUT);
|
||||
RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_OUT_SOUND, FS_OUTPUT);
|
||||
RPI_SetGpioPinFunction((rpi_gpio_pin_t)PIGPIO_OUT_LED, FS_OUTPUT);
|
||||
|
@ -337,7 +344,8 @@ public:
|
|||
}
|
||||
|
||||
static void ReadBrowseMode(void);
|
||||
static void ReadEmulationMode(void);
|
||||
static void ReadEmulationMode1541(void);
|
||||
static void ReadEmulationMode1581(void);
|
||||
|
||||
static void WaitUntilReset(void)
|
||||
{
|
||||
|
@ -365,21 +373,28 @@ public:
|
|||
VIA_Data = (status & (unsigned char)VIAPORTPINS_DATAOUT) != 0; // VIA DATAout PB1 inverted and then connected to DIN DATA
|
||||
VIA_Clock = (status & (unsigned char)VIAPORTPINS_CLOCKOUT) != 0; // VIA CLKout PB3 inverted and then connected to DIN CLK
|
||||
|
||||
if (VIA)
|
||||
{
|
||||
// Emulate the XOR gate UD3
|
||||
AtnaDataSetToOut = (VIA_Atna != PI_Atn);
|
||||
}
|
||||
else
|
||||
{
|
||||
AtnaDataSetToOut = (VIA_Atna & PI_Atn);
|
||||
}
|
||||
|
||||
if (AtnaDataSetToOut)
|
||||
{
|
||||
// if the output of the XOR gate is high (ie VIA_Atna != PI_Atn) then this is inverted and pulls DATA low (activating it)
|
||||
PI_Data = true;
|
||||
if (VIA) VIA->GetPortB()->SetInput(VIAPORTPINS_DATAIN, true); // simulate the read in software
|
||||
if (port) port->SetInput(VIAPORTPINS_DATAIN, true); // simulate the read in software
|
||||
}
|
||||
|
||||
if (VIA)
|
||||
if (VIA && port)
|
||||
{
|
||||
// If the VIA's data and clock outputs ever get set to inputs the real hardware reads these lines as asserted.
|
||||
bool PB1SetToInput = (VIA->GetPortB()->GetDirection() & 2) == 0;
|
||||
bool PB3SetToInput = (VIA->GetPortB()->GetDirection() & 8) == 0;
|
||||
bool PB1SetToInput = (port->GetDirection() & 2) == 0;
|
||||
bool PB3SetToInput = (port->GetDirection() & 8) == 0;
|
||||
if (PB1SetToInput) VIA_Data = true;
|
||||
if (PB3SetToInput) VIA_Clock = true;
|
||||
}
|
||||
|
@ -390,18 +405,18 @@ public:
|
|||
if (!oldDataSetToOut && DataSetToOut)
|
||||
{
|
||||
PI_Data = true;
|
||||
if (VIA) VIA->GetPortB()->SetInput(VIAPORTPINS_DATAOUT, true); // simulate the read in software
|
||||
if (port) port->SetInput(VIAPORTPINS_DATAOUT, true); // simulate the read in software
|
||||
}
|
||||
|
||||
if (!oldClockSetToOut && ClockSetToOut)
|
||||
{
|
||||
PI_Clock = true;
|
||||
if (VIA) VIA->GetPortB()->SetInput(VIAPORTPINS_CLOCKIN, true); // simulate the read in software
|
||||
if (port) port->SetInput(VIAPORTPINS_CLOCKIN, true); // simulate the read in software
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static inline void RefreshOuts(void)
|
||||
static inline void RefreshOuts1541(void)
|
||||
{
|
||||
unsigned set = 0;
|
||||
unsigned clear = 0;
|
||||
|
@ -419,8 +434,6 @@ public:
|
|||
}
|
||||
else
|
||||
{
|
||||
clear |= 1 << PIGPIO_OUT_ATN;
|
||||
|
||||
if (AtnaDataSetToOut || DataSetToOut) set |= 1 << PIGPIO_OUT_DATA;
|
||||
else clear |= 1 << PIGPIO_OUT_DATA;
|
||||
|
||||
|
@ -444,6 +457,51 @@ public:
|
|||
write32(ARM_GPIO_GPCLR0, clear);
|
||||
}
|
||||
|
||||
static inline void RefreshOuts1581(void)
|
||||
{
|
||||
unsigned set = 0;
|
||||
unsigned clear = 0;
|
||||
unsigned tmp;
|
||||
|
||||
if (!splitIECLines)
|
||||
{
|
||||
unsigned outputs = 0;
|
||||
|
||||
if (AtnaDataSetToOut || DataSetToOut) outputs |= (FS_OUTPUT << ((PIGPIO_DATA - 10) * 3));
|
||||
if (ClockSetToOut) outputs |= (FS_OUTPUT << ((PIGPIO_CLOCK - 10) * 3));
|
||||
//if (SRQSetToOut) outputs |= (FS_OUTPUT << ((PIGPIO_SRQ - 10) * 3)); // For Option A hardware we should not support pulling more than 2 lines low at any one time!
|
||||
|
||||
unsigned nValue = (myOutsGPFSEL1 & PI_OUTPUT_MASK_GPFSEL1) | outputs;
|
||||
write32(ARM_GPIO_GPFSEL1, nValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (AtnaDataSetToOut || DataSetToOut) set |= 1 << PIGPIO_OUT_DATA;
|
||||
else clear |= 1 << PIGPIO_OUT_DATA;
|
||||
|
||||
if (ClockSetToOut) set |= 1 << PIGPIO_OUT_CLOCK;
|
||||
else clear |= 1 << PIGPIO_OUT_CLOCK;
|
||||
|
||||
if (!SRQSetToOut) set |= 1 << PIGPIO_OUT_SRQ; // fast clock is pulled high but we have an inverter in our hardware so to compensate we invert in software now
|
||||
else clear |= 1 << PIGPIO_OUT_SRQ;
|
||||
|
||||
if (!invertIECOutputs) {
|
||||
tmp = set;
|
||||
set = clear;
|
||||
clear = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
if (OutputLED) set |= 1 << PIGPIO_OUT_LED;
|
||||
else clear |= 1 << PIGPIO_OUT_LED;
|
||||
|
||||
if (OutputSound) set |= 1 << PIGPIO_OUT_SOUND;
|
||||
else clear |= 1 << PIGPIO_OUT_SOUND;
|
||||
|
||||
write32(ARM_GPIO_GPSET0, set);
|
||||
write32(ARM_GPIO_GPCLR0, clear);
|
||||
}
|
||||
|
||||
static void WaitMicroSeconds(u32 amount)
|
||||
{
|
||||
u32 count;
|
||||
|
@ -461,6 +519,17 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// 1581 Fast Serial
|
||||
static inline void SetFastSerialData(bool value)
|
||||
{
|
||||
DataSetToOut = value;
|
||||
}
|
||||
static inline void SetFastSerialSRQ(bool value)
|
||||
{
|
||||
SRQSetToOut = value;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Manual methods used by IEC_Commands
|
||||
static inline void AssertData()
|
||||
|
@ -468,7 +537,7 @@ public:
|
|||
if (!DataSetToOut)
|
||||
{
|
||||
DataSetToOut = true;
|
||||
RefreshOuts();
|
||||
RefreshOuts1541();
|
||||
}
|
||||
}
|
||||
static inline void ReleaseData()
|
||||
|
@ -476,7 +545,7 @@ public:
|
|||
if (DataSetToOut)
|
||||
{
|
||||
DataSetToOut = false;
|
||||
RefreshOuts();
|
||||
RefreshOuts1541();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -485,7 +554,7 @@ public:
|
|||
if (!ClockSetToOut)
|
||||
{
|
||||
ClockSetToOut = true;
|
||||
RefreshOuts();
|
||||
RefreshOuts1541();
|
||||
}
|
||||
}
|
||||
static inline void ReleaseClock()
|
||||
|
@ -493,10 +562,11 @@ public:
|
|||
if (ClockSetToOut)
|
||||
{
|
||||
ClockSetToOut = false;
|
||||
RefreshOuts();
|
||||
RefreshOuts1541();
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool GetPI_SRQ() { return PI_SRQ; }
|
||||
static inline bool GetPI_Atn() { return PI_Atn; }
|
||||
static inline bool IsAtnAsserted() { return PI_Atn; }
|
||||
static inline bool IsAtnReleased() { return !PI_Atn; }
|
||||
|
@ -542,6 +612,7 @@ public:
|
|||
PI_Atn = !PI_Atn;
|
||||
PI_Data = !PI_Data;
|
||||
PI_Clock = !PI_Clock;
|
||||
PI_SRQ = !PI_SRQ;
|
||||
PI_Reset = !PI_Reset;
|
||||
}
|
||||
}
|
||||
|
@ -562,6 +633,8 @@ public:
|
|||
// CA2, CB1 and CB2 are not connected
|
||||
// - check if pulled high or low
|
||||
static m6522* VIA;
|
||||
static m8520* CIA;
|
||||
static IOPort* port;
|
||||
|
||||
static inline void Reset(void)
|
||||
{
|
||||
|
@ -578,15 +651,21 @@ public:
|
|||
|
||||
DataSetToOut = false;
|
||||
ClockSetToOut = false;
|
||||
SRQSetToOut = false;
|
||||
|
||||
PI_Atn = false;
|
||||
PI_Data = false;
|
||||
PI_Clock = false;
|
||||
PI_SRQ = false;
|
||||
|
||||
if (VIA)
|
||||
AtnaDataSetToOut = (VIA_Atna != PI_Atn);
|
||||
else
|
||||
AtnaDataSetToOut = (VIA_Atna & PI_Atn);
|
||||
|
||||
if (AtnaDataSetToOut) PI_Data = true;
|
||||
|
||||
RefreshOuts();
|
||||
RefreshOuts1581();
|
||||
}
|
||||
|
||||
static bool GetInputButtonPressed(int buttonIndex) { return InputButton[buttonIndex] && !InputButtonPrev[buttonIndex]; }
|
||||
|
@ -615,6 +694,7 @@ private:
|
|||
static bool PI_Atn;
|
||||
static bool PI_Data;
|
||||
static bool PI_Clock;
|
||||
static bool PI_SRQ;
|
||||
static bool PI_Reset;
|
||||
|
||||
static bool VIA_Atna;
|
||||
|
@ -624,6 +704,7 @@ private:
|
|||
static bool DataSetToOut;
|
||||
static bool AtnaDataSetToOut;
|
||||
static bool ClockSetToOut;
|
||||
static bool SRQSetToOut;
|
||||
static bool Resetting;
|
||||
|
||||
static u32 myOutsGPFSEL0;
|
||||
|
|
864
src/m8520.cpp
Normal file
864
src/m8520.cpp
Normal file
|
@ -0,0 +1,864 @@
|
|||
// Pi1541 - A Commodore 1541 disk drive emulator
|
||||
// Copyright(C) 2018 Stephen White
|
||||
//
|
||||
// This file is part of Pi1541.
|
||||
//
|
||||
// Pi1541 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.
|
||||
//
|
||||
// Pi1541 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 Pi1541. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#include "m8520.h"
|
||||
|
||||
// The 8520 contains a programmable baud rate generator which is used for fast serial transfers.
|
||||
// Timer A is used for the baud rate generator. In the output mode data is shifted out on SP at 1/2 the underflow rate of Timer A.
|
||||
// The maximum baud rate possible is phi 2 divided by 4, but the maximum usable baud rate will be determined by line loading and the speed at which the receiver responds to the input data.
|
||||
// Transmission will start following a write to the Serial Data Register (provided Timer A is running and in continuous mode).
|
||||
// The clock derived from Timer A appears on the CNT pin.
|
||||
// The Data in the Serial Data Register will be loaded into the shift register then shifted out to the SP pin.
|
||||
// After 8 pulses on the CNT pin, a bit in the ICR (interrupt control register) is set and if desired, an interrupt may be generated.
|
||||
// All incoming fast bytes generate an interrupt within the Fast Serial Drive.
|
||||
// Bytes are shifted out; most significant bit first.
|
||||
|
||||
// The serial port is a buffered, 8-bit synchronous shift register system.
|
||||
// A control bit selects input or output mode.
|
||||
// In input mode, data on the SP pin is shifted into the shift register on the rising edge of the signal applied to the CNT pin.
|
||||
// After 8 CNT pulses, the data in the shift register is dumped into the Serial Data Register and an interrupt is generated.
|
||||
// In the output mode, TIMER A is used for the baud rate generator.
|
||||
// Data is shifted out on the SP pin at 1/2 the underflow rate of TIMER A.
|
||||
// The maximum baud rate possible is (212 divided by 4, but the maximum useable baud rate will be determined byline loading and the speed at which the receiver responds to input data.
|
||||
// Transmission will start following a write to the Serial Data Register (provided TIMER A is running and in continuous mode).
|
||||
// The clock signal derived from TIMER A appears as an output on the CNT pin.
|
||||
// The data in the Serial Data Register will be loaded into the shift register then shift out to the SP pin when a CNT pulse occurs.
|
||||
// Datashifted out becomes valid on the falling edge of CNT and remains valid until the next falling edge.
|
||||
// After 8 CNT pulses, an interrupt is generated to indicate more data can be sent.
|
||||
// If the Serial Data Register was loaded with new information prior to this interrupt, the new data will automatically be loaded into the shift register and transmission will continue.
|
||||
// If the microprocessor stays one byte ahead of the shift register, transmission will be continuous.
|
||||
// If no further data is to be transmitted, after the 8th CNT pulse, CNT will return high and SP will remain at the level of the last data bit transmitted.
|
||||
// SDR data is shifted out MSB first and serial input data should also appear in this format.
|
||||
// The bidirectional capability of the Serial Port and CNT clock allows many 6526 devices to be connected to a common serial communication bus on which one 6526 acts as a master,
|
||||
// sourcing data and shift clock, while all other 6526 chips act as slaves.
|
||||
// Both CNT and SP outputs are open drain to allow such a common bus.
|
||||
// Protocol for master / slave selection can be transmitted over the serial bus, or via dedicated handshaking lines.
|
||||
|
||||
// Reset
|
||||
// sdr_valid = 0
|
||||
// sr_bits = 0
|
||||
|
||||
// SR write
|
||||
// SR = value
|
||||
// if in output mode
|
||||
// sdr_valid = 1
|
||||
// SR Read
|
||||
// value = SR
|
||||
//
|
||||
// Update
|
||||
// if TA times out
|
||||
// if in ouput
|
||||
// if sr_bits
|
||||
// sr_bits--
|
||||
// if sr_bits == 0
|
||||
// flag IRQ
|
||||
// SR = shifter
|
||||
// endif
|
||||
// endif
|
||||
// if sr_bits == 0 && sdr_valid
|
||||
// shifter = SR
|
||||
// sdr_valid = 0
|
||||
// sr_bits = 14
|
||||
// endif
|
||||
// endif
|
||||
|
||||
|
||||
// A control bit allows the timer output to appear on a PORT B output line(PB6 for TIMER A and PB7 for TIMER B).
|
||||
// This function overrides the DDRB control bit and forces the appropriate PB line to an output.
|
||||
|
||||
extern u16 pc;
|
||||
extern bool bLoggingCYCs;
|
||||
|
||||
|
||||
m8520::m8520()
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
void m8520::Reset()
|
||||
{
|
||||
// The port pins are set as inputs and port registers to zero(although a read of the ports will return all highs because of passive pullups).
|
||||
portA.SetDirection(0);
|
||||
portB.SetDirection(0);
|
||||
|
||||
PCAsserted = 0;
|
||||
|
||||
FLAGPin = true; // external devices should be setting this
|
||||
CNTPin = false; // external devices should be setting this
|
||||
CNTPinOld = false;
|
||||
SPPin = false; // external devices should be setting this
|
||||
TODPin = false; // external devices should be setting this
|
||||
|
||||
//CRARegister = 0;
|
||||
//CRBRegister = 0;
|
||||
Write(CRA, 0);
|
||||
Write(CRB, 0);
|
||||
|
||||
// The timer control registers are set to zero and the timer latches to all ones.
|
||||
timerACounter = 0;
|
||||
timerALatch = 0xffff;
|
||||
timerAActive = false;
|
||||
timerAOutputOnPB6 = false;
|
||||
timerAToggle = false;
|
||||
timerAOneShot = false;
|
||||
timerAMode = TA_MODE_PHI2;
|
||||
timerA50Hz = false;
|
||||
//timerATimeOutCount = 0;
|
||||
ta_pb6 = true;
|
||||
timerAReloaded = false;
|
||||
|
||||
timerBCounter = 0;
|
||||
timerBLatch = 0xffff;
|
||||
timerBActive = false;
|
||||
timerBOutputOnPB7 = false;
|
||||
timerBToggle = false;
|
||||
timerBOneShot = false;
|
||||
timerBMode = TB_MODE_PHI2;
|
||||
timerBAlarm = false;
|
||||
tb_pb7 = true;
|
||||
timerBReloaded = false;
|
||||
|
||||
serialPortMode = SP_MODE_INPUT;
|
||||
serialPortRegister = 0;
|
||||
serialShiftRegister = 0;
|
||||
serialBitsShiftedSoFar = 8;
|
||||
|
||||
TODActive = false;
|
||||
TODAlarm = 0;
|
||||
TODClock = 0;
|
||||
TODLatch = 0;
|
||||
|
||||
ICRMask = 0;
|
||||
ICRData = 0;
|
||||
//OutputIRQ();
|
||||
}
|
||||
|
||||
extern u16 pc;
|
||||
|
||||
// Update for a single cycle
|
||||
void m8520::Execute()
|
||||
{
|
||||
bool timerATimedOut = false;
|
||||
bool timerBTimedOut = false;
|
||||
// In oneshot mode, the timer will count down from the latched value to zero, generate an interrupt, reload the latched value, then stop.
|
||||
// In continuous mode, the timer will count from the latched value to zero, generate an interrupt, reload the latched value and repeat the procedure continuously.
|
||||
|
||||
// The timer latch is loaded into the timer on any timer underflow
|
||||
if (timerAActive && !timerAReloaded)
|
||||
{
|
||||
switch (timerAMode)
|
||||
{
|
||||
case m8520::TA_MODE_PHI2:
|
||||
timerATimedOut = timerACounter == 0;
|
||||
timerACounter--;
|
||||
break;
|
||||
case m8520::TA_MODE_CNT_PVE:
|
||||
if (serialPortMode == SP_MODE_OUTPUT)
|
||||
{
|
||||
if (CNTPin && !CNTPinOld)
|
||||
{
|
||||
timerATimedOut = timerACounter == 0;
|
||||
timerACounter--; // counts positive CNT transitions.
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
//timerATimedOut = timerACounter == 0;
|
||||
|
||||
if (timerATimedOut)
|
||||
{
|
||||
//timerATimeOutCount++;
|
||||
|
||||
SetInterrupt(IR_TA);
|
||||
|
||||
ReloadTimerA();
|
||||
|
||||
if (timerAOneShot)
|
||||
{
|
||||
timerAActive = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (serialPortMode == SP_MODE_OUTPUT)
|
||||
{
|
||||
//The individual data bits now appear at half the timeout rate of timer A on the SP line and the clock signal from timer A
|
||||
// appears on the CNT line(it changes value on each timeout so that the next bit appears on the SP line on each negative transition[high to low]).
|
||||
// The transfer begins with the MSB of the data byte.Once all eight bits have been output, CNT remains high and the SP line retains the value of the last bit sent
|
||||
// in addition, the SP bit in the interrupt control register is set to show that the shift register can be supplied with new data.
|
||||
//DEBUG_LOG("o %d\r\n", serialBitsShiftedSoFar);
|
||||
|
||||
if (serialBitsShiftedSoFar >= 8)
|
||||
{
|
||||
// If no further data is to be transmitted, after the 8th CNT pulse, CNT will return high and SP will remain at the level of the last data bit transmitted.
|
||||
CNTPin = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Data is shifted out on the SP pin at 1 / 2 the underflow rate of TIMER A.
|
||||
// (provided TIMER A is running and in continuous mode)
|
||||
|
||||
bool oldCNT = CNTPin;
|
||||
// The clock signal derived from TIMER A appears as an output on the CNT pin.
|
||||
CNTPin = !CNTPin;
|
||||
|
||||
// Datashifted out becomes valid on the falling edge of CNT and remains valid until the next falling edge.
|
||||
if (!CNTPin) //(timerATimeOutCount & 1) == 0)
|
||||
{
|
||||
// SDR data is shifted out MSB first and serial input data should also appear in this format.
|
||||
SPPin = (serialShiftRegister & 0x80) != 0;
|
||||
serialShiftRegister <<= 1;
|
||||
|
||||
//DEBUG_LOG("o%d\r\n", serialBitsShiftedSoFar);
|
||||
|
||||
serialBitsShiftedSoFar++;
|
||||
|
||||
if (serialBitsShiftedSoFar == 8)
|
||||
{
|
||||
//DEBUG_LOG("o %04x\r\n", pc);
|
||||
SetInterrupt(IR_SDR);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//else
|
||||
//{
|
||||
// CNTPin = true;
|
||||
//}
|
||||
}
|
||||
|
||||
ta_pb6 = !ta_pb6;
|
||||
//if (timerAOutputOnPB6)
|
||||
//{
|
||||
// // This function overrides the DDRB control bit and forces the appropriate PB line to an output.
|
||||
// unsigned char ddr = portB.GetDirection();
|
||||
// if (ddr & 0x80)
|
||||
// {
|
||||
// // the signal on PB6 is inverted each time the counter reaches zero
|
||||
// if (!ta_pb6) portB.SetOutput(portB.GetOutput() & (~0x40));
|
||||
// else portB.SetOutput(portB.GetOutput() | 0x40);
|
||||
// }
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
//timerBTimedOut = timerBCounter == 0;
|
||||
|
||||
if (timerBActive && !timerBReloaded)
|
||||
{
|
||||
//DEBUG_LOG("TB %04x\r\n", timerBCounter);
|
||||
|
||||
switch (timerBMode)
|
||||
{
|
||||
case m8520::TB_MODE_PHI2:
|
||||
timerBTimedOut = timerBCounter == 0;
|
||||
timerBCounter--;
|
||||
break;
|
||||
case m8520::TB_MODE_CNT_PVE:
|
||||
if (serialPortMode == SP_MODE_OUTPUT)
|
||||
{
|
||||
if (CNTPin && !CNTPinOld)
|
||||
{
|
||||
timerBTimedOut = timerBCounter == 0;
|
||||
timerBCounter--; // counts positive CNT transitions.
|
||||
}
|
||||
}
|
||||
break;
|
||||
case m8520::TB_MODE_TA_UNDEFLOW:
|
||||
if (timerATimedOut)
|
||||
{
|
||||
timerBTimedOut = timerBCounter == 0;
|
||||
timerBCounter--;
|
||||
}
|
||||
break;
|
||||
case m8520::TB_MODE_TA_UNDEFLOW_CNT_PVE:
|
||||
if (serialPortMode == SP_MODE_OUTPUT)
|
||||
{
|
||||
if (timerATimedOut && CNTPin)
|
||||
{
|
||||
timerBTimedOut = timerBCounter == 0;
|
||||
timerBCounter--;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (timerBTimedOut)
|
||||
{
|
||||
//DEBUG_LOG("TB out\r\n");
|
||||
SetInterrupt(IR_TB);
|
||||
|
||||
ReloadTimerB();
|
||||
|
||||
if (timerBOneShot)
|
||||
{
|
||||
timerBActive = false;
|
||||
}
|
||||
|
||||
tb_pb7 = !tb_pb7;
|
||||
//if (timerBOutputOnPB7)
|
||||
//{
|
||||
// // This function overrides the DDRB control bit and forces the appropriate PB line to an output.
|
||||
// unsigned char ddr = portB.GetDirection();
|
||||
// if (ddr & 0x80)
|
||||
// {
|
||||
// // the signal on PB7 is inverted each time the counter reaches zero
|
||||
// if (!tb_pb7) portB.SetOutput(portB.GetOutput() & (~0x80));
|
||||
// else portB.SetOutput(portB.GetOutput() | 0x80);
|
||||
// }
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
//switch (serialPortMode)
|
||||
//{
|
||||
// case SP_MODE_OUTPUT:
|
||||
|
||||
// break;
|
||||
// case SP_MODE_INPUT:
|
||||
// // input mode is handled by the rising edge of CNT in SetPinCNT
|
||||
// break;
|
||||
//}
|
||||
|
||||
if (PCAsserted)
|
||||
PCAsserted--;
|
||||
|
||||
CNTPinOld = CNTPin;
|
||||
timerAReloaded = false;
|
||||
timerBReloaded = false;
|
||||
}
|
||||
|
||||
void m8520::SetPinFLAG(bool value) // Active low
|
||||
{
|
||||
if (FLAGPin && !value)
|
||||
{
|
||||
// Any negative transition on FLAG will set the FLAG interrupt bit.
|
||||
SetInterrupt(IR_FLG);
|
||||
//DEBUG_LOG("IR_FLG\r\n");
|
||||
}
|
||||
FLAGPin = value;
|
||||
}
|
||||
|
||||
void m8520::SetPinCNT(bool value)
|
||||
{
|
||||
if (serialPortMode == SP_MODE_INPUT)
|
||||
{
|
||||
if (!CNTPin && value) // rising edge?
|
||||
{
|
||||
//DEBUG_LOG("C%d\r\n", serialBitsShiftedSoFar);
|
||||
if (serialBitsShiftedSoFar < 8)
|
||||
{
|
||||
// In input mode, data on the SP pin is shifted into the shift register on the rising edge of the signal applied to the CNT pin.
|
||||
// After 8 CNT pulses, the data in the shift register is dumped into the Serial Data Register and an interrupt is generated.
|
||||
|
||||
serialShiftRegister <<= 1;
|
||||
serialShiftRegister |= SPPin;
|
||||
|
||||
//DEBUG_LOG("i%d\r\n", serialBitsShiftedSoFar);
|
||||
serialBitsShiftedSoFar++;
|
||||
|
||||
if (serialBitsShiftedSoFar == 8)
|
||||
{
|
||||
//DEBUG_LOG("ib=%02x %d\r\n", serialShiftRegister, pc);
|
||||
serialPortRegister = serialShiftRegister;
|
||||
//serialBitsShiftedSoFar = 0;
|
||||
SetInterrupt(IR_SDR);
|
||||
}
|
||||
}
|
||||
}
|
||||
CNTPin = value;
|
||||
}
|
||||
}
|
||||
|
||||
void m8520::SetPinSP(bool value)
|
||||
{
|
||||
SPPin = value;
|
||||
}
|
||||
|
||||
|
||||
void m8520::SetPinTOD(bool value)
|
||||
{
|
||||
// Posistive edge transitions on this pin cause the binary counter to increment.
|
||||
if (value && !TODPin && TODActive)
|
||||
{
|
||||
TODClock++;
|
||||
TODClock &= 0xffffff;
|
||||
if (TODClock == TODAlarm)
|
||||
{
|
||||
SetInterrupt(IR_TOD);
|
||||
}
|
||||
}
|
||||
TODPin = value;
|
||||
}
|
||||
|
||||
unsigned char m8520::Read(unsigned int address)
|
||||
{
|
||||
unsigned char value = 0;
|
||||
|
||||
switch (address & 0xf)
|
||||
{
|
||||
case ORA:
|
||||
value = ReadPortA();
|
||||
break;
|
||||
case ORB:
|
||||
value = ReadPortB();
|
||||
// The 8520 datasheet contradicts itself;-
|
||||
// PC will go low forone cycle following a read orwrite of PORT B.
|
||||
// PC will go low on the 3rd cycle after a PORT B access.
|
||||
PCAsserted = 3;
|
||||
break;
|
||||
case DDRA:
|
||||
value = portA.GetDirection();
|
||||
break;
|
||||
case DDRB:
|
||||
value = portB.GetDirection();
|
||||
break;
|
||||
|
||||
// Data read from the timer are the present contents of the Timer Counter.
|
||||
case TALO:
|
||||
value = timerACounter & 0xff;
|
||||
break;
|
||||
case TAHI:
|
||||
value = timerACounter >> 8;
|
||||
break;
|
||||
case TBLO:
|
||||
value = timerACounter & 0xff;
|
||||
break;
|
||||
case TBHI:
|
||||
value = timerACounter >> 8;
|
||||
break;
|
||||
|
||||
// Since a carry from one stage to the next can occur at any time with respect to a read operation, a latching function is included to keep all Time of Day information constant during a read sequence.
|
||||
// All TOD registers latch on a read of MSB event and remain latched until after a read of LSB Event.
|
||||
// The TOD clock continues to count when the output registers are latched.
|
||||
// If only one register is to be read, there is no carry problem and the register can be read “on the fly", provided that any read of MSB Event is followed by a read of LSB Event to disable the latching.
|
||||
case EVENT_LSB:
|
||||
value = (unsigned char)(TODLatch);
|
||||
break;
|
||||
case EVENT_8_15:
|
||||
value = (unsigned char)(TODLatch >> 8);
|
||||
break;
|
||||
case EVENT_MSB:
|
||||
TODLatch = TODClock;
|
||||
value = (unsigned char)(TODLatch >> 16);
|
||||
break;
|
||||
|
||||
|
||||
case NC:
|
||||
break;
|
||||
case SDR:
|
||||
value = serialPortRegister;
|
||||
//DEBUG_LOG("rsr%02x\r\n", value);
|
||||
//serialBitsShiftedSoFar = 0;
|
||||
break;
|
||||
case ICR:
|
||||
// The interrupt DATA register is cleared and the IRQ line returns high following a read of the DATA register.
|
||||
value = ICRData;
|
||||
//if (ICRData & IR_FLG)
|
||||
//{
|
||||
// DEBUG_LOG("IRFLG %04x\r\n", pc);
|
||||
// bLoggingCYCs = true;
|
||||
//}
|
||||
ClearInterrupt(ICRData & (IR_FLG | IR_SDR | IR_TOD | IR_TB | IR_TA));
|
||||
ICRData = 0;
|
||||
break;
|
||||
case CRA:
|
||||
value = CRARegister;
|
||||
break;
|
||||
case CRB:
|
||||
value = CRBRegister;
|
||||
break;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
unsigned char m8520::Peek(unsigned int address)
|
||||
{
|
||||
unsigned char value = 0;
|
||||
|
||||
switch (address & 0xf)
|
||||
{
|
||||
case ORA:
|
||||
value = PeekPortA();
|
||||
break;
|
||||
case ORB:
|
||||
value = PeekPortB();
|
||||
break;
|
||||
case DDRA:
|
||||
value = portA.GetDirection();
|
||||
break;
|
||||
case DDRB:
|
||||
value = portB.GetDirection();
|
||||
break;
|
||||
case TALO:
|
||||
value = timerACounter & 0xff;
|
||||
break;
|
||||
case TAHI:
|
||||
value = timerACounter >> 8;
|
||||
break;
|
||||
case TBLO:
|
||||
value = timerACounter & 0xff;
|
||||
break;
|
||||
case TBHI:
|
||||
value = timerACounter >> 8;
|
||||
break;
|
||||
case EVENT_LSB:
|
||||
value = (unsigned char)(TODLatch);
|
||||
break;
|
||||
case EVENT_8_15:
|
||||
value = (unsigned char)(TODLatch >> 8);
|
||||
break;
|
||||
case EVENT_MSB:
|
||||
TODLatch = TODClock;
|
||||
value = (unsigned char)(TODLatch >> 16);
|
||||
break;
|
||||
case NC:
|
||||
break;
|
||||
case SDR:
|
||||
value = serialPortRegister;
|
||||
break;
|
||||
case ICR:
|
||||
value = ICRData;
|
||||
break;
|
||||
case CRA:
|
||||
// bit 4 will always read back a zero and writing a zero has no effect
|
||||
value = CRARegister;
|
||||
break;
|
||||
case CRB:
|
||||
value = CRBRegister;
|
||||
break;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
void m8520::Write(unsigned int address, unsigned char value)
|
||||
{
|
||||
unsigned char ddr;
|
||||
|
||||
switch (address & 0xf)
|
||||
{
|
||||
case ORA:
|
||||
WritePortA(value);
|
||||
break;
|
||||
case ORB:
|
||||
WritePortB(value);
|
||||
// The 8520 datasheet contradicts itself;-
|
||||
// PC will go low forone cycle following a read orwrite of PORT B.
|
||||
// PC will go low on the 3rd cycle after a PORT B access.
|
||||
PCAsserted = 3;
|
||||
break;
|
||||
case DDRA:
|
||||
portA.SetDirection(value);
|
||||
break;
|
||||
case DDRB:
|
||||
portB.SetDirection(value);
|
||||
break;
|
||||
|
||||
// Data written to the timer are latched in the Timer Latch.
|
||||
case TALO:
|
||||
timerALatch = (timerBLatch & 0xff00) | value;
|
||||
break;
|
||||
case TAHI:
|
||||
timerALatch = (timerBLatch & 0xff) | (value << 8);
|
||||
// In oneshot mode; a write to Timer High will transfer the timer latch to the counter and initiate counting regardless of the start bit.
|
||||
|
||||
// The timer latch is loaded into the timer following a write to the high byte of the prescaler while the timer is stopped.
|
||||
|
||||
// The timer latch is loaded into the timer on any timer underflow, on a force load or following a write to the high byte of the prescaler while the timer is stopped.
|
||||
// If the timer is running, a write to the high byte will load the timer latch, but not reload the counter.
|
||||
|
||||
if (!timerAActive/* || timerAOneShot*/)
|
||||
ReloadTimerA();
|
||||
break;
|
||||
case TBLO:
|
||||
timerBLatch = (timerBLatch & 0xff00) | value;
|
||||
break;
|
||||
case TBHI:
|
||||
timerBLatch = (timerBLatch & 0xff) | (value << 8);
|
||||
// In oneshot mode; a write to Timer High will transfer the timer latch to the counter and initiate counting regardless of the start bit.
|
||||
|
||||
// The timer latch is loaded into the timer following a write to the high byte of the prescaler while the timer is stopped.
|
||||
if (!timerBActive/* || timerBOneShot*/)
|
||||
ReloadTimerB();
|
||||
break;
|
||||
|
||||
|
||||
// TOD is automatically stopped whenever a write to the regiser occurs.
|
||||
case EVENT_LSB:
|
||||
if (timerBAlarm)
|
||||
{
|
||||
TODAlarm = (TODAlarm & 0xffff00) | value;
|
||||
}
|
||||
else
|
||||
{
|
||||
TODActive = true; // The clock will not start again until after a write to the LSB Event Register.
|
||||
TODClock = (TODClock & 0xffff00) | value;
|
||||
}
|
||||
break;
|
||||
case EVENT_8_15:
|
||||
if (timerBAlarm)
|
||||
{
|
||||
TODAlarm = (TODAlarm & 0xff00ff) | ((unsigned)value << 8);
|
||||
}
|
||||
else
|
||||
{
|
||||
TODActive = false;
|
||||
TODClock = (TODClock & 0xff00ff) | ((unsigned)value << 8);
|
||||
}
|
||||
break;
|
||||
case EVENT_MSB:
|
||||
if (timerBAlarm)
|
||||
{
|
||||
TODAlarm = (TODAlarm & 0xffff) | ((unsigned)value << 16);
|
||||
}
|
||||
else
|
||||
{
|
||||
TODActive = false;
|
||||
TODClock = (TODClock & 0xffff) | ((unsigned)value << 16);
|
||||
}
|
||||
break;
|
||||
|
||||
case NC:
|
||||
break;
|
||||
case SDR:
|
||||
//DEBUG_LOG("wsr%02x %04x\r\n", value, pc);
|
||||
serialPortRegister = value;
|
||||
//serialShiftRegister = value;
|
||||
if ((CRARegister & CRA_SPMODE))
|
||||
{
|
||||
serialBitsShiftedSoFar = 0;
|
||||
//DEBUG_LOG("SDR W 0\r\n");
|
||||
}
|
||||
break;
|
||||
case ICR:
|
||||
// The MASK register provides convenient control of Individual mask bits. When writing to the MASK register,
|
||||
// if bit 7 (SET / CLEAR) of the data written is a ZERO, any mask bit written with a one will be cleared, while
|
||||
// those mask bits written with a zero will be unaffected. If bit 7 of the data written is a ONE, any mask bit written
|
||||
// with a one will be set, while those mask bits written with a zero will be unaffected.
|
||||
// In order for an interrupt flag to set IR and generate an Interrupt Request, the corresponding MASK bit must be set.
|
||||
if ((value & IR_SET) == 0)
|
||||
ICRMask &= ~(value & (IR_FLG | IR_SDR | IR_TOD | IR_TB | IR_TA));
|
||||
else
|
||||
ICRMask |= (value & (IR_FLG | IR_SDR | IR_TOD | IR_TB | IR_TA));
|
||||
|
||||
//DEBUG_LOG("irqm %02x %04x\r\n", ICRMask, pc);
|
||||
|
||||
OutputIRQ();
|
||||
break;
|
||||
case CRA:
|
||||
{
|
||||
unsigned char CRARegisterOld = CRARegister;
|
||||
|
||||
CRARegister = value;
|
||||
if (CRARegister & CRA_START)
|
||||
{
|
||||
// Timer A start
|
||||
timerAActive = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Timer A stop
|
||||
timerAActive = false;
|
||||
}
|
||||
|
||||
if (CRARegister & CRA_PBON)
|
||||
{
|
||||
// Timer A output appears on PB6
|
||||
timerAOutputOnPB6 = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// PB6 normal operation
|
||||
timerAOutputOnPB6 = false;
|
||||
}
|
||||
|
||||
if (CRARegister & CRA_OUTPUTMODE)
|
||||
{
|
||||
// Toggle
|
||||
timerAToggle = true;
|
||||
|
||||
// The toggle output is set high whenever the timer is started and is set low by RES
|
||||
}
|
||||
else
|
||||
{
|
||||
// Pulse
|
||||
timerAToggle = false;
|
||||
}
|
||||
|
||||
if (CRARegister & CRA_RUNMODE)
|
||||
{
|
||||
// One shot
|
||||
|
||||
if (!timerAOneShot)
|
||||
{
|
||||
ReloadTimerA();
|
||||
}
|
||||
|
||||
timerAOneShot = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Continuous
|
||||
timerAOneShot = false;
|
||||
}
|
||||
|
||||
// bit 4 will always read back a zero and writing a zero has no effect
|
||||
if (CRARegister & CRA_LOAD)
|
||||
{
|
||||
// Force load
|
||||
// A strobe bit allows the timer latch to be loaded into the timer counter at any time, whether the timer is running or not.
|
||||
ReloadTimerA();
|
||||
}
|
||||
|
||||
if (CRARegister & CRA_INMODE)
|
||||
{
|
||||
// Timer A counts positive CNT transitions
|
||||
timerAMode = TA_MODE_CNT_PVE;
|
||||
}
|
||||
else
|
||||
{
|
||||
// A counts phi2
|
||||
timerAMode = TA_MODE_PHI2;
|
||||
}
|
||||
|
||||
//if ((CRARegisterOld ^ CRARegister) & CRA_SPMODE)
|
||||
if ((CRARegisterOld & CRA_SPMODE) ^ (CRARegister & CRA_SPMODE))
|
||||
{
|
||||
if (CRARegister & CRA_SPMODE)
|
||||
{
|
||||
// Serial port output - CNT sources shift clock
|
||||
serialPortMode = SP_MODE_OUTPUT;
|
||||
//DEBUG_LOG("o %04x\r\n", pc);
|
||||
//DEBUG_LOG("o\r\n");
|
||||
|
||||
serialBitsShiftedSoFar = 8;
|
||||
serialShiftRegister = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Serial port input - (external shift clock required)
|
||||
serialPortMode = SP_MODE_INPUT;
|
||||
|
||||
//DEBUG_LOG("i %04x\r\n", pc);
|
||||
//DEBUG_LOG("i\r\n");
|
||||
|
||||
serialBitsShiftedSoFar = 0;
|
||||
serialShiftRegister = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (CRARegister & CRA_TODIN)
|
||||
{
|
||||
timerA50Hz = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
timerA50Hz = false;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case CRB:
|
||||
CRBRegister = value;
|
||||
|
||||
CRBRegister = value;
|
||||
if (CRBRegister & CRB_START)
|
||||
{
|
||||
// Timer B start
|
||||
timerBActive = true;
|
||||
//DEBUG_LOG("TB A\r\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Timer B stop
|
||||
timerBActive = false;
|
||||
}
|
||||
|
||||
if (CRBRegister & CRB_PBON)
|
||||
{
|
||||
// Timer A output appears on PB6
|
||||
timerBOutputOnPB7 = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// PB6 normal operation
|
||||
timerBOutputOnPB7 = false;
|
||||
}
|
||||
|
||||
|
||||
// Toggle / Pulse
|
||||
// A control bit selects the output applied to PORT B.
|
||||
// On every timer underflow the output can either toggle or generate a single positive pulse of one cycle duration.
|
||||
// The toggle output is set high whenever the timer is started and is set low by RES.
|
||||
if (CRBRegister & CRB_OUTPUTMODE)
|
||||
{
|
||||
// Toggle
|
||||
timerBToggle = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Pulse
|
||||
timerBToggle = false;
|
||||
}
|
||||
|
||||
if (CRBRegister & CRB_RUNMODE)
|
||||
{
|
||||
// One shot
|
||||
timerBOneShot = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Continuous
|
||||
timerBOneShot = false;
|
||||
}
|
||||
|
||||
// bit 4 will always read back a zero and writing a zero has no effect
|
||||
if (CRBRegister & CRB_LOAD)
|
||||
{
|
||||
// Force load
|
||||
// A strobe bit allows the timer latch to be loaded into the timer counter at any time, whether the timer is running or not.
|
||||
ReloadTimerB();
|
||||
}
|
||||
|
||||
switch ((CRBRegister & (CRB_INMODE1 | CRB_INMODE0)) >> 5)
|
||||
{
|
||||
case 0:
|
||||
timerBMode = TB_MODE_PHI2;
|
||||
break;
|
||||
case 1:
|
||||
timerBMode = TB_MODE_CNT_PVE;
|
||||
break;
|
||||
case 2:
|
||||
timerBMode = TB_MODE_TA_UNDEFLOW;
|
||||
break;
|
||||
case 3:
|
||||
timerBMode = TB_MODE_TA_UNDEFLOW_CNT_PVE;
|
||||
break;
|
||||
}
|
||||
|
||||
if (CRBRegister & CRB_ALARM)
|
||||
{
|
||||
timerBAlarm = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
timerBAlarm = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
294
src/m8520.h
Normal file
294
src/m8520.h
Normal file
|
@ -0,0 +1,294 @@
|
|||
// Pi1541 - A Commodore 1541 disk drive emulator
|
||||
// Copyright(C) 2018 Stephen White
|
||||
//
|
||||
// This file is part of Pi1541.
|
||||
//
|
||||
// Pi1541 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.
|
||||
//
|
||||
// Pi1541 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 Pi1541. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#ifndef M8520_H
|
||||
#define M8520_H
|
||||
|
||||
#include "IOPort.h"
|
||||
#include "m6502.h"
|
||||
#include "debug.h"
|
||||
|
||||
// PA0 SIDE0
|
||||
// PA1 !RDY
|
||||
// PA2 !MOTOR
|
||||
// PA3 ID 1
|
||||
// PA4 ID 2
|
||||
// PA5 POWER LED
|
||||
// PA6 ACT LED
|
||||
// PA7 !DISK_CHNG
|
||||
// PB0 DATA IN
|
||||
// PB1 DATA OUT
|
||||
// PB2 CLK IN
|
||||
// PB3 CLK OUT
|
||||
// PB4 ATNA
|
||||
// PB5 FAST SER DIR
|
||||
// PB6 /WPAT
|
||||
// PB7 ATN IN
|
||||
|
||||
// !FLAG = !ATN IN
|
||||
|
||||
|
||||
class m8520
|
||||
{
|
||||
enum Registers
|
||||
{
|
||||
ORA, // 0 Port A
|
||||
ORB, // 1 Port B
|
||||
DDRA, // 2 Data direction register for port A
|
||||
DDRB, // 3 Data direction register for port B
|
||||
|
||||
TALO, // 4 Timer A low
|
||||
TAHI, // 5 Timer A high
|
||||
TBLO, // 6 Timer B low
|
||||
TBHI, // 7 Timer B high
|
||||
EVENT_LSB, // 8
|
||||
EVENT_8_15, // 9
|
||||
EVENT_MSB, // 10
|
||||
|
||||
NC, // 11 No connect
|
||||
SDR, // 12 Serial data register
|
||||
|
||||
ICR, // 13 Interrupt control register
|
||||
CRA, // 14 Control register A
|
||||
CRB, // 15 Control register B
|
||||
};
|
||||
|
||||
enum IR
|
||||
{
|
||||
IR_TA = 0x01,
|
||||
IR_TB = 0x02,
|
||||
IR_TOD = 0x04,
|
||||
IR_SDR = 0x08,
|
||||
IR_FLG = 0x10,
|
||||
IR_SET = 0x80
|
||||
};
|
||||
|
||||
enum CRA_BIT
|
||||
{
|
||||
CRA_START = 0x01,
|
||||
CRA_PBON = 0x02,
|
||||
CRA_OUTPUTMODE = 0x04,
|
||||
CRA_RUNMODE = 0x08,
|
||||
CRA_LOAD = 0x10,
|
||||
CRA_INMODE = 0x20,
|
||||
CRA_SPMODE = 0x40,
|
||||
CRA_TODIN = 0x80
|
||||
};
|
||||
|
||||
enum CRB_BIT
|
||||
{
|
||||
CRB_START = 0x01,
|
||||
CRB_PBON = 0x02,
|
||||
CRB_OUTPUTMODE = 0x04,
|
||||
CRB_RUNMODE = 0x08,
|
||||
CRB_LOAD = 0x10,
|
||||
CRB_INMODE0 = 0x20,
|
||||
CRB_INMODE1 = 0x40,
|
||||
CRB_ALARM = 0x80
|
||||
};
|
||||
|
||||
enum TimerAMode
|
||||
{
|
||||
TA_MODE_PHI2,
|
||||
TA_MODE_CNT_PVE
|
||||
};
|
||||
|
||||
enum TimerBMode
|
||||
{
|
||||
TB_MODE_PHI2,
|
||||
TB_MODE_CNT_PVE,
|
||||
TB_MODE_TA_UNDEFLOW,
|
||||
TB_MODE_TA_UNDEFLOW_CNT_PVE
|
||||
};
|
||||
|
||||
enum SerialPortMode
|
||||
{
|
||||
SP_MODE_OUTPUT,
|
||||
SP_MODE_INPUT
|
||||
};
|
||||
|
||||
public:
|
||||
m8520();
|
||||
|
||||
void Reset();
|
||||
void ConnectIRQ(Interrupt* irq) { this->irq = irq; }
|
||||
|
||||
inline IOPort* GetPortA() { return &portA; }
|
||||
inline IOPort* GetPortB() { return &portB; }
|
||||
|
||||
void Execute();
|
||||
|
||||
unsigned char Read(unsigned int address);
|
||||
unsigned char Peek(unsigned int address);
|
||||
void Write(unsigned int address, unsigned char value);
|
||||
|
||||
bool IsPCAsserted() const { return PCAsserted; }
|
||||
void SetPinFLAG(bool value); // active low
|
||||
void SetPinCNT(bool value);
|
||||
bool GetPinCNT() const { return CNTPin; }
|
||||
void SetPinSP(bool value);
|
||||
bool GetPinSP() const { return SPPin; }
|
||||
void SetPinTOD(bool value);
|
||||
|
||||
//private:
|
||||
inline unsigned char ReadPortB()
|
||||
{
|
||||
unsigned char ddr = portB.GetDirection();
|
||||
unsigned char value = (unsigned char)((portB.GetInput() & ~ddr) | (portB.GetOutput() & ddr));
|
||||
return value;
|
||||
}
|
||||
|
||||
inline void WritePortB(unsigned char value)
|
||||
{
|
||||
portB.SetOutput(value);
|
||||
}
|
||||
|
||||
inline unsigned char ReadPortA()
|
||||
{
|
||||
unsigned char ddr = portA.GetDirection();
|
||||
unsigned char value = (unsigned char)((portA.GetInput() & ~ddr) | (portA.GetOutput() & ddr));
|
||||
return value;
|
||||
}
|
||||
|
||||
inline unsigned char PeekPortA()
|
||||
{
|
||||
unsigned char ddr = portA.GetDirection();
|
||||
unsigned char value = (unsigned char)((portA.GetInput() & ~ddr) | (portA.GetOutput() & ddr));
|
||||
return value;
|
||||
}
|
||||
|
||||
inline void WritePortA(unsigned char value)
|
||||
{
|
||||
portA.SetOutput(value);
|
||||
}
|
||||
|
||||
inline unsigned char PeekPortB()
|
||||
{
|
||||
unsigned char ddr = portB.GetDirection();
|
||||
unsigned char value = (unsigned char)((portB.GetInput() & ~ddr) | (portB.GetOutput() & ddr));
|
||||
return value;
|
||||
}
|
||||
|
||||
inline void SetInterrupt(unsigned char flag)
|
||||
{
|
||||
if (!(ICRData & flag))
|
||||
{
|
||||
ICRData |= flag;
|
||||
//DEBUG_LOG("Setting IRQ %02x\r\n", flag);
|
||||
OutputIRQ();
|
||||
}
|
||||
}
|
||||
|
||||
inline void ClearInterrupt(unsigned char flag)
|
||||
{
|
||||
if (ICRData & flag)
|
||||
{
|
||||
ICRData &= ~flag;
|
||||
//DEBUG_LOG("Clearing IRQ %02x %02x\r\n", flag, ICRData);
|
||||
OutputIRQ();
|
||||
}
|
||||
}
|
||||
|
||||
inline void OutputIRQ()
|
||||
{
|
||||
// Any interrupt which is enabled by the MASK register will set the IR bit(MSB) of the DATA register and bring the IRQ pin low.
|
||||
if (ICRMask & ICRData & (IR_FLG | IR_SDR | IR_TOD | IR_TB | IR_TA))
|
||||
{
|
||||
if ((ICRData & IR_SET) == 0)
|
||||
{
|
||||
ICRData |= IR_SET;
|
||||
if (irq) irq->Assert();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ICRData & IR_SET)
|
||||
{
|
||||
//DEBUG_LOG("Releasing IRQ %02x\r\n", ICRData);
|
||||
ICRData &= ~IR_SET;
|
||||
if (irq) irq->Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void ReloadTimerA()
|
||||
{
|
||||
timerACounter = timerALatch;
|
||||
timerAReloaded = true;
|
||||
}
|
||||
|
||||
inline void ReloadTimerB()
|
||||
{
|
||||
timerBCounter = timerBLatch;
|
||||
timerBReloaded = true;
|
||||
}
|
||||
|
||||
Interrupt* irq;
|
||||
|
||||
IOPort portA;
|
||||
IOPort portB;
|
||||
|
||||
unsigned char ICRMask;
|
||||
unsigned char ICRData;
|
||||
|
||||
unsigned char CRARegister;
|
||||
unsigned char CRBRegister;
|
||||
|
||||
u32 PCAsserted;
|
||||
bool FLAGPin;
|
||||
bool CNTPin;
|
||||
bool CNTPinOld;
|
||||
bool SPPin;
|
||||
bool TODPin;
|
||||
|
||||
unsigned short timerACounter;
|
||||
unsigned short timerALatch;
|
||||
bool timerAActive;
|
||||
bool timerAOutputOnPB6;
|
||||
bool timerAToggle;
|
||||
bool timerAOneShot;
|
||||
TimerAMode timerAMode;
|
||||
bool timerA50Hz;
|
||||
bool ta_pb6;
|
||||
bool timerAReloaded;
|
||||
|
||||
unsigned short timerBCounter;
|
||||
unsigned short timerBLatch;
|
||||
bool timerBActive;
|
||||
bool timerBOutputOnPB7;
|
||||
bool timerBToggle;
|
||||
bool timerBOneShot;
|
||||
TimerBMode timerBMode;
|
||||
bool timerBAlarm;
|
||||
bool tb_pb7;
|
||||
bool timerBReloaded;
|
||||
|
||||
bool TODActive;
|
||||
unsigned TODAlarm;
|
||||
unsigned TODClock;
|
||||
unsigned TODLatch;
|
||||
|
||||
SerialPortMode serialPortMode;
|
||||
unsigned char serialPortRegister;
|
||||
unsigned char serialShiftRegister;
|
||||
unsigned serialBitsShiftedSoFar;
|
||||
bool serialShiftingEnabled;
|
||||
//unsigned timerATimeOutCount;
|
||||
};
|
||||
|
||||
#endif
|
848
src/main.cpp
848
src/main.cpp
File diff suppressed because it is too large
Load diff
|
@ -170,6 +170,7 @@ Options::Options(void)
|
|||
ROMNameSlot6[0] = 0;
|
||||
ROMNameSlot7[0] = 0;
|
||||
ROMNameSlot8[0] = 0;
|
||||
ROMName1581[0] = 0;
|
||||
}
|
||||
|
||||
#define ELSE_CHECK_DECIMAL_OPTION(Name) \
|
||||
|
@ -265,6 +266,10 @@ void Options::Process(char* buffer)
|
|||
else if (strcasecmp(pValue, "sh1106_128x64") == 0)
|
||||
i2cLcdModel = LCD_1106_128x64;
|
||||
}
|
||||
else if ((strcasecmp(pOption, "ROM1581") == 0))
|
||||
{
|
||||
strncpy(ROMName1581, pValue, 255);
|
||||
}
|
||||
else if ((strcasecmp(pOption, "ROM") == 0) || (strcasecmp(pOption, "ROM1") == 0))
|
||||
{
|
||||
strncpy(ROMName, pValue, 255);
|
||||
|
@ -344,3 +349,9 @@ const char* Options::GetRomName(int index) const
|
|||
}
|
||||
return ROMName;
|
||||
}
|
||||
|
||||
const char* Options::GetRomName1581() const
|
||||
{
|
||||
return ROMName1581;
|
||||
}
|
||||
|
||||
|
|
|
@ -51,6 +51,7 @@ public:
|
|||
inline const char* GetAutoMountImageName() const { return autoMountImageName; }
|
||||
inline const char* GetRomFontName() const { return ROMFontName; }
|
||||
const char* GetRomName(int index) const;
|
||||
const char* GetRomName1581() const;
|
||||
inline const char* GetStarFileName() const { return starFileName; }
|
||||
inline unsigned int GetExtraRAM() const { return extraRAM; }
|
||||
inline unsigned int GetRAMBOard() const { return RAMBOard; }
|
||||
|
@ -163,5 +164,6 @@ private:
|
|||
char ROMNameSlot6[256];
|
||||
char ROMNameSlot7[256];
|
||||
char ROMNameSlot8[256];
|
||||
char ROMName1581[256];
|
||||
};
|
||||
#endif
|
||||
|
|
1402
src/wd177x.cpp
Normal file
1402
src/wd177x.cpp
Normal file
File diff suppressed because it is too large
Load diff
265
src/wd177x.h
Normal file
265
src/wd177x.h
Normal file
|
@ -0,0 +1,265 @@
|
|||
// Pi1541 - A Commodore 1541 disk drive emulator
|
||||
// Copyright(C) 2018 Stephen White
|
||||
//
|
||||
// This file is part of Pi1541.
|
||||
//
|
||||
// Pi1541 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.
|
||||
//
|
||||
// Pi1541 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 Pi1541. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#ifndef WD177X_H
|
||||
#define WD177X_H
|
||||
|
||||
#include "IOPort.h"
|
||||
#include "DiskImage.h"
|
||||
#include "m6502.h"
|
||||
|
||||
//32x 4e
|
||||
// For 10 sectors
|
||||
// 12x 00 // SYNC
|
||||
// 3x a1
|
||||
// 1x fe // ID
|
||||
// 1x TrackNo (0 indexed)
|
||||
// 1x Head
|
||||
// 1x sector (1 indexed)
|
||||
// 1x 02 (sector size)
|
||||
// 1x crc high byte
|
||||
// 1x crc low byte
|
||||
// 22x 4e (gap2)
|
||||
// For 2 loops (logical sectors to physical sectors)
|
||||
// if first loop
|
||||
// 12x 00 // SYNC
|
||||
// 3x a1
|
||||
// 1x fb // Data mark
|
||||
// endif
|
||||
// 256x data (indexed sequentailly through D81)
|
||||
// end for
|
||||
// 1x crc high byte
|
||||
// 1x crc low byte
|
||||
// 35x 4e (gap3)
|
||||
// end for
|
||||
|
||||
//PHYSICAL:
|
||||
// Cylinders 0 thru 79
|
||||
// Sectors 1 thru 10 on Side 1
|
||||
// Sectors 1 thru 10 on Side 2
|
||||
// Sector Size 512
|
||||
//LOGICAL :
|
||||
// Tracks 1 thru 80
|
||||
// Sectors 0 thru 39 (Using physical Sectors 1 ... 10 - Side 1 and 2)
|
||||
// Sector Size 256 Bytes
|
||||
|
||||
|
||||
// This emulator emulates at a command level and does not go into emulating actual micro controller instructions.
|
||||
|
||||
#define CYCLES_8Mhz_PER_INDEX_HOLE 64
|
||||
|
||||
class WD177x
|
||||
{
|
||||
|
||||
enum Registers
|
||||
{
|
||||
STATUS = 0,
|
||||
COMMAND = 0,
|
||||
TRACK,
|
||||
SECTOR,
|
||||
DATA
|
||||
};
|
||||
|
||||
enum StatusBits
|
||||
{
|
||||
// This bit is 1 when the 177x is busy. This bit is 0 when the 177x is free for CPU commands.
|
||||
BUSY = 0x01, // 0
|
||||
|
||||
// For Type I commands, this bit is high during the index pulse that occurs once per disk rotation low otherwise.
|
||||
// For Type II and III commands, high signals the CPU to handle the data register in order to maintain a continuous flow of data.
|
||||
// High when the data register is full during a read or when the data register is empty during a write.
|
||||
INDEX_DATAREQUEST = 0x02, // 1
|
||||
|
||||
// For Type I commands, this bit is 0 if the mechanism is at track zero and 1 if the head is not.
|
||||
// For Type II or III commands, this bit is 1 if the CPU did not respond to Data Request (INDEX_DATAREQUEST) in time for the 177x to maintain a continuous data flow.
|
||||
TRACKZERO_LOSTDATA = 0x04, // 2
|
||||
|
||||
CRC_ERROR = 0x08, // 3
|
||||
|
||||
// This bit is set if the 177x cannot find the track, sector, or side.
|
||||
SEEK_ERROR = 0x10, // 4
|
||||
|
||||
// For Type I commands, this bit is low during the 6 - revolution motor spin - up time.
|
||||
// For Type II and Type III commands, low indicates a normal data mark, high indicates a deleted data mark.
|
||||
SPINUP_DATAMARK = 0x20, // 5
|
||||
|
||||
WRITE_PROTECT = 0x40, // 6
|
||||
MOTOR_ON = 0x80 // 7
|
||||
};
|
||||
|
||||
// Command Bit 7 B6 B5 B4 B3 B2 B1 Bit 0
|
||||
// -------- ----- -- -- -- -- -- -- -----
|
||||
// Restore 0 0 0 0 h V r1 r0
|
||||
// Seek 0 0 0 1 h V r1 r0
|
||||
// Step 0 0 1 u h V r1 r0
|
||||
// Step in 0 1 0 u h V r1 r0
|
||||
// Step out 0 1 1 u h V r1 r0
|
||||
|
||||
enum Commands
|
||||
{
|
||||
RESTORE = 0,
|
||||
SEEK = 1,
|
||||
STEP = 2,
|
||||
STEP_IN = 4,
|
||||
STEP_OUT = 6,
|
||||
|
||||
READ_SECTOR = 8,
|
||||
WRITE_SECTOR = 0xa,
|
||||
|
||||
READ_ADDRESS = 0xc,
|
||||
|
||||
FORCE_INTERRUPT = 0xd,
|
||||
|
||||
READ_TRACK = 0xe,
|
||||
WRITE_TRACK = 0xf,
|
||||
};
|
||||
|
||||
enum CommandBit
|
||||
{
|
||||
COMMANDBIT_A = 0x01,
|
||||
COMMANDBIT_H = 0x08,
|
||||
COMMANDBIT_U = 0x10,
|
||||
COMMANDBIT_E = 0x04,
|
||||
COMMANDBIT_M = 0x10
|
||||
};
|
||||
|
||||
enum CommandSubState
|
||||
{
|
||||
SEARCHING_FOR_NEXT_ID,
|
||||
WAIT_FOR_TRACK,
|
||||
WAIT_FOR_SIDE,
|
||||
WAIT_FOR_SECTOR,
|
||||
WAIT_FOR_SECTOR_LENGTH,
|
||||
SEARCHING_FOR_NEXT_DATAID = WAIT_FOR_SECTOR_LENGTH,
|
||||
WAIT_FOR_CRC1,
|
||||
WAIT_FOR_CRC2,
|
||||
READING_SECTOR,
|
||||
WRITING_SECTOR
|
||||
};
|
||||
|
||||
public:
|
||||
|
||||
WD177x();
|
||||
|
||||
inline void ConnectIRQ(Interrupt* irq) { this->irq = irq; }
|
||||
|
||||
void Insert(DiskImage* diskImage);
|
||||
|
||||
void Reset();
|
||||
|
||||
void Execute();
|
||||
|
||||
unsigned char Read(unsigned int address);
|
||||
unsigned char Peek(unsigned int address);
|
||||
void Write(unsigned int address, unsigned char value);
|
||||
|
||||
inline bool GetDIRECTIONPin() const { return stepDirection > 0; }
|
||||
//bool GetSTEPPin() const { return STEPPulse; }
|
||||
inline bool GetTR00Pin() const { return !(currentTrack == 0); } // active low
|
||||
// IP is the index pulse
|
||||
inline bool GetIPPin() const { return !(rotationCycle < CYCLES_8Mhz_PER_INDEX_HOLE); } // active low
|
||||
//bool GetDRQPin() const { return ; } // This active high output indicates that the Data Register is full(on a Read) or empty(on a Write operation).
|
||||
bool GetWPRTPin() const; // active low
|
||||
void SetWPRTPin(bool value); // active low
|
||||
|
||||
inline unsigned int GetCurrentTrack() const { return currentTrack; }
|
||||
|
||||
// void SetSide(int side) { currentSide = (side ^ 1); }
|
||||
void SetSide(int side) { currentSide = side; }
|
||||
void AssertExternalMotor(bool assert);
|
||||
inline bool IsExternalMotorAsserted() const { return externalMotorAsserted; }
|
||||
|
||||
private:
|
||||
bool SpinUp();
|
||||
void SpinDown();
|
||||
|
||||
void UpdateCommandType1();
|
||||
void UpdateCommandType2();
|
||||
void UpdateCommandType3();
|
||||
void UpdateCommandType4();
|
||||
void CommandComplete();
|
||||
|
||||
bool CheckForSeekError()
|
||||
{
|
||||
// SEEK_ERROR after 5 disk revolutions without finding track/sector
|
||||
return rotationCountForSeekError > 5;
|
||||
}
|
||||
|
||||
//void UpdateReadWriteByte();
|
||||
bool ReadByte(unsigned char& byte, bool& syncByte);
|
||||
|
||||
void AssertIRQ();
|
||||
void ReleaseIRQ();
|
||||
|
||||
void SetDataRegister(unsigned char byteRead);
|
||||
//inline void SetDataRegister(unsigned char byteRead)
|
||||
//{
|
||||
// if (statusRegister & INDEX_DATAREQUEST) // If the CPU has not read the last value yet
|
||||
// statusRegister |= TRACKZERO_LOSTDATA; // then it missed the bus.
|
||||
// statusRegister |= INDEX_DATAREQUEST;
|
||||
// dataRegister = byteRead;
|
||||
//}
|
||||
unsigned char GetDataRegister();
|
||||
|
||||
unsigned char trackRegister;
|
||||
unsigned char sectorRegister;
|
||||
unsigned char statusRegister;
|
||||
unsigned char dataRegister;
|
||||
unsigned char currentByteRead;
|
||||
unsigned char sectorFromHeader;
|
||||
|
||||
unsigned char commandRegister;
|
||||
unsigned char commandRegisterPrevious;
|
||||
unsigned char commandType;
|
||||
|
||||
unsigned int motorSpinCount;
|
||||
|
||||
int currentTrack;
|
||||
int currentSide;
|
||||
bool externalMotorAsserted;
|
||||
bool writeProtectAsserted;
|
||||
|
||||
int stepDirection;
|
||||
|
||||
unsigned char command;
|
||||
unsigned char commandValue;
|
||||
unsigned int commandStage;
|
||||
|
||||
unsigned int headDataOffset;
|
||||
bool lastByteWasASync;
|
||||
|
||||
DiskImage* diskImage;
|
||||
|
||||
unsigned int rotationCountForSeekError;
|
||||
unsigned int rotationCycle;
|
||||
unsigned int byteRotationCycle;
|
||||
unsigned int inactiveRotationCount;
|
||||
unsigned int settleCycleDelay;
|
||||
|
||||
unsigned int readAddressState;
|
||||
unsigned int sectorByteIndex;
|
||||
|
||||
Interrupt* irq;
|
||||
|
||||
int delayTimer;
|
||||
|
||||
unsigned short crc;
|
||||
unsigned char dataAddressMark;
|
||||
};
|
||||
|
||||
#endif
|
Loading…
Reference in a new issue