前回の記事では、Sokken PLR-7 AFR ANALYZERをPCとUSBで接続し、C#アプリでリアルタイムにA/F値を取得するところまでを紹介しました。
今回はその続きとして、取得したA/F値をCSV形式で保存するロガー機能を追加したので、その内容をまとめておきます。
■ 改めて:PLR-7とのUSB接続と通信仕様
PLR-7はUSB経由でPCに接続すると、「USB Serial Port(COMポート)」として認識されます。
デバイスマネージャーで確認したCOMポート番号(例:COM9)を、アプリ内で使用します。
通信仕様は以下のとおりです:
| 設定項目 | 値 |
|---|---|
| ボーレート | 115200 bps |
| データビット | 8 ビット |
| パリティ | なし |
| ストップビット | 1 ビット |
■ 通信の基本的な流れ
- アプリ起動時にPLR-7をリモートモード(
SRM,01\r\n)へ - 測定モード(
AOP,01\r\n)に切り替え - 0.1秒ごとに
RMD\r\nを送信して測定値を取得 - 応答(例:
RMD,2,3,0,2, 120.41)の最後の値がA/F値 - これをTextBoxに表示し、必要に応じてログ記録
■ ロガー機能の仕様
- 「ロガースタート」ボタンを押すと記録を開始
- A/F値をタイムスタンプ付きで記録
- 「ロガーストップ&保存」ボタンで記録を終了し、CSVファイルとして保存
- 保存先はファイルダイアログでユーザーが選択可能
🧾 実際のC#コード(Windowsフォーム)
使用UI要素
| コントロール | 名前 | 用途 |
|---|---|---|
TextBox | textBox_AF_Value | A/F値のリアルタイム表示 |
Button | button_logger_start | ロガー開始 |
Button | button_logger_stop_and_save | ロガー終了&CSV保存 |
■ Form1.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Ports;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace AF計の値出力アプリ
{
public partial class Form1 : Form
{
private SerialPort serialPort;
private Timer requestTimer;
private StringBuilder receiveBuffer = new StringBuilder();
private bool isLogging = false;
private List<string> logData = new List<string>();
public Form1()
{
InitializeComponent();
serialPort = new SerialPort("COM9", 115200, Parity.None, 8, StopBits.One);
serialPort.DataReceived += SerialPort_DataReceived;
try
{
serialPort.Open();
InitializeRemoteAndMeasurementModeAsync();
}
catch (Exception ex)
{
MessageBox.Show("シリアルポート接続エラー: " + ex.Message);
}
}
private async void InitializeRemoteAndMeasurementModeAsync()
{
try
{
serialPort.Write("SRM,01\r\n");
await Task.Delay(500);
serialPort.Write("AOP,01\r\n");
await Task.Delay(5000);
requestTimer = new Timer();
requestTimer.Interval = 100;
requestTimer.Tick += RequestTimer_Tick;
requestTimer.Start();
}
catch (Exception ex)
{
Debug.WriteLine("初期化エラー: " + ex.Message);
}
}
private void RequestTimer_Tick(object sender, EventArgs e)
{
if (serialPort.IsOpen)
{
try
{
serialPort.Write("RMD\r\n");
}
catch (Exception ex)
{
Debug.WriteLine("送信エラー: " + ex.Message);
}
}
}
private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
try
{
string incoming = serialPort.ReadExisting();
receiveBuffer.Append(incoming);
while (receiveBuffer.ToString().Contains("\r\n"))
{
string buffer = receiveBuffer.ToString();
int endIndex = buffer.IndexOf("\r\n");
string oneLine = buffer.Substring(0, endIndex);
receiveBuffer.Remove(0, endIndex + 2);
Match match = Regex.Match(oneLine, @"RMD,\d+,\d+,\d+,\d+,\s*(-?\d+\.\d+)");
if (match.Success)
{
string afValue = match.Groups[1].Value;
Invoke(new Action(() =>
{
textBox_AF_Value.Text = afValue;
if (isLogging)
{
string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
logData.Add($"{timestamp},{afValue}");
}
}));
}
}
}
catch (Exception ex)
{
Debug.WriteLine("受信エラー: " + ex.Message);
}
}
private void button_logger_start_Click(object sender, EventArgs e)
{
isLogging = true;
logData.Clear();
}
private void button_logger_stop_and_save_Click(object sender, EventArgs e)
{
isLogging = false;
if (logData.Count == 0)
{
MessageBox.Show("ログデータがありません。", "情報", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
using (SaveFileDialog dialog = new SaveFileDialog())
{
dialog.Filter = "CSVファイル (*.csv)|*.csv";
dialog.FileName = $"AF_log_{DateTime.Now:yyyyMMdd_HHmmss}.csv";
if (dialog.ShowDialog() == DialogResult.OK)
{
try
{
File.WriteAllLines(dialog.FileName, logData);
MessageBox.Show("ログを保存しました。", "完了", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
MessageBox.Show("保存に失敗しました: " + ex.Message, "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (serialPort != null && serialPort.IsOpen)
{
serialPort.Close();
}
}
private void textBox_AF_Value_TextChanged(object sender, EventArgs e)
{
}
}
}
■ コードのポイントと補足
receiveBuffer と \r\n 処理
- PLR-7の応答が分割されて届くことを考慮して、バッファに溜めてから改行区切りで1行ずつ処理しています。
Regex によるA/F抽出
Regex.Match(oneLine, @"RMD,\d+,\d+,\d+,\d+,\s*(-?\d+\.\d+)");
- 応答の最後の数値(A/F値)だけを抜き出しています。
CSVログ
logData.Add($"{timestamp},{afValue}");
タイムスタンプとA/F値を1行ずつ記録して、保存時に .csv 形式で出力しています。
■ 出力されるCSV形式(例)
2025-03-21 15:45:32.101,120.41
2025-03-21 15:45:32.201,120.43
...
■ まとめ
- C#とPLR-7の組み合わせで、リアルタイムなA/Fモニタリングとデータ記録が簡単に実現できる
- USB接続、リモート・測定モード切替、測定値取得まで自動化
- ロガー機能を使えば、CSV保存による記録や分析も容易
今後は、このCSVデータをリアルタイムグラフに表示したり、しきい値でアラートを出すなど、さらに機能を拡張していけたらと考えています。
PLR-7とPCの連携に挑戦している方の参考になれば幸いです。
ここまで読んでいただきありがとうございました。
技術書の購入コストを抑えてスキルアップするなら

ここまで読んでいただきありがとうございます。最後に宣伝をさせてください。
プログラミングの技術書や参考書は、1冊3,000円〜5,000円するものも多く、出費がかさみがちです。Kindle Unlimitedであれば、月額980円で500万冊以上の書籍が読み放題となります。
気になる言語の入門書から、アルゴリズム、基本設計の専門書まで、手元のスマホやPCですぐに参照可能です。現在は「30日間の無料体験」や、対象者限定の「3か月499円プラン」なども実施されています。まずはご自身のアカウントでどのようなオファーが表示されるか確認してみてください。
