diff --git a/CSpectPlugins.sln b/CSpectPlugins.sln
index 60b1e0c..a871ad2 100644
--- a/CSpectPlugins.sln
+++ b/CSpectPlugins.sln
@@ -11,7 +11,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RTC", "RTC\RTC.csproj", "{0
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "I2CTestHarness", "I2CTestHarness\I2CTestHarness.csproj", "{3708E183-3E15-4D20-8726-46C452B951C7}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UARTForwarder", "UARTForwarder\UARTForwarder.csproj", "{B620589D-3613-40FB-ACFB-9C825D474D7F}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UARTReplacement", "UARTReplacement\UARTReplacement.csproj", "{B620589D-3613-40FB-ACFB-9C825D474D7F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
diff --git a/UARTForwarder/SerialPort.cs b/UARTForwarder/SerialPort.cs
deleted file mode 100644
index 761476f..0000000
--- a/UARTForwarder/SerialPort.cs
+++ /dev/null
@@ -1,106 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Plugins.UARTForwarder
-{
- public class SerialPort : IDisposable
- {
- private System.IO.Ports.SerialPort port;
-
- public SerialPort(string PortName, int BaudRate)
- {
- try
- {
- port = new System.IO.Ports.SerialPort();
- port.PortName = PortName;
- port.BaudRate = BaudRate;
- port.Parity = System.IO.Ports.Parity.None;
- port.DataBits = 8;
- port.StopBits = System.IO.Ports.StopBits.One;
- port.Handshake = System.IO.Ports.Handshake.None;
- //port.ReadTimeout = 1;
- //port.WriteTimeout = 500;
- port.Open();
- }
- catch
- {
- port = null;
- }
- }
-
- public void Write(byte[] buffer, int offset, int count)
- {
- //try
- //{
- if (port != null)
- port.Write(buffer, offset, count);
- //}
- //catch (TimeoutException)
- //{
- //}
- }
-
- public byte ReadByte(out bool Success)
- {
- try
- {
- if (port != null)
- {
- var b = Convert.ToByte(port.ReadByte());
- Success = true;
- return b;
- }
- }
- catch
- {
- }
- Success = false;
- return 0xff;
- }
-
- #region IDisposable Support
- private bool disposedValue = false; // To detect redundant calls
-
- protected virtual void Dispose(bool disposing)
- {
- if (!disposedValue)
- {
- if (disposing)
- {
- // Dispose managed state (managed objects).
- if (port != null)
- {
- if (port.IsOpen)
- port.Close();
- port.Dispose();
- port = null;
- }
- }
-
- // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
- // TODO: set large fields to null.
-
- disposedValue = true;
- }
- }
-
- // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
- // ~SerialPort() {
- // // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
- // Dispose(false);
- // }
-
- // This code added to correctly implement the disposable pattern.
- public void Dispose()
- {
- // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
- Dispose(true);
- // TODO: uncomment the following line if the finalizer is overridden above.
- // GC.SuppressFinalize(this);
- }
- #endregion
- }
-}
diff --git a/UARTForwarder/Properties/AssemblyInfo.cs b/UARTReplacement/Properties/AssemblyInfo.cs
similarity index 88%
rename from UARTForwarder/Properties/AssemblyInfo.cs
rename to UARTReplacement/Properties/AssemblyInfo.cs
index 0dbc6cf..4fcee3b 100644
--- a/UARTForwarder/Properties/AssemblyInfo.cs
+++ b/UARTReplacement/Properties/AssemblyInfo.cs
@@ -5,12 +5,12 @@
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
-[assembly: AssemblyTitle("UARTForwarder")]
+[assembly: AssemblyTitle("UARTReplacement")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("UARTForwarder")]
-[assembly: AssemblyCopyright("Copyright © 2020")]
+[assembly: AssemblyProduct("UARTReplacement")]
+[assembly: AssemblyCopyright("Copyright © 2020 Robin Verhagen-Guest")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
diff --git a/UARTReplacement/SerialPort.cs b/UARTReplacement/SerialPort.cs
new file mode 100644
index 0000000..55ee84d
--- /dev/null
+++ b/UARTReplacement/SerialPort.cs
@@ -0,0 +1,250 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Plugins.UARTReplacement
+{
+ public class SerialPort : IDisposable
+ {
+ private System.IO.Ports.SerialPort port;
+ private int clock = 27000000; // CSpect defaults to HDMI timings
+ private int prescaler = 234; // Next baud defaults to 115200 (more accurately 115384 with integer division)
+
+ public SerialPort(string PortName, int BaudRate)
+ {
+ try
+ {
+ port = new System.IO.Ports.SerialPort();
+ port.PortName = PortName;
+ int baud = Baud;
+ port.BaudRate = baud > 0 ? baud : 115200;
+ port.Parity = System.IO.Ports.Parity.None;
+ port.DataBits = 8;
+ port.StopBits = System.IO.Ports.StopBits.One;
+ port.Handshake = System.IO.Ports.Handshake.None;
+ LogClock(false);
+ LogPrescaler("");
+ port.Open();
+ }
+ catch (Exception /*ex*/)
+ {
+ port = null;
+ }
+ }
+
+ public void Write(byte[] buffer, int offset, int count)
+ {
+ if (port != null)
+ port.Write(buffer, offset, count);
+ }
+
+ ///
+ /// If there is a byte available in the UART buffer return it, otherwise a value representing no data.
+ ///
+ /// Output parameter indicating whether the result was a valid byte or no data.
+ /// A byte from the UART buffer, or a value representing no data.
+ public byte ReadByte(out bool Success)
+ {
+ try
+ {
+ if (port != null)
+ {
+ var b = Convert.ToByte(port.ReadByte());
+ Success = true;
+ return b;
+ }
+ }
+ catch
+ {
+ }
+ Success = false;
+ return 0xff;
+ }
+
+ ///
+ /// Given a raw byte read from REG_VIDEO_TIMING, and another raw byte written to PORT_UART_CONTROL,
+ /// updates the clock and potentially also bits 17:13 of the prescaler.
+ ///
+ /// Read from REG_VIDEO_TIMING, used to update the clock.
+ /// Written to PORT_UART_CONTROL, used to update bits 17:13 of the prescaler.
+ ///
+ public int SetPrescalerAndClock(byte BaudByte, byte VideoTimingByte)
+ {
+ if (port == null)
+ return 0;
+ int mode = VideoTimingByte & 7;
+ switch (mode)
+ {
+ case 0:
+ clock = 28000000;
+ break;
+ case 1:
+ clock = 28571429;
+ break;
+ case 2:
+ clock = 29464286;
+ break;
+ case 3:
+ clock = 30000000;
+ break;
+ case 4:
+ clock = 31000000;
+ break;
+ case 5:
+ clock = 32000000;
+ break;
+ case 6:
+ clock = 33000000;
+ break;
+ case 7:
+ clock = 27000000;
+ break;
+ default:
+ clock = 28000000;
+ break;
+ }
+ if ((BaudByte & 0x10) == 0x10)
+ {
+ // Get bits 14..17 of the new prescaler
+ int newBits = (BaudByte & 0x07) << 14;
+ // Mask out everything of the existing prescaler except bits 14..17
+ int oldBits = prescaler & 0x3fff;
+ // Combine the two sets of bits
+ prescaler = oldBits | newBits;
+ port.BaudRate = Baud;
+ LogClock(false);
+ LogPrescaler("17:14");
+ }
+ else
+ {
+ port.BaudRate = Baud;
+ LogClock();
+ }
+ return clock;
+ }
+
+ ///
+ /// Given a raw byte written to PORT_UART_RX, parses the high bit to decide whether it represents a change
+ /// to bits 6:0 or 13:7, and updates the prescaler accordingly.
+ ///
+ /// The raw byte written to I/O port PORT_UART_RX.
+ /// Returns the newly recalculated baud.
+ public int SetPrescaler(byte BaudByte)
+ {
+ if (port == null)
+ return 0;
+ if ((BaudByte & 0x80) == 0)
+ {
+ // Get bits 0..6 of the new prescaler
+ int newBits = BaudByte & 0x7f;
+ // Mask out everything of the existing prescaler except bits 0..6
+ int oldBits = prescaler & 0x1ff80;
+ // Combine the two sets of bits
+ prescaler = oldBits | newBits;
+ port.BaudRate = Baud;
+ LogPrescaler("6:0");
+ }
+ else
+ {
+ // Get bits 7..13 of the new prescaler
+ int newBits = (BaudByte & 0x7f) << 7;
+ // Mask out everything of the existing prescaler except bits 7..13
+ int oldBits = prescaler & 0x1c07f;
+ // Combine the two sets of bits
+ prescaler = oldBits | newBits;
+ port.BaudRate = Baud;
+ LogPrescaler("13:7");
+ }
+ return Baud;
+ }
+
+ ///
+ /// Baud is always calculated dynamically from the clock and prescaler, using integer division.
+ ///
+ public int Baud
+ {
+ get
+ {
+ //
+ if (prescaler == 0)
+ return 0;
+ return Convert.ToInt32(Math.Truncate((Convert.ToDecimal(clock) / prescaler)));
+ }
+ }
+
+ ///
+ /// Convenience method to log the clock and calculated baud to the debug console, every time the video timing clock changes.
+ ///
+ ///
+ /// Optionally choose not to log the calculated baud, if you know the prescaler is about to be changed
+ /// and logged straight afterwareds.
+ ///
+ private void LogClock(bool LogBaud = true)
+ {
+ if (port == null)
+ return;
+ Debug.WriteLine("Clock: " + clock);
+ if (LogBaud)
+ Debug.WriteLine("Baud: " + Baud);
+ }
+
+ ///
+ /// Convenience method to log the prescaler and calculated baud to the debug console, every time the prescaler changes.
+ ///
+ ///
+ private void LogPrescaler(string BitsChanged)
+ {
+ if (port == null)
+ return;
+ string bstr = Convert.ToString(prescaler, 2).PadLeft(17, '0');
+ Debug.WriteLine("Prescaler: " + bstr.Substring(0, 3) + " " + bstr.Substring(3, 7) + " " + bstr.Substring(10, 7)
+ + " (" + prescaler + (string.IsNullOrWhiteSpace(BitsChanged) ? "" : ", changed bits " + BitsChanged) + ")");
+ Debug.WriteLine("Baud: " + Baud);
+ }
+
+ #region IDisposable Support
+ private bool disposedValue = false; // To detect redundant calls
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!disposedValue)
+ {
+ if (disposing)
+ {
+ // Dispose managed state (managed objects).
+ if (port != null)
+ {
+ if (port.IsOpen)
+ port.Close();
+ port.Dispose();
+ port = null;
+ }
+ }
+
+ // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
+ // TODO: set large fields to null.
+
+ disposedValue = true;
+ }
+ }
+
+ // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
+ // ~SerialPort() {
+ // // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
+ // Dispose(false);
+ // }
+
+ // This code added to correctly implement the disposable pattern.
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
+ Dispose(true);
+ // TODO: uncomment the following line if the finalizer is overridden above.
+ // GC.SuppressFinalize(this);
+ }
+ #endregion
+ }
+}
diff --git a/UARTForwarder/UARTForwarder.csproj b/UARTReplacement/UARTReplacement.csproj
similarity index 94%
rename from UARTForwarder/UARTForwarder.csproj
rename to UARTReplacement/UARTReplacement.csproj
index 216a525..c7d8264 100644
--- a/UARTForwarder/UARTForwarder.csproj
+++ b/UARTReplacement/UARTReplacement.csproj
@@ -7,8 +7,8 @@
{B620589D-3613-40FB-ACFB-9C825D474D7F}
Library
Properties
- Plugins.UARTForwarder
- UARTForwarder
+ Plugins.UARTReplacement
+ UARTReplacement
v4.5.2
512
@@ -48,7 +48,7 @@
-
+
diff --git a/UARTForwarder/UARTForwarder_Device.cs b/UARTReplacement/UARTReplacement_Device.cs
similarity index 62%
rename from UARTForwarder/UARTForwarder_Device.cs
rename to UARTReplacement/UARTReplacement_Device.cs
index 30750cc..4739528 100644
--- a/UARTForwarder/UARTForwarder_Device.cs
+++ b/UARTReplacement/UARTReplacement_Device.cs
@@ -6,13 +6,15 @@
using System.Threading.Tasks;
using Plugin;
-namespace Plugins.UARTForwarder
+namespace Plugins.UARTReplacement
{
- public class UARTLogger_Device : iPlugin
+ public class UARTReplacement_Device : iPlugin
{
private const ushort PORT_UART_TX = 0x133b;
private const ushort PORT_UART_RX = 0x143b;
private const ushort PORT_UART_CONTROL = 0x153b;
+ private const byte REG_VIDEO_TIMING = 0x11;
+
private iCSpect CSpect;
private UARTTargets Target;
@@ -41,10 +43,11 @@ public List Init(iCSpect _CSpect)
// create a list of the ports we're interested in, but only if we're logging
List ports = new List();
- ports.Add(new sIO(PORT_UART_TX, eAccess.Port_Write));
- ports.Add(new sIO(PORT_UART_TX, eAccess.Port_Read));
- ports.Add(new sIO(PORT_UART_RX, eAccess.Port_Read));
- ports.Add(new sIO(PORT_UART_CONTROL, eAccess.Port_Write));
+ ports.Add(new sIO(PORT_UART_TX, eAccess.Port_Write));
+ ports.Add(new sIO(PORT_UART_TX, eAccess.Port_Read));
+ ports.Add(new sIO(PORT_UART_RX, eAccess.Port_Write));
+ ports.Add(new sIO(PORT_UART_RX, eAccess.Port_Read));
+ ports.Add(new sIO(PORT_UART_CONTROL, eAccess.Port_Write));
return ports;
}
@@ -63,15 +66,31 @@ public bool Write(eAccess _type, int _port, byte _value)
switch (_port)
{
case PORT_UART_CONTROL:
+ // Writes to this port contain siwtches between the ESP and Pi UARTs (we only handle the former),
+ // and also potential changes to the prescaler which cause the UART baud to change.
+ // We might not need to query REG_VIDEO_TIMING every single time the baud or UART selector changes,
+ // But just in case it can vary, let's do it. It doesn't seem to cause performance issues to do so.
var target = (_value & 64) == 0 ? UARTTargets.ESP : UARTTargets.Pi;
Target = target;
- //Debug.WriteLine("Switched UART to " + target.ToString());
+ if (Target == UARTTargets.ESP)
+ espPort.SetPrescalerAndClock(_value, CSpect.GetNextRegister(REG_VIDEO_TIMING));
// We are transparently logging without handling the write, so return false
return false;
case PORT_UART_TX:
if (Target == UARTTargets.ESP)
{
+ // This is an outgoing UART byte which should be sent to our buffered UART implementation
espPort.Write(new byte[] { _value }, 0, 1);
+ // If we are actively handling the write, return true.
+ return true;
+ }
+ // If we are not handling the write, return false so CSpect's own Pi UART will handle it.
+ return false;
+ case PORT_UART_RX:
+ if (Target == UARTTargets.ESP)
+ {
+ // Writes to this port represent changes to the prescaler which cause the UART baud to change
+ espPort.SetPrescaler(_value);
return true;
}
return false;
@@ -97,6 +116,8 @@ public byte Read(eAccess _type, int _port, out bool _isvalid)
// then read the actual value, then handle the callback with the read value.
if (Target == UARTTargets.ESP)
{
+ // This is an incoming UART byte which should be read from our buffered UART implementation
+ // Z80 code will only call this if the status flag indicates data is ready when reading PORT_UART_TX.
_isvalid = true;
if (readBuffer.Count > 0)
{
@@ -108,10 +129,12 @@ public byte Read(eAccess _type, int _port, out bool _isvalid)
else
return 0xff;
}
+ // If not already handled, any remaining reads from this port represent data reads
+ // from the Pi UART. So pass this through to let CSpect handle it (which initiates
+ // another recursive call to this method, which we handle above).
UART_RX_Internal = true;
byte val = CSpect.InPort(PORT_UART_RX);
UART_RX_Internal = false;
- //Debug.WriteLine("RX: " + val.ToString("X2"));
_isvalid = true;
return val;
case PORT_UART_TX:
@@ -124,16 +147,20 @@ public byte Read(eAccess _type, int _port, out bool _isvalid)
}
if (Target == UARTTargets.ESP)
{
+ // Reads from this port return a status flag in bit 0 indicating whether
+ // there is any data available to read from the UART buffer.
_isvalid = true;
if (readBuffer.Count > 0)
return 1;
else
return 0;
}
+ // If not already handled, any remaining reads from this port represent data ready status flag
+ // reads from the Pi UART. So pass this through to let CSpect handle it (which initiates
+ // another recursive call to this method, which we handle above).
UART_RX_Internal = true;
byte val2 = CSpect.InPort(PORT_UART_TX);
UART_RX_Internal = false;
- //Debug.WriteLine("RX: " + val.ToString("X2"));
_isvalid = true;
return val2;
}
@@ -142,6 +169,11 @@ public byte Read(eAccess _type, int _port, out bool _isvalid)
return 0xff;
}
+
+ // This method is runs on a background thread to continuously read all available bytes
+ // proactively from the plugin's serial port, and fill the UART read buffer with them.
+ // Access to the buffer from both threads is synchronised with a mutex via the lock() construct.
+ // This method It may not offer the best performance, but so far I have not needed to optimise it.
public void ESPWork()
{
while(true)
diff --git a/UARTForwarder/UARTTargets.cs b/UARTReplacement/UARTTargets.cs
similarity index 84%
rename from UARTForwarder/UARTTargets.cs
rename to UARTReplacement/UARTTargets.cs
index 9f27ddd..e51a0c2 100644
--- a/UARTForwarder/UARTTargets.cs
+++ b/UARTReplacement/UARTTargets.cs
@@ -4,7 +4,7 @@
using System.Text;
using System.Threading.Tasks;
-namespace Plugins.UARTForwarder
+namespace Plugins.UARTReplacement
{
public enum UARTTargets
{