C# SerialPort クラスを使用して RTS/CTS を手動で制御する方法


ご存じの方、ご教示頂きたくお願いいたします。

現在、C# で `SerialPort` クラスを使用してモデムテスターを開発しています。

`System.IO.Ports.Handshake.RequestToSend` を使用せずに RTS/CTS を手動で制御する方法はありますか?

データ送受信の流れは以下の通りです。

  0. シリアルポートを開きます。
  1. RTS を ON にします。
  2. CTS の ON を待ちます。
  3. CTS が ON になったら、バイトデータを送信します。
  4. データ送信が完了したら RTS を OFF します。
  5. 相手側からのデータ受信を待ちます。
  6. 相手側からのデータを受信します。

上記 1. ～ 6. を繰り返します。

上記の 1. ～ 2. と 4. を実現するため `SerialPort` クラスのプロパティは

  * `SerialPort.RtsEnable` → `true`: RTS ON, `false`: RTS OFF
  * `SerialPort.CtsHolding` → `true`: CTS ON, `false`: CTS OFF

と考えています。

試しに、RTS信号の出力がCTS信号に入力されるようにRS232Cケーブルを配線してみました。  
この RS232C ケーブルを使って以下のコードで検証しましたが、`SerialPort.CtsHolding` が `true`
にならずタイムアウトしてしまいます。

`SerialPort.RtsEnable` を `true` に設定しても、RTS は ON(Hight) にはならないのでしょうか?  
(Win32API を使用するしかないのでしょうか...)

[code]

    public class SerialCommunicationTester : System.IDisposable
    {
        private System.IO.Ports.SerialPort _serialPort = null;
    
        // 0. Open SerialPort.
        public bool Open(
            string portName,
            int baudRate,
            int dataBits,
            System.IO.Ports.Parity parity,
            System.IO.Ports.StopBits stopBits,
            int readTimeout,
            int writeTimeout,
        ) {
            _serialPort.PortName = portName;
            _serialPort.BaudRate = baudRate;
            _serialPort.DataBits = dataBits;
            _serialPort.Parity = parity;
            _serialPort.StopBits = stopBits;
            _serialPort.Handshake = Handshake.None;
            _serialPort.ReadTimeout = readTimeout;
            _serialPort.WriteTimeout = writeTimeout;
    
            try {
                _serialPort.Open();
            } catch (Exception ex) {
                Console.WriteLine("SerialPort#Open Failed...{0}", ex);
            }
    
            return _serialPort.IsOpen;
        }
    
        // 1. Set to RTS "High" and 2. Wait for CTS to go "High".
        public bool RtsCtsFlowCtrl(uint timeout) {
    
            if (!_serialPort.IsOpen) return false;
    
            bool result = true;
            // 1. Set to RTS "High"
            _serialPort.RtsEnable = true;
            Stopwatch sw = new Stopwatch();
            sw.Start();
            // 2. Wait for CTS to go "High".
            while (true) {
                Thread.Sleep(0);
                if (_serialPort.CtsHolding) break;
                if (0 < timeout && timeout <= sw.ElapsedMilliseconds) {
                    result = false;
                    break;
                }
            }
            sw.Stop();
            return result;
        }
    
        // 4. Set to RTS "Low"
        public void RtsOff() {
            if (_serialPort.IsOpen) _serialPort.RtsEnable = false;
        }
    }
    
[/code]




.NETのソースコードを読む限り、期待通りの動作をしています。つまり、

[`SerialPort.RtsEnable`プロパティは最終的に](https://github.com/dotnet/runtime/blob/v7.0.0/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs#L419)

[code]

    EscapeCommFunction(_handle, <SerialPort.RtsEnableの値> ? SETRTS : CLRRTS);
    
[/code]

が呼ばれますし、[`SerialPort.CtsHolding`プロパティ](https://github.com/dotnet/runtime/blob/v7.0.0/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs#L501-L511)は

[code]

    <GetCommModemStatus()の結果> & MS_CTS_ON
    
[/code]

を指しています。

* * *

> SerialPort.CtsHolding が true にならずタイムアウトしてしまいます。

は原因が別のところにあるかもしれません。例えば、タイムアウトを **1秒** に設定したが、

>
[code]

>     if (0 < timeout && timeout <= sw.ElapsedMilliseconds) {
>  
[/code]

の比較により **1ミリ秒** でタイムアウト判定されてしまった、なども考えられます。

ちなみに.NETではこのような時間単位の誤りを防ぐために [`TimeSpan`型](https://learn.microsoft.com/ja-
jp/dotnet/api/system.timespan?view=net-7.0)が用意されています。
`TimeSpan.FromSeconds(1)` や `TimeSpan.FromMilliseconds(1)` という記述が可能です。




.NETのソースコードを読む限り、期待通りの動作をしています。つまり、

[`SerialPort.RtsEnable`プロパティは最終的に](https://github.com/dotnet/runtime/blob/v7.0.0/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs#L419)

[code]

    EscapeCommFunction(_handle, <SerialPort.RtsEnableの値> ? SETRTS : CLRRTS);
    
[/code]

が呼ばれますし、[`SerialPort.CtsHolding`プロパティ](https://github.com/dotnet/runtime/blob/v7.0.0/src/libraries/System.IO.Ports/src/System/IO/Ports/SerialStream.Windows.cs#L501-L511)は

[code]

    <GetCommModemStatus()の結果> & MS_CTS_ON
    
[/code]

を指しています。

* * *

> SerialPort.CtsHolding が true にならずタイムアウトしてしまいます。

は原因が別のところにあるかもしれません。例えば、タイムアウトを **1秒** に設定したが、

>
[code]

>     if (0 < timeout && timeout <= sw.ElapsedMilliseconds) {
>  
[/code]

の比較により **1ミリ秒** でタイムアウト判定されてしまった、なども考えられます。

ちなみに.NETではこのような時間単位の誤りを防ぐために [`TimeSpan`型](https://learn.microsoft.com/ja-
jp/dotnet/api/system.timespan?view=net-7.0)が用意されています。
`TimeSpan.FromSeconds(1)` や `TimeSpan.FromMilliseconds(1)` という記述が可能です。

