「C Sharpの基礎 - シリアル通信」の版間の差分
|  (→受信) |  (→受信) | ||
| 306行目: | 306行目: | ||
|               Console.WriteLine($"受信中にエラーが発生: {ex.Message}"); |               Console.WriteLine($"受信中にエラーが発生: {ex.Message}"); | ||
|            } |            } | ||
|        } | |||
|     } | |||
|  } | |||
|  </syntaxhighlight> | |||
| <br><br> | |||
| == その他のシリアル通信の機能 == | |||
| 以下の例では、バッファリング、再接続機能、イベントベースの受信を行っている。<br> | |||
| これにより、大量のデータを扱う場合や不安定な接続環境での使用に適している。<br> | |||
| <br> | |||
| * バッファリング | |||
| *: ConcurrentQueue<byte[]>を使用して、受信データをバッファリングする。 | |||
| *: また、バッファの最大サイズを制限して、オーバーフローを防ぐ。 | |||
| * 再接続機能 | |||
| *: 接続が失敗した場合に複数回試行する。 | |||
| *: また、接続状態を監視して、切断された場合に再接続を試みる。 | |||
| * イベントベースの受信 | |||
| *: SerialPort_DataReceivedイベントハンドラを使用して、データ受信時の処理を行う。 | |||
| * 非同期処理 | |||
| *: バッファリングされたデータを非同期に処理する。 | |||
| <br> | |||
|  <syntaxhighlight lang="c#"> | |||
|  using System; | |||
|  using System.Text; | |||
|  using System.IO.Ports; | |||
|  using System.Threading; | |||
|  using System.Threading.Tasks; | |||
|  using System.Collections.Concurrent; | |||
|  class AdvancedAsyncReceiver | |||
|  { | |||
|     private static SerialPort _serialPort; | |||
|     private static ConcurrentQueue<byte[]> _dataBuffer = new ConcurrentQueue<byte[]>(); | |||
|     private static int _maxBufferSize     = 10;    // 受信バッファの最大サイズ | |||
|     private static int _reconnectAttempts = 5;     // 再接続の試行回数 | |||
|     private static int _reconnectDelay    = 5000;  // 再接続の待機時間 (ミリ秒) | |||
|     static async Task Main(string[] args) | |||
|     { | |||
|        string portName   = "/dev/ttyS0";  // ポート名を適切に設定 | |||
|        int baudRate      = 9600;          // ボーレート 9600[bps] | |||
|        Parity parity     = Parity.None;   // パリティ無し | |||
|        int dataBits      = 8;             // データ長は8ビット | |||
|        StopBits stopBits = StopBits.One;  // ストップビットは1ビット | |||
|        try | |||
|        { | |||
|           _serialPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits); | |||
|           _serialPort.Handshake = Handshake.None;  // フロー制御の設定 | |||
|           _serialPort.ReadTimeout = 500;           // 読み取りタイムアウトの設定 (ミリ秒) | |||
|           _serialPort.WriteTimeout = 500;          // 書き込みタイムアウトの設定 (ミリ秒) | |||
|           _serialPort.DataReceived += SerialPort_DataReceived; // イベントハンドラの登録 | |||
|           await ConnectWithRetry(); | |||
|           Console.WriteLine("シリアルポートをオープン"); | |||
|           Console.WriteLine("終了するには [Ctrl] + [C]キーを押下"); | |||
|           using (var cts = new CancellationTokenSource()) | |||
|           { | |||
|              Console.CancelKeyPress += (s, e) => | |||
|              { | |||
|                 e.Cancel = true; | |||
|                 cts.Cancel(); | |||
|              }; | |||
|              await Task.WhenAll( | |||
|                       ProcessBufferedDataAsync(cts.Token), | |||
|                       MonitorConnectionAsync(cts.Token) | |||
|              ); | |||
|           } | |||
|        } | |||
|        catch (OperationCanceledException) | |||
|        { | |||
|           Console.WriteLine("エラー: 受信が中断"); | |||
|        } | |||
|        catch (Exception ex) | |||
|        { | |||
|           Console.WriteLine($"エラーが発生: {ex.Message}"); | |||
|        } | |||
|        finally | |||
|        { | |||
|           _serialPort?.Close(); | |||
|        } | |||
|     } | |||
|     private static async Task ConnectWithRetry() | |||
|     { | |||
|        for (int i = 0; i < _reconnectAttempts; i++) | |||
|        { | |||
|           try | |||
|           { | |||
|              _serialPort.Open(); | |||
|              return; | |||
|           } | |||
|           catch (Exception ex) | |||
|           { | |||
|              Console.WriteLine($"接続試行 {i + 1} 失敗: {ex.Message}"); | |||
|              if (i < _reconnectAttempts - 1) | |||
|              { | |||
|                 await Task.Delay(_reconnectDelay); | |||
|              } | |||
|           } | |||
|        } | |||
|        throw new Exception("接続に失敗"); | |||
|     } | |||
|     private static void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e) | |||
|     { | |||
|        int bytesToRead = _serialPort.BytesToRead; | |||
|        byte[] buffer = new byte[bytesToRead]; | |||
|        _serialPort.Read(buffer, 0, bytesToRead); | |||
|        if (_dataBuffer.Count < _maxBufferSize) | |||
|        { | |||
|           _dataBuffer.Enqueue(buffer); | |||
|        } | |||
|        else | |||
|        { | |||
|           Console.WriteLine("警告: バッファオーバーフロー"); | |||
|           Console.WriteLine("データが破棄されました"); | |||
|        } | |||
|     } | |||
|     private static async Task ProcessBufferedDataAsync(CancellationToken cancellationToken) | |||
|     { | |||
|        while (!cancellationToken.IsCancellationRequested) | |||
|        { | |||
|           if (_dataBuffer.TryDequeue(out byte[] data)) | |||
|           { | |||
|              string receivedData = Encoding.UTF8.GetString(data); | |||
|              Console.WriteLine($"受信したデータ: {receivedData.Trim()}"); | |||
|           } | |||
|           else | |||
|           { | |||
|              await Task.Delay(100, cancellationToken);  // バッファが空の場合は、100[mS]待機 | |||
|           } | |||
|        } | |||
|     } | |||
|     private static async Task MonitorConnectionAsync(CancellationToken cancellationToken) | |||
|     { | |||
|        while (!cancellationToken.IsCancellationRequested) | |||
|        { | |||
|           if (!_serialPort.IsOpen) | |||
|           { | |||
|              Console.WriteLine("接続が切断されたため再接続を試行..."); | |||
|              await ConnectWithRetry(); | |||
|              Console.WriteLine("再接続に成功"); | |||
|           } | |||
|           await Task.Delay(1000, cancellationToken);  // 1[秒]ごとに接続状態を確認 | |||
|         } |         } | ||
|      } |      } | ||
2024年9月17日 (火) 02:58時点における版
概要
シリアル通信は、データを1ビットずつ順番に送受信する通信方式である。
C#では、System.IO.Ports名前空間のSerialPortクラスを使用してシリアル通信を実装することができる。
まず、SerialPortクラスのインスタンスを生成して、ポート名、ボーレート、データビット、ストップビット、パリティ等の通信パラメータを設定する。
これらのパラメータは、通信相手のデバイスと一致させる必要がある。
通信を開始するには、SerialPort.Openメソッドを実行する。
データを送信する場合は、WriteメソッドやWriteLineメソッドを実行する。
データを受信する場合は、同期的な方法と非同期的な方法がある。
- 同期的な方法
- ReadLineメソッドや- Readメソッドを使用してデータを受信する。
- これらのメソッドは、データが到着するまでプログラムの実行をブロックすることに注意する。
 
- 非同期的な方法
- DataReceivedイベントを使用する。
- このイベントは、データが受信された時に発生し、イベントハンドラ内でデータを処理する。
- これにより、UIの応答性を維持しながらデータを受信することができる。
 
通信が完了した後は、必ずCloseメソッドを実行してポートを閉じる。
エラーハンドリングでは、タイムアウトの設定、例外処理、リソースの適切な解放等を考慮する必要がある。
シリアル通信は、組み込みシステム、産業用機器、DAQ等の様々な分野で利用されている。
C#の豊富なライブラリとイベント駆動型のプログラミングモデルにより、効率的なシリアル通信アプリケーションの開発が可能である。
実務では、デバッグツールやシリアルモニターを活用して、通信の動作を確認することも重要である。
同期通信
送信
以下に示すパラメータは、通信相手のデバイスの設定と一致している必要がある。
使用時では、接続するデバイスの仕様に合わせてこれらの値を適切に調整すること。
- パリティ
- データの整合性チェックのために使用する。
- 以下の例では、Parity.Noneに設定している。
- 必要に応じて、Parity.Even、Parity.Odd、Parity.Mark、Parity.Spaceを指定する。
 
- データビット
- 各バイトのビット数を指定する。
- 一般的に8ビットが使用されるが、7ビットや5ビット等も可能である。
 
- ストップビット
- 各バイトの終わりを示すビットを指定する。
- 必要に応じて、StopBits.One、StopBits.OnePointFive、StopBits.Twoを指定する。
 
- ハンドシェイク
- フロー制御の方法を指定する。
- ハードウェアフロー制御 (RTS / CTS)
- ソフトウェアフロー制御 (XON / XOFF)
- 以下の例では、Handshake.Noneに設定している。
- 必要に応じて、Handshake.RequestToSend、Handshake.XOnXOff等を指定する。
 
- タイムアウト
- 読み取りと書き込みのタイムアウトを設定している。
- これにより、操作が無限に待機することを防ぐことができる。
 
 using System;
 using System.IO.Ports;
 
 class Sender
 {
    static void Main(string[] args)
    {
       string portName = "/dev/ttyS0";    // ポート名を適切に設定
       int baudRate    = 9600;            // ボーレート 9600[bps]
       Parity parity   = Parity.None;     // パリティ無し
       int dataBits    = 8;               // データ長は8ビット
       StopBits stopBits = StopBits.One;  // ストップビットは1ビット
 
       try
       {
          using (SerialPort serialPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits))
          {
             serialPort.Handshake    = Handshake.None;  // フロー制御の設定
             serialPort.ReadTimeout  = 500;             // 読み取りタイムアウトの設定 (ミリ秒)
             serialPort.WriteTimeout = 500;             // 書き込みタイムアウトの設定 (ミリ秒)
 
             serialPort.Open();
             Console.WriteLine("シリアルポートをオープン")
             Console.WriteLine("終了するには 'exit' と入力");
 
             while (true)
             {
                Console.Write("送信するメッセージを入力: ");
                string message = Console.ReadLine();
 
                if (message.ToLower() == "exit") break;
 
                serialPort.WriteLine(message);
                Console.WriteLine("メッセージが送信された");
             }
 
             serialPort.Close();
          }
       }
       catch (TimeoutException)
       {
          Console.WriteLine("送信操作がタイムアウト");
       }
       catch (Exception ex)
       {
          Console.WriteLine($"エラーが発生: {ex.Message}");
       }
    }
 }
受信
以下の例では、シリアル通信を同期処理で受信している。
 using System;
 using System.IO.Ports;
 
 class Receiver
 {
    static void Main(string[] args)
    {
       string portName = "/dev/ttyS0";    // ポート名を適切に設定
       int baudRate    = 9600;            // ボーレート 9600[bps]
       Parity parity   = Parity.None;     // パリティ無し
       int dataBits    = 8;               // データ長は8ビット
       StopBits stopBits = StopBits.One;  // ストップビットは1ビット
 
       try
       {
          using (SerialPort serialPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits))
          {
             serialPort.Handshake    = Handshake.None;  // フロー制御の設定
             serialPort.ReadTimeout  = 500;             // 読み取りタイムアウトの設定 (ミリ秒)
             serialPort.WriteTimeout = 500;             // 書き込みタイムアウトの設定 (ミリ秒)
 
             serialPort.Open();
             Console.WriteLine("シリアルポートをオープン")
             Console.WriteLine("終了するには [Ctrl] + [C]キーを押下");
 
             serialPort.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);
 
             // プログラムを実行し続けるためのループ
             while (true)
             {
                System.Threading.Thread.Sleep(100);
             }
          }
       }
       catch (TimeoutException)
       {
          Console.WriteLine("受信操作がタイムアウト");
       }
       catch (Exception ex)
       {
          Console.WriteLine($"エラーが発生: {ex.Message}");
       }
    }
 
    private static void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
    {
       SerialPort sp = (SerialPort)sender;
 
       try
       {
          string indata = sp.ReadExisting();
          Console.WriteLine("受信したデータ: " + indata);
       }
       catch (TimeoutException)
       {
          Console.WriteLine("データの読み取り中にタイムアウトが発生");
       }
    }
 }
非同期通信
送信
以下の例では、シリアル通信を非同期処理で送信している。
 using System;
 using System.IO.Ports;
 using System.Threading.Tasks;
 
 class AsyncSender
 {
    static async Task Main(string[] args)
    {
       string portName = "/dev/ttyS0";    // ポート名を適切に設定
       int baudRate    = 9600;            // ボーレート 9600[bps]
       Parity parity   = Parity.None;     // パリティ無し
       int dataBits    = 8;               // データ長は8ビット
       StopBits stopBits = StopBits.One;  // ストップビットは1ビット
 
       try
       {
          using (SerialPort serialPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits))
          {
             serialPort.Handshake    = Handshake.None;  // フロー制御の設定
             serialPort.ReadTimeout  = 500;             // 読み取りタイムアウトの設定 (ミリ秒)
             serialPort.WriteTimeout = 500;             // 書き込みタイムアウトの設定 (ミリ秒)
 
             serialPort.Open();
             Console.WriteLine("シリアルポートをオープン");
             Console.WriteLine("終了するには 'exit' と入力");
 
             while (true)
             {
                Console.Write("送信するメッセージを入力: ");
                string message = Console.ReadLine();
 
                if (message.ToLower() == "exit") break;
 
                byte[] buffer = System.Text.Encoding.UTF8.GetBytes(message + Environment.NewLine);
                await serialPort.BaseStream.WriteAsync(buffer, 0, buffer.Length);
 
                Console.WriteLine("メッセージが非同期で送信完了");
             }
 
             serialPort.Close();
          }
       }
       catch (Exception ex)
       {
          Console.WriteLine($"エラーが発生: {ex.Message}");
       }
    }
 }
受信
以下の例では、シリアル通信を非同期処理で受信している。
 using System;
 using System.IO.Ports;
 using System.Threading;
 using System.Threading.Tasks;
 
 class AsyncReceiver
 {
    static async Task Main(string[] args)
    {
       string portName = "/dev/ttyS0";    // ポート名を適切に設定
       int baudRate    = 9600;            // ボーレート 9600[bps]
       Parity parity   = Parity.None;     // パリティ無し
       int dataBits    = 8;               // データ長は8ビット
       StopBits stopBits = StopBits.One;  // ストップビットは1ビット
 
       try
       {
          using (SerialPort serialPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits))
          {
             serialPort.Handshake    = Handshake.None;  // フロー制御の設定
             serialPort.ReadTimeout  = 500;             // 読み取りタイムアウトの設定 (ミリ秒)
             serialPort.WriteTimeout = 500;             // 書き込みタイムアウトの設定 (ミリ秒)
 
             serialPort.Open();
             Console.WriteLine("シリアルポートをオープン");
             Console.WriteLine("終了するには [Ctrl] + [C]キーを押下");
 
             using (var cts = new CancellationTokenSource())
             {
                Console.CancelKeyPress += (s, e) =>
                {
                   e.Cancel = true;
                   cts.Cancel();
                };
 
                await ReceiveDataAsync(serialPort, cts.Token);
             }
 
             serialPort.Close();
          }
       }
       catch (OperationCanceledException)
       {
          Console.WriteLine("エラー: 受信が中断");
       }
       catch (Exception ex)
       {
          Console.WriteLine($"エラーが発生: {ex.Message}");
       }
    }
 
    private static async Task ReceiveDataAsync(SerialPort serialPort, CancellationToken cancellationToken)
    {
       byte[] buffer = new byte[1024];
       while (!cancellationToken.IsCancellationRequested)
       {
          try
          {
             int bytesRead = await serialPort.BaseStream.ReadAsync(buffer, 0, buffer.Length, cancellationToken);
             if (bytesRead > 0)
             {
                string receivedData = System.Text.Encoding.UTF8.GetString(buffer, 0, bytesRead);
                Console.WriteLine($"受信したデータ: {receivedData.Trim()}");
             }
          }
          catch (OperationCanceledException)
          {
             throw;
          }
          catch (Exception ex)
          {
             Console.WriteLine($"受信中にエラーが発生: {ex.Message}");
          }
       }
    }
 }
その他のシリアル通信の機能
以下の例では、バッファリング、再接続機能、イベントベースの受信を行っている。
これにより、大量のデータを扱う場合や不安定な接続環境での使用に適している。
- バッファリング
- ConcurrentQueue<byte[]>を使用して、受信データをバッファリングする。
- また、バッファの最大サイズを制限して、オーバーフローを防ぐ。
 
- 再接続機能
- 接続が失敗した場合に複数回試行する。
- また、接続状態を監視して、切断された場合に再接続を試みる。
 
- イベントベースの受信
- SerialPort_DataReceivedイベントハンドラを使用して、データ受信時の処理を行う。
 
- 非同期処理
- バッファリングされたデータを非同期に処理する。
 
 using System;
 using System.Text;
 using System.IO.Ports;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Collections.Concurrent;
 
 class AdvancedAsyncReceiver
 {
    private static SerialPort _serialPort;
    private static ConcurrentQueue<byte[]> _dataBuffer = new ConcurrentQueue<byte[]>();
    private static int _maxBufferSize     = 10;    // 受信バッファの最大サイズ
    private static int _reconnectAttempts = 5;     // 再接続の試行回数
    private static int _reconnectDelay    = 5000;  // 再接続の待機時間 (ミリ秒)
 
    static async Task Main(string[] args)
    {
       string portName   = "/dev/ttyS0";  // ポート名を適切に設定
       int baudRate      = 9600;          // ボーレート 9600[bps]
       Parity parity     = Parity.None;   // パリティ無し
       int dataBits      = 8;             // データ長は8ビット
       StopBits stopBits = StopBits.One;  // ストップビットは1ビット
 
       try
       {
          _serialPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits);
          _serialPort.Handshake = Handshake.None;  // フロー制御の設定
          _serialPort.ReadTimeout = 500;           // 読み取りタイムアウトの設定 (ミリ秒)
          _serialPort.WriteTimeout = 500;          // 書き込みタイムアウトの設定 (ミリ秒)
 
          _serialPort.DataReceived += SerialPort_DataReceived; // イベントハンドラの登録
 
          await ConnectWithRetry();
 
          Console.WriteLine("シリアルポートをオープン");
          Console.WriteLine("終了するには [Ctrl] + [C]キーを押下");
 
          using (var cts = new CancellationTokenSource())
          {
             Console.CancelKeyPress += (s, e) =>
             {
                e.Cancel = true;
                cts.Cancel();
             };
 
             await Task.WhenAll(
                      ProcessBufferedDataAsync(cts.Token),
                      MonitorConnectionAsync(cts.Token)
             );
          }
       }
       catch (OperationCanceledException)
       {
          Console.WriteLine("エラー: 受信が中断");
       }
       catch (Exception ex)
       {
          Console.WriteLine($"エラーが発生: {ex.Message}");
       }
       finally
       {
          _serialPort?.Close();
       }
    }
 
    private static async Task ConnectWithRetry()
    {
       for (int i = 0; i < _reconnectAttempts; i++)
       {
          try
          {
             _serialPort.Open();
             return;
          }
          catch (Exception ex)
          {
             Console.WriteLine($"接続試行 {i + 1} 失敗: {ex.Message}");
             if (i < _reconnectAttempts - 1)
             {
                await Task.Delay(_reconnectDelay);
             }
          }
       }
       throw new Exception("接続に失敗");
    }
 
    private static void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
       int bytesToRead = _serialPort.BytesToRead;
       byte[] buffer = new byte[bytesToRead];
       _serialPort.Read(buffer, 0, bytesToRead);
 
       if (_dataBuffer.Count < _maxBufferSize)
       {
          _dataBuffer.Enqueue(buffer);
       }
       else
       {
          Console.WriteLine("警告: バッファオーバーフロー");
          Console.WriteLine("データが破棄されました");
       }
    }
 
    private static async Task ProcessBufferedDataAsync(CancellationToken cancellationToken)
    {
       while (!cancellationToken.IsCancellationRequested)
       {
          if (_dataBuffer.TryDequeue(out byte[] data))
          {
             string receivedData = Encoding.UTF8.GetString(data);
             Console.WriteLine($"受信したデータ: {receivedData.Trim()}");
          }
          else
          {
             await Task.Delay(100, cancellationToken);  // バッファが空の場合は、100[mS]待機
          }
       }
    }
 
    private static async Task MonitorConnectionAsync(CancellationToken cancellationToken)
    {
       while (!cancellationToken.IsCancellationRequested)
       {
          if (!_serialPort.IsOpen)
          {
             Console.WriteLine("接続が切断されたため再接続を試行...");
             await ConnectWithRetry();
             Console.WriteLine("再接続に成功");
          }
 
          await Task.Delay(1000, cancellationToken);  // 1[秒]ごとに接続状態を確認
       }
    }
 }