【VB.NET】6chシリアル受信データをリアルタイムグラフできた<Chartでは遅すぎる>

6作目で実用的な細折れ線のリアルタイムグラフですが、5作目は点プロットだったのですが
6作目はLINEプロットなので、少し面倒な扱いになってますがデバッグ終わったらアップします。

VB.NETのプログラミング練習も5作目となって、受信データをリアルタイム
グラフ表示するプログラムに取り組みました。結構苦戦して、3日x6時間=18時間まるまるかかりました。リアルタイム速度限界を測定した動画をアップしてあります。(Lenovo ThinkPadX230 Core i5 3320M RAM
6GB)
グラフィック画面サイズ:600×240です。面積に反比例して遅くなります。
PCのタスク状態で10%近く速度ばらつきが発生します。
※未だ、LatencyTimerを短くしてないときの実験値ですので後日
グラフ仕様が完成したところでTOTALスループット実験します。
=>9月7日に一応完成させました。
【VB.NET】7CHリアルタイムモニターver1.0完成<Serial.CloseでPgmハングする対策>
※BM設定1msecにして完成状態のプログラムのリアルタイム速度実験してみました。
■リアルタイム速度(センサを手で押して、折れ線が反応する遅延を触覚_視覚的に評価)
①1対1(1ドット1データ)で600×240で3.5-4秒で6msec~9msec周期になりました。しかし、連続して描画していると400~600dotの後半になると、遅延が0.1秒くらい発生することがあります。
②1対2(1ドット2データ)以上にすればほぼ、遅延は感じられなくなります。
=>グラフィック描画でも、この程度遅延が発生するのでdebug.print、Chart等、プログラム処理を少しでもLOOP処理を重くするとリアルタイム性が悪化するほど、シビアだと感じました。

■10月になって、USB2ポート16CHリアルタイムグラフプログラムを作成しましたが、
USB2ポートとなると、PCの負荷が大きく、私のおんぼろPCでは、サンプリング周期50msecより遅くしないとリアルタイムにはなりませんでした。リアルタイムにするには、シリアル通信バッファをほとんど使わないで、データの欠落が発生しないPCとプログラムの処理速度が必要なことが

判りました。USB1ポートでは、10msec以内でもOKでしたが、2ポートマルチタスクにするとか、プログラム内のCPU、グラフィック処理を増やせばふやすほど、速度が落ちていきますので、グラフィック性能が高いPCが欲しいです。
測定方法と私のPCでの速度測定のレポートはこちらです

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

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

 

■マイコンからのシリアルボーレート毎のリアルタイム速度
11520bpsの場合:6CH7msec周期までリアルタイム対応できます。
460800bpsの場合:6CH5msec周期までリアルタイム対応できます。
921600bpsの場合:6CH5msec周期なのでプログラムの限界となってます。

 ■必須項目:Win10の設定
=>本設定、BM値調整機能がついているUSBSerialDriver用です。
STMicroのSTLINKDriverはBM値調整機能がついてませんので未確認です。

USBシリアルFTDIドライバのLatencyTimer機能が働いていて、64byte以下の少量データの連続転送時に遅延が発生するようになってるそうです。
詳細はこちらのページです。https://www.hdl.co.jp/USB/FTDI/lt/
この設定をBM
設定といいます。これが、デフォルトだと16msecとなってるとバッファ遅延でリアルタイムになりませんので、BM=1msecに設定しなおせば、リアルタイムになります。
●変更方法:
デバイスマネージャ>ポート(COMxx)>Property>ポートの設定タブ
>詳細設定>BMオプション待ち時間を1msecにします。
QIITAのこちらの記事に感謝です。

※追記 2020年9月30日 本記事は1ポートリアルタイム受信グラフを扱ってますが、2ポート16CHJの受信プログラムを作りました。2ポートになるとリアルタイムが厳しくなって、50msecまで受信周期を遅くしないと破綻してしまうことがわかりました。これは私の使っているPCのグラフィック速度が遅すぎるにが原因です。
いずれにせよ、リアルタイムグラフを余裕で使うには、グラフィック速度の速いPCが必須です。

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

●当初はCHARTオブジェクトでグラフを作っていた
2日がかりでCHARTを使って、作ってみたのですが、
FASTLINEを使っても、6CHだとスクロール速度が遅くて
とてもリアルタイム表示ができませんでした。
頑張ればそこそこ使えたかもしれませんが、CHARTの関数群とPropertyを覚える手間を考えると、GraphicDrawで折れ線だけ描画したほうが簡単で手間がないし、描画速度が有利だというメリットもあるので、以下GraphicDraw方式をとることにしました。

 

●QIITAでプロの方がグラフの速度について投稿がありました。

https://qiita.com/twentyfourhours/items/c31922ffe94649cd3a00

CHARTオブジェクトだとFASTLINEで1周期30msecだそうで、とても、マイコンからの数msec周期のデータをリアルタイムに表示できませんので、グラフィックとして、データをプロットする方式でないとリアルタイム速度がでないと説明されています。この記事に感謝いたします。

■VB.NETでの高速描画の解説記事で新しいものが検索ではない=>たった一つDOBON.NET様にあった
VB.NETに限らず、グラフ描画の解説記事は、非常に少ないし
2012年から2015年くらいのものが多く、当時のバージョンのプログラムが現在のVS2019では動作しないので、初心者としては困ります。
その中で、何とか使える過去記事を見つけました。
有名なDOBON.NET様の記事にありました。感謝です。
https://dobon.net/vb/dotnet/graphics/createimage.html

描画順方式:PictureBOXのimageとしてBITMAPオブジェクトを用意してBITMAPに描画して、imageにBITMAPを書き込むことで、毎回更新していく描画方式です。
この記事の一番下のサンプルプログラムを参考にさせていただきました。

「PictureBox.Imageプロパティに設定している画像を描き変えた時、すぐに表示に反映する」

TIPS:
初回だけPictureBoxとBITMAPの定義を通して、次回からはパスさせることが重要です。これをしないと過去プロットが全部消えて今のプロットしかでてきません。PictureBox1.imageという名称で扱うことがポイントです。

別の名称にすると名前定義がされてないとエラーになってしまいました。

'Imports System.Drawing

'PictureBox.ImageプログラミングにImageオブジェクトを設定する
If PictureBox1.Image Is Nothing Then
    PictureBox1.Image = New Bitmap(100, 50)
End If

'ImageオブジェクトのGraphicsオブジェクトを作成する
Dim g As Graphics = Graphics.FromImage(PictureBox1.Image)

'全体を白で塗りつぶす
g.FillRectangle(Brushes.White, g.VisibleClipBounds)
'現在の時刻を描画する
g.DrawString(DateTime.Now.ToLongTimeString(), _
             SystemFonts.DefaultFont, Brushes.Black, 10, 10)

'Graphicsオブジェクトのリソースを解放する
g.Dispose()

'Imageプロパティの変更を反映させるために、PictureBox1を再描画する
PictureBox1.Invalidate()

 

■MSDocsの解説記事リンク
imports  System.Drawing を使って描画します。

グラフィックス プログラミングについて

Graphics クラス


ここで、私がプロットに使っているのは

Graphics.FillEllipse メソッド

6本分の色をbrush型変数=Brushes.色名で変更してます。

Brushes クラス

■Plotのメソッド関数plotC()のソースは下記です。
データ表示のメソッドPrintdata()メソッドから毎回呼び出されて動作します。

Sub plotC(ByVal dataNo As Integer, ByVal value As Integer, ByVal colorN As Integer)
If PictureBox1.Image Is Nothing Then ‘初回だけBITMAPを定義する Picture1.imageという名称をつかうこと
PictureBox1.Image = New Bitmap(600, 240)
End If
Dim g As Graphics = Graphics.FromImage(PictureBox1.Image)
Dim px As Integer
Dim py As Integer’======================COLORS=========================================
Dim b As BrushSelect Case colorN
Case 1
b = Brushes.Red
Case 2
b = Brushes.Blue
Case 3
b = Brushes.Green
Case 4
b = Brushes.Magenta
Case 5
b = Brushes.Orange
Case 6
b = Brushes.BlackEnd Select’===========-PLOT パラメータの定義 手計算で処理========================
‘Dim yh As Integer = 240
‘Dim xw As Integer = 600
‘Dim Ymax As Integer = 7000
‘Dim Ymin As Integer = 0
‘Dim Xmax As Integer = 600
‘Dim Xmin As Integer = 0
‘Dim dstep As Double
‘Dim dvalue As Double
‘————-ドット変換値は手計算する——-
‘dstep = xw / (Xmax – Xmin)
‘=600/(600-0)=1
‘dvalue = yh / (Ymax – Ymin)
‘=240/(7000)=0.0342857’===================================================================
If dataNo Mod 600 = 0 Then ‘1画面終了したらimageクリアtp = sw.Elapsed
totalsec = tp.TotalSeconds
‘Debug.Print(“tp.sec=” & totalsec)
sw.Stop()
If totalsec > 0.6 Then
TextBox11.Text = CStr(totalsec)
End Ifsw.Reset()
sw.Start()PictureBox1.Image = Nothing
Elsepx = Int(1 * dataNo Mod 600)
py = Int(0.0342857 * value)
‘Debug.Print(“px=” & CStr(px) & “py=” & CStr(py))
g.FillEllipse(b, px, py, 3, 3)
g.Dispose()
PictureBox1.Invalidate()End IfEnd Sub

マイコン側のテスト用プログラムは6個のデータを値の大小を6水準ずらしてSINカーブで波形データを作って送信してます。mbed NucleoF446ZEを使って、高速なデータ通信まで対応してます。最後にタイムスタンプをつけて7個のデータを送信してます。

//mbed プログラム

#include “mbed.h”
Serial pc(USBTX,USBRX);
Timer t;
int i=0;
float rads,ds;
int ch1,ch2,ch3,ch4,ch5,ch6;
void main()
{
pc.baud(115200);
t.start();
while(1)
{
i++;
rads=(i*10%360)*0.01745286;
ds=400*sin(rads) ;
ch1=1000+ds;
ch2=2000+ds;
ch3=3000+ds;
ch4=4000+ds;
ch5=5000+ds;
ch6=6000+ds;
//pc.printf(“%d,%3.3f,%3.3f,%d,%d,%d,%d,%d,%d,%d\n\r”,i,rads,ds,ch1,ch2,ch3,ch4,ch5,ch6,t.read_ms());
//pc.printf(“%4.3f,%4.3f,%d,%d,%d,%d,%d,%d,%d\n\r”,rads,ds,ch1,ch2,ch3,ch4,ch5,ch6,t.read_ms());
pc.printf(“%d,%d,%d,%d,%d,%d,%d\n\r”,ch1,ch2,ch3,ch4,ch5,ch6,t.read_ms());
wait_ms(4.4);//VB.NET 7msecOK
}

}

■6CHリアルタイムプロット サンプルププログラム
①Form1は、ご自分で作成してください。
ボタン3個とTEXTBOX12個とPictureBox1を1個つくるだけです。
SerialPortは、マイコンによりますが、COM10 115200です。
PictureBox1のPropertyでsizeは、600×240に設定してください。

※詳しく解説されてなくて済みません。
どうもよくわからないという方は、信州MAKERS掲示板に質問をいれてください。公開さてますが、多分あなたが判らないことは、読者の皆さんみんなが判らないことだと思いますので、勇気をもって、掲示板に投稿してください。公開されますので。メルアドは、
yahooなどのは差しさわりのないものでお願いします。
https://9226.teacup.com/shinshu_makers/bbs

②GISTにコードアップしてあります。
https://gist.github.com/dj1711572002/f196dce54c1d80cc931dbd7b351c9a43

 

‘Seril Port Recieve Sample Program
‘Form1 Object must create ,button1,2 & textbox 1,2Imports System.Drawing
Imports System.IO.PortsPublic Class Form1
Dim onetime As Integer = 1
Delegate Sub DataDelegate(ByVal sdata As String)
‘=========Available Parameters in thie Calss=================
Dim dataAry(600000, 7) As Long
Dim dataNo As Long
Dim Mave() As Double
Dim sw As New System.Diagnostics.Stopwatch()
Dim tp As TimeSpan
Dim timestamp As Integer
Dim stime As Integer ‘plot start sampling time
Dim etime As Integer ‘plot end sampling time
Dim totalsec As Double
‘=============================================================
Private Sub PrintData(ByVal sdata As String)Dim delimiter As String = “,”
Dim i As Integer
Dim dcount As String
Timer1.Interval = 1
‘タイマー開始
Timer1.Enabled = True
‘TextBox1.text=sdata
dcount = sdata & “:” & TextBox2.Text
‘ListBox1.Items.Add(dcount)
‘Debug.Print(“sdata=” & sdata)
‘Debug.Print(“timestamp=” & CStr(timestamp))
Dim dStr() As String = Split(sdata, delimiter, -1, CompareMethod.Text)
Dim colN As Integer
colN = 7
ReDim Mave(colN)
If (dStr.Length = 7) Then
dataNo += 1
For i = 0 To dStr.Length – 1
dataAry(dataNo, i) = CInt(dStr(i))
timestamp = dataAry(dataNo, 6)
‘=================PLOT SUBへ=================
If i < 6 Then
plotC(dataNo, dataAry(dataNo, i), i + 1) ‘plotC(x,y,colorN)
End If
‘===========================================

TextBox2.Text = dataNo
TextBox3.Text = dStr.Length
TextBox4.Text = dStr(0)
TextBox5.Text = dStr(1)
TextBox6.Text = dStr(2)
TextBox7.Text = dStr(3)
TextBox8.Text = dStr(4)
TextBox9.Text = dStr(5)
TextBox10.Text = dStr(6)
‘Debug.Print(“dataAry(” & CStr(dataNo) & “,” & CStr(i) & “)=” & CStr(dataAry(dataNo, i)))
‘If dataNo > 100 Then
‘ ‘Fuction MoveAve(dataArray(,) as long ,ColumnNo,MA,rowNo)
‘ Mave = MovAve(dataAry, 7, 10, dataNo)
‘End If
‘For i = 0 To colN – 1
‘ Debug.Print(“Mave(” & CStr(i) & “)=” & CStr(Mave(i)))
‘Next
Next
End IfEnd Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
SerialPort1.PortName = TextBox1.Text ‘オープンするポート名を格納
SerialPort1.Open() ‘ポートオープンEnd SubPrivate Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
If SerialPort1.IsOpen = True Then ‘ポートオープン済み
SerialPort1.Close() ‘ポートクローズ
End If
End SubPrivate Sub SerialPort1_DataReceived(sender As Object, e As SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
Dim ReceivedData As String = ” ” ‘受信データ用変数を宣言しますTryReceivedData = SerialPort1.ReadLine ‘データを受信しますCatch ex As Exception
ReceivedData = ex.Message ‘例外処理を行いますEnd Try
‘Invokeメソッドにより実行されるメソッドへのデリゲートの宣言を行い、受信データを表示します
Dim adre As New DataDelegate(AddressOf PrintData)
Me.Invoke(adre, ReceivedData)End SubFunction MovAve(ByRef dA(,) As Long, ByVal colN As Integer, ByVal MA As Integer, ByVal rowN As Integer) As Double()
Dim i, j As Integer
Dim dataSum() As Long
Dim dSum As Long
Dim dataA() As Double ‘= {0, 0, 0, 0, 0, 0, 0}
‘Debug.Print(“====colN=” & CStr(colN) & “MA=” & CStr(MA) & “rowN=” & CStr(rowN))
ReDim dataSum(colN + 1)
ReDim dataA(colN + 1)For j = 0 To colN – 1
dSum = 0
For i = rowN – MA To rowN
dSum = dSum + dA(i, j)
‘Debug.Print(“dA(” & CStr(i) & “,” & CStr(j) & “)=” & CStr(dA(i, j)) & CStr(dataSum(j)))
Next
dataA(j) = CDbl(dSum / MA)
Next
MovAve = dataA
End FunctionPrivate Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
Dim canvas As New Bitmap(PictureBox1.Width, PictureBox1.Height)
‘ImageオブジェクトのGraphicsオブジェクトを作成する
Dim g As Graphics = Graphics.FromImage(canvas)’位置(10, 20)に100×80の四角を赤色で描く
g.DrawRectangle(Pens.Red, 10, 20, 100, 80)
‘先に描いた四角に内接する楕円を黒で描く
g.FillEllipse(Brushes.Black, 100, 100, 5, 5)’リソースを解放する
g.Dispose()’PictureBox1に表示する
PictureBox1.Image = canvas
End SubSub plotC(ByVal dataNo As Integer, ByVal value As Integer, ByVal colorN As Integer)
If PictureBox1.Image Is Nothing Then ‘初回だけBITMAPを定義する Picture1.imageという名称をつかうこと
PictureBox1.Image = New Bitmap(600, 240)
End If
Dim g As Graphics = Graphics.FromImage(PictureBox1.Image)
Dim px As Integer
Dim py As Integer’======================COLORS=========================================
Dim b As BrushSelect Case colorN
Case 1
b = Brushes.Red
Case 2
b = Brushes.Blue
Case 3
b = Brushes.Green
Case 4
b = Brushes.Magenta
Case 5
b = Brushes.Orange
Case 6
b = Brushes.BlackEnd Select’===========-PLOT パラメータの定義 手計算で処理========================
‘Dim yh As Integer = 240
‘Dim xw As Integer = 600
‘Dim Ymax As Integer = 7000
‘Dim Ymin As Integer = 0
‘Dim Xmax As Integer = 600
‘Dim Xmin As Integer = 0
‘Dim dstep As Double
‘Dim dvalue As Double
‘————-ドット変換値は手計算する——-
‘dstep = xw / (Xmax – Xmin)
‘=600/(600-0)=1
‘dvalue = yh / (Ymax – Ymin)
‘=240/(7000)=0.0342857’===================================================================
If dataNo Mod 600 = 0 Then ‘1画面終了したらimageクリアtp = sw.Elapsed
totalsec = tp.TotalSeconds
‘Debug.Print(“tp.sec=” & totalsec)
sw.Stop()
If totalsec > 0.6 Then
TextBox11.Text = CStr(totalsec)
End Ifsw.Reset()
sw.Start()PictureBox1.Image = Nothing
Elsepx = Int(1 * dataNo Mod 600)
py = Int(0.0342857 * value)
‘Debug.Print(“px=” & CStr(px) & “py=” & CStr(py))
g.FillEllipse(b, px, py, 3, 3)
g.Dispose()
PictureBox1.Invalidate()End IfEnd SubEnd Class

コメントを残す

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