「C Sharpの基礎 - シリアル通信」の版間の差分
(→受信) |
|||
(同じ利用者による、間の1版が非表示) | |||
441行目: | 441行目: | ||
<br><br> | <br><br> | ||
== その他のシリアル通信の機能 == | == その他のシリアル通信の機能 : バッファリング、再接続機能 == | ||
==== 送信 ==== | ==== 送信 ==== | ||
以下の例では、バッファリング、再接続機能を使用して、非同期でデータを送信している。<br> | 以下の例では、バッファリング、再接続機能を使用して、非同期でデータを送信している。<br> | ||
744行目: | 744行目: | ||
await Task.Delay(1000, cancellationToken); // 1[秒]ごとに接続状態を確認 | await Task.Delay(1000, cancellationToken); // 1[秒]ごとに接続状態を確認 | ||
} | } | ||
} | |||
} | |||
</syntaxhighlight> | |||
<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(); | |||
} | } | ||
} | } |
2025年1月15日 (水) 04:07時点における最新版
概要
シリアル通信は、データを1ビットずつ順番に送受信する通信方式である。
C#では、System.IO.Ports
名前空間のSerialPort
クラスを使用してシリアル通信を実装することができる。
まず、SerialPortクラスのインスタンスを生成して、ポート名、ボーレート、データビット、ストップビット、パリティ等の通信パラメータを設定する。
これらのパラメータは、通信相手のデバイスと一致させる必要がある。
通信を開始するには、SerialPort.Open
メソッドを実行する。
データを送信する場合は、Write
メソッドやWriteLine
メソッドを実行する。
データを受信する場合は、同期的な方法と非同期的な方法がある。
- 同期的な方法
ReadLine
メソッドやRead
メソッドを使用してデータを受信する。- これらのメソッドは、データが到着するまでプログラムの実行をブロックすることに注意する。
- 非同期的な方法
DataReceived
イベントを使用する。- このイベントは、データが受信された時に発生し、イベントハンドラ内でデータを処理する。
- これにより、UIの応答性を維持しながらデータを受信することができる。
通信が完了した後は、必ずClose
メソッドを実行してポートを閉じる。
エラーハンドリングでは、タイムアウトの設定、例外処理、リソースの適切な解放等を考慮する必要がある。
シリアル通信は、組み込みシステム、産業用機器、DAQ等の様々な分野で利用されている。
C#の豊富なライブラリとイベント駆動型のプログラミングモデルにより、効率的なシリアル通信アプリケーションの開発が可能である。
実務では、デバッグツールやシリアルモニターを活用して、通信の動作を確認することも重要である。
フロー制御
ソフトウェアフロー制御 (XON / XOFF) およびハードウェアフロー制御 (RTS / CTS) は、シリアル通信におけるデータの流れを管理するための重要な方法である。
これらの制御方式は、送信側と受信側のデバイス間でデータの転送速度を調整して、データの損失を防ぐために使用される。
これらの制御方式の選択は、通信の速度、信頼性の要求、ハードウェアの制約、コスト等の要因によって決まる。
高速で信頼性の高い通信が必要な場合は、ハードウェアフロー制御が適している。
一方、既存のシステムでの簡単な実装やコスト削減が重要な場合は、ソフトウェアフロー制御が選択されることがある。
応用例として、モデム通信やプリンタとコンピュータ間の通信等でこれらのフロー制御方式が使用されている。
また、産業用機器や医療機器等、データの正確さが極めて重要な分野でも、これらのフロー制御方式が重要な役割を果たしている。
フロー制御の選択と実装は、システムの要件や制約を十分に考慮して行う必要がある。
適切なフロー制御を使用することにより、データの損失を防ぎ、通信の信頼性を向上させることができる。
ソフトウェアフロー制御
ソフトウェアフロー制御 (XON / XOFF) は、特別な制御文字を使用してデータの流れを制御する。
XON (伝送再開) と XOFF (伝送停止) という2つの制御文字が使用される。
受信側のバッファがほぼ一杯になる時、XOFF信号を送信して送信側にデータの送信を一時停止するよう指示する。
バッファに余裕ができた時、XON信号を送って送信再開を伝える。
この方法は追加のハードウェアを必要としないため、コスト効率が良く、既存のシステムに容易に実装できるというメリットがある。
しかし、バイナリデータを送信する場合、制御文字と実際のデータが混同される可能性があるため注意が必要である。
ハードウェアフロー制御
ハードウェアフロー制御 (RTS / CTS) は、追加の信号線を使用してデータの流れを制御する。
RTS (送信要求) と CTS (送信可) という2つの信号が使用される。
送信側デバイスは、RTS信号を送信して、データを送信する準備ができたことを伝える。
受信側デバイスは、データを受信する準備ができている時にCTS信号を送り返す。
この方法は追加のハードウェア (信号線) を必要とするが、ソフトウェアフロー制御よりも信頼性が高く、高速な通信に適している。
また、バイナリデータの送信時にも問題が生じにくいというメリットがある。
同期通信
送信
以下に示すパラメータは、通信相手のデバイスの設定と一致している必要がある。
使用時では、接続するデバイスの仕様に合わせてこれらの値を適切に調整すること。
- パリティ
- データの整合性チェックのために使用する。
- 以下の例では、
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}");
}
}
}
}
タイマベースの送信
定期的にデータを送信する必要がある場合、タイマイベントを使用してデータを送信する。
定期的なデータ送信が必要なアプリケーション、例えば、環境モニタリングシステムやIoTデバイス等に適している。
タイマベースの送信により、DAQ等の一定間隔でのデータ収集と送信を自動化することができる。
using System;
using System.IO.Ports;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;
class TimerBasedAsyncSender
{
private static SerialPort _serialPort;
private static System.Timers.Timer _timer;
private static Random _random = new Random();
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)
{
Handshake = Handshake.None, // フロー制御の設定
ReadTimeout = 500, // 読み取りタイムアウトの設定 (ミリ秒)
WriteTimeout = 500 // 書き込みタイムアウトの設定 (ミリ秒)
};
_serialPort.Open();
Console.WriteLine("シリアルポートをオープン");
Console.WriteLine("10秒ごとにセンサーデータを送信");
Console.WriteLine("終了するには 'exit' を押下");
_timer = new System.Timers.Timer(10000); // 10秒ごとに実行
_timer.Elapsed += TimerElapsed; // タイマイベントハンドラの設定
_timer.Start(); // タイマの開始
while (true)
{
string input = Console.ReadLine();
if (input?.ToLower() == "exit") break;
}
}
catch (Exception ex)
{
Console.WriteLine($"エラーが発生: {ex.Message}");
}
finally
{
_timer?.Stop();
_timer?.Dispose();
_serialPort?.Close();
Console.WriteLine("プログラムの終了");
}
}
private static async void TimerElapsed(object sender, ElapsedEventArgs e)
{
try
{
string sensorData = GenerateSensorData();
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(sensorData + Environment.NewLine);
await _serialPort.BaseStream.WriteAsync(buffer, 0, buffer.Length);
Console.WriteLine($"送信したデータ: {sensorData}");
}
catch (Exception ex)
{
Console.WriteLine($"データ送信中にエラーが発生: {ex.Message}");
}
}
private static string GenerateSensorData()
{
// センサデータのシミュレーション
double temperature = Math.Round(_random.NextDouble() * 30 + 10, 2); // 10℃から40℃
double humidity = Math.Round(_random.NextDouble() * 60 + 20, 2); // 20%から80%
return $"温度: {temperature}℃, 湿度: {humidity}%";
}
}
その他のシリアル通信の機能 : バッファリング、再接続機能
送信
以下の例では、バッファリング、再接続機能を使用して、非同期でデータを送信している。
これにより、大量のデータを扱う場合や不安定な接続環境での使用に適している。
- バッファリング
- ConcurrentQueue<string>を使用して、送信データをバッファリングする。
- また、バッファの最大サイズを制限して、オーバーフローを防ぐ。
- 再接続機能
- 接続が失敗した場合に複数回試行する。
- また、接続状態を監視して、切断された場合に再接続を試みる。
- 非同期処理
- バッファリングされたデータを非同期に送信する。
バッファリング機能により、一時的な接続問題や送信の遅延がある場合でもデータ損失のリスクを軽減する。
using System;
using System.Text;
using System.IO.Ports;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Concurrent;
class AdvancedAsyncSender
{
private static SerialPort _serialPort;
private static ConcurrentQueue<string> _sendBuffer = new ConcurrentQueue<string>();
private static int _maxBufferSize = 100; // 送信バッファの最大サイズ
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; // 書き込みタイムアウトの設定 (ミリ秒)
await ConnectWithRetry();
Console.WriteLine("シリアルポートをオープン");
Console.WriteLine("終了するには 'exit' と入力");
using (var cts = new CancellationTokenSource())
{
var sendTask = SendDataAsync(cts.Token);
var monitorTask = MonitorConnectionAsync(cts.Token);
while (true)
{
Console.Write("送信するメッセージを入力: ");
string message = Console.ReadLine();
if (message.ToLower() == "exit")
{
cts.Cancel();
break;
}
if (_sendBuffer.Count < _maxBufferSize)
{
_sendBuffer.Enqueue(message);
}
else
{
Console.WriteLine("警告: 送信バッファのオーバーフローが発生 (データを破棄)");
}
}
await Task.WhenAll(sendTask, monitorTask);
}
}
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 async Task SendDataAsync(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
if (_sendBuffer.TryDequeue(out string message))
{
try
{
byte[] buffer = Encoding.UTF8.GetBytes(message + Environment.NewLine);
await _serialPort.BaseStream.WriteAsync(buffer, 0, buffer.Length, cancellationToken);
await _serialPort.BaseStream.FlushAsync(cancellationToken);
Console.WriteLine("メッセージが非同期で送信完了");
}
catch (Exception ex)
{
Console.WriteLine($"送信中にエラーが発生: {ex.Message}");
_sendBuffer.Enqueue(message); // 送信失敗したメッセージを再度キューに追加
}
}
else
{
await Task.Delay(100, cancellationToken); // バッファが空の場合は100[ミリ秒]待機
}
}
}
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秒ごとに接続状態をチェック
}
}
}
受信
以下の例では、バッファリング、再接続機能、イベントベースの受信を行っている。
これにより、大量のデータを扱う場合や不安定な接続環境での使用に適している。
- バッファリング
- 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[秒]ごとに接続状態を確認
}
}
}
その他のシリアル通信の機能 : ACK信号待機
以下の例では、RS-232C通信で送受信の制御を行うものであり、送信後にACK信号を待機して、ACK信号を受信した場合は待機解除して次のデータを送信している。
送受信は非同期処理で行い、特に受信処理は別タスクで常時監視を行う。
また、以下の例では、5秒のタイムアウト処理を実装している。
- 通信設定
- 8N1形式 (データ長 8[bit]、パリティ無し、ストップビット 1[bit])
- デフォルトのボーレートは9600[bps]
- ハンドシェイクは無効化
- ACK制御
- ACKの待機には、SemaphoreSlimを使用する。
- 送信後、5秒のタイムアウトでACKを待機する。
- ACK信号 (0x06) を受信した後、セマフォを解放して待機を解除する。
最初のデータを送信する時、以下に示すような順序で処理を実行する。
- 送信データをUTF-8でエンコードして送信する。
- ACK待機状態に入る。(最大10[秒])
- ACK信号を受信した場合は待機を解除する。
- 次のデータを送信する
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();
}
}