「C Sharpの基礎 - シリアル通信」の版間の差分

ナビゲーションに移動 検索に移動
441行目: 441行目:
<br><br>
<br><br>


== その他のシリアル通信の機能 ==
== その他のシリアル通信の機能 : バッファリング、再接続機能 ==
==== 送信 ====
==== 送信 ====
以下の例では、バッファリング、再接続機能を使用して、非同期でデータを送信している。<br>
以下の例では、バッファリング、再接続機能を使用して、非同期でデータを送信している。<br>
748行目: 748行目:
  </syntaxhighlight>
  </syntaxhighlight>
<br><br>
<br><br>
== その他のシリアル通信の機能 : ACK信号待機 ==
以下の例では、RS-232C通信で送受信の制御を行うものであり、送信後にACK信号を待機して、ACK信号を受信した場合は待機解除して次のデータを送信している。<br>
<br>
送受信は非同期処理で行い、特に受信処理は別タスクで常時監視を行う。<br>
<br>
また、以下の例では、5秒のタイムアウト処理を実装している。<br>
<br>
* 通信設定
*: 8N1形式 (データ長 8[bit]、パリティ無し、ストップビット 1[bit])
*: デフォルトのボーレートは9600[bps]
*: ハンドシェイクは無効化
*: <br>
* ACK制御
*: ACKの待機には、SemaphoreSlimを使用する。
*: 送信後、5秒のタイムアウトでACKを待機する。
*: ACK信号 (0x06) を受信した後、セマフォを解放して待機を解除する。
<br>
最初のデータを送信する時、以下に示すような順序で処理を実行する。<br>
# 送信データをUTF-8でエンコードして送信する。
# ACK待機状態に入る。(最大10[秒])
# ACK信号を受信した場合は待機を解除する。
# 次のデータを送信する
<br>
<syntaxhighlight lang="c#">
using System;
using System.IO.Ports;
using System.Threading;
using System.Threading.Tasks;
/// <summary>
/// RS-232C シリアル通信を制御するクラス
/// 8N1形式(8ビットデータ、パリティなし、ストップビット1)での通信を行い、
/// 送信後にACK応答の待機と制御を行います。
/// </summary>
class SerialCommunication
{
    // 任意のACK信号の値 (ここでは、16進数で0x06とする)
    // ASCIIコードでは、Acknowledgmentの意味を持つ制御文字
    private const byte ACK = 0x06;
    // シリアルポートの制御用オブジェクト
    private readonly SerialPort _serialPort;
    // ACK信号の待機制御用セマフォ
    // 初期値0, 最大値1のセマフォでACK受信時に解放される
    private readonly SemaphoreSlim _ackSemaphore;
    // 受信タスクのキャンセル制御用トークンソース
    private CancellationTokenSource _cts;
    // 受信処理を行う非同期タスク
    private Task _receiveTask;
    /// <summary>
    /// コンストラクタ
    /// シリアルポートの初期設定と必要なオブジェクトの初期化を行う
    /// </summary>
    /// <param name="portName">使用するシリアルポート名 (例: COM1, /dev/ttyS0)</param>
    /// <param name="baudRate">ボーレート (デフォルト: 9600[bps])</param>
    public SerialCommunication(string portName, int baudRate = 9600)
    {
      // シリアルポートの設定
      _serialPort = new SerialPort(portName, baudRate)
      {
          Parity      = Parity.None,    // パリティビット無し
          DataBits    = 8,              // データビット長 8[ビット]
          StopBits    = StopBits.One,    // ストップビット 1[ビット]
          Handshake    = Handshake.None,  // フロー制御なし
          ReadTimeout  = 1000,            // 読み取りタイムアウト 1秒
          WriteTimeout = 1000            // 書き込みタイムアウト 1秒
      };
      // ACK待機用セマフォの初期化 (初期値0, 最大値1)
      _ackSemaphore = new SemaphoreSlim(0, 1);
      // キャンセルトークンソースの初期化
      _cts = new CancellationTokenSource();
    }
    /// <summary>
    /// 通信処理を開始する非同期メソッド
    /// ポートのオープン、受信タスクの起動、メッセージ送信ループを実行する
    /// </summary>
    public async Task StartAsync()
    {
      try
      {
          // シリアルポートをオープン
          _serialPort.Open();
          Console.WriteLine("シリアルポートをオープンしました");
          // ACK信号の受信待機タスクを開始
          // 別スレッドで常時受信監視を行う
          _receiveTask = ReceiveDataAsync(_cts.Token);
          // メッセージ送信ループ
          while (true)
          {
            Console.Write("送信するメッセージを入力 (終了する場合は 'exit'): ");
            string message = Console.ReadLine();
            // 終了コマンドの確認
            if (message?.ToLower() == "exit") break;
            // メッセージを送信して、ACK信号の待機を行う
            await SendMessageWithAckAsync(message);
          }
      }
      finally
      {
          // 終了時の後処理
          await StopAsync();
      }
    }
    /// <summary>
    /// データを送信して、ACK信号の応答を待機する非同期メソッド
    /// </summary>
    /// <param name="message">送信するメッセージ</param>
    private async Task SendMessageWithAckAsync(string message)
    {
      try
      {
          // メッセージをUTF-8でバイト配列にエンコード
          // 末尾に改行コードを追加
          byte[] buffer = System.Text.Encoding.UTF8.GetBytes(message + Environment.NewLine);
          // データをシリアルポートに送信
          await _serialPort.BaseStream.WriteAsync(buffer, 0, buffer.Length);
          Console.WriteLine("データを送信しました");
          Console.WriteLine("ACK待機中...");
          // ACK信号待機用のタイムアウト設定 (10秒)
          using var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
          // メインのキャンセルトークンとタイムアウトトークンを連結
          using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(timeoutCts.Token, _cts.Token);
          try
          {
            // ACK信号を待機 (最大10秒)
            await _ackSemaphore.WaitAsync(linkedCts.Token);
            Console.WriteLine("ACKを受信しました");
          }
          catch (OperationCanceledException) when (timeoutCts.IsCancellationRequested)
          {
            // タイムアウト発生時の処理
            Console.WriteLine("ACK待機がタイムアウトしました");
          }
      }
      catch (Exception ex)
      {
          Console.WriteLine($"送信エラー : {ex.Message}");
      }
    }
    /// <summary>
    /// データ受信を監視する非同期メソッド
    /// 別タスクで常時実行されて、受信データの処理とACKの検出を行う
    /// </summary>
    /// <param name="cancellationToken">キャンセル制御用トークン</param>
    private async Task ReceiveDataAsync(CancellationToken cancellationToken)
    {
      // 受信バッファ (1024バイト)
      byte[] buffer = new byte[1024];
      // キャンセルされるまでループ
      while (!cancellationToken.IsCancellationRequested)
      {
          try
          {
            // データの非同期読み取り
            int bytesRead = await _serialPort.BaseStream.ReadAsync(buffer, 0, buffer.Length, cancellationToken);
            if (bytesRead > 0)
            {
                // 受信データ内のACK信号の検索
                for (int i = 0; i < bytesRead; i++)
                {
                  if (buffer[i] == ACK)
                  {
                      // ACK信号を検出した場合は、セマフォを解放して待機を解除
                      _ackSemaphore.Release();
                      break;
                  }
                }
                // ACK信号以外の受信データの表示処理
                string receivedData = System.Text.Encoding.UTF8.GetString(buffer, 0, bytesRead);
                if (!string.IsNullOrWhiteSpace(receivedData))
                {
                  Console.WriteLine($"受信データ : {receivedData.Trim()}");
                }
            }
          }
          catch (OperationCanceledException)
          {
            // キャンセル時は上位に例外を再スロー
            throw;
          }
          catch (Exception ex)
          {
            Console.WriteLine($"受信エラー : {ex.Message}");
          }
      }
    }
    /// <summary>
    /// シリアル通信を停止して、リソースを解放する非同期メソッド
    /// </summary>
    public async Task StopAsync()
    {
      // 受信タスクをキャンセル
      _cts.Cancel();
      if (_receiveTask != null)
      {
          try
          {
            // 受信タスクの完了を待機
            await _receiveTask;
          }
          catch (OperationCanceledException)
          {
            // キャンセルによる例外は無視
          }
      }
      // 使用したリソースの解放
      _serialPort.Close();      // シリアルポートを閉じる
      _cts.Dispose();          // キャンセルトークンソースの破棄
      _ackSemaphore.Dispose();  // セマフォの破棄
    }
    /// <summary>
    /// プログラムのエントリーポイント
    /// </summary>
    static async Task Main(string[] args)
    {
      // ポート名
      string portName = "<ポート名  例: /dev/ttyS0>";
      // シリアル通信を行う
      var serialComm = new SerialCommunication(portName);
      await serialComm.StartAsync();
    }
}
</syntaxhighlight>
<br>
<syntaxhighlight lang="c#">
// 使用例
var serialComm = new SerialCommunication("<シリアルポート名  例: /dev/ttyS0>");
await serialComm.StartAsync();
</syntaxhighlight>
<br><br>




__FORCETOC__
__FORCETOC__
[[カテゴリ:C_Sharp]]
[[カテゴリ:C_Sharp]]

案内メニュー