//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? #include "Supermodel.h" #include #include 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) { // Assign ROM (note that the ROM data has not yet been loaded) m_rom = romPtr; // Check have a valid ROM and force feedback is enabled m_attached = m_rom && g_Config.forceFeedback; 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(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"); }