6作目で実用的な細折れ線のリアルタイムグラフですが、5作目は点プロットだったのですが
6作目はLINEプロットなので、少し面倒な扱いになってますがデバッグ終わったらアップします。
本記事、シリアル受信Pgmが終わって、リアルタイム高速グラフの始めた頃の記事です。
※2022年5月30日追記 本記事から2年後「本記事習い始めの頃なのでbitmapを使ったグラフになってません」
本記事、PictureBoxに直接1データごとにプロットする方法なので、PCの描画速度に大きく依存します。2021年夏以降は、bitmapを作ってそこに描画してから任意範囲を切り取ってPictureBoxにコピーしてます。そうすることで、遅いPCでも、高速アニメーション、スクロール、拡大、カーソルが自由に作れるようになりました。 リアルタイムグラフでたくさんの記事を書いてありますので、ご自分に合った記事をおさがしください。「リアルタイムグラフ」で本ブログを検索したリンクを閲覧してください。
※グラフィック備忘録:本記事執筆から1年後の感想「CHARTを使わなかったのが正解でした」
Bitmapから切り取ってグラフ表示することで、データの閲覧性、カーソルでの読み取り利便性が得られます。リアルタイムグラフのBitmap処理は、グラフィックが遅いPCでも高速で瞬時にコピー、書き込みができるので、Bitmap上で各種処理して最後にPictureBoxへ書き込むので高速です。
●ダイナミックな動きのあるグラフが作れます。移動体、スポーツなどの表現に適してます。
※2022年2月の作品 アニメーションとグラフカーソルが同期して動きます。
2021年6月 上記アニメーションの基礎
【STA】RTKデータ解析用グラフィック機能開発ーその1ー<自動追尾機能便利>
●リアルタイムで測定直後に、データの確認(拡大縮小、カーソル)作業が出来るようになりました。
【VB.NET】リアルタイムグラフのBITMAP展開<その1小技在り>
【VB.NET】リアルタイムグラフBitmap展開<その2カーソルと拡大縮小>
●CPLTではできない課題を解決していきたい
①460800bpsでの受信ができない
②カーソル位置が時間軸に固定されてないので、時間軸を動かすとカーソルが設定位置からずれてしまう。
③ログデータの数表をみるには、ExcelでCSVファイルを読み込んでからでないと確認できない
④初回の無負荷時のゼロ点を自動で平均してくれない
===========ここから下は古い方法なのであまり参考になりません==========
VB.NETのプログラミング練習も5作目となって、受信データをリアルタイム
グラフ表示するプログラムに取り組みました。結構苦戦して、3日x6時間=18時間まるまるかかりました。リアルタイム速度限界を測定した動画をアップしてあります。(Lenovo ThinkPadX230 Core i5 3320M RAM6GB)
グラフィック画面サイズ: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が必須です。
●当初は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では動作しないので、初心者としては困ります。
その中で、何とか使える過去記事を見つけました。
※2022年2月修正:本記事2年前で、直接pictureboxに書き込んでいるため、遅いです。高速でグラフを動かすテクニックはこちらの記事群でどうぞ。
有名な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. Fill Ellipse メソッド
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” } |
■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 |
※ログしたデータを高速に表示する方法
Bitamapに書き込んでおいて、それを切り取りながら表示すると速いです。
======================================================
※2021年6月末追記 VB.NETで初めてグラフィックを使う場合のPictureBoxとBirmapとGraphicsの基本的な学習備忘録アップ
※2021年6月追記=>10か月後「CHARTを使わないGraphicsでプログラム作ってきてよかった」
扱うデータ行数が数千から数万行と膨大な場合Chartでなく、BitmapとGraphicsを使ったグラフを書いたほうが高速でフレキシブルです。
【グラフィックボード無しのPCでも高速にグラフ表示する方法が見つかりました。】
「グラフィックの高速化について、PCの性能以外にBITMAPを複数枚もって表示用に切り取って表示すると高速化できることが分かってきました。」BITMAP処理でプロットすればPCのグラフィック性能関係無しです、PCの能力はBITMAPを一括で表示する速度が注目です。
本記事執筆してから10か月経過して、VB.NETの学習も進んでおります。特にグラフィックの扱い方ですが、
初心者の頃は、PictureBoxにデータをプロットするのが精いっぱいでそれを基礎学習してきたのですが、現在では
巨大なBITMAPに全データを書き込んでしまって表示したい部分の矩形だけ切り取ってPictureBoxへ表示する方法でグラフを書いております。そうすることで、グラフの更新の高速化、回転移動、拡大縮小を瞬時にできるとか便利機能を追加することができるレベルまで来ております。しかもそんなに難しくないので最初からやっておけばよかったと後悔しております。なかなか高速でグラフを自由自在に描画する事例紹介がないので、信州MAKERSブログでまとめてやってみようと思ってます。とりあえず、スキーターンの先端だけ固定してグラフを高速で動かす動画ご覧ください。これは、1GB以上の巨大なBITMAPでcm単位のデータを750mx40m面積を表現できるものです。下記動画は、グラフィックボード付きのDESKTOPで撮影したものですが、ほぼ同速度で古いノートPC ThinkPadX230でも自動追尾グラフを同様に動作できましたので、BITMAPを切り取って描画する速度は、グラフィックボード有無は関係なくそれなりに速いので、リアルタイムグラフでもまずは大きなBITMAPへ描画して、新しいプロットをした後に表示する部分だけ切り取って表示すれば、高速リアルタイグラフは可能だと見込んでます。2021年秋までに、歩行中のRTK測定をリアルタイムで行うのでその時に実証します。ご興味のある方は、下記3個の解説記事を読まれれば、実現できると存じます。
RTKデータ解析用グラフィック機能開発ーその1ー<自動追尾機能便利>
RTKデータ解析用グラフィック機能開発ーその2ー<ソースBITMAP表示基礎>
RTKデータ解析用グラフィック機能開発ーその3ー<自動追尾機能の中身>
※2021年6月3日追記メモ (グラフいじりだして10か月、最初からこの方法知ってれば良かった)
グラフ描画の速度向上で別のアプローチを検討しました。一旦BITMAPでメモリーに格納しておいて、BITMAPの任意の範囲を切り取ってPictureBoxへ登録する方法です、アニメーションで使う方法なので高速です。理由はDISPLAYへのPLOT LOOP動作しない点です。速さを19秒の動画でご覧ください。詳細記事はこちらです。
=============================================
appendix
①PictureBoxとBitmapを定義してしまう場合
参考LINK PictureBox.Imageプロパティに設定している画像を描き変えた時、すぐに表示に反映する
PictureBoxは、額縁に相当します。額縁に差し込む絵は、紙であるBitmapオブジェクトです。
Bitmapに描画するには、それぞれのBitamap専用のグラフィック処理クラス graphicsを定義します。
Dim bmp as new Bitmap(PictureBox1.Width,PictureBox1.Height)’bmpオブジェクトを定義
Dim g as Graphics =Graphics.FromImage(bmp)’bmpのグラフィクス処理を定義
g.DrawLine(Pens Red,x0,y0,x1,y1)’ bmpビットマップへ線が描画されてPictureBoxに反映
②PictureBoxとは独立させてBitmapを処理するしてからPictureBoxに反映させる場合
(参考https://dobon.net/vb/dotnet/graphics/createimage.html )
Dim bmp as new Bitmap(1000,400)’1000×400にビットマップを定義
Dim g as Graphics =Graphics.FromImage(bmp)’bmpのグラフィクス処理を定義
g.DrawLine(Pens Red,x0,y0,x1,y1)’ bmpビットマップへ線が描画する
PictureBox1.Image=bmp ‘bmpをPictureBox1へ差し込む
この場合ビットマップに各種処理をした後で、PictureBox表示できるので、便利です。