2011-09-08 01:07:06 +00:00
|
|
|
//TODO: save state must record whether the drive board is active (if we load a state with inactive drive board while drive board is active, should
|
|
|
|
// disable currently active drive board. Perhaps this should be done in Model3.cpp?
|
2011-09-07 07:06:40 +00:00
|
|
|
#include "Supermodel.h"
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <math.h>
|
|
|
|
|
|
|
|
bool CDriveBoard::IsAttached(void)
|
|
|
|
{
|
|
|
|
return m_attached;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CDriveBoard::IsSimulated(void)
|
|
|
|
{
|
|
|
|
return m_simulated;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CDriveBoard::GetDIPSwitches(UINT8 &dip1, UINT8 &dip2)
|
|
|
|
{
|
|
|
|
dip1 = m_dip1;
|
|
|
|
dip2 = m_dip2;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CDriveBoard::SetDIPSwitches(UINT8 dip1, UINT8 dip2)
|
|
|
|
{
|
|
|
|
m_dip1 = dip1;
|
|
|
|
m_dip2 = dip2;
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned CDriveBoard::GetSteeringStrength()
|
|
|
|
{
|
|
|
|
return ((~(m_dip1>>2))&7) + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CDriveBoard::SetSteeringStrength(unsigned steeringStrength)
|
|
|
|
{
|
|
|
|
m_dip1 = (m_dip1&0xE3) | (((~(steeringStrength - 1))&7)<<2);
|
|
|
|
}
|
|
|
|
|
|
|
|
void CDriveBoard::Get7SegDisplays(UINT8 &seg1Digit1, UINT8 &seg1Digit2, UINT8 &seg2Digit1, UINT8 &seg2Digit2)
|
|
|
|
{
|
|
|
|
seg1Digit1 = m_seg1Digit1;
|
|
|
|
seg1Digit2 = m_seg1Digit2;
|
|
|
|
seg2Digit1 = m_seg2Digit1;
|
|
|
|
seg2Digit2 = m_seg2Digit2;
|
|
|
|
}
|
|
|
|
|
|
|
|
CZ80 *CDriveBoard::GetZ80(void)
|
|
|
|
{
|
|
|
|
return &m_z80;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CDriveBoard::SaveState(CBlockFile *SaveState)
|
|
|
|
{
|
|
|
|
SaveState->NewBlock("DriveBoard", __FILE__);
|
|
|
|
|
|
|
|
// Overall config
|
|
|
|
SaveState->Write(&m_simulated, sizeof(m_simulated));
|
|
|
|
|
|
|
|
// DIP switches and digit displays
|
|
|
|
SaveState->Write(&m_dip1, sizeof(m_dip1));
|
|
|
|
SaveState->Write(&m_dip2, sizeof(m_dip2));
|
|
|
|
//SaveState->Write(&m_seg1Digit1, sizeof(m_seg1Digit1)); // No point in saving these
|
|
|
|
//SaveState->Write(&m_seg1Digit2, sizeof(m_seg1Digit2));
|
|
|
|
//SaveState->Write(&m_seg2Digit1, sizeof(m_seg2Digit1));
|
|
|
|
//SaveState->Write(&m_seg2Digit2, sizeof(m_seg2Digit2));
|
|
|
|
|
|
|
|
// RAM
|
|
|
|
SaveState->Write(&m_ram, sizeof(m_ram));
|
|
|
|
|
|
|
|
// Board emulation state
|
|
|
|
SaveState->Write(&m_initialized, sizeof(m_initialized));
|
|
|
|
SaveState->Write(&m_allowInterrupts, sizeof(m_allowInterrupts));
|
|
|
|
SaveState->Write(&m_dataSent, sizeof(m_dataSent));
|
|
|
|
SaveState->Write(&m_dataReceived, sizeof(m_dataReceived));
|
|
|
|
SaveState->Write(&m_adcPortRead, sizeof(m_adcPortRead));
|
|
|
|
SaveState->Write(&m_adcPortBit, sizeof(m_adcPortBit));
|
|
|
|
SaveState->Write(&m_uncenterVal1, sizeof(m_uncenterVal1));
|
|
|
|
SaveState->Write(&m_uncenterVal2, sizeof(m_uncenterVal2));
|
|
|
|
|
|
|
|
// TODO - board simulation state
|
|
|
|
|
|
|
|
// CPU
|
|
|
|
m_z80.SaveState(SaveState, "DriveBoard Z80");
|
|
|
|
}
|
|
|
|
|
|
|
|
void CDriveBoard::LoadState(CBlockFile *SaveState)
|
|
|
|
{
|
|
|
|
if (SaveState->FindBlock("DriveBoard") != OKAY)
|
|
|
|
{
|
|
|
|
ErrorLog("Unable to load DriveBoard state. Save state file is corrupted.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
SaveState->Read(&m_simulated, sizeof(m_simulated));
|
|
|
|
|
|
|
|
SaveState->Read(&m_dip1, sizeof(m_dip1));
|
|
|
|
SaveState->Read(&m_dip2, sizeof(m_dip2));
|
|
|
|
//SaveState->Read(&m_seg1Digit1, sizeof(m_seg1Digit1));
|
|
|
|
//SaveState->Read(&m_seg1Digit2, sizeof(m_seg1Digit2));
|
|
|
|
//SaveState->Read(&m_seg2Digit1, sizeof(m_seg2Digit1));
|
|
|
|
//SaveState->Read(&m_seg2Digit2, sizeof(m_seg2Digit2));
|
|
|
|
|
|
|
|
SaveState->Read(&m_ram, sizeof(m_ram));
|
|
|
|
|
|
|
|
SaveState->Read(&m_initialized, sizeof(m_initialized));
|
|
|
|
SaveState->Read(&m_allowInterrupts, sizeof(m_allowInterrupts));
|
|
|
|
SaveState->Read(&m_dataSent, sizeof(m_dataSent));
|
|
|
|
SaveState->Read(&m_dataReceived, sizeof(m_dataReceived));
|
|
|
|
SaveState->Read(&m_adcPortRead, sizeof(m_adcPortRead));
|
|
|
|
SaveState->Read(&m_adcPortBit, sizeof(m_adcPortBit));
|
|
|
|
SaveState->Read(&m_uncenterVal1, sizeof(m_uncenterVal1));
|
|
|
|
SaveState->Read(&m_uncenterVal2, sizeof(m_uncenterVal2));
|
|
|
|
|
|
|
|
m_z80.LoadState(SaveState, "DriveBoard Z80");
|
|
|
|
|
|
|
|
SendStopAll();
|
|
|
|
}
|
|
|
|
|
|
|
|
BOOL CDriveBoard::Init(const UINT8 *romPtr)
|
|
|
|
{
|
2011-09-08 01:07:06 +00:00
|
|
|
// Assign ROM (note that the ROM data has not yet been loaded)
|
2011-09-07 07:06:40 +00:00
|
|
|
m_rom = romPtr;
|
|
|
|
|
|
|
|
// Check have a valid ROM and force feedback is enabled
|
2011-09-08 01:07:06 +00:00
|
|
|
m_attached = m_rom && g_Config.forceFeedback;
|
2011-09-07 07:06:40 +00:00
|
|
|
if (!m_attached)
|
|
|
|
return OKAY;
|
|
|
|
|
|
|
|
// Allocate memory for RAM
|
|
|
|
m_ram = new (std::nothrow) UINT8[RAM_SIZE];
|
|
|
|
if (NULL == m_ram)
|
|
|
|
{
|
|
|
|
float ramSizeMB = (float)RAM_SIZE/(float)0x100000;
|
|
|
|
return ErrorLog("Insufficient memoy for drive board (needs %1.1f MB).", ramSizeMB);
|
|
|
|
}
|
|
|
|
memset(m_ram, 0, RAM_SIZE);
|
|
|
|
|
|
|
|
// Configure options
|
|
|
|
m_simulated = g_Config.simulateDrvBoard;
|
|
|
|
SetSteeringStrength(g_Config.steeringStrength);
|
|
|
|
|
|
|
|
// Initialize Z80
|
|
|
|
m_z80.Init(this, NULL);
|
|
|
|
|
|
|
|
return OKAY;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CDriveBoard::AttachInputs(CInputs *InputsPtr, unsigned gameInputFlags)
|
|
|
|
{
|
|
|
|
m_inputs = InputsPtr;
|
|
|
|
m_inputFlags = gameInputFlags;
|
|
|
|
|
|
|
|
DebugLog("DriveBoard attached inputs\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
void CDriveBoard::Reset(void)
|
|
|
|
{
|
|
|
|
m_initialized = false;
|
|
|
|
m_allowInterrupts = false;
|
|
|
|
|
|
|
|
m_seg1Digit1 = 0xFF;
|
|
|
|
m_seg1Digit2 = 0xFF;
|
|
|
|
m_seg2Digit1 = 0xFF;
|
|
|
|
m_seg2Digit2 = 0xFF;
|
|
|
|
|
|
|
|
m_dataSent = 0;
|
|
|
|
m_dataReceived = 0;
|
|
|
|
m_adcPortRead = 0;
|
|
|
|
m_adcPortBit = 0;
|
|
|
|
m_port42Out = 0;
|
|
|
|
m_port46Out = 0;
|
|
|
|
m_prev42Out = 0;
|
|
|
|
m_prev46Out = 0;
|
|
|
|
|
|
|
|
m_initState = 0;
|
|
|
|
m_boardMode = 0;
|
|
|
|
m_readMode = 0;
|
|
|
|
m_wheelCenter = 0x80;
|
|
|
|
m_uncenterVal1 = 0;
|
|
|
|
m_uncenterVal2 = 0;
|
|
|
|
|
|
|
|
m_lastConstForce = 0;
|
|
|
|
m_lastSelfCenter = 0;
|
|
|
|
m_lastFriction = 0;
|
|
|
|
m_lastVibrate = 0;
|
|
|
|
|
|
|
|
if (!m_simulated)
|
|
|
|
m_z80.Reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
UINT8 CDriveBoard::Read(void)
|
|
|
|
{
|
|
|
|
// TODO - simulate initialization sequence even when emulating to get rid of long pause at boot up (drive board can
|
|
|
|
// carry on booting whilst game starts)
|
|
|
|
if (m_simulated)
|
|
|
|
return SimulateRead();
|
|
|
|
else
|
|
|
|
return m_dataReceived;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CDriveBoard::Write(UINT8 data)
|
|
|
|
{
|
|
|
|
if (data >= 0x01 && data <= 0x0F ||
|
|
|
|
data >= 0x20 && data <= 0x2F ||
|
|
|
|
data >= 0x30 && data <= 0x3F ||
|
|
|
|
data >= 0x40 && data <= 0x4F ||
|
|
|
|
data >= 0x70 && data <= 0x7F)
|
|
|
|
printf("DriveBoard.Write(%02X)\n", data);
|
|
|
|
if (m_simulated)
|
|
|
|
SimulateWrite(data);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
m_dataSent = data;
|
|
|
|
if (data == 0xCB)
|
|
|
|
m_initialized = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
UINT8 CDriveBoard::SimulateRead(void)
|
|
|
|
{
|
|
|
|
if (m_initialized)
|
|
|
|
{
|
|
|
|
switch (m_readMode)
|
|
|
|
{
|
|
|
|
case 0x0: return m_statusFlags; // Status flags
|
|
|
|
case 0x1: return m_dip1; // DIP switch 1 value
|
|
|
|
case 0x2: return m_dip2; // DIP switch 2 value
|
|
|
|
case 0x3: return m_wheelCenter; // Wheel center
|
|
|
|
case 0x4: return 0x80; // Cockpit banking center
|
|
|
|
case 0x5: return m_inputs->steering->value; // Wheel position
|
|
|
|
case 0x6: return 0x80; // Cockpit banking position
|
|
|
|
case 0x7: return m_echoVal; // Init status/echo test
|
|
|
|
default: return 0xFF;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
switch (m_initState / 5)
|
|
|
|
{
|
|
|
|
case 0: return 0xCF; // Initiate start
|
|
|
|
case 1: return 0xCE;
|
|
|
|
case 2: return 0xCD;
|
|
|
|
case 3: return 0xCC; // Centering wheel
|
|
|
|
default:
|
|
|
|
m_initialized = true;
|
|
|
|
return 0x80;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CDriveBoard::SimulateWrite(UINT8 cmd)
|
|
|
|
{
|
|
|
|
// Following are commands for Scud Race. Daytona 2 has a compatible command set while Sega Rally 2 is completely different
|
|
|
|
// TODO - finish for Scud Race and Daytona 2
|
|
|
|
// TODO - implement for Sega Rally 2
|
|
|
|
UINT8 type = cmd>>4;
|
|
|
|
UINT8 val = cmd&0xF;
|
|
|
|
switch (type)
|
|
|
|
{
|
|
|
|
case 0: // 0x00-0F Play sequence
|
|
|
|
/* TODO */
|
|
|
|
break;
|
|
|
|
case 1: // 0x10-1F Set centering strength
|
|
|
|
if (val == 0)
|
|
|
|
// Disable auto-centering
|
|
|
|
// TODO - is 0x10 for disable?
|
|
|
|
SendSelfCenter(0);
|
|
|
|
else
|
|
|
|
// Enable auto-centering (0x1 = weakest, 0xF = strongest)
|
|
|
|
SendSelfCenter(val * 0x11);
|
|
|
|
break;
|
|
|
|
case 2: // 0x20-2F Friction strength
|
|
|
|
if (val == 0)
|
|
|
|
// Disable friction
|
|
|
|
// TODO - is 0x20 for disable?
|
|
|
|
SendFriction(0);
|
|
|
|
else
|
|
|
|
// Enable friction (0x1 = weakest, 0xF = strongest)
|
|
|
|
SendFriction(val * 0x11);
|
|
|
|
break;
|
|
|
|
case 3: // 0x30-3F Uncentering (vibrate)
|
|
|
|
if (val == 0)
|
|
|
|
// Disable uncentering
|
|
|
|
SendVibrate(0);
|
|
|
|
else
|
|
|
|
// Enable uncentering (0x1 = weakest, 0xF = strongest)
|
|
|
|
SendVibrate(val * 0x11);
|
|
|
|
break;
|
|
|
|
case 4: // 0x40-4F Play power-slide sequence
|
|
|
|
/* TODO */
|
|
|
|
break;
|
|
|
|
case 5: // 0x50-5F Rotate wheel right
|
|
|
|
SendConstantForce((val + 1) * 0x5);
|
|
|
|
break;
|
|
|
|
case 6: // 0x60-6F Rotate wheel left
|
|
|
|
SendConstantForce(-(val + 1) * 0x5);
|
|
|
|
break;
|
|
|
|
case 7: // 0x70-7F Set steering parameters
|
|
|
|
/* TODO */
|
|
|
|
break;
|
|
|
|
case 8: // 0x80-8F Test Mode
|
|
|
|
switch (val&0x7)
|
|
|
|
{
|
|
|
|
case 0: SendStopAll(); break; // 0x80 Stop motor
|
|
|
|
case 1: SendConstantForce(20); break; // 0x81 Roll wheel right
|
|
|
|
case 2: SendConstantForce(-20); break; // 0x82 Roll wheel left
|
|
|
|
case 3: /* Ignore - no clutch */ break; // 0x83 Clutch on
|
|
|
|
case 4: /* Ignore - no clutch */ break; // 0x84 Clutch off
|
|
|
|
case 5: m_wheelCenter = m_inputs->steering->value; break; // 0x85 Set wheel center position
|
|
|
|
case 6: /* Ignore */ break; // 0x86 Set cockpit banking position
|
|
|
|
case 7: /* Ignore */ break; // 0x87 Lamp on/off
|
|
|
|
}
|
|
|
|
case 0x9: // 0x90-9F ??? Don't appear to have any effect with Scud Race ROM
|
|
|
|
/* TODO */
|
|
|
|
break;
|
|
|
|
case 0xA: // 0xA0-AF ??? Don't appear to have any effect with Scud Race ROM
|
|
|
|
/* TODO */
|
|
|
|
break;
|
|
|
|
case 0xB: // 0xB0-BF Invalid command (reserved for use by PPC to send cabinet type 0xB0 or 0xB1 during initialization)
|
|
|
|
/* Ignore */
|
|
|
|
break;
|
|
|
|
case 0xC: // 0xC0-CF Set board mode (0xCB = reset board)
|
|
|
|
SendStopAll();
|
|
|
|
if (val >= 0xB)
|
|
|
|
{
|
|
|
|
// Reset board
|
|
|
|
m_initialized = false;
|
|
|
|
m_initState = 0;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
m_boardMode = val;
|
|
|
|
break;
|
|
|
|
case 0xD: // 0xD0-DF Set read mode
|
|
|
|
m_readMode = val&0x7;
|
|
|
|
break;
|
|
|
|
case 0xE: // 0xE0-EF Invalid command
|
|
|
|
/* Ignore */
|
|
|
|
break;
|
|
|
|
case 0xF: // 0xF0-FF Echo test
|
|
|
|
m_echoVal = val;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CDriveBoard::RunFrame(void)
|
|
|
|
{
|
|
|
|
if (m_simulated)
|
|
|
|
SimulateFrame();
|
|
|
|
else
|
|
|
|
EmulateFrame();
|
|
|
|
}
|
|
|
|
|
|
|
|
void CDriveBoard::SimulateFrame(void)
|
|
|
|
{
|
|
|
|
if (!m_initialized)
|
|
|
|
m_initState++;
|
|
|
|
// TODO - update m_statusFlags and play preset scripts according to board mode
|
|
|
|
}
|
|
|
|
|
|
|
|
void CDriveBoard::EmulateFrame(void)
|
|
|
|
{
|
|
|
|
// Assuming Z80 runs @ 4.0MHz and NMI triggers @ 60.0KHz
|
|
|
|
// TODO - find out if Z80 frequency is correct and exact frequency of NMI interrupts (just guesswork at the moment!)
|
|
|
|
int cycles = 4.0 * 1000000 / 60;
|
|
|
|
int loopCycles = 10000;
|
|
|
|
while (cycles > 0)
|
|
|
|
{
|
|
|
|
if (m_allowInterrupts)
|
|
|
|
m_z80.TriggerNMI();
|
|
|
|
cycles -= m_z80.Run(min<int>(loopCycles, cycles));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
UINT8 CDriveBoard::Read8(UINT32 addr)
|
|
|
|
{
|
|
|
|
if (addr < 0x9000) // ROM is 0x0000-0x8FFF
|
|
|
|
return m_rom[addr];
|
|
|
|
else if (addr >= 0xE000) // RAM is 0xE000-0xFFFF
|
|
|
|
return m_ram[addr-0xE000];
|
|
|
|
else
|
|
|
|
{
|
|
|
|
printf("Unhandled Z80 read of %08X (at PC = %04X)\n", addr, m_z80.GetPC());
|
|
|
|
return 0xFF;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CDriveBoard::Write8(UINT32 addr, UINT8 data)
|
|
|
|
{
|
|
|
|
if (addr >= 0xE000) // ROM is 0x0000-0x8FFF
|
|
|
|
m_ram[addr-0xE000] = data;
|
|
|
|
else
|
|
|
|
printf("Unhandled Z80 write to %08X (at PC = %04X)\n", addr, m_z80.GetPC());
|
|
|
|
}
|
|
|
|
|
|
|
|
UINT8 CDriveBoard::IORead8(UINT32 portNum)
|
|
|
|
{
|
|
|
|
UINT8 adcVal;
|
|
|
|
switch (portNum)
|
|
|
|
{
|
|
|
|
case 32: // DIP 1 value
|
|
|
|
return m_dip1;
|
|
|
|
case 33: // DIP 2 value
|
|
|
|
return m_dip2;
|
|
|
|
case 36: // ADC channel 1 - not connected
|
|
|
|
case 37: // ADC channel 2 - steering wheel position (0x00 = full left, 0x80 = center, 0xFF = full right)
|
|
|
|
case 38: // ADC channel 3 - cockpit bank position (deluxe cabinets) (0x00 = full left, 0x80 = center, 0xFF = full right)
|
|
|
|
case 39: // ADC channel 4 - not connected
|
|
|
|
if (portNum == m_adcPortRead && m_adcPortBit-- > 0)
|
|
|
|
{
|
|
|
|
switch (portNum)
|
|
|
|
{
|
|
|
|
case 36: // Not connected
|
|
|
|
adcVal = 0x00;
|
|
|
|
break;
|
|
|
|
case 37: // Steering wheel for twin racing cabinets - TODO - check actual range of steering, suspect it is not really 0x00-0xFF
|
|
|
|
if (m_initialized)
|
|
|
|
adcVal = (UINT8)m_inputs->steering->value;
|
|
|
|
else
|
|
|
|
adcVal = 0x80; // If not initialized, return 0x80 so that wheel centering test does not fail
|
|
|
|
break;
|
|
|
|
case 38: // Cockpit bank position for deluxe racing cabinets
|
|
|
|
adcVal = 0x80;
|
|
|
|
break;
|
|
|
|
case 39: // Not connected
|
|
|
|
adcVal = 0x00;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
printf("Unhandled Z80 input on ADC port %u (at PC = %04X)\n", portNum, m_z80.GetPC());
|
|
|
|
return 0xFF;
|
|
|
|
}
|
|
|
|
return (adcVal>>m_adcPortBit)&0x01;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
printf("Unhandled Z80 input on ADC port %u (at PC = %04X)\n", portNum, m_z80.GetPC());
|
|
|
|
return 0xFF;
|
|
|
|
}
|
|
|
|
case 40: // PPC command
|
|
|
|
return m_dataSent;
|
|
|
|
case 44: // Encoder error reporting (kept at 0x00 for no error)
|
|
|
|
// Bits 0 & 1 clear = no error
|
|
|
|
// Bit 1 set =
|
|
|
|
// Bit 2 set =
|
|
|
|
// Bit 3 set =
|
|
|
|
return 0x00;
|
|
|
|
default:
|
|
|
|
printf("Unhandled Z80 input on port %u (at PC = %04X)\n", portNum, m_z80.GetPC());
|
|
|
|
return 0xFF;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CDriveBoard::IOWrite8(UINT32 portNum, UINT8 data)
|
|
|
|
{
|
|
|
|
switch (portNum)
|
|
|
|
{
|
|
|
|
case 16: // Unsure? - single byte 0x03 sent at initialization, then occasionally writes 0x07 & 0xFA to port
|
|
|
|
return;
|
|
|
|
case 17: // Interrupt control
|
|
|
|
if (data == 0x57)
|
|
|
|
m_allowInterrupts = true;
|
|
|
|
else if (data == 0x53) // Strictly speaking 0x53 then 0x04
|
|
|
|
m_allowInterrupts = false;
|
|
|
|
return;
|
|
|
|
case 28: // Unsure? - two bytes 0xFF, 0xFF sent at initialization only
|
|
|
|
case 29: // Unsure? - two bytes 0x0F, 0x17 sent at initialization only
|
|
|
|
case 30: // Unsure? - same as port 28
|
|
|
|
case 31: // Unsure? - same as port 31
|
|
|
|
return;
|
|
|
|
case 32: // Left digit of 7-segment display 1
|
|
|
|
m_seg1Digit1 = data;
|
|
|
|
return;
|
|
|
|
case 33: // Right digit of 7-segment display 1
|
|
|
|
m_seg1Digit2 = data;
|
|
|
|
return;
|
|
|
|
case 34: // Left digit of 7-segment display 2
|
|
|
|
m_seg2Digit1 = data;
|
|
|
|
return;
|
|
|
|
case 35: // Right digit of 7-segment display 2
|
|
|
|
m_seg2Digit2 = data;
|
|
|
|
return;
|
|
|
|
case 36: // ADC channel 1 control
|
|
|
|
case 37: // ADC channel 2 control
|
|
|
|
case 38: // ADC channel 3 control
|
|
|
|
case 39: // ADC channel 4 control
|
|
|
|
m_adcPortRead = portNum;
|
|
|
|
m_adcPortBit = 8;
|
|
|
|
return;
|
|
|
|
case 41: // Reply for PPC
|
|
|
|
m_dataReceived = data;
|
|
|
|
if (data == 0xCC)
|
|
|
|
m_initialized = true;
|
|
|
|
return;
|
|
|
|
case 42: // Encoder motor data
|
|
|
|
m_port42Out = data;
|
|
|
|
ProcessEncoderCmd();
|
|
|
|
return;
|
|
|
|
case 45: // Clutch/lamp control (deluxe cabinets)
|
|
|
|
return;
|
|
|
|
case 46: // Encoder motor control
|
|
|
|
m_port46Out = data;
|
|
|
|
return;
|
|
|
|
case 240: // Unsure? - single byte 0xBB sent at initialization only
|
|
|
|
return;
|
|
|
|
case 241: // Unsure? - single byte 0x4E sent regularly - some sort of watchdog?
|
|
|
|
return;
|
|
|
|
default:
|
|
|
|
printf("Unhandled Z80 output on port %u (at PC = %04X)\n", portNum, m_z80.GetPC());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CDriveBoard::ProcessEncoderCmd(void)
|
|
|
|
{
|
|
|
|
if (m_prev42Out != m_port42Out || m_prev46Out != m_port46Out)
|
|
|
|
{
|
|
|
|
printf("46 [%02X] / 42 [%02X]\n", m_port46Out, m_port42Out);
|
|
|
|
switch (m_port46Out)
|
|
|
|
{
|
|
|
|
case 0xFB:
|
|
|
|
// Friction? Sent during power slide
|
|
|
|
SendFriction(m_port42Out);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xFC:
|
|
|
|
// Centering / uncentering (vibrate)
|
|
|
|
// Bit 2 = on for centering, off for uncentering
|
|
|
|
if (m_port42Out&0x04)
|
|
|
|
{
|
|
|
|
// Centering
|
|
|
|
// Bit 7 = on for disable, off for enable
|
|
|
|
if (m_port42Out&0x80)
|
|
|
|
{
|
|
|
|
// Disable centering
|
|
|
|
SendSelfCenter(0);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Bits 3-6 = centering strength 0x0-0xF. This is scaled to range 0x0F-0xFF
|
|
|
|
UINT8 strength = ((m_port42Out&0x78)>>3) * 0x10 + 0xF;
|
|
|
|
SendSelfCenter(strength);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Uncentering
|
|
|
|
// Bits 0-1 = data sequence number 0-3
|
|
|
|
UINT8 seqNum = m_port42Out&0x03;
|
|
|
|
// Bits 4-7 = data values
|
|
|
|
UINT16 data = (m_port42Out&0xF0)>>4;
|
|
|
|
switch (seqNum)
|
|
|
|
{
|
|
|
|
case 0: m_uncenterVal1 = data<<4; break;
|
|
|
|
case 1: m_uncenterVal1 |= data; break;
|
|
|
|
case 2: m_uncenterVal2 = data<<4; break;
|
|
|
|
case 3: m_uncenterVal2 |= data; break;
|
|
|
|
}
|
|
|
|
if (seqNum == 3)
|
|
|
|
{
|
|
|
|
if (m_uncenterVal1 == 0)
|
|
|
|
{
|
|
|
|
// Disable uncentering
|
|
|
|
SendVibrate(0);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Uncentering - unsure exactly how values sent map to strength or whether they specify some other attributes of effect
|
|
|
|
// For now just attempting to map them to a sensible value in range 0x00-0xFF
|
|
|
|
UINT8 strength = ((m_uncenterVal1>>1) - 7) * 0x50 + ((m_uncenterVal2>>1) - 5) * 0x10 + 0xF;
|
|
|
|
SendVibrate(strength);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xFD:
|
|
|
|
// Unsure? Sent as velocity changes, similar to self-centering
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xFE:
|
|
|
|
// Apply constant force to wheel
|
|
|
|
// Value is: 0x80 = stop motor, 0x80-0xC0 = roll wheel left, 0x40-0x80 = roll wheel right, scale to range -0xFF-0xFF
|
|
|
|
SendConstantForce((0x80 - m_port42Out) * 4);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0xFF:
|
|
|
|
// Stop all effects
|
|
|
|
if (m_port42Out == 0)
|
|
|
|
SendStopAll();
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
printf("Unknown = 46 [%02X] / 42 [%02X]\n", m_port46Out, m_port42Out);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_prev42Out = m_port42Out;
|
|
|
|
m_prev46Out = m_port46Out;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CDriveBoard::SendStopAll(void)
|
|
|
|
{
|
|
|
|
printf(">> Stop All Effects\n");
|
|
|
|
|
|
|
|
ForceFeedbackCmd ffCmd;
|
|
|
|
ffCmd.id = FFStop;
|
|
|
|
m_inputs->steering->SendForceFeedbackCmd(ffCmd);
|
|
|
|
}
|
|
|
|
|
|
|
|
void CDriveBoard::SendConstantForce(INT8 val)
|
|
|
|
{
|
|
|
|
if (val == m_lastConstForce)
|
|
|
|
return;
|
|
|
|
if (val >= 0)
|
|
|
|
printf(">> Constant Force Right %02X\n", val);
|
|
|
|
else
|
|
|
|
printf(">> Constant Force Left %02X\n", -val);
|
|
|
|
|
|
|
|
ForceFeedbackCmd ffCmd;
|
|
|
|
ffCmd.id = FFConstantForce;
|
|
|
|
ffCmd.force = (float)val / (val >= 0 ? 127.0f : 128.0f);
|
|
|
|
m_inputs->steering->SendForceFeedbackCmd(ffCmd);
|
|
|
|
|
|
|
|
m_lastConstForce = val;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CDriveBoard::SendSelfCenter(UINT8 val)
|
|
|
|
{
|
|
|
|
if (val == m_lastSelfCenter)
|
|
|
|
return;
|
|
|
|
printf(">> Self center %02X\n", val);
|
|
|
|
|
|
|
|
ForceFeedbackCmd ffCmd;
|
|
|
|
ffCmd.id = FFSelfCenter;
|
|
|
|
ffCmd.force = (float)val / 255.0f;
|
|
|
|
m_inputs->steering->SendForceFeedbackCmd(ffCmd);
|
|
|
|
|
|
|
|
m_lastSelfCenter = val;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CDriveBoard::SendFriction(UINT8 val)
|
|
|
|
{
|
|
|
|
if (val == m_lastFriction)
|
|
|
|
return;
|
|
|
|
printf(">> Friction %02X\n", val);
|
|
|
|
|
|
|
|
ForceFeedbackCmd ffCmd;
|
|
|
|
ffCmd.id = FFFriction;
|
|
|
|
ffCmd.force = (float)val / 255.0f;
|
|
|
|
m_inputs->steering->SendForceFeedbackCmd(ffCmd);
|
|
|
|
|
|
|
|
m_lastFriction = val;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CDriveBoard::SendVibrate(UINT8 val)
|
|
|
|
{
|
|
|
|
if (val == m_lastVibrate)
|
|
|
|
return;
|
|
|
|
printf(">> Vibrate %02X\n", val);
|
|
|
|
|
|
|
|
ForceFeedbackCmd ffCmd;
|
|
|
|
ffCmd.id = FFVibrate;
|
|
|
|
ffCmd.force = (float)val / 255.0f;
|
|
|
|
m_inputs->steering->SendForceFeedbackCmd(ffCmd);
|
|
|
|
|
|
|
|
m_lastVibrate = val;
|
|
|
|
}
|
|
|
|
|
|
|
|
CDriveBoard::CDriveBoard() : m_attached(false), m_simulated(false), m_rom(NULL), m_ram(NULL), m_inputs(NULL), m_dip1(0xCF), m_dip2(0xFF)
|
|
|
|
{
|
|
|
|
DebugLog("Built Drive Board\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
CDriveBoard::~CDriveBoard(void)
|
|
|
|
{
|
|
|
|
if (m_ram != NULL)
|
|
|
|
{
|
|
|
|
delete[] m_ram;
|
|
|
|
m_ram = NULL;
|
|
|
|
}
|
|
|
|
m_rom = NULL;
|
|
|
|
m_inputs = NULL;
|
|
|
|
|
|
|
|
DebugLog("Destroyed Drive Board\n");
|
|
|
|
}
|