【VB.NET】USB2ポート16CHリアルタイムモニター作った<PCの性能次第>

※2022年6月追記 本記事から1年8か月経過してグラフィックプログラミングの技が向上しました。
大きな違いは、プロットの仕方でデータが入るごとに、グラフィックDISPLAYに描画していたのが、本記事です。
技が向上して、プロットは、グラフィックメモリーにプロットして、一括したグラフ1面をDISPLAYに描画したほうが速くていろいろなことできるようになりました。PCの性能もカバーできますが、やはり、遅いPCでグラフィックは、速いPCには負けます。以下の記事がBITMAP描画です、動画をご覧ください。

【VB.NET】リアルタイムグラフのBITMAP展開<その1小技在り>

VB.NETも学習から実用へ移行しております。
信州MAKERSでは、多分力センサの開発用の校正システムを開発してます。
入力データと出力データの線形性を確認して、相関係数と相互干渉補正計算を行うシステムです。6分力センサを入力と出力で使うと12CH必要で、
データの連番と時刻データで4CH必要ですので、最大16CHが必要だということで、フォーム画面いっぱいにCHボックスを並べました。
前回のリアルタイムグラフrev010から入力と出力データの同期方式を

実装して3週間近くかかってしまいました。
【結論】2020年9月29日までのデバッグ解析の結果

①マルチポートシリアル受信のリアルタイムグラフは、シリアル受信部からの計算と描画を受信速度に合わせないといけないため、PC全体の性能(計算、グラフィック)次第で受信速度に制限が生じます。高性能のPCでないと高速(5~20msec)のサンプリング周期でリアルタイム2ポート受信ができないことがわかりました。。1ポートなら問題なくできます
=>私のThinkPadX230程度だと50msecサンプリングで16CHまでです。
=>PCの性能をどこまであげるかは、Pgmを使いながら後日検討します。
下記記事で実測した結果PCの描画速度がネックであることが判りました。

【VB.NET】リアルタイム受信Pgmの処理別速度測定した<描画速度ネック>

②シリアルポート2個は独立したタスクで、それぞれのバッファを最後尾でクリアすることで、データの同期が1周期以内でそろえることができる。
=>上記PCに余裕がある範囲
③データ処理は、少しでもCPUに負担をかける処理を行うと比例して対応周期が遅くなります。リアルタイムグラフ無しにファイルにログして後で、データをグラフで読む使い方なら、バッファをクリアしないで、ため込んでファイルにログすれば遅いPCでも5-15msecの16CHログはできます。

50msec周期まで遅くして確認したrev062のグラフです。

GISTにコードを備忘録しておきます。
https://gist.github.com/dj1711572002/22d281bde051d2818db261380f7898c0

Serial_Graphic_Monitor_rev062

 

■ESP32でのデータ集計断念
前回までは、ESP32ベースで同期システムを構築するマイコンプログラムを作ったのですが、いろいろ実験してみると、入力、出力間のデータの受信順序がばらばらになってしまう現象が発生しました。
=>通常
は、1個から3個くらい離れてデータが入ってくるのですが、時々、10個くらい離れてしまったりする現象が確認されました。入力出力のデータ自体は、タイムスタンプと連番で同期がとれていてデータ落ちは皆無なのですが、ESP32のバッファの調子次第で受信順序が大きくばらついたので、マイコンでマルチ受信のリアルタイム処理は断念しました。データをログするだけなら十分使えるのですが、今回のシステムは、数十msec以内のリアルタイム性<入力プローブを押したらその瞬間に6分力センサと同期したデータがPCのグラフに表示される>ことを目標にしているので、マイコンでマルチポート受信は断念しました。

全部断念ではなく、データの受信をマイコンでしないだけで、同期信号を
ESP-NOWで1秒に一回ブロードキャストして各マイコン間での同期をとる方式は、正確に動作しているので同期方式そのものは使ってます。
■VB.NETで
USBSerialマルチポート同期させる方式
 これも準備してあったので、基本動作ができていたので、3日前から
スタートをかけて何とか、同期をとれるように作りこみました。
がいざ、実験しだすと数分でデータが暴れてNGとなりました。
●同期デバッグの備忘録
(結局PCの性能次第という結論で、データの周期を遅くする対策とりました)

デバッグ記録
デバッグ1:
2020年9月13日の記事
【VB.NET】2ポート同時受信データ間の同期精度測定<タスク内複数受信が良い>を9月29日に再現実験した
当初の出発点の基礎確認プログラムを再度検証した

再検証結果1:独立したタスクで2ポート受信してみたデータ順は3-5周期ずれたままで一定。
再検証結果2:同一タスク内で2ポート受信した2データを1つのデータにまとめて
dataprint()に渡す方法のほうが周期ずれが1周期程度で一定

検証結果として、9月13日の結果は、再現性はとれている

デバッグ2:
2020年9月28日rev060デバッグ結果
実際に長時間使ってみると、数分経過するとデータに飛び抜けが発生してバラバラになる頻度が増加してきたので、rev060では同期が崩れてしまっていると判断
デバッグ3:
2020年9月28日rev061に変更してシンプルなプログラムでデバッグ
マイコンからの2ポートデータを独立したタスクで受信した場合のVBプログラムの所要時間を測定した。
条件:VBプログラム上での遅延要因を極力排除するために、ポート受信したデータをStringのままListBoxにAddするだけにして、時間計測は、毎データ毎でなく80データカウントに1回計測データを記録することで遅延要因を排除した。
検証1:マイコンからは、12.5msec周期でCOM6とCOM11に入ってくるが1秒毎に同期信号を送ってカウンタ、タイマリセットしているので、1秒間80個データのタイムスタンプの差は5msec以内に収まっている。
検証2:1ポート受信で80個毎にStopWatch時間を記録するとVBは正確に1秒でListBoxに記録していた。周期精度は12.48msecであった。
検証3:2ポート受信で80個毎にStopWatch時間を記録するとVBは、6.24msec周期でデータを記録していた。2ポートだと1周期が倍早くなるので、リアルタイムは保たれている
デバッグ4:rev061
2020年9月29日デバッグ
条件:プログラム起動時点では、マイコンの同期信号を停止して、シリアルポートOPENしてデータ待ち状態にしてから同期信号を発信して、受信開始してListBoxとファイルLOG保存をする。
検証4:ファイル保存追加しても時間周期は6.24msecを保っている
検証5:遅延は、分単位で1周期ずつ増えていくが、周期は正確に一定値維持されてる
スタート時1周期が4分後は50周期も遅延してずれる現象が確認された。ListBoxとファイルログ同時で同データが記録されていた。
デバッグ5:rev061
2020年9月29日デバッグ
条件:同上+受信タスクの頭にバッファクリアをいれてみた
検証6:100秒までは遅延無しで受信するが100秒すぎるとデータ抜けが発生する
デバッグ6:rev061
2020年9月29日デバッグ
条件:ListBoxのみと+Fileログで受信タスクの最後尾にバッファクリアをいれてみた
検証7:ListBoxのみ:300秒でも遅延なく同期しているTOTAL 47680dataを3321231msecで周期6.847msecと若干遅くなっている。
検証8:ListBox+Fileログ:400秒でも遅延なく同期しているTOTAL 55600dataを421005msecで周期7.57msecとListBox単独より遅くなってきている
デバッグ7:rev061
2020年9月29日デバッグ
条件:上記条件が良かったので、
検証結果9:測定時間を2083035msec(約27分)で326480data を受信してListBoxとファイルへログしました。周期6.38msecとほぼマイコンの測定周期を守ってます。集計するとエラー率0.7%と悪いですが使えない数値ではありません。
同期の瞬間付近での乱れが多いためマイコンの周期乱れとVBの乱れが加算されたものと思われます。現時点での、ベスト方式が受信タスク最後尾でバッファリセットなのでそれで、プログラムを実装して、時間遅延の影響の大きい機能は削っていく方針で進めます。
デバッグ7:rev062
2020年9月29日デバッグ
条件:2個のデータを1個にまとめる処理を追加
検証結果10:処理がはいるだけで、周期が7.49msecと遅なって、データの尻切れが発生し始めたので、マイコン側周期を12.5msecから20msecまで遅延させてみる


①シリアルポートバッファを
定期的に 毎回空にしておくこと
=>1秒毎にシリアルポートバッファを空にしないと、ゴミのように
受信タスクの最後尾にバッファクリアをいれることでリアルタイムになります。
たまったデータが同期ズレをおこします。バッファを空にしないと5データくらいずれてばらつきます。
マルチタスクのdataReceivedメソッドに下記をSerialPort1用とSerialPort2用の最後尾にいれます。

Private Sub SerialPort1_DataReceived(sender As Object, e As SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived

If CheckBox9.Checked = True And com1_None = 0 Then

Try

ReceivedData1 = “A,” + SerialPort1.ReadLine ‘データを受信します
‘If SerialPort2.IsOpen = True Then
‘ ‘SerialPort2.DiscardInBuffer()
‘ ReceivedData2 = SerialPort2.ReadLine
‘ ReceivedData3 = ReceivedData1 & “,” & ReceivedData2
‘Else
‘ReceivedData3 = ReceivedData1
‘End If

‘Invokeメソッドにより実行されるメソッドへのデリゲートの宣言を行い、受信データを表示します
Dim adre As New DataDelegate(AddressOf PrintData)
Me.Invoke(adre, ReceivedData1)
Catch ex As Exception
ReceivedData1 = ex.Message ‘例外処理を行います
‘Console.WriteLine(“Error in DataReceived1”)
End Try
SerialPort1.DiscardInBuffer()
End If

End Sub

 


グラフで早期具合をみると2dot/dataでもズレは見えません。
●2ポート間同期測定結果
OPEN直後は同期が乱れるので、マイコン側の同期を停止させておいてOPENしてから、マイコン側の同期をスタートさせる操作が必要です。停止時もマイコン側同期を停止させてからVB側で操作をします。
・遅延は、1周期程度収まっているので、プログラムで
データの順番を変更する必要はありません。
=>2020年9月28日現在、データを取り始めると

1ポートで時々、データ受信が崩れる現象が発生してます。まだ、本プログラムはデバッグ中ですので、解析結果をお待ちください。
=>2020年10月29日追記
最終的には、受信タスクの最後にバッファクリアを入れて、毎回バッファクリアするとCH間の同期遅延は1周期以内に収まることが判りました。
つまりバッファ無しでないとリアルタイムにならないということです。


10CHのプローブで押して、5CHセンサのオレンジが反応してます。

■16CHのフォームとグラフの備忘録
①ListBoxの場所がなくなったので、PropertyのVisibleをFlaseにして
 グラフの位置上にLISTBOXを設置しましたチェックBOXで現れます。
 LISTBOXを見るときにはグラフを使わない仕様です。

②チェックボックス16個とデータボックス16個の設定が大変
16CHを自由自在に使うために、チェックボックスで使う使わないを
選択してデータ処理を行えるようにしてあります。コントール配列を使ってループで処理する関数を作ってあります。
●CheckBR()は、チェックボックスを監視して配列selCH()=1or0にします

Private Function CheckBR() As Integer()
Dim selCh(16) As Integer ‘= {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
Dim CH_checkbox() As CheckBox
CH_checkbox = {Me.CheckBox1, Me.CheckBox2, Me.CheckBox3, Me.CheckBox4, Me.CheckBox5, Me.CheckBox6, Me.CheckBox7, Me.CheckBox13, Me.CheckBox14, Me.CheckBox15, Me.CheckBox16, Me.CheckBox17, Me.CheckBox18, Me.CheckBox19, Me.CheckBox20, Me.CheckBox21}For i = 0 To 15
If CH_checkbox(i).Checked = True Then
selCh(i) = 1
Else
selCh(i) = 0
End IfNext iReturn selCh
End Function

●CheckCH()は、初期のCH数を見て、使うCHを設定します。
初回以降は手動で自由に選択できます。

Private Function CheckCH()
Dim CH_checkbox() As CheckBox
CH_checkbox = {Me.CheckBox1, Me.CheckBox2, Me.CheckBox3, Me.CheckBox4, Me.CheckBox5, Me.CheckBox6, Me.CheckBox7, Me.CheckBox13, Me.CheckBox14, Me.CheckBox15, Me.CheckBox16, Me.CheckBox17, Me.CheckBox18, Me.CheckBox19, Me.CheckBox20, Me.CheckBox21}
Dim dataTextBox() As TextBox
dataTextBox = {Me.TextBox4, Me.TextBox5, Me.TextBox6, Me.TextBox7, Me.TextBox8, Me.TextBox9, Me.TextBox10, Me.TextBox29, Me.TextBox31, Me.TextBox33, Me.TextBox35, Me.TextBox37, Me.TextBox39, Me.TextBox41, Me.TextBox43, Me.TextBox45}For i = 0 To CInt(TextBox3.Text) – 1
CH_checkbox(i).Checked = True
Next
For i = CInt(TextBox3.Text) To 15
CH_checkbox(i).Checked = False
dataTextBox(i).Text = “”
NextLabel3.Text = “CH selected”End Function

③グラフ描画前にオートレベルでセンタリングします。

16CHでばらばらな数値が入ってくるので、まず無負荷初期状態で
平均値をとって、ゼロレベルとしてグラフセンタに全データがまっすぐでるようにしてからグラフをスタートさせます。
Averageボタンをおして、dave関数で全データの平均値を計算します。
描画中でもいつでもゼロレベルにできるので、便利です。

‘========================================================================
‘================Average Graph mid Data==================================
‘========================================================================
Private Sub Button5_Click(sender As Object, e As EventArgs) Handles Button5.Click
Try
Dim endnum As Integer
Dim startnum As Integer
Dim midave(16) As Double
endnum = CInt(TextBox2.Text)
startnum = endnum – 100midave = dave(dataAry, startnum, endnum, CInt(TextBox3.Text))
Debug.Print(“AveButtn:” & CStr(startnum) & “,” & CStr(endnum) & “,” & CStr(midave(0)))
TextBox13.Text = midave(0)
TextBox14.Text = midave(1)
TextBox15.Text = midave(2)
TextBox16.Text = midave(3)
TextBox17.Text = midave(4)
TextBox18.Text = midave(5)
TextBox19.Text = midave(6)
TextBox28.Text = midave(7)
TextBox30.Text = midave(8)
TextBox32.Text = midave(9)
TextBox34.Text = midave(10)
TextBox36.Text = midave(11)
TextBox38.Text = midave(12)
TextBox40.Text = midave(13)
TextBox42.Text = midave(14)
TextBox44.Text = midave(15)
Catch ex As Exception
ReceivedData3 = ex.Message ‘例外処理を行います
End TryEnd Sub

 ④各CH毎に描画命令ユニットをまとめた
16CHループで描画ユニットにまとめようと思ったのですが、オブジェクトがたくさんあって配列が大変なので、1CHずつ命令文の塊にしました。
データは dataAry(dataNo,CH)に収納されてます。
Mag,dstepがX軸倍率(dot/data)
midvalueがY軸センタリング用のゼロレベル平均値
TBpositionは、センター位置をスライダーで自由に移動させる値
pは、ペンオブジェクトで色と線の太さ

 

‘******************Plot Unit12ch******************************
If Ch_sel(11) = 1 Then
px = Int((dstep * dataNo Mod CInt(600 / Mag)) * Mag)
px_1 = Int((dstep * (dataNo – 1) Mod CInt(600 / Mag)) * Mag)
py = yh – (Int(rate(11) * (dataAry(dataNo, 11) – midvalue(11))) + TBposition)
py_1 = yh – (Int(rate(11) * (dataAry(dataNo – 1, 11) – midvalue(11))) + TBposition)
g.DrawLine(p12, CSng(px_1), CSng(py_1), CSng(px), CSng(py))
End If
‘**********************************************************

⑤リアルタイムグラフ速度
1ポート6CH以下だと1dot\dataでリアルタイム描画できるのですが
2ポート6CH以上になるとさすがに描画速度が間に合いません。
PCの描画性能次第ですが、私のThinkPadX230でリアルタイムを実現
するために、データの間引き表示をする仕様になってます。

使い方としては、数十秒でデータをみる場合もできます。

 

●プログラムコード備忘録
GISTにForm1.vbを載せておきます。
https://gist.github.com/dj1711572002/22d281bde051d2818db261380f7898c0

SolutionフォルダーはZIPでダウンロードできます。
Serial_Graphic_Monitor_rev062

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です