From 5ad20af6f34079d08fd9161e49dc9692780fcf02 Mon Sep 17 00:00:00 2001 From: Chris Needham Date: Fri, 5 Jul 2024 21:22:26 +0100 Subject: [PATCH 001/101] Disable joystick when switching model to Master ET --- Src/BeebWin.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Src/BeebWin.cpp b/Src/BeebWin.cpp index 5b1170fc..afd3a81e 100644 --- a/Src/BeebWin.cpp +++ b/Src/BeebWin.cpp @@ -802,6 +802,11 @@ void BeebWin::ResetBeebSystem(Model NewModelType, bool LoadRoms) AtoDInit(); SetRomMenu(); + if (NewModelType == Model::MasterET) + { + SetJoystickOption(JoystickOption::Disabled); + } + char szDiscName[2][MAX_PATH]; strcpy(szDiscName[0], DiscInfo[0].FileName); strcpy(szDiscName[1], DiscInfo[1].FileName); @@ -5944,6 +5949,7 @@ MessageResult BeebWin::ReportV(MessageType type, const char *format, va_list arg case MessageType::Confirm: Type = MB_ICONWARNING | MB_OKCANCEL; + break; } hCBTHook = SetWindowsHookEx(WH_CBT, CBTMessageBox, nullptr, GetCurrentThreadId()); From 82ea0d3a720297162f2b6029e1406bc86893fdec Mon Sep 17 00:00:00 2001 From: Chris Needham Date: Sat, 6 Jul 2024 12:29:43 +0100 Subject: [PATCH 002/101] Removed unused function --- Src/BeebWinIo.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Src/BeebWinIo.cpp b/Src/BeebWinIo.cpp index 5cd64805..31336155 100644 --- a/Src/BeebWinIo.cpp +++ b/Src/BeebWinIo.cpp @@ -1357,11 +1357,6 @@ void BeebWin::LoadFDC(char *DLLName, bool save) DisplayCycles = 0; } -void BeebWin::KillDLLs() -{ - Ext1770Reset(); -} - void BeebWin::SetDriveControl(unsigned char Value) { // This takes a value from the mem/io decoder, as written by the CPU, From 1f9b6714ca9d0bc7815551dd6149ae4fb6bf71fc Mon Sep 17 00:00:00 2001 From: Chris Needham Date: Sun, 7 Jul 2024 22:55:37 +0100 Subject: [PATCH 003/101] Refactored serial port code - Added error checks - Added WriteChar() method --- Src/Serial.cpp | 165 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 109 insertions(+), 56 deletions(-) diff --git a/Src/Serial.cpp b/Src/Serial.cpp index d9814ff6..f88bb9f9 100644 --- a/Src/Serial.cpp +++ b/Src/Serial.cpp @@ -136,9 +136,11 @@ class Win32SerialPort bool Init(const char* PortName); void Close(); - void SetBaudRate(int BaudRate); - void Configure(unsigned char DataBits, unsigned char StopBits, unsigned char Parity); - void SetRTS(bool RTS); + bool SetBaudRate(int BaudRate); + bool Configure(unsigned char DataBits, unsigned char StopBits, unsigned char Parity); + bool SetRTS(bool RTS); + + bool WriteChar(unsigned char Data); public: HANDLE hSerialPort; // Serial port handle @@ -185,18 +187,21 @@ Win32SerialPort::Win32SerialPort() : bWaitingForStat(false), bCharReady(false) { - memset(&olSerialPort, 0, sizeof(olSerialPort)); - memset(&olSerialWrite, 0, sizeof(olSerialWrite)); - memset(&olStatus, 0, sizeof(olStatus)); + ZeroMemory(&olSerialPort, sizeof(olSerialPort)); + ZeroMemory(&olSerialWrite, sizeof(olSerialWrite)); + ZeroMemory(&olStatus, sizeof(olStatus)); } /*--------------------------------------------------------------------------*/ bool Win32SerialPort::Init(const char* PortName) { - char FileName[_MAX_PATH]; + char FileName[MAX_PATH]; sprintf(FileName, "\\\\.\\%s", PortName); + COMMTIMEOUTS CommTimeOuts{}; + DCB dcb{}; // Serial port device control block + hSerialPort = CreateFile(FileName, GENERIC_READ | GENERIC_WRITE, 0, // dwShareMode @@ -209,43 +214,59 @@ bool Win32SerialPort::Init(const char* PortName) { return false; } - else + + if (!SetupComm(hSerialPort, 1024, 1024)) { - BOOL bPortStat = SetupComm(hSerialPort, 1280, 1280); + goto Fail; + } - DCB dcbSerialPort{}; // Serial port device control block - dcbSerialPort.DCBlength = sizeof(dcbSerialPort); + dcb.DCBlength = sizeof(dcb); - bPortStat = GetCommState(hSerialPort, &dcbSerialPort); + if (!GetCommState(hSerialPort, &dcb)) + { + goto Fail; + } - dcbSerialPort.fBinary = TRUE; - dcbSerialPort.BaudRate = 9600; - dcbSerialPort.Parity = NOPARITY; - dcbSerialPort.StopBits = ONESTOPBIT; - dcbSerialPort.ByteSize = 8; - dcbSerialPort.fDtrControl = DTR_CONTROL_DISABLE; - dcbSerialPort.fOutxCtsFlow = FALSE; - dcbSerialPort.fOutxDsrFlow = FALSE; - dcbSerialPort.fOutX = FALSE; - dcbSerialPort.fDsrSensitivity = FALSE; - dcbSerialPort.fInX = FALSE; - dcbSerialPort.fRtsControl = RTS_CONTROL_DISABLE; // Leave it low (do not send) for now + dcb.fBinary = TRUE; + dcb.BaudRate = 9600; + dcb.Parity = NOPARITY; + dcb.StopBits = ONESTOPBIT; + dcb.ByteSize = 8; + dcb.fDtrControl = DTR_CONTROL_DISABLE; + dcb.fOutxCtsFlow = FALSE; + dcb.fOutxDsrFlow = FALSE; + dcb.fOutX = FALSE; + dcb.fDsrSensitivity = FALSE; + dcb.fInX = FALSE; + dcb.fRtsControl = RTS_CONTROL_DISABLE; // Leave it low (do not send) for now + + if (!SetCommState(hSerialPort, &dcb)) + { + goto Fail; + } - bPortStat = SetCommState(hSerialPort, &dcbSerialPort); + CommTimeOuts.ReadIntervalTimeout = MAXDWORD; + CommTimeOuts.ReadTotalTimeoutConstant = 0; + CommTimeOuts.ReadTotalTimeoutMultiplier = 0; + CommTimeOuts.WriteTotalTimeoutConstant = 0; + CommTimeOuts.WriteTotalTimeoutMultiplier = 0; + + if (!SetCommTimeouts(hSerialPort, &CommTimeOuts)) + { + goto Fail; + } - COMMTIMEOUTS CommTimeOuts{}; - CommTimeOuts.ReadIntervalTimeout = MAXDWORD; - CommTimeOuts.ReadTotalTimeoutConstant = 0; - CommTimeOuts.ReadTotalTimeoutMultiplier = 0; - CommTimeOuts.WriteTotalTimeoutConstant = 0; - CommTimeOuts.WriteTotalTimeoutMultiplier = 0; + if (!SetCommMask(hSerialPort, EV_CTS | EV_RXCHAR | EV_ERR)) + { + goto Fail; + } - SetCommTimeouts(hSerialPort, &CommTimeOuts); + return true; - SetCommMask(hSerialPort, EV_CTS | EV_RXCHAR | EV_ERR); +Fail: + Close(); - return true; - } + return false; } /*--------------------------------------------------------------------------*/ @@ -261,39 +282,73 @@ void Win32SerialPort::Close() /*--------------------------------------------------------------------------*/ -void Win32SerialPort::SetBaudRate(int BaudRate) +bool Win32SerialPort::SetBaudRate(int BaudRate) { - DCB dcbSerialPort{}; // Serial port device control block - dcbSerialPort.DCBlength = sizeof(dcbSerialPort); + DCB dcb{}; // Serial port device control block + dcb.DCBlength = sizeof(dcb); + + if (!GetCommState(hSerialPort, &dcb)) + { + return false; + } + + dcb.BaudRate = BaudRate; + dcb.DCBlength = sizeof(dcb); + + if (!SetCommState(hSerialPort, &dcb)) + { + return false; + } - GetCommState(hSerialPort, &dcbSerialPort); - dcbSerialPort.BaudRate = BaudRate; - dcbSerialPort.DCBlength = sizeof(dcbSerialPort); - SetCommState(hSerialPort, &dcbSerialPort); + return true; } /*--------------------------------------------------------------------------*/ -void Win32SerialPort::Configure(unsigned char DataBits, unsigned char StopBits, unsigned char Parity) +bool Win32SerialPort::Configure(unsigned char DataBits, unsigned char StopBits, unsigned char Parity) { - DCB dcbSerialPort{}; // Serial port device control block - dcbSerialPort.DCBlength = sizeof(dcbSerialPort); + DCB dcb{}; // Serial port device control block + dcb.DCBlength = sizeof(dcb); + + if (!GetCommState(hSerialPort, &dcb)) + { + return false; + } + + dcb.ByteSize = DataBits; + dcb.StopBits = (StopBits == 1) ? ONESTOPBIT : TWOSTOPBITS; + dcb.Parity = Parity; + dcb.DCBlength = sizeof(dcb); + + if (!SetCommState(hSerialPort, &dcb)) + { + return false; + } - GetCommState(hSerialPort, &dcbSerialPort); + return true; +} - dcbSerialPort.ByteSize = DataBits; - dcbSerialPort.StopBits = (StopBits == 1) ? ONESTOPBIT : TWOSTOPBITS; - dcbSerialPort.Parity = Parity; - dcbSerialPort.DCBlength = sizeof(dcbSerialPort); +/*--------------------------------------------------------------------------*/ - SetCommState(hSerialPort, &dcbSerialPort); +bool Win32SerialPort::SetRTS(bool RTS) +{ + if (!EscapeCommFunction(hSerialPort, RTS ? CLRRTS : SETRTS)) + { + return false; + } + + return true; } /*--------------------------------------------------------------------------*/ -void Win32SerialPort::SetRTS(bool RTS) +bool Win32SerialPort::WriteChar(unsigned char Data) { - EscapeCommFunction(hSerialPort, RTS ? CLRRTS : SETRTS); + SerialWriteBuffer = Data; + + WriteFile(hSerialPort, &SerialWriteBuffer, 1, &BytesOut, &olSerialWrite); + + return true; } /*--------------------------------------------------------------------------*/ @@ -429,9 +484,7 @@ static void HandleTXData(unsigned char Data) } else { - SerialPort.SerialWriteBuffer = SerialACIA.TDSR; - - WriteFile(SerialPort.hSerialPort, &SerialPort.SerialWriteBuffer, 1, &SerialPort.BytesOut, &SerialPort.olSerialWrite); + SerialPort.WriteChar(SerialACIA.TDSR); } //WriteLog("Serial: TX TDR=%02X, TDSR=%02x TxD=%d Status=%02x Tx_Rate=%d\n", TDR, TDSR, TxD, SerialACIA.Status, Tx_Rate); From 0a4edd6da2279295f96e583ffb44b842f254ab90 Mon Sep 17 00:00:00 2001 From: Chris Needham Date: Mon, 8 Jul 2024 19:42:58 +0100 Subject: [PATCH 004/101] Rewrote the Windows serial port implementation * Received characters are now buffered to prevent being lost * Use only one worker thread, for reading the serial port * Avoid accessing Beeb Serial ACIA/ULA from multiple threads --- CHANGES.md | 2 + Src/BeebEm.vcxproj | 2 + Src/BeebEm.vcxproj.filters | 6 + Src/BeebWin.cpp | 55 ++-- Src/BeebWin.h | 10 +- Src/BeebWinPrefs.cpp | 19 +- Src/IP232.cpp | 23 +- Src/Main.cpp | 15 +- Src/RingBuffer.cpp | 23 +- Src/RingBuffer.h | 7 +- Src/Serial.cpp | 471 +++---------------------------- Src/Serial.h | 7 +- Src/SerialPort.cpp | 554 +++++++++++++++++++++++++++++++++++++ Src/SerialPort.h | 72 +++++ Src/TouchScreen.cpp | 4 +- 15 files changed, 767 insertions(+), 503 deletions(-) create mode 100644 Src/SerialPort.cpp create mode 100644 Src/SerialPort.h diff --git a/CHANGES.md b/CHANGES.md index 49e4eca0..199a4e1c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -44,6 +44,8 @@ Steve Inglis, Alistair Cree, Ken Lowe, Mark Usher, Martin Mather, Tom Seddon - Fixed loading CSW files from the command line. * Improved serial port emulation, and fixed data loss when using serial over a TCP connection. +* Rewrote the Windows serial port implementation. Received characters + are now buffered to prevent being lost. * Added Set Keyboard Links command, which allows you to change the default screen mode and other options (Model B/B+ only). * Fixed installer to not delete Econet and key map config files. diff --git a/Src/BeebEm.vcxproj b/Src/BeebEm.vcxproj index 96894e5c..bafd1b45 100644 --- a/Src/BeebEm.vcxproj +++ b/Src/BeebEm.vcxproj @@ -223,6 +223,7 @@ + @@ -392,6 +393,7 @@ + diff --git a/Src/BeebEm.vcxproj.filters b/Src/BeebEm.vcxproj.filters index 2245ca72..74a08c4e 100644 --- a/Src/BeebEm.vcxproj.filters +++ b/Src/BeebEm.vcxproj.filters @@ -198,6 +198,9 @@ Source Files + + Source Files + Source Files @@ -497,6 +500,9 @@ Header Files + + Header Files + Header Files diff --git a/Src/BeebWin.cpp b/Src/BeebWin.cpp index afd3a81e..7cf21faf 100644 --- a/Src/BeebWin.cpp +++ b/Src/BeebWin.cpp @@ -661,6 +661,8 @@ void BeebWin::Shutdown() CloseTextView(); IP232Close(); + SerialClose(); + CloseTape(); WSACleanup(); @@ -3519,18 +3521,27 @@ void BeebWin::ToggleSerial() SerialPortEnabled = !SerialPortEnabled; - if (SerialPortEnabled && SerialDestination == SerialType::IP232) + if (SerialPortEnabled) { - if (!IP232Open()) + if (SerialDestination == SerialType::IP232) { - SerialPortEnabled = false; - UpdateSerialMenu(); + if (!IP232Open()) + { + SerialPortEnabled = false; + UpdateSerialMenu(); + } } - } + else if (SerialDestination == SerialType::SerialPort) + { + if (!SerialInit(m_SerialPort.c_str())) + { + Report(MessageType::Error, + "Could not open serial port %s", m_SerialPort.c_str()); - if (SerialDestination == SerialType::SerialPort) - { - bSerialStateChanged = true; + SerialPortEnabled = false; + UpdateSerialMenu(); + } + } } UpdateSerialMenu(); @@ -3541,7 +3552,7 @@ void BeebWin::ConfigureSerial() SerialPortDialog Dialog(hInst, m_hWnd, SerialDestination, - SerialPortName, + m_SerialPort.c_str(), IP232Address, IP232Port, IP232Raw, @@ -3557,7 +3568,7 @@ void BeebWin::ConfigureSerial() if (SerialDestination == SerialType::SerialPort) { - SelectSerialPort(Dialog.GetSerialPortName().c_str()); + m_SerialPort = Dialog.GetSerialPortName(); } else if (SerialDestination == SerialType::IP232) { @@ -3571,7 +3582,13 @@ void BeebWin::ConfigureSerial() { if (SerialDestination == SerialType::SerialPort) { - SerialPortEnabled = true; + SerialPortEnabled = SerialInit(m_SerialPort.c_str()); + + if (!SerialPortEnabled) + { + Report(MessageType::Error, + "Could not open serial port %s", m_SerialPort.c_str()); + } } else if (SerialDestination == SerialType::TouchScreen) { @@ -3598,11 +3615,11 @@ void BeebWin::ConfigureSerial() void BeebWin::DisableSerial() { - /* if (SerialDestination == SerialType::SerialPort) + if (SerialDestination == SerialType::SerialPort) { SerialClose(); - } */ - if (SerialDestination == SerialType::TouchScreen) + } + else if (SerialDestination == SerialType::TouchScreen) { TouchScreenClose(); } @@ -3612,16 +3629,6 @@ void BeebWin::DisableSerial() } } -void BeebWin::SelectSerialPort(const char* PortName) -{ - // DisableSerial(); - strcpy(SerialPortName, PortName); - SerialDestination = SerialType::SerialPort; - bSerialStateChanged = true; - - UpdateSerialMenu(); -} - void BeebWin::UpdateSerialMenu() { CheckMenuItem(IDM_SERIAL, SerialPortEnabled); diff --git a/Src/BeebWin.h b/Src/BeebWin.h index 35a6f888..7b8ab585 100644 --- a/Src/BeebWin.h +++ b/Src/BeebWin.h @@ -252,9 +252,9 @@ class BeebWin void Shutdown(); static LRESULT CALLBACK WndProc(HWND hWnd, - UINT nMessage, - WPARAM wParam, - LPARAM lParam); + UINT nMessage, + WPARAM wParam, + LPARAM lParam); LRESULT WndProc(UINT nMessage, WPARAM wParam, LPARAM lParam); @@ -413,7 +413,6 @@ class BeebWin void ToggleSerial(); void DisableSerial(); void ConfigureSerial(); - void SelectSerialPort(const char *PortName); void UpdateSerialMenu(); void OnIP232Error(int Error); @@ -769,6 +768,9 @@ class BeebWin std::string m_PrinterFileName; std::string m_PrinterDevice; + // Serial + std::string m_SerialPort; + // Command line char m_CommandLineFileName1[_MAX_PATH]; char m_CommandLineFileName2[_MAX_PATH]; diff --git a/Src/BeebWinPrefs.cpp b/Src/BeebWinPrefs.cpp index 4e7def0a..36c915f4 100644 --- a/Src/BeebWinPrefs.cpp +++ b/Src/BeebWinPrefs.cpp @@ -1085,23 +1085,26 @@ void BeebWin::LoadTapePreferences(int Version) void BeebWin::LoadSerialPortPreferences(int Version) { - if (m_Preferences.GetStringValue(CFG_SERIAL_PORT, SerialPortName)) + if (m_Preferences.GetStringValue(CFG_SERIAL_PORT, m_SerialPort)) { // For backwards compatibility with Preferences.cfg files from // BeebEm 4.18 and earlier, which stored the port number as a // binary value - if (strlen(SerialPortName) == 2 && - isxdigit(SerialPortName[0]) && - isxdigit(SerialPortName[1])) + if (m_SerialPort.size() == 2 && + isxdigit(m_SerialPort[0]) && + isxdigit(m_SerialPort[1])) { int Port; - sscanf(SerialPortName, "%x", &Port); - sprintf(SerialPortName, "COM%d", Port); + sscanf(m_SerialPort.c_str(), "%x", &Port); + + char PortName[20]; + sprintf(PortName, "COM%d", Port); + m_SerialPort = PortName; } } else { - strcpy(SerialPortName, "COM2"); + m_SerialPort = "COM2"; } m_Preferences.GetBoolValue(CFG_SERIAL_PORT_ENABLED, SerialPortEnabled, false); @@ -1719,7 +1722,7 @@ void BeebWin::SavePreferences(bool saveAll) m_Preferences.SetBoolValue(CFG_UNLOCK_TAPE, TapeState.Unlock); // Serial port - m_Preferences.SetStringValue(CFG_SERIAL_PORT, SerialPortName); + m_Preferences.SetStringValue(CFG_SERIAL_PORT, m_SerialPort); m_Preferences.SetBoolValue(CFG_SERIAL_PORT_ENABLED, SerialPortEnabled); m_Preferences.SetBoolValue(CFG_TOUCH_SCREEN_ENABLED, SerialDestination == SerialType::TouchScreen); m_Preferences.SetBoolValue(CFG_IP232_ENABLED, SerialDestination == SerialType::IP232); diff --git a/Src/IP232.cpp b/Src/IP232.cpp index 76c4ada7..ecd825fc 100644 --- a/Src/IP232.cpp +++ b/Src/IP232.cpp @@ -70,15 +70,13 @@ int IP232Port; static bool IP232FlagReceived = false; -static bool IP232RTS = false; - // IP232 routines use InputBuffer as data coming in from the modem, // and OutputBuffer for data to be sent out to the modem. // StatusBuffer is used for changes to the serial ACIA status // registers -static RingBuffer InputBuffer; -static RingBuffer OutputBuffer; -static RingBuffer StatusBuffer; +static RingBuffer InputBuffer(1024); +static RingBuffer OutputBuffer(1024); +static RingBuffer StatusBuffer(128); CycleCountT IP232RxTrigger = CycleCountTMax; @@ -284,17 +282,12 @@ unsigned char IP232ReadStatus() void IP232SetRTS(bool RTS) { - if (RTS != IP232RTS) - { - DebugTrace("IP232SetRTS: RTS=%d\n", (int)RTS); - - if (IP232Handshake) - { - IP232Write(255); - IP232Write(static_cast(RTS)); - } + DebugTrace("IP232SetRTS: RTS=%d\n", (int)RTS); - IP232RTS = RTS; + if (IP232Handshake) + { + IP232Write(255); + IP232Write(static_cast(RTS)); } } diff --git a/Src/Main.cpp b/Src/Main.cpp index 2c0428df..b493a92f 100644 --- a/Src/Main.cpp +++ b/Src/Main.cpp @@ -34,7 +34,8 @@ Boston, MA 02110-1301, USA. #include "BeebWin.h" #include "Log.h" #include "SelectKeyDialog.h" -#include "Serial.h" + +/****************************************************************************/ Model MachineType; BeebWin *mainWin = nullptr; @@ -42,6 +43,8 @@ HINSTANCE hInst; HWND hCurrentDialog = nullptr; HACCEL hCurrentAccelTable = nullptr; +/****************************************************************************/ + int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE /* hPrevInstance */, LPSTR /* lpszCmdLine */, int /* nCmdShow */) { @@ -62,9 +65,6 @@ int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE /* hPrevInstance */, OpenLog(); - // Create serial threads - SerialInit(); - for (;;) { MSG msg; @@ -107,16 +107,17 @@ int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE /* hPrevInstance */, } } - if (!mainWin->IsFrozen() && !mainWin->IsPaused()) { + if (!mainWin->IsFrozen() && !mainWin->IsPaused()) + { Exec6502Instruction(); } } CloseLog(); - SerialClose(); - delete mainWin; return 0; } + +/****************************************************************************/ diff --git a/Src/RingBuffer.cpp b/Src/RingBuffer.cpp index 701252d0..5713ea57 100644 --- a/Src/RingBuffer.cpp +++ b/Src/RingBuffer.cpp @@ -24,13 +24,22 @@ Boston, MA 02110-1301, USA. /*****************************************************************************/ -RingBuffer::RingBuffer() +RingBuffer::RingBuffer(int Size) : + m_pBuffer(new unsigned char[Size]), + m_Size(Size) { Reset(); } /*****************************************************************************/ +RingBuffer::~RingBuffer() +{ + delete [] m_pBuffer; +} + +/*****************************************************************************/ + void RingBuffer::Reset() { m_Head = 0; @@ -51,8 +60,8 @@ unsigned char RingBuffer::GetData() { assert(m_Length > 0); - unsigned char Data = m_Buffer[m_Head]; - m_Head = (m_Head + 1) % BUFFER_SIZE; + unsigned char Data = m_pBuffer[m_Head]; + m_Head = (m_Head + 1) % m_Size; m_Length--; return Data; @@ -62,10 +71,10 @@ unsigned char RingBuffer::GetData() bool RingBuffer::PutData(unsigned char Data) { - if (m_Length != BUFFER_SIZE) + if (m_Length != m_Size) { - m_Buffer[m_Tail] = Data; - m_Tail = (m_Tail + 1) % BUFFER_SIZE; + m_pBuffer[m_Tail] = Data; + m_Tail = (m_Tail + 1) % m_Size; m_Length++; return true; } @@ -77,7 +86,7 @@ bool RingBuffer::PutData(unsigned char Data) int RingBuffer::GetSpace() const { - return BUFFER_SIZE - m_Length; + return m_Size - m_Length; } /*****************************************************************************/ diff --git a/Src/RingBuffer.h b/Src/RingBuffer.h index c1e956b9..a146db8b 100644 --- a/Src/RingBuffer.h +++ b/Src/RingBuffer.h @@ -24,9 +24,10 @@ Boston, MA 02110-1301, USA. class RingBuffer { public: - RingBuffer(); + explicit RingBuffer(int Size); RingBuffer(const RingBuffer&) = delete; RingBuffer& operator=(const RingBuffer&) = delete; + ~RingBuffer(); public: void Reset(); @@ -37,8 +38,8 @@ class RingBuffer int GetLength() const; private: - static const int BUFFER_SIZE = 10240; - unsigned char m_Buffer[BUFFER_SIZE]; + unsigned char* m_pBuffer; + int m_Size; int m_Head; int m_Tail; int m_Length; diff --git a/Src/Serial.cpp b/Src/Serial.cpp index f88bb9f9..ed9f83ff 100644 --- a/Src/Serial.cpp +++ b/Src/Serial.cpp @@ -46,6 +46,7 @@ Boston, MA 02110-1301, USA. #include "Log.h" #include "Main.h" #include "Resource.h" +#include "SerialPort.h" #include "Sound.h" #include "TapeControlDialog.h" #include "TapeMap.h" @@ -61,7 +62,7 @@ static bool FirstReset = true; static UEFFileReader UEFReader; static UEFFileWriter UEFWriter; -char TapeFileName[256]; // Filename of current tape file +char TapeFileName[MAX_PATH]; // Filename of current tape file static bool UEFFileOpen = false; static bool CSWFileOpen = false; @@ -111,55 +112,8 @@ constexpr int TAPECYCLES = CPU_CYCLES_PER_SECOND / 5600; // 5600 is normal tape // This bit is the Serial Port stuff bool SerialPortEnabled; -char SerialPortName[_MAX_PATH]; -class SerialPortReadThread : public Thread -{ - public: - virtual unsigned int ThreadFunc(); -}; - -class SerialPortStatusThread : public Thread -{ - public: - virtual unsigned int ThreadFunc(); -}; - -SerialPortReadThread SerialReadThread; -SerialPortStatusThread SerialStatusThread; -volatile bool bSerialStateChanged = false; - -class Win32SerialPort -{ - public: - Win32SerialPort(); - - bool Init(const char* PortName); - void Close(); - bool SetBaudRate(int BaudRate); - bool Configure(unsigned char DataBits, unsigned char StopBits, unsigned char Parity); - bool SetRTS(bool RTS); - - bool WriteChar(unsigned char Data); - - public: - HANDLE hSerialPort; // Serial port handle - unsigned int SerialBuffer; - unsigned int SerialWriteBuffer; - DWORD BytesIn; - DWORD BytesOut; - DWORD dwCommEvent; - OVERLAPPED olSerialPort; - OVERLAPPED olSerialWrite; - OVERLAPPED olStatus; - volatile bool bWaitingForData; - volatile bool bWaitingForStat; - volatile bool bCharReady; -}; - -static Win32SerialPort SerialPort; - -static void InitSerialPort(); +static SerialPort SerialPort; SerialACIAType SerialACIA; @@ -176,183 +130,6 @@ static SerialULAType SerialULA; /*--------------------------------------------------------------------------*/ -Win32SerialPort::Win32SerialPort() : - hSerialPort(INVALID_HANDLE_VALUE), - SerialBuffer(0), - SerialWriteBuffer(0), - BytesIn(0), - BytesOut(0), - dwCommEvent(0), - bWaitingForData(false), - bWaitingForStat(false), - bCharReady(false) -{ - ZeroMemory(&olSerialPort, sizeof(olSerialPort)); - ZeroMemory(&olSerialWrite, sizeof(olSerialWrite)); - ZeroMemory(&olStatus, sizeof(olStatus)); -} - -/*--------------------------------------------------------------------------*/ - -bool Win32SerialPort::Init(const char* PortName) -{ - char FileName[MAX_PATH]; - sprintf(FileName, "\\\\.\\%s", PortName); - - COMMTIMEOUTS CommTimeOuts{}; - DCB dcb{}; // Serial port device control block - - hSerialPort = CreateFile(FileName, - GENERIC_READ | GENERIC_WRITE, - 0, // dwShareMode - nullptr, // lpSecurityAttributes - OPEN_EXISTING, - FILE_FLAG_OVERLAPPED, - nullptr); // hTemplateFile - - if (hSerialPort == INVALID_HANDLE_VALUE) - { - return false; - } - - if (!SetupComm(hSerialPort, 1024, 1024)) - { - goto Fail; - } - - dcb.DCBlength = sizeof(dcb); - - if (!GetCommState(hSerialPort, &dcb)) - { - goto Fail; - } - - dcb.fBinary = TRUE; - dcb.BaudRate = 9600; - dcb.Parity = NOPARITY; - dcb.StopBits = ONESTOPBIT; - dcb.ByteSize = 8; - dcb.fDtrControl = DTR_CONTROL_DISABLE; - dcb.fOutxCtsFlow = FALSE; - dcb.fOutxDsrFlow = FALSE; - dcb.fOutX = FALSE; - dcb.fDsrSensitivity = FALSE; - dcb.fInX = FALSE; - dcb.fRtsControl = RTS_CONTROL_DISABLE; // Leave it low (do not send) for now - - if (!SetCommState(hSerialPort, &dcb)) - { - goto Fail; - } - - CommTimeOuts.ReadIntervalTimeout = MAXDWORD; - CommTimeOuts.ReadTotalTimeoutConstant = 0; - CommTimeOuts.ReadTotalTimeoutMultiplier = 0; - CommTimeOuts.WriteTotalTimeoutConstant = 0; - CommTimeOuts.WriteTotalTimeoutMultiplier = 0; - - if (!SetCommTimeouts(hSerialPort, &CommTimeOuts)) - { - goto Fail; - } - - if (!SetCommMask(hSerialPort, EV_CTS | EV_RXCHAR | EV_ERR)) - { - goto Fail; - } - - return true; - -Fail: - Close(); - - return false; -} - -/*--------------------------------------------------------------------------*/ - -void Win32SerialPort::Close() -{ - if (hSerialPort != INVALID_HANDLE_VALUE) - { - CloseHandle(hSerialPort); - hSerialPort = INVALID_HANDLE_VALUE; - } -} - -/*--------------------------------------------------------------------------*/ - -bool Win32SerialPort::SetBaudRate(int BaudRate) -{ - DCB dcb{}; // Serial port device control block - dcb.DCBlength = sizeof(dcb); - - if (!GetCommState(hSerialPort, &dcb)) - { - return false; - } - - dcb.BaudRate = BaudRate; - dcb.DCBlength = sizeof(dcb); - - if (!SetCommState(hSerialPort, &dcb)) - { - return false; - } - - return true; -} - -/*--------------------------------------------------------------------------*/ - -bool Win32SerialPort::Configure(unsigned char DataBits, unsigned char StopBits, unsigned char Parity) -{ - DCB dcb{}; // Serial port device control block - dcb.DCBlength = sizeof(dcb); - - if (!GetCommState(hSerialPort, &dcb)) - { - return false; - } - - dcb.ByteSize = DataBits; - dcb.StopBits = (StopBits == 1) ? ONESTOPBIT : TWOSTOPBITS; - dcb.Parity = Parity; - dcb.DCBlength = sizeof(dcb); - - if (!SetCommState(hSerialPort, &dcb)) - { - return false; - } - - return true; -} - -/*--------------------------------------------------------------------------*/ - -bool Win32SerialPort::SetRTS(bool RTS) -{ - if (!EscapeCommFunction(hSerialPort, RTS ? CLRRTS : SETRTS)) - { - return false; - } - - return true; -} - -/*--------------------------------------------------------------------------*/ - -bool Win32SerialPort::WriteChar(unsigned char Data) -{ - SerialWriteBuffer = Data; - - WriteFile(hSerialPort, &SerialWriteBuffer, 1, &BytesOut, &olSerialWrite); - - return true; -} - -/*--------------------------------------------------------------------------*/ - // mm static void SerialUpdateACIAInterruptStatus() { @@ -383,8 +160,6 @@ void SerialACIAWriteControl(unsigned char Value) "Serial: Write ACIA control %02X", (int)Value); } - SerialACIA.Control = Value; // This is done for safe keeping - // Master reset - clear all bits in the status register, except for // external conditions on CTS and DCD. if ((Value & MC6850_CONTROL_COUNTER_DIVIDE) == MC6850_CONTROL_MASTER_RESET) @@ -415,6 +190,13 @@ void SerialACIAWriteControl(unsigned char Value) SetTrigger(TAPECYCLES, TapeTrigger); } + unsigned char OldWordSelect = SerialACIA.Control & MC6850_CONTROL_WORD_SELECT; + unsigned char NewWordSelect = Value & MC6850_CONTROL_WORD_SELECT; + + bool OldRTS = SerialACIA.RTS; + + SerialACIA.Control = Value; // This is done for safe keeping + // Clock Divide if ((Value & MC6850_CONTROL_COUNTER_DIVIDE) == 0x00) SerialACIA.ClkDivide = 1; if ((Value & MC6850_CONTROL_COUNTER_DIVIDE) == 0x01) SerialACIA.ClkDivide = 16; @@ -442,14 +224,25 @@ void SerialACIAWriteControl(unsigned char Value) { if (SerialDestination == SerialType::SerialPort) { - SerialPort.Configure(SerialACIA.DataBits, SerialACIA.StopBits, SerialACIA.Parity); + if (NewWordSelect != OldWordSelect) + { + SerialPort.Configure(SerialACIA.DataBits, + SerialACIA.StopBits, + SerialACIA.Parity); + } // Check RTS - SerialPort.SetRTS(SerialACIA.RTS); + if (SerialACIA.RTS != OldRTS) + { + SerialPort.SetRTS(SerialACIA.RTS); + } } else if (SerialDestination == SerialType::IP232) { - IP232SetRTS(SerialACIA.RTS); + if (SerialACIA.RTS != OldRTS) + { + IP232SetRTS(SerialACIA.RTS); + } } } @@ -1050,209 +843,31 @@ void SerialPoll(int Cycles) } else // SerialType::SerialPort { - if (!SerialPort.bWaitingForStat && !bSerialStateChanged) + if (SerialPort.HasRxData() && SerialACIA.RxD < 2) { - WaitCommEvent(SerialPort.hSerialPort, &SerialPort.dwCommEvent, &SerialPort.olStatus); - SerialPort.bWaitingForStat = true; - } + unsigned char Data = SerialPort.GetRxData(); - if (!bSerialStateChanged && SerialPort.bCharReady && !SerialPort.bWaitingForData && SerialACIA.RxD < 2) - { - if (!ReadFile(SerialPort.hSerialPort, &SerialPort.SerialBuffer, 1, &SerialPort.BytesIn, &SerialPort.olSerialPort)) - { - if (GetLastError() == ERROR_IO_PENDING) - { - SerialPort.bWaitingForData = true; - } - else - { - mainWin->Report(MessageType::Error, "Serial Port Error"); - } - } - else - { - if (SerialPort.BytesIn > 0) - { - HandleData((unsigned char)SerialPort.SerialBuffer); - } - else - { - DWORD CommError; - ClearCommError(SerialPort.hSerialPort, &CommError, nullptr); - SerialPort.bCharReady = false; - } - } + HandleData(Data); } - } - } -} - -/*--------------------------------------------------------------------------*/ - -static void InitThreads() -{ - SerialPort.Close(); - SerialPort.bWaitingForData = false; - SerialPort.bWaitingForStat = false; - - if (SerialPortEnabled && - SerialDestination == SerialType::SerialPort && - SerialPortName[0] != '\0') - { - InitSerialPort(); // Set up the serial port if its enabled. - - if (SerialPort.olSerialPort.hEvent) - { - CloseHandle(SerialPort.olSerialPort.hEvent); - SerialPort.olSerialPort.hEvent = nullptr; - } - - SerialPort.olSerialPort.hEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); // Create the serial port event signal - - if (SerialPort.olSerialWrite.hEvent) - { - CloseHandle(SerialPort.olSerialWrite.hEvent); - SerialPort.olSerialWrite.hEvent = nullptr; - } - - SerialPort.olSerialWrite.hEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); // Write event, not actually used. - - if (SerialPort.olStatus.hEvent) - { - CloseHandle(SerialPort.olStatus.hEvent); - SerialPort.olStatus.hEvent = nullptr; - } - - SerialPort.olStatus.hEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); // Status event, for WaitCommEvent - } - - bSerialStateChanged = false; -} - -/*--------------------------------------------------------------------------*/ - -unsigned int SerialPortStatusThread::ThreadFunc() -{ - DWORD dwOvRes = 0; - - while (1) - { - if (SerialDestination == SerialType::SerialPort && - SerialPortEnabled && - (WaitForSingleObject(SerialPort.olStatus.hEvent, 10) == WAIT_OBJECT_0)) - { - if (GetOverlappedResult(SerialPort.hSerialPort, &SerialPort.olStatus, &dwOvRes, FALSE)) + if (SerialPort.HasStatus()) { - // Event waiting in dwCommEvent - if ((SerialPort.dwCommEvent & EV_RXCHAR) && !SerialPort.bWaitingForData) - { - SerialPort.bCharReady = true; - } + // CTS line change + unsigned char ModemStatus = SerialPort.GetStatus(); - if (SerialPort.dwCommEvent & EV_CTS) + // Invert for CTS bit, Keep for TDRE bit + if (ModemStatus & MS_CTS_ON) { - // CTS line change - DWORD LineStat; - GetCommModemStatus(SerialPort.hSerialPort, &LineStat); - - // Invert for CTS bit, Keep for TDRE bit - if (LineStat & MS_CTS_ON) - { - SerialACIA.Status &= ~MC6850_STATUS_CTS; - SerialACIA.Status |= MC6850_STATUS_TDRE; - } - else - { - SerialACIA.Status |= MC6850_STATUS_CTS; - SerialACIA.Status &= ~MC6850_STATUS_TDRE; - } + SerialACIA.Status &= ~MC6850_STATUS_CTS; + SerialACIA.Status |= MC6850_STATUS_TDRE; } - } - - SerialPort.bWaitingForStat = false; - } - else - { - Sleep(100); // Don't hog CPU if nothing happening - } - - if (bSerialStateChanged && !SerialPort.bWaitingForData) - { - // Shut off serial port, and re-initialise - InitThreads(); - bSerialStateChanged = false; - } - - Sleep(0); - } - - return 0; -} - -/*--------------------------------------------------------------------------*/ - -unsigned int SerialPortReadThread::ThreadFunc() -{ - // New Serial port thread - 7:35pm 16/10/2001 GMT - // This sorta runs as a seperate process in effect, checking - // enable status, and doing the monitoring. - - while (1) - { - if (!bSerialStateChanged && SerialPortEnabled && SerialDestination == SerialType::SerialPort && SerialPort.bWaitingForData) - { - DWORD Result = WaitForSingleObject(SerialPort.olSerialPort.hEvent, INFINITE); // 10ms to respond - - if (Result == WAIT_OBJECT_0) - { - if (GetOverlappedResult(SerialPort.hSerialPort, &SerialPort.olSerialPort, &SerialPort.BytesIn, FALSE)) + else { - // sucessful read, screw any errors. - if (SerialULA.RS423 && SerialPort.BytesIn > 0) - { - HandleData((unsigned char)SerialPort.SerialBuffer); - } - - if (SerialPort.BytesIn == 0) - { - SerialPort.bCharReady = false; - - DWORD CommError; - ClearCommError(SerialPort.hSerialPort, &CommError, nullptr); - } - - SerialPort.bWaitingForData = false; + SerialACIA.Status |= MC6850_STATUS_CTS; + SerialACIA.Status &= ~MC6850_STATUS_TDRE; } } } - else - { - Sleep(100); // Don't hog CPU if nothing happening - } - - Sleep(0); - } - - return 0; -} - -/*--------------------------------------------------------------------------*/ - -static void InitSerialPort() -{ - // Initialise COM port - if (SerialPortEnabled && - SerialDestination == SerialType::SerialPort && - SerialPortName[0] != '\0') - { - if (!SerialPort.Init(SerialPortName)) - { - mainWin->Report(MessageType::Error, "Could not open serial port %s", SerialPortName); - bSerialStateChanged = true; - SerialPortEnabled = false; - mainWin->UpdateSerialMenu(); - } } } @@ -1281,12 +896,11 @@ static void CloseCSWFile() /*--------------------------------------------------------------------------*/ -void SerialInit() -{ - InitThreads(); +// Set up the serial port. - SerialReadThread.Start(); - SerialStatusThread.Start(); +bool SerialInit(const char* PortName) +{ + return SerialPort.Init(PortName); } /*--------------------------------------------------------------------------*/ @@ -1312,7 +926,6 @@ void SerialReset() void SerialClose() { - CloseTape(); SerialPort.Close(); } @@ -1743,3 +1356,5 @@ void DebugSerialState() SerialACIA.RxRate, SerialACIA.TxRate); } + +/*--------------------------------------------------------------------------*/ diff --git a/Src/Serial.h b/Src/Serial.h index d22b5057..ee7878b7 100644 --- a/Src/Serial.h +++ b/Src/Serial.h @@ -98,9 +98,8 @@ void SerialULAWrite(unsigned char Value); unsigned char SerialULARead(); extern bool SerialPortEnabled; -extern char SerialPortName[_MAX_PATH]; -void SerialInit(); +bool SerialInit(const char* PortName); void SerialReset(); void SerialPoll(int Cycles); void SerialClose(); @@ -109,8 +108,6 @@ CSWResult LoadCSWTape(const char *FileName); void CloseTape(); void RewindTape(); -extern volatile bool bSerialStateChanged; - struct TapeStateType { bool Playing; @@ -136,7 +133,7 @@ void SerialStopTapeRecording(bool ReloadTape); void SerialEjectTape(); int SerialGetTapeClock(); -extern char TapeFileName[256]; +extern char TapeFileName[MAX_PATH]; enum class SerialTapeState { diff --git a/Src/SerialPort.cpp b/Src/SerialPort.cpp new file mode 100644 index 00000000..dfb01682 --- /dev/null +++ b/Src/SerialPort.cpp @@ -0,0 +1,554 @@ +/**************************************************************** +BeebEm - BBC Micro and Master 128 Emulator +Copyright (C) 2024 Chris Needham + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public +License along with this program; if not, write to the Free +Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +Boston, MA 02110-1301, USA. +****************************************************************/ + +#include + +#include +#include + +#include "SerialPort.h" +#include "DebugTrace.h" + +/*--------------------------------------------------------------------------*/ + +SerialPort::SerialPort() : + m_hSerialPort(INVALID_HANDLE_VALUE), + m_hReadThread(nullptr), + m_hReadStartUpEvent(nullptr), + m_hReadShutDownEvent(nullptr), + m_dwCommEvent(0), + m_RxBuffer(1024), + m_StatusBuffer(128) +{ + ZeroMemory(&m_OverlappedRead, sizeof(m_OverlappedRead)); + ZeroMemory(&m_OverlappedWrite, sizeof(m_OverlappedWrite)); + ZeroMemory(&m_BufferLock, sizeof(m_BufferLock)); +} + +/*--------------------------------------------------------------------------*/ + +SerialPort::~SerialPort() +{ + Close(); +} + +/*--------------------------------------------------------------------------*/ + +bool SerialPort::Init(const char* PortName) +{ + char FileName[MAX_PATH]; + sprintf(FileName, "\\\\.\\%s", PortName); + + COMMTIMEOUTS CommTimeOuts{}; + DCB dcb{}; // Serial port device control block + + m_hSerialPort = CreateFile(FileName, + GENERIC_READ | GENERIC_WRITE, + 0, // dwShareMode + nullptr, // lpSecurityAttributes + OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, + nullptr); // hTemplateFile + + if (m_hSerialPort == INVALID_HANDLE_VALUE) + { + goto Fail; + } + + if (!SetupComm(m_hSerialPort, 1024, 1024)) + { + goto Fail; + } + + dcb.DCBlength = sizeof(dcb); + + if (!GetCommState(m_hSerialPort, &dcb)) + { + goto Fail; + } + + dcb.fBinary = TRUE; + dcb.BaudRate = 9600; + dcb.Parity = NOPARITY; + dcb.StopBits = ONESTOPBIT; + dcb.ByteSize = 8; + dcb.fDtrControl = DTR_CONTROL_DISABLE; + dcb.fOutxCtsFlow = FALSE; + dcb.fOutxDsrFlow = FALSE; + dcb.fOutX = FALSE; + dcb.fDsrSensitivity = FALSE; + dcb.fInX = FALSE; + dcb.fRtsControl = RTS_CONTROL_DISABLE; // Leave it low (do not send) for now + + if (!SetCommState(m_hSerialPort, &dcb)) + { + goto Fail; + } + + CommTimeOuts.ReadIntervalTimeout = MAXDWORD; + CommTimeOuts.ReadTotalTimeoutConstant = 0; + CommTimeOuts.ReadTotalTimeoutMultiplier = 0; + CommTimeOuts.WriteTotalTimeoutConstant = 0; + CommTimeOuts.WriteTotalTimeoutMultiplier = 0; + + if (!SetCommTimeouts(m_hSerialPort, &CommTimeOuts)) + { + goto Fail; + } + + // Configure the conditions for WaitCommEvent() to signal an event. + if (!SetCommMask(m_hSerialPort, EV_CTS | EV_RXCHAR | EV_ERR)) + { + goto Fail; + } + + return InitThread(); + +Fail: + Close(); + + return false; +} + +/*--------------------------------------------------------------------------*/ + +// Create the thread to wait on the I/O event object. Wait for the thread +// to start up before returning. + +bool SerialPort::InitThread() +{ + InitializeCriticalSection(&m_BufferLock); + + ZeroMemory(&m_OverlappedRead, sizeof(m_OverlappedRead)); + ZeroMemory(&m_OverlappedWrite, sizeof(m_OverlappedWrite)); + + m_hReadStartUpEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); + + if (m_hReadStartUpEvent == nullptr) + { + return false; + } + + m_hReadShutDownEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); + + if (m_hReadShutDownEvent == nullptr) + { + return false; + } + + m_OverlappedRead.hEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); + + if (m_OverlappedRead.hEvent == nullptr) + { + return false; + } + + // Create the thread. + + unsigned int ThreadID = 0; + + m_hReadThread = (HANDLE)_beginthreadex( + nullptr, // security + 0, // stack_size + ReadThreadFunc, // start_address + this, // arglist + 0, // initflag + &ThreadID // thrdaddr + ); + + if (m_hReadThread == nullptr) + { + return false; + } + + DWORD dwResult = WaitForSingleObject(m_hReadStartUpEvent, INFINITE); + + if (dwResult != WAIT_OBJECT_0) + { + return false; + } + + // Wait for a comm event. + + m_dwCommEvent = 0; + + if (!WaitCommEvent(m_hSerialPort, &m_dwCommEvent, &m_OverlappedRead)) + { + if (GetLastError() != ERROR_IO_PENDING) + { + DebugTrace("Failed to initiate port event wait\n"); + return false; + } + } + + m_RxBuffer.Reset(); + m_StatusBuffer.Reset(); + + return true; +} + +/*--------------------------------------------------------------------------*/ + +void SerialPort::Close() +{ + if (m_hReadThread != nullptr) + { + SetEvent(m_hReadShutDownEvent); + + WaitForSingleObject(m_hReadThread, INFINITE); + + CloseHandle(m_hReadThread); + m_hReadThread = nullptr; + } + + if (m_hSerialPort != INVALID_HANDLE_VALUE) + { + CloseHandle(m_hSerialPort); + m_hSerialPort = INVALID_HANDLE_VALUE; + } + + if (m_hReadStartUpEvent != nullptr) + { + CloseHandle(m_hReadStartUpEvent); + m_hReadStartUpEvent = nullptr; + } + + if (m_hReadShutDownEvent != nullptr) + { + CloseHandle(m_hReadShutDownEvent); + m_hReadShutDownEvent = nullptr; + } + + if (m_OverlappedRead.hEvent != nullptr) + { + CloseHandle(m_OverlappedRead.hEvent); + m_OverlappedRead.hEvent = nullptr; + } + + DeleteCriticalSection(&m_BufferLock); + + m_RxBuffer.Reset(); + m_StatusBuffer.Reset(); +} + +/*--------------------------------------------------------------------------*/ + +bool SerialPort::SetBaudRate(int BaudRate) +{ + DebugTrace("SerialPort::SetBaudRate\n"); + + if (m_hSerialPort == INVALID_HANDLE_VALUE) + { + return false; + } + + DCB dcb{}; // Serial port device control block + dcb.DCBlength = sizeof(dcb); + + if (!GetCommState(m_hSerialPort, &dcb)) + { + return false; + } + + dcb.BaudRate = BaudRate; + dcb.DCBlength = sizeof(dcb); + + if (!SetCommState(m_hSerialPort, &dcb)) + { + return false; + } + + return true; +} + +/*--------------------------------------------------------------------------*/ + +bool SerialPort::Configure(unsigned char DataBits, + unsigned char StopBits, + unsigned char Parity) +{ + DebugTrace("SerialPort::Configure DataBits=%d StopBits=%d Parity=%d\n", DataBits, StopBits, Parity); + + if (m_hSerialPort == INVALID_HANDLE_VALUE) + { + return false; + } + + DCB dcb{}; // Serial port device control block + dcb.DCBlength = sizeof(dcb); + + if (!GetCommState(m_hSerialPort, &dcb)) + { + return false; + } + + dcb.ByteSize = DataBits; + dcb.StopBits = (StopBits == 1) ? ONESTOPBIT : TWOSTOPBITS; + dcb.Parity = Parity; + dcb.DCBlength = sizeof(dcb); + + if (!SetCommState(m_hSerialPort, &dcb)) + { + return false; + } + + return true; +} + +/*--------------------------------------------------------------------------*/ + +bool SerialPort::SetRTS(bool RTS) +{ + DebugTrace("SerialPort::SetRTS\n"); + + if (m_hSerialPort == INVALID_HANDLE_VALUE) + { + return false; + } + + if (!EscapeCommFunction(m_hSerialPort, RTS ? CLRRTS : SETRTS)) + { + return false; + } + + return true; +} + +/*--------------------------------------------------------------------------*/ + +bool SerialPort::WriteChar(unsigned char Data) +{ + DWORD BytesSent = 0; + + bool Success = !!WriteFile(m_hSerialPort, + &Data, + 1, + &BytesSent, + &m_OverlappedWrite); + + if (!Success) + { + DWORD Error = GetLastError(); + + if (Error == ERROR_IO_PENDING) + { + DWORD WaitResult = WaitForSingleObject(m_OverlappedWrite.hEvent, INFINITE); + + Success = WaitResult == WAIT_OBJECT_0; + } + else + { + DebugTrace("WriteFile failed, code %lu", (ULONG)Error); + } + } + + return Success; +} + +/*--------------------------------------------------------------------------*/ + +bool SerialPort::HasRxData() const +{ + return m_RxBuffer.HasData(); +} + +/*--------------------------------------------------------------------------*/ + +unsigned char SerialPort::GetRxData() +{ + EnterCriticalSection(&m_BufferLock); + + unsigned char Data = m_RxBuffer.GetData(); + + LeaveCriticalSection(&m_BufferLock); + + return Data; +} + +/*--------------------------------------------------------------------------*/ + +bool SerialPort::HasStatus() const +{ + return m_StatusBuffer.HasData(); +} + +/*--------------------------------------------------------------------------*/ + +unsigned char SerialPort::GetStatus() +{ + EnterCriticalSection(&m_BufferLock); + + unsigned char Data = m_StatusBuffer.GetData(); + + LeaveCriticalSection(&m_BufferLock); + + return Data; +} + +/*--------------------------------------------------------------------------*/ + +unsigned int __stdcall SerialPort::ReadThreadFunc(void* pParameter) +{ + SerialPort* pSerialPort = static_cast(pParameter); + + if (pSerialPort != nullptr) + { + pSerialPort->ReadThreadFunc(); + } + + return 0; +} + +/*--------------------------------------------------------------------------*/ + +void SerialPort::ReadThreadFunc() +{ + // Notify the parent thread that this thread has started running. + SetEvent(m_hReadStartUpEvent); + + DebugTrace("ReadThreadFunc started\n"); + + bool bQuit = false; + + while (!bQuit) + { + HANDLE hEvents[] = { m_OverlappedRead.hEvent, m_hReadShutDownEvent }; + + // Wait for one of the event objects to become signalled. + DWORD Result = WaitForMultipleObjects(_countof(hEvents), hEvents, FALSE, INFINITE); + + switch (Result) + { + case WAIT_OBJECT_0 + 0: + // Clear comms port line errors, if any. + if (m_dwCommEvent & EV_ERR) + { + DWORD Error = 0; + + if (ClearCommError(m_hSerialPort, &Error, nullptr)) + { + DebugTrace("Comms error %lu (framing, overrun, parity)\n", Error); + } + } + + // Process received characters, if any. + if (m_dwCommEvent & EV_RXCHAR) + { + const int BUFFER_LENGTH = 256; + unsigned char Buffer[BUFFER_LENGTH]; + + int Pos = 0; + + for (;;) + { + unsigned char Data; + DWORD BytesRead = 0; + + OVERLAPPED Overlapped{}; + + BOOL Success = ReadFile(m_hSerialPort, // hFile + &Data, // lpBuffer + 1, // nNumberOfBytesToRead + &BytesRead, // lpNumberOfBytesRead + &Overlapped); // lpOverlapped + + if (Success && BytesRead == 1) + { + Buffer[Pos] = Data; + + if (++Pos == BUFFER_LENGTH) + { + PutRxData(Buffer, BUFFER_LENGTH); + Pos = 0; + } + } + else + { + break; + } + } + + if (Pos > 0) + { + PutRxData(Buffer, Pos); + } + } + + if (m_dwCommEvent & EV_CTS) + { + DWORD ModemStatus = 0; + + if (GetCommModemStatus(m_hSerialPort, &ModemStatus)) + { + PutCommStatus((unsigned char)ModemStatus); + } + } + + // Prepare for the next comms event. + ResetEvent(m_OverlappedRead.hEvent); + WaitCommEvent(m_hSerialPort, &m_dwCommEvent, &m_OverlappedRead); + break; + + case WAIT_OBJECT_0 + 1: + // The thread has been signalled to terminate. + DebugTrace("Overlapped I/O read thread shutdown event signalled\n"); + + bQuit = true; + break; + + default: + // Unexpected return code - terminate the thread. + Result = GetLastError(); + DebugTrace("Overlapped I/O thread WaitForMultipleObjects failed, code %lu\n", (ULONG)Result); + + bQuit = true; + break; + } + } + + DebugTrace("ReadThreadFunc stopped\n"); +} + +/*--------------------------------------------------------------------------*/ + +void SerialPort::PutRxData(const unsigned char* pData, int Length) +{ + EnterCriticalSection(&m_BufferLock); + + for (int i = 0; i < Length; i++) + { + m_RxBuffer.PutData(pData[i]); + } + + LeaveCriticalSection(&m_BufferLock); +} + +/*--------------------------------------------------------------------------*/ + +void SerialPort::PutCommStatus(unsigned char Status) +{ + EnterCriticalSection(&m_BufferLock); + + m_StatusBuffer.PutData(Status); + + LeaveCriticalSection(&m_BufferLock); +} + +/*--------------------------------------------------------------------------*/ diff --git a/Src/SerialPort.h b/Src/SerialPort.h new file mode 100644 index 00000000..a703a388 --- /dev/null +++ b/Src/SerialPort.h @@ -0,0 +1,72 @@ +/**************************************************************** +BeebEm - BBC Micro and Master 128 Emulator +Copyright (C) 2024 Chris Needham + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public +License along with this program; if not, write to the Free +Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +Boston, MA 02110-1301, USA. +****************************************************************/ + +#ifndef SERIAL_PORT_HEADER +#define SERIAL_PORT_HEADER + +#include "RingBuffer.h" + +class SerialPort +{ + public: + SerialPort(); + SerialPort(const SerialPort&) = delete; + SerialPort& operator=(const SerialPort&) = delete; + ~SerialPort(); + + public: + bool Init(const char* PortName); + void Close(); + + bool SetBaudRate(int BaudRate); + bool Configure(unsigned char DataBits, unsigned char StopBits, unsigned char Parity); + bool SetRTS(bool RTS); + + bool WriteChar(unsigned char Data); + + bool HasRxData() const; + unsigned char GetRxData(); + + bool HasStatus() const; + unsigned char GetStatus(); + + private: + bool InitThread(); + + static unsigned int __stdcall ReadThreadFunc(void* pParameter); + void ReadThreadFunc(); + + void PutRxData(const unsigned char* pData, int Length); + void PutCommStatus(unsigned char Status); + + private: + HANDLE m_hSerialPort; + HANDLE m_hReadThread; + HANDLE m_hReadStartUpEvent; + HANDLE m_hReadShutDownEvent; + DWORD m_dwCommEvent; + OVERLAPPED m_OverlappedRead; + OVERLAPPED m_OverlappedWrite; + RingBuffer m_RxBuffer; + RingBuffer m_StatusBuffer; + CRITICAL_SECTION m_BufferLock; +}; + +#endif diff --git a/Src/TouchScreen.cpp b/Src/TouchScreen.cpp index 3bfcd61d..c1d410fb 100644 --- a/Src/TouchScreen.cpp +++ b/Src/TouchScreen.cpp @@ -36,8 +36,8 @@ static int TouchScreenDelay; // Note: Touchscreen routines use InputBuffer for data into the // touchscreen and OutputBuffer as data from the touchscreen. -static RingBuffer InputBuffer; -static RingBuffer OutputBuffer; +static RingBuffer InputBuffer(1024); +static RingBuffer OutputBuffer(1024); /*--------------------------------------------------------------------------*/ From a6573cd506e103909db2b9335469eececfe30397 Mon Sep 17 00:00:00 2001 From: Chris Needham Date: Mon, 8 Jul 2024 21:46:40 +0100 Subject: [PATCH 005/101] Fixed some compiler warnings --- Src/Disc1770.cpp | 14 ++++++++++---- Src/Disc8271.cpp | 4 ++-- Src/DiscEdit.cpp | 12 ++++++------ Src/Ide.cpp | 25 +++++++++++++++---------- 4 files changed, 33 insertions(+), 22 deletions(-) diff --git a/Src/Disc1770.cpp b/Src/Disc1770.cpp index 1df645c4..28e857fc 100644 --- a/Src/Disc1770.cpp +++ b/Src/Disc1770.cpp @@ -568,10 +568,16 @@ void Poll1770(int NCycles) { // If reading multiple sectors, and ByteCount== :- // 256..2: read + DRQ (255x) // 1: read + DRQ + rotate disc + go back to 256 - if (ByteCount > 0 && !feof(CurrentDisc)) { - Data = fgetc(CurrentDisc); - Status |= WD1770_STATUS_DATA_REQUEST; - NMIStatus |= 1 << nmi_floppy; + if (ByteCount > 0 && !feof(CurrentDisc)) + { + int Value = fgetc(CurrentDisc); + + if (Value != EOF) + { + Data = (unsigned char)Value; + Status |= WD1770_STATUS_DATA_REQUEST; + NMIStatus |= 1 << nmi_floppy; + } } if (ByteCount == 0 || (ByteCount == 1 && MultiSect)) { diff --git a/Src/Disc8271.cpp b/Src/Disc8271.cpp index f43ab4c0..d5d95d99 100644 --- a/Src/Disc8271.cpp +++ b/Src/Disc8271.cpp @@ -2281,11 +2281,11 @@ static bool DriveHeadMotorUpdate() if (FDCState.DriveHeadPosition[Drive] < FDCState.FSDPhysicalTrack[Drive]) { - FDCState.DriveHeadPosition[Drive] += Tracks; + FDCState.DriveHeadPosition[Drive] = (unsigned char)(FDCState.DriveHeadPosition[Drive] + Tracks); } else { - FDCState.DriveHeadPosition[Drive] -= Tracks; + FDCState.DriveHeadPosition[Drive] = (unsigned char)(FDCState.DriveHeadPosition[Drive] - Tracks); } return true; diff --git a/Src/DiscEdit.cpp b/Src/DiscEdit.cpp index c84a7394..bcd44b60 100644 --- a/Src/DiscEdit.cpp +++ b/Src/DiscEdit.cpp @@ -12,8 +12,8 @@ but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. -You should have received a copy of the GNU General Public -License along with this program; if not, write to the Free +You should have received a copy of the GNU General Public +License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ****************************************************************/ @@ -153,7 +153,7 @@ bool dfs_get_catalogue(const char *szDiscFile, dfsCat->numFiles = buffer[DFS_SECTOR_SIZE + 5] / 8; dfsCat->numSectors = ((buffer[DFS_SECTOR_SIZE + 6] & 3) << 8) + buffer[DFS_SECTOR_SIZE + 7]; dfsCat->bootOpts = (buffer[DFS_SECTOR_SIZE + 6] >> 4) & 3; - + // Read first 31 files dfs_get_files_from_cat(buffer, buffer + DFS_SECTOR_SIZE, dfsCat->numFiles, dfsCat->fileAttrs); @@ -371,7 +371,7 @@ bool dfs_import_file(const char *szDiscFile, } // Build file names - char filename[_MAX_PATH]; + char filename[MAX_PATH]; strcpy(filename, szImportFolder); strcat(filename, "/"); @@ -622,13 +622,13 @@ bool dfs_import_file(const char *szDiscFile, // Update catalogue sectors n = (dfsCat->numFiles > 31) ? 31 : dfsCat->numFiles; - buffer[DFS_SECTOR_SIZE + 5] = n * 8; + buffer[DFS_SECTOR_SIZE + 5] = (unsigned char)(n * 8); dfs_write_files_to_cat(buffer, buffer + DFS_SECTOR_SIZE, n, attrs); if (dfsCat->watford62) { n = (dfsCat->numFiles > 31) ? dfsCat->numFiles - 31 : 0; - buffer[DFS_SECTOR_SIZE * 3 + 5] = n * 8; + buffer[DFS_SECTOR_SIZE * 3 + 5] = (unsigned char)(n * 8); dfs_write_files_to_cat(buffer + DFS_SECTOR_SIZE * 2, buffer + DFS_SECTOR_SIZE * 3, n, &attrs[31]); } diff --git a/Src/Ide.cpp b/Src/Ide.cpp index 8a649ea2..fe8d1d3f 100644 --- a/Src/Ide.cpp +++ b/Src/Ide.cpp @@ -131,7 +131,7 @@ void IDEWrite(int Address, unsigned char Value) unsigned char IDERead(int Address) { - unsigned char data = 0xff; + unsigned char Data = 0xff; switch (Address) { @@ -152,31 +152,36 @@ unsigned char IDERead(int Address) // If in data read cycle, read data byte from file if (IDEData > 0) { - data = fgetc(IDEDisc[IDEDrive]); - IDEData--; + int Value = fgetc(IDEDisc[IDEDrive]); - // If read all data, reset Data Ready - if (IDEData == 0) + if (Value != EOF) { - IDEStatus &= ~0x08; + Data = (unsigned char)Value; + IDEData--; + + // If read all data, reset Data Ready + if (IDEData == 0) + { + IDEStatus &= ~0x08; + } } } break; case 0x01: // Error - data = IDEError; + Data = IDEError; break; case 0x07: // Status - data = IDEStatus; + Data = IDEStatus; break; default: // Other registers - data = IDERegs[Address]; + Data = IDERegs[Address]; break; } - return data; + return Data; } void IDEClose() From e87b30d549a276c7b0f2ea689fe0079e829431fb Mon Sep 17 00:00:00 2001 From: Chris Needham Date: Tue, 9 Jul 2024 08:52:56 +0100 Subject: [PATCH 006/101] Disable compiler warnings from zlib files --- Src/BeebEm.vcxproj | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Src/BeebEm.vcxproj b/Src/BeebEm.vcxproj index bafd1b45..2a0a8e51 100644 --- a/Src/BeebEm.vcxproj +++ b/Src/BeebEm.vcxproj @@ -263,10 +263,10 @@ 4131 - 4131 - 4131 - 4131 - 4131 + 4131;4244;4127 + 4131;4244;4127 + 4131;4244;4127 + 4131;4244;4127 4131 @@ -311,10 +311,10 @@ 4131 - 4131 - 4131 - 4131 - 4131 + 4131;4244 + 4131;4244 + 4131;4244 + 4131;4244 4131 From d805e6d3194334ee54a04a89db2fece1dece72e2 Mon Sep 17 00:00:00 2001 From: Chris Needham Date: Tue, 9 Jul 2024 20:13:11 +0100 Subject: [PATCH 007/101] Updated help documentation for serial port emulation --- Help/serial.html | 59 ++++++++++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/Help/serial.html b/Help/serial.html index 8de1a651..ec395e4e 100644 --- a/Help/serial.html +++ b/Help/serial.html @@ -60,14 +60,41 @@

Serial Port

BeebEm RS423 dialog box +

Serial Port

+ +

+ When the Serial Port option is selected, BeebEm + will use one of the host PC's serial ports. Enter the port name, e.g., + COM1 into the Port field. +

+ +

Microvitec Touchtech 501

+ +

+ When the Microvitec Touch Screen option is + selected, together with the Options → Analogue + Mousestick option, mouse input in the BeebEm window is translated + into touch screen input. Software for the Microvitec Touchtech 501 + can be downloaded from + Paul + Vernon's website. +

+

Serial over IP

- You can use the "tcpser" package, which emulates a modem. Select - 127.0.0.1 and 25232 as the IP Address and Port. Download a prebuilt Windows binary of - tcpser.zip. The tcpser - source code is available + When the IP option is selected, BeebEm will + make a TCP connection to the specified IP Address. + and Port. This could be the address of a host + to connect to over the internet, or the "tcpser" package, which emulates + a modem and handles the connection to a remote system. +

+ +

+ To use tcpser, select 127.0.0.1 and 25232 as the IP + Address and Port. Download a prebuilt + Windows binary of tcpser.zip. + The tcpser source code is available here.

@@ -92,8 +119,8 @@

Serial over IP

Add the Commstar.rom to your ROM configuration (see ROM Software) and start up BeebEm. In the RS423 dialog box, select IP, - IP Address "127.0.0.1", and Port "25232". Click OK to + IP Address 127.0.0.1, and Port 25232. Click OK to close the dialog box then select RS432 On/Off from the Comms menu. Type *COMMSTAR to start CommStar. In Commstar: @@ -114,12 +141,6 @@

Serial over IP

server using ATDishar.com:9999.

-

- To make a direct connection to a server, select IP - and put the server's IP address and port into the RS423 dialog box. -

-

The IP232 Raw Comms option controls special processing of character 255. When enabled, no special handling is @@ -147,18 +168,6 @@

Serial over IP

realistic experience when talking at 1200 baud to a Viewdata host! Transmitted data is just sent out as fast as it will go.

- -

Microvitec Touchtech 501

- -

- When the Microvitec Touch Screen option is - selected, together with the Options → Analogue - Mousestick option, mouse input in the BeebEm window is translated - into touch screen input. Software for the Microvitec Touchtech 501 - can be downloaded from - Paul - Vernon's website. -