Added control pad to Kempston interface

This commit is contained in:
2026-04-29 16:27:13 +01:00
parent 96b06ffc4e
commit 952db4767b
5 changed files with 53 additions and 30 deletions

View File

@@ -6,7 +6,8 @@ namespace Core.Io
{ {
public class IO_Bus public class IO_Bus
{ {
public byte BorderColorIndex { get; set; } = 7; public byte BorderColourIndex { get; set; } = 7;
public byte KempstonState { get; set; } = 0x00;
public bool BeeperState { get; private set; } = false; public bool BeeperState { get; private set; } = false;
public byte[] KeyboardRows = new byte[8] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; public byte[] KeyboardRows = new byte[8] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
TapManager _tapManager = new TapManager(); TapManager _tapManager = new TapManager();
@@ -18,14 +19,13 @@ namespace Core.Io
public byte ReadPort(ushort portAddress) public byte ReadPort(ushort portAddress)
{ {
// The Spectrum ULA responds to any even port address (where the lowest bit is 0) //ULA responds to any even port address (where the lowest bit is 0)
if ((portAddress & 0x01) == 0) //Port 0xFE) if ((portAddress & 0x01) == 0) //Port 0xFE)
{ {
byte highByte = (byte)(portAddress >> 8); // The B register! byte highByte = (byte)(portAddress >> 8); // The B register
byte result = 0xFF; // Start assuming no keys are pressed byte result = 0xFF; // Start assuming no keys are pressed
// The ROM pulls a specific bit low (0) in the high byte to request a row.
// Sometimes it pulls multiple bits low to scan multiple rows at once, so we AND the results.
if ((highByte & 0x01) == 0) result &= KeyboardRows[0]; // 0xFE: CAPS, Z, X, C, V if ((highByte & 0x01) == 0) result &= KeyboardRows[0]; // 0xFE: CAPS, Z, X, C, V
if ((highByte & 0x02) == 0) result &= KeyboardRows[1]; // 0xFD: A, S, D, F, G if ((highByte & 0x02) == 0) result &= KeyboardRows[1]; // 0xFD: A, S, D, F, G
if ((highByte & 0x04) == 0) result &= KeyboardRows[2]; // 0xFB: Q, W, E, R, T if ((highByte & 0x04) == 0) result &= KeyboardRows[2]; // 0xFB: Q, W, E, R, T
@@ -37,8 +37,6 @@ namespace Core.Io
if (_tapManager.EarBit) result |= 0x40; // Set Bit 6 high if (_tapManager.EarBit) result |= 0x40; // Set Bit 6 high
else result &= 0xBF; // Set Bit 6 low else result &= 0xBF; // Set Bit 6 low
//return result;
//The top 3 bits (5, 6, 7) are unused by the keyboard and usually return 1 on a real Spectrum //The top 3 bits (5, 6, 7) are unused by the keyboard and usually return 1 on a real Spectrum
return (byte)(result | 0xA0); return (byte)(result | 0xA0);
} }
@@ -46,7 +44,7 @@ namespace Core.Io
//Kempston Joystick Port //Kempston Joystick Port
if ((portAddress & 0xFF) == 0x1F) if ((portAddress & 0xFF) == 0x1F)
{ {
return 0x00; // 0x00 means no joystick connected/no buttons pressed return KempstonState;
} }
//Return 0xFF for unhandled ports //Return 0xFF for unhandled ports
@@ -59,7 +57,7 @@ namespace Core.Io
if ((portAddress & 0x01) == 0) if ((portAddress & 0x01) == 0)
{ {
// The bottom 3 bits (0-2) define the border color // The bottom 3 bits (0-2) define the border color
BorderColorIndex = (byte)(portValue & 0x07); BorderColourIndex = (byte)(portValue & 0x07);
// Bit 4 controls the speaker // Bit 4 controls the speaker
BeeperState = (portValue & 0x10) != 0; BeeperState = (portValue & 0x10) != 0;

View File

@@ -17,7 +17,7 @@ namespace Core.Io
private int _ulaFrameCount = 0; private int _ulaFrameCount = 0;
// The hardware color palette belongs to the ULA, not the Windows Form! // The hardware color palette belongs to the ULA, not the Windows Form!
private readonly int[] SpectrumColors = new int[] private readonly int[] SpectrumColours = new int[]
{ {
unchecked((int)0xFF000000), unchecked((int)0xFF0000D7), unchecked((int)0xFF000000), unchecked((int)0xFF0000D7),
unchecked((int)0xFFD70000), unchecked((int)0xFFD700D7), unchecked((int)0xFFD70000), unchecked((int)0xFFD700D7),
@@ -59,7 +59,7 @@ namespace Core.Io
// 2. Calculate our visual Y coordinate (0 to 255) for the bitmap array // 2. Calculate our visual Y coordinate (0 to 255) for the bitmap array
int renderY = scanline - 32; int renderY = scanline - 32;
int currentBorderColor = SpectrumColors[_simpleIoBus.BorderColorIndex]; int currentBorderColor = SpectrumColours[_simpleIoBus.BorderColourIndex];
// --- Are we in the Top or Bottom Border? --- // --- Are we in the Top or Bottom Border? ---
if (scanline < 64 || scanline > 255) if (scanline < 64 || scanline > 255)
@@ -102,8 +102,8 @@ namespace Core.Io
int brightOffset = (attr & 0x40) != 0 ? 8 : 0; int brightOffset = (attr & 0x40) != 0 ? 8 : 0;
bool isFlashSet = (attr & 0x80) != 0; bool isFlashSet = (attr & 0x80) != 0;
int inkColor = SpectrumColors[ink + brightOffset]; int inkColor = SpectrumColours[ink + brightOffset];
int paperColor = SpectrumColors[paper + brightOffset]; int paperColor = SpectrumColours[paper + brightOffset];
if (isFlashSet && invertFlashPhase) if (isFlashSet && invertFlashPhase)
{ {

View File

@@ -292,7 +292,7 @@ namespace Core
// Cpu.InterruptMode = snaData[25]; // Cpu.InterruptMode = snaData[25];
// THE BUG FIX: Restore the ULA Border Color! // THE BUG FIX: Restore the ULA Border Color!
IoBus.BorderColorIndex = snaData[26]; IoBus.BorderColourIndex = snaData[26];
// 2. Load the 48K RAM Dump // 2. Load the 48K RAM Dump
for (int i = 0; i < 49152; i++) for (int i = 0; i < 49152; i++)
@@ -340,7 +340,7 @@ namespace Core
bw.Write(Cpu.AF.Low); bw.Write(Cpu.AF.High); bw.Write(Cpu.AF.Low); bw.Write(Cpu.AF.High);
bw.Write((byte)(Cpu.SP & 0xFF)); bw.Write((byte)(Cpu.SP >> 8)); bw.Write((byte)(Cpu.SP & 0xFF)); bw.Write((byte)(Cpu.SP >> 8));
bw.Write((byte)Cpu.InterruptMode); bw.Write((byte)Cpu.InterruptMode);
bw.Write(IoBus.BorderColorIndex); bw.Write(IoBus.BorderColourIndex);
for (int i = 0x4000; i <= 0xFFFF; i++) for (int i = 0x4000; i <= 0xFFFF; i++)
{ {

View File

@@ -56,6 +56,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="NAudio" Version="2.3.0" /> <PackageReference Include="NAudio" Version="2.3.0" />
<PackageReference Include="Vortice.XInput" Version="3.8.3" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -1,7 +1,7 @@
using Core; // <-- This gives us access to SpectrumMachine using Core;
using Core.Io; using Core.Io;
using System.Diagnostics;
using System.Drawing.Imaging; using System.Drawing.Imaging;
using Vortice.XInput;
using System.Reflection; using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@@ -17,12 +17,6 @@ namespace Desktop
private Bitmap _screenBitmap = null!; private Bitmap _screenBitmap = null!;
private string _baseTitle = ""; private string _baseTitle = "";
// ====================================================================
// DEBUGGER PASS-THROUGHS
// ====================================================================
// The DebuggerForm still looks at Form1 for data. These properties act
// as a bridge, instantly passing the request down to the actual engine!
public ushort? Breakpoint public ushort? Breakpoint
{ {
get => _machine?.Breakpoint; get => _machine?.Breakpoint;
@@ -84,11 +78,12 @@ namespace Desktop
private void Machine_OnFrameReady(int[] pixels) private void Machine_OnFrameReady(int[] pixels)
{ {
// We must use BeginInvoke because the Machine is running on a background thread! PollGamepad();
this.BeginInvoke((System.Windows.Forms.MethodInvoker)delegate this.BeginInvoke((System.Windows.Forms.MethodInvoker)delegate
{ {
UpdateScreenBitmap(pixels); UpdateScreenBitmap(pixels);
this.Invalidate(); // Triggers OnPaint this.Invalidate();
}); });
} }
@@ -384,6 +379,35 @@ namespace Desktop
_machine.Resume(); _machine.Resume();
} }
//Control pad method
private void PollGamepad()
{
// Only check Player 1 (Index 0)
if (XInput.GetState(0, out State state))
{
byte kempston = 0x00;
Gamepad gamepad = state.Gamepad;
// Map D-Pad to Kempston Bits
if ((gamepad.Buttons & GamepadButtons.DPadRight) != 0) kempston |= 0x01; // Bit 0
if ((gamepad.Buttons & GamepadButtons.DPadLeft) != 0) kempston |= 0x02; // Bit 1
if ((gamepad.Buttons & GamepadButtons.DPadDown) != 0) kempston |= 0x04; // Bit 2
if ((gamepad.Buttons & GamepadButtons.DPadUp) != 0) kempston |= 0x08; // Bit 3
// Map A Button (or whichever you prefer) to Fire
if ((gamepad.Buttons & GamepadButtons.A) != 0) kempston |= 0x10; // Bit 4
// Send the final byte down to the emulator core!
_machine.IoBus.KempstonState = kempston;
}
else
{
// Controller disconnected, zero it out
_machine.IoBus.KempstonState = 0x00;
}
}
} }
} }