Timerコントロールの精度 への返答

投稿で使用できる特殊コードの説明。(別タブで開きます。)
本名は入力しないようにしましょう。
投稿した後で削除するときに使うパスワードです。返答があった後は削除できません。
返答する人が目安にします。相手が小学生か社会人かで返答の仕方も変わります。
最初の投稿が質問の場合、質問者が解決時にチェックしてください。(以降も追加書き込み・返信は可能です。)
※「過去ログ」について書くときはその過去ログのURLも書いてください。

以下の返答は逆順(新しい順)に並んでいます。

投稿者 shu  (社会人) 投稿日時 2017/10/12 12:35:46
送信側というのは

どのように送信をしているのでしょうか?
シリアル通信をする場合、
要求を出して返答して終わりを繰り返す場合と
絶えずデータを流し続ける場合があるかと
思います。
今回は後者なのでしょうか?
だとすると送信側は受信側の状態に関わらず
一定間隔でデータを出しているはずなので

初期データ取得時間 + 受信バイト数(ブロック数) * 送信間隔

で時間を求めるのがよいと思います。処理しきれないデータが
発生してきたら廃棄し初期データ取得時間も更新するようにして
いくとよいと思います。

投稿者 vb素人  (学生) 投稿日時 2017/10/12 11:36:41
毎回の仮面弁士さま

いつもありがとうございます。

>次の座標の F2,F0 が送られてくるまでの最短間隔(ピーク時の頻度)を知りたいです。
送信側から送っているデータは、F2-F0-XH-XL-YH-YL-ZH-ZL と8バイトのデータを送っています。

↓の記録は、DataReceived内で記述して確認した結果です。

11:31:04.1776044 , 1 Bytes
11:31:04.2886108 , 2 Bytes
11:31:04.4006172 , 2 Bytes
11:31:04.5126236 , 5 Bytes
11:31:04.6246300 , 5 Bytes
11:31:04.7366364 , 5 Bytes
11:31:04.8486428 , 6 Bytes
11:31:04.9606492 , 6 Bytes
11:31:05.0726556 , 6 Bytes
11:31:05.1846620 , 6 Bytes
11:31:05.2966684 , 7 Bytes
11:31:05.5206812 , 7 Bytes



>座標データの『先頭(F2,F0)』を検出したときの時刻と
>座標データの『末尾(ZZ,ZZ)』を検出したときの時刻の
>どちらを取得していますか?

少しコードが長くなりますが、↓のように時刻を取得しています。

        If SerialPort1.BytesToRead >= 8 Then
            Dim bin(7) As Byte

            SerialPort1.Read(bin, 0, 2)
            If bin(0) = &HF2 AndAlso bin(1) = &HF0 Then
                'F2-F0-x1-x2-y1-y2-z1-z2 パターン  (コード省略)
                Received(x, y, z)
                Return
            End If


            SerialPort1.Read(bin, 2, 1)
            If bin(1) = &HF2 AndAlso bin(2) = &HF0 Then
                Return
            End If


            SerialPort1.Read(bin, 3, 1)
            If bin(2) = &HF2 AndAlso bin(3) = &HF0 Then
                'z1-z2-F2-F0-x1-x2-y1-y2パターン (コード省略) 
                Received(x, y, z)
                Return
            End If


            SerialPort1.Read(bin, 4, 1)
            If bin(3) = &HF2 AndAlso bin(4) = &HF0 Then
                Return
            End If


            SerialPort1.Read(bin, 5, 1)
            If bin(4) = &HF2 AndAlso bin(5) = &HF0 Then
                'y1-y2-z1-z2-F2-F0-x1-x2 パターン  (コード省略)
                Received(x, y, z)
                Return

            End If

              SerialPort1.Read(bin, 6, 1)
            If bin(5) = &HF2 AndAlso bin(6) = &HF0 Then
                Return

            End If

            SerialPort1.Read(bin, 6, 1)
            If bin(6) = &HF2 AndAlso bin(7) = &HF0 Then
                'x1-x2-y1-y2-z1-z2-F2-F0 パターン  (コード省略)
                Received(x, y, z)
                Return


            ElseIf bin(7) = &HF2 AndAlso bin(0) = &HF0 Then
                'F0-x1-x2-y1-y2-z1-z2-F2 パターン (コード省略)
                Received(x, y, z)
                Return
            End If

            '合致パターン無し! 
            Debug.WriteLine("不正なデータ:" & BitConverter.ToString(bin))

        End If

        Trace.WriteLine(String.Format("{0:HH:mm:ss.fffffff} , {1} Bytes", Now, SerialPort1.BytesToRead))

        Dim dtCurrent As Date = DateTime.Now   'データを受信した時刻
        Dim span As TimeSpan = dtCurrent - dtBegin  '経過時間
        totalSeconds = span.TotalSeconds '何秒経過したのか

        'Dim totalMilliseconds As Double = span.TotalMilliseconds
        'Enqueue メソッドの引数に指定したデータは、コレクションの末尾に追加される
        qx.Enqueue(Tuple.Create(totalseconds, xdata))
        qy.Enqueue(Tuple.Create(totalSeconds, ydata))
        qz.Enqueue(Tuple.Create(totalSeconds, zdata))

        CountData += 1

    End Sub

投稿者 魔界の仮面弁士  (社会人) 投稿日時 2017/10/5 16:35:59
> 送信側からは、次の間隔でデータを送っています。

そちらではなく、F2,F0 で始まるXYZ座標が送られてから、
次の座標の F2,F0 が送られてくるまでの最短間隔(ピーク時の頻度)を知りたいです。

なお、DataReceived がどの程度の頻度で発生しているかは、下記のようにして確認できます。

 Private Sub SerialPort1_DataReceived(sender As Object, e As IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
  Trace.WriteLine(String.Format("{0:HH:mm:ss.fffffff} , {1} Bytes", Now, SerialPort1.BytesToRead))
 End Sub



> 遅延よりも欠損を許容したいです。

座標データの『先頭(F2,F0)』を検出したときの時刻と
座標データの『末尾(ZZ,ZZ)』を検出したときの時刻の
どちらを取得していますか?


> たまにこのカウントが遅れることがあります。

本来期待されるカウントと、実際のカウントとのズレがどの程度なのか教えてください。
投稿者 vb素人  (学生) 投稿日時 2017/10/5 14:51:56
魔界の仮面弁士さま

ありがとうございます。

>そもそも、どの程度の精度を求めていますか?

送信側からは、次の間隔でデータを送っています。
画面への反映頻度(とcsvファイルへの保存)は、なるべく送信頻度と同じに近づけたいです。
遅延よりも欠損を許容したいです。

F2
(1msec)
F0
(1msec)
xデータ1
(1msec)
yデータ2
(1msec)
yデータ1
(1msec)
yデータ2
(1msec)
zデータ1
(1msec)
zデータ2

※データ1は、10bitデータの上位2bitを1byte送信。データ2は、10bitデータの下位8bit。


>「3 つのデータ」というのは、

「F2,F0,x1,x2,y1,y2,z1,z3」という意味です。



>今の実装は『逐次』処理になってしまっているように見うけます。
>データを受信するたびに画面に表示する形にしてしまうと、

逐次処理となっています。
Serial portのスレッドは、受信処理(受信から3つのデータの並び替え)だけにして、
受信データ数のカウント値表示、グラフへの描写をメインスレッドを基点に行うには、どのようにすれば良いのかが分かりません。


投稿者 魔界の仮面弁士  (社会人) 投稿日時 2017/10/5 11:59:38
そもそも、どの程度の精度を求めていますか?
各回に送出されるデータ長がどの程度のサイズで、それがどの程度の時間間隔で発信されるのか。
そして画面側への反映頻度は、その送信頻度よりも低頻度でも構わないのか。
(欠損と遅延のどちらを許容するのか)


> たまにこのカウントが遅れることがあります。
画面操作(TextBox、Chart、Label 等の読み書き)を行うための Sub Received を
DataReceived のイベントから呼び出しているのが、要因の一つかと思います。

もう一度念押ししておきますが、SerialPort のイベントからの画面操作は避けてください。

画面操作が行われるのは、常にメインスレッドを基点として行われるべきです。
SerialPort のスレッドから呼びだしてはいけません。Invoke も避けましょう。


> (このときは、xy2つのデータでしたが、今は3つのデータとなっていて、
> 先頭F2-F0を検出するために、条件分岐処理で時間が掛かっているのかと考えています。)

各座標は 0000~03FF の範囲でしたよね。「3 つのデータ」というのが、
「F2,F0,x1,x2,x3,y1,y2,y3」の意味なのか
「F2,F0,x1,x2,y1,y2,z1,z3」の意味なのか
分かりにくいですが、後者の意味でしょうか。

条件分岐処理も含め、「受信時刻」の記録方法に問題がある可能性はありますが、
実際のコードが分からない以上、現時点では判断できません。

BytesToRead が返してきた値未満のサイズしか Read していなかったとすれば、
次のデータが届く(DataReceived が発生する)までの間、データの読み取りが
その分遅れる事になるわけですが、その点も考慮した設計になっているかを
再確認してみてはいかがでしょう。


> このようにしています。
SerialPort が動作するスレッドと、画面処理のスレッドは異なります。
何のために別スレッドになっているのかを念頭において設計しましょう。

今の実装は『逐次』処理になってしまっているように見うけます。
データを受信するたびに画面に表示する形にしてしまうと、
描画完了まで次の受信が遅れることになります。
それが今回の要因であるかどうかは別として。
投稿者 (削除されました)  () 投稿日時 2017/10/5 11:57:35
(削除されました)
投稿者 vb素人  (学生) 投稿日時 2017/10/4 13:49:26
魔界の仮面弁士さま

ありがとうございます。
いただいたヒントを参考にして、DateTime.Nowで取得した時間からグラフを作成してみました。

変更前の構成は、
①シリアル通信開始
②Private Sub SerialPort1_DataReceived
  'データ受信・データ並び替え
  Received(x, y, z)へ
③Private Sub Received
  'x,y,xデータをグローバル変数xdata,ydata,zdataへ代入
  'グラフの設定(ライン、マーカー)
  'テキストBoxへデータ表示
④Private Sub Button3_Click
  '保存ボタンクリック・Timer有効
  'グラフのSeries設定
  'csvファイルへタイトル行書き込み
⑤Private Sub Timer1_Tick
  '受信データをグラフへ描写
  'データをcsvファイルへ保存

としていましたが、

変更後の構成は、
①シリアル通信開始
  '通信開始時刻を記録
  'グラフのSeries設定
  'グラフの設定(ライン、マーカー)
  'csvファイルへタイトル行書き込み
②Private Sub SerialPort1_DataReceived
  'データ受信・データ並び替え
  '現時刻を記録
  '通信開始時刻-現時刻から経過時間を算出
  '☆データカウント
  Received(x, y, z)へ
③Private Sub Received
  'x,y,xデータをグローバル変数xdata,ydata,zdataへ代入
  'テキストBoxへデータ表示
  'グラフ描画
   ⇒このとき、横軸は②で算出した経過時間とする。
  'データをcsvファイルへ保存
  '☆データカウントラベル表示

このようにしています。

csvファイルに書き込まれたデータ(時間)を見ても、細かい時間周期でデータが取得できています。
☆印の所で、データを受信する(xyzのデータが揃う)たびにカウントし、カウント値を表示させていますが、

たまにこのカウントが遅れることがあります。
これは、
↓のときに教えていただいた、並び替えで時間が掛かってしまっていることが推測されますでしょうか。
http://rucio.cloudapp.net/ThreadDetail.aspx?ThreadId=30252
(このときは、xy2つのデータでしたが、今は3つのデータとなっていて、
先頭F2-F0を検出するために、条件分岐処理で時間が掛かっているのかと考えています。)

投稿者 魔界の仮面弁士  (社会人) 投稿日時 2017/10/4 10:25:23
>> Dim dtBegin As Date = [SerialPortをOpenした時の時刻]
> この記述がよくわかっていません。=の右側は、どう記述すれば良いのでしょうか?
 
[2014/10/30 18:18:36](VB初心者)【SerialPortコントロールについて】
http://rucio.cloudapp.net/ThreadDetail.aspx?ThreadId=20686

たとえば上記には、「SerialPortをOpenする処理」のために、Button1 を用意しています。
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    '(中略) 
    SerialPort1.Open()
    '(中略) 
End Sub


これに相当する処理を実行する際に、現在時刻(Now)をフィールド変数に保存しておき、
それを受信処理で利用するということです。

たとえばこんな感じ。(実際には Try~Catch も必要ですが)
Private portOpenTime As Date
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    '(中略) 
    portOpenTime = Now
    SerialPort1.Open()
    '(中略) 
End Sub

Private Sub SerialPort1_DataReceived(…
    Dim dtCurrent As Date = DateTime.Now   'データを受信した時刻 
    Dim span As TimeSpan = dtCurrent - portOpenTime  'Open してからの経過時間 
    Dim totalMilliseconds As Double = span.TotalMilliseconds '何ミリ秒経過したのか 
    '(中略) 
End Sub
投稿者 vb素人  (学生) 投稿日時 2017/10/3 23:10:21
魔界の仮面弁士さま

ありがとうございます。
1つ教えて下さい。

>Dim dtBegin As Date = [SerialPortをOpenした時の時刻]
この記述がよくわかっていません。=の右側は、どう記述すれば良いのでしょうか?
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2017/10/3 17:06:59
DataReceived イベント内から メッセージボックスを呼び出してはいけません。

MessageBox を表示した段階でコードの実行がそこで止まってしまいますので、
処理の確認を行いたいのであれば、Debug.WriteLine メソッド等を使うようにしましょう。
(同様の理由で、ブレークポイント等による一時停止も避けるようにします)

> Private Sub SerialPort1_DataReceived(…
>  Dim dtBegin As Date = DateTime.Now
>  Dim dtCurrent As Date = DateTime.Now   'データを受信した時刻
>  Dim span As TimeSpan = dtCurrent - dtBegin  '経過時間
>  Dim totalSeconds As Double = span.TotalSeconds '何秒経過したのか


dtBegin の部分が明らかにおかしいです。上記は要するに、
 Dim span As TimeSpan = Now - Now
 Dim totalSeconds As Double = span.TotalSeconds
とほぼ同義ですよね。これでは totalSeconds ≒ 0.0 になるのも当然です。

私が先に書いたのは
  Dim dtBegin As Date = [SerialPortをOpenした時の時刻]
であって、
  Dim dtBegin As Date = [DataReceived イベントが呼ばれたときの時刻]
ではありませんよ。

dtBegin は、「SerialPortをOpenした時の時刻」もしくは
「DataReceived イベントが最初の 1 回目に呼ばれたときの時刻」にしましょう。


> 通信開始時刻と現時刻をミリ秒単位で取得して
実際の分解能はさておき、Now で得られる日付値は
100ナノ秒単位まで取得できるようになっていますので、
  Dim totalSeconds As Double = span.TotalSeconds
の部分を
  Dim totalMilliseconds As Double = span.TotalMilliseconds
としておけば十分かと思います。
投稿者 vb素人  (学生) 投稿日時 2017/10/3 16:07:48
魔界の仮面弁士さま

次の部分について。
>DataReceived が呼ばれたときに、
>Dim dtBegin As Date = [SerialPortをOpenした時の時刻]
>Dim dtCurrent As Date = DateTime.Now    'データを受信した時刻
>Dim span As TimeSpan = dtCurrent - dtBegin  '経過時間
>Dim totalSeconds As Double = span.TotalSeconds '何秒経過したのか
>'Enqueue メソッドの引数に指定したデータは、コレクションの末尾に追加される
>qx.Enqueue(Tuple.Create(totalSeconds, xdata))
>qy.Enqueue(Tuple.Create(totalSeconds, ydata))
>qz.Enqueue(Tuple.Create(totalSeconds, zdata))

↓のように記述してみました。

☆の所でtotolSecondsをMessageBoxに表示させていまが、
MessageBoxに表示される値は、0です。
MessageBoxが表示される間隔をみていると、データを受信しているタイミング(x,y,zの3つのデータが揃うタイミング)がよく分かります。


グラフの横軸は、msec単位にしています。
msec単位で描写させる場合、通信開始時刻と現時刻をミリ秒単位で取得して、ミリ秒単位のtotalSecondsで描写させなければならないと思っていますが、正しい理解でしょうか。

↓の記述も含めて間違いがあれば指摘していただけると助かります。




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

Dim dtBegin As Date = DateTime.Now

'データ受信処理
'受信したデータはReceivedプロージャへ
Received(x, y, z)

        Dim dtCurrent As Date = DateTime.Now   'データを受信した時刻
        Dim span As TimeSpan = dtCurrent - dtBegin  '経過時間
        Dim totalSeconds As Double = span.TotalSeconds '何秒経過したのか
        'Enqueue メソッドの引数に指定したデータは、コレクションの末尾に追加される
        qx.Enqueue(Tuple.Create(totalSeconds, xdata))
        qy.Enqueue(Tuple.Create(totalSeconds, ydata))
        qz.Enqueue(Tuple.Create(totalSeconds, zdata))

'☆
        MessageBox.Show(CStr(totalSeconds))

    End Sub


    Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick

        Dim plot As Tuple(Of Double, UInteger) = Nothing
        Do While qx.TryDequeue(plot)
            Chart1.Series("X-Axis").Points.AddXY(plot.Item1, CDbl(plot.Item2))
        Loop
        Do While qy.TryDequeue(plot)
            Chart1.Series("Y-Axis").Points.AddXY(plot.Item1, CDbl(plot.Item2))
        Loop
        Do While qz.TryDequeue(plot)
            Chart1.Series("Z-Axis").Points.AddXY(plot.Item1, CDbl(plot.Item2))
        Loop

    End Sub
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2017/10/3 12:04:40
> 描画終了までの時間が長くなっているということは、
グラフへの描画は、データの受信処理に比べると遥かに低速なので、
それを考慮して設計してみてください。


> Chart1.Series(0).Points.AddXY(time, xdata)

各 Series には名前がセットされているのですから、
 Chart1.Series("X-Axis").Points.AddXY(time , xdata)
の方が良いでしょう。

もしくは、毎回名前やインデックスで検索するのではなく、
「Dim xaxis As Series」な『ローカル変数』を削除して、かわりに
「Private xaxis As Series」な『フィールド変数』へと変更し、
 xaxis = Chart1.Series.Add("X-Axis")
で設定しておいた Series オブジェクトを
 xaxis.Points.AddXY(time, xdata)
のように利用するようにします。


>(a) 受信処理:シリアル通信で送られてきたデータをフィールド変数に蓄えていく
DataReceived のスレッドがフィールド変数に書き込んでいる最中に、
メインスレッドがフィールド変数を同時に読み書きすることが無い様、
フィールド変数が「スレッドセーフ」であることを保証しておく必要もあります。
https://msdn.microsoft.com/ja-jp/library/dd997305.aspx

たとえば、スレッドセーフなコレクションとして、.NET Framework 4 以上で利用可能な
「ConcurrentQueue クラス」を使うといった方法があります。

(a) では、フィールド変数として
 Private qx As New System.Collections.Concurrent.ConcurrentQueue(Of Tuple(Of Double, UInteger))
 Private qy As New System.Collections.Concurrent.ConcurrentQueue(Of Tuple(Of Double, UInteger))
 Private qz As New System.Collections.Concurrent.ConcurrentQueue(Of Tuple(Of Double, UInteger))
などを用意しておき、DataReceived が呼ばれたときに、
  Dim dtBegin As Date = [SerialPortをOpenした時の時刻]
  Dim dtCurrent As Date = DateTime.Now    'データを受信した時刻
  Dim span As TimeSpan = dtCurrent - dtBegin  '経過時間
  Dim totalSeconds As Double = span.TotalSeconds '何秒経過したのか
  'Enqueue メソッドの引数に指定したデータは、コレクションの末尾に追加される
  qx.Enqueue(Tuple.Create(totalSeconds, xdata))
  qy.Enqueue(Tuple.Create(totalSeconds, ydata))
  qz.Enqueue(Tuple.Create(totalSeconds, zdata))
のようにします。


一方、プロットする側(c)においては、任意のタイミング(たとえば Timer1_Tick)で、
下記のようにしてみます。
 Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
  '今回の場合、plot の Item1 As Double が「経過秒数」、Item2 As UInteger が「受信データ」です
  Dim plot As Tuple(Of Double, UInteger) = Nothing
  'TryDequeue メソッドは、先頭にあるデータを 1 つ取り出し、それをコレクションから取り除きます。
  Do While qx.TryDequeue(plot)
   xaxis.Points.AddXY(plot.Item1, CDbl(plot.Item2))
  Loop
  Do While qy.TryDequeue(plot)
   yaxis.Points.AddXY(plot.Item1, CDbl(plot.Item2))
  Loop
  Do While qz.TryDequeue(plot)
   zaxis.Points.AddXY(plot.Item1, CDbl(plot.Item2))
  Loop
 End Sub

このプロット作業は、データの受信タイミングとは無関係に、並列的に行われます。
データがまだ受信されていなければ、上記の処理では何もプロットされませんし、
データが複数件受信されていれば、それらすべてがまとめてプロットされます。


ただし上記の例は、「受信したデータすべて」をプロットするためのものです。

画面の解像度は、精々 1000ドット~2000ドットぐらいしかありませんので、
数千個のデータを受信してしまうと、グラフでは表現しきれないかもしれません。
その場合は、プロットするデータを減らすようにする工夫も必要です。

たとえば直近のn件のみが描画されるようにするため、
一定の件数を超えたら、xaxis.Points.RemoveAt(0) などで古いデータを削除するとか、
あるいは ConcurrentQueue から取り出したデータすべてを AddXY するのではなく、
適当な時間軸単位に「平均を取る」か「間引く」などしてデータを減らすなど。
投稿者 vb素人  (学生) 投稿日時 2017/10/3 09:50:40
魔界の仮面弁士さま

返信ありがとうございます。
受信からプロットまでの流れを↓にまとめてみました。

3つの独立した作業について、実際のコードは次のようになっていると考えています。(①~④)
>(a) 受信処理:シリアル通信で送られてきたデータをフィールド変数に蓄えていく

>(b) 加工処理:受信データをグラフ用の座標データに切り出す

>(c) 描画処理:座標データを Chart にプロットしていく
③、④


Timer10msec設定(データプロット数100): 約6秒
Timer1msec設定(データプロット数1000): 約3分10秒

Timerインターバルの時間を短くすると、描画終了までの時間が長くなっているということは、
送信~②まででに時間が掛かっているということでしょうか。

10msec設定で約6秒掛かっていたので、
60msecにすれば、1秒に近づくのではと思い、実験してみたところ、約1秒(ストップウォッチ測定なので、あまり正確ではありませんが…)となりました。

これは、送信~②まで(データが揃うまで)最低でも60msecは掛かるので、
60msec以下のインターバルで測定する場合は、
実際の時間とプロットする時間軸がずれるという理解で良いのでしょうか。

この考えが正しいとして…
60msec以下のインターバルでも実時間に近い時間間隔でプロットさせるためには、
送信~②までの部分で改善が必要という理解で良いでしょうか。



'①受信したデータを正しい順番a-b-cの順番でReceivedへ渡す
Private Sub SerialPort1_DataReceived(sender As Object, e As SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived

Received(a, b, c)

End Sub

'②a,b,cをグローバル変数へ代入。テキストボックスに受信データを表示
Private Sub Received(a As UShort, b As UShort, c As UShort)

xdata = 1023 - x
ydata = 1023 - y
zdata = 1023 - z

RcvXTextBox.AppendText(Str(x))
RcvYTextBox.AppendText(Str(y))
RcvZTextBox.AppendText(Str(z))

'グラフ設定(ラインの色設定、マーカーの大きさ、色設定)

End Sub



'③Timer有効、Series設定
Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click

'Timerインターバル設定、Timer有効化
Timer1.Interval = CInt(TextBox1.Text)
Timer1.Enabled = True

'Series設定
Dim xaxis = Chart1.Series.Add("X-Axis")
Dim yaxis = Chart1.Series.Add("Y-Axis")
Dim zaxis = Chart1.Series.Add("Z-Axis")

'グラフタイプの設定
xaxis.BorderWidth = 3
xaxis.BorderDashStyle = ChartDashStyle.Solid
xaxis.ChartType = SeriesChartType.Line

yaxis.BorderWidth = 3
yaxis.BorderDashStyle = ChartDashStyle.Solid
yaxis.ChartType = SeriesChartType.Line

zaxis.BorderWidth = 3
zaxis.BorderDashStyle = ChartDashStyle.Solid
zaxis.ChartType = SeriesChartType.Line

'csvファイルへのタイトル書き込み
Dim sw As System.IO.StreamWriter
sw = New System.IO.StreamWriter("accel.csv", False,
                                                  System.Text.Encoding.GetEncoding(932))

sw.WriteLine("x" & ",y" & ",z" & ",time")
sw.Close()

End Sub

'④データプロット
Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick

'描画処理
Chart1.Series(0).Points.AddXY(time, xdata)
Chart1.Series(1).Points.AddXY(time, ydata)
Chart1.Series(2).Points.AddXY(time, zdata)

'データ書き込み
Dim sw As System.IO.StreamWriter
sw = New System.IO.StreamWriter("accel.csv", True,
                                              System.Text.Encoding.GetEncoding(932))
sw.WriteLine(xdata & "," & ydata & "," & zdata & "," & time)
sw.Close()

End Sub
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2017/9/21 15:22:24
> このように実際の時間の何倍もかかってしまっていますが、

どういうコードによってその結果が齎されているのかが分かりませんでした。


ひとまず、タイマーを何のために用いているのかにもよりますが、そもそも
「受信処理」のスレッドは、画面系(UI)のスレッドとは分離されていますよね。

また、タイマーにもいろいろあるわけで…。
http://www.atmarkit.co.jp/ait/articles/0511/11/news117.html
http://www.atmarkit.co.jp/ait/articles/0511/11/news118.html
http://www.atmarkit.co.jp/fdotnet/dotnettips/374timerstimer/timerstimer.html



> これはシステム(PCの性能やその時の稼働状況)に依存するものでしょうか。

データの受信タイミングと、グラフの描画タイミングはそもそも無関係です。
データを 1 件受け取って、直ちに描画を始めるのではなく、

(a) 受信処理:シリアル通信で送られてきたデータをフィールド変数に蓄えていく
(b) 加工処理:受信データをグラフ用の座標データに切り出す
(c) 描画処理:座標データを Chart にプロットしていく

という 3 つの独立した作業が存在するという前提で考えてみては如何でしょう。


(a) は DataReceived イベントのことで、データ受信のたびに呼び出されるものです。
ここでは、グラフへのプロットなどの「時間のかかる処理」を行わないようにします。
(Invoke メソッドの呼び出しも極力避けます)


(c) は .Points.AddXY などを呼び出すための処理で、a とは別のスレッドで動作します。


(b) の加工作業は、さらに別の作業スレッドで行わせることもできなくはないですが、
時間を要するものでないなら、a もしくは c のいずれかの作業時に一緒に行えば十分でしょう。



データは逐次送られてくるはずなので、a の処理は「複数のデータを蓄えられるようにしておく」か、
もしくは「直近のデータのみを保持し、以前のデータは読み捨てるようにする」必要があります。

ただし、a と c はスレッドが異なりますので、呼び出されるタイミングは必ずしも一致しません。

たとえば、a の処理が 7 回発生したけれど、c は 1 回しか呼ばなかった場合、
「溜めておいた 7 件のデータをまとめて追加プロット」させるようにするか、もしくは
「未処理の 6 回分は読み捨てて、7 回目の直近データのみを追加プロット」させます。

逆に、a の受信処理が発生しないタイミングで c が呼ばれるようなことがあれば、
c は「グラフを一切操作することなく Return する」ということになるでしょう。


c をどのタイミングで呼び出されるようにするか、という点に関してはいろいろな考え方があります。
たとえば、「データ更新」ボタンを押したときだけグラフを再描画しなおすようにしてもよいですし、
a が発生するたびに BeginInvoke すると言う手もあります。あるいはタイマーを通じて、
一定間隔で「新しいデータが来ていた時だけ追記する」形にすることもできます。あるいは
Application クラスの Idle イベントで、追記データの有無を確認するとか。


また、受信するデータ量が非常に多いような場合には、
受信したデータすべてをプロットするのか、適宜間引くのかも検討せねばなりません。
(間引く場合は、b または c の段階で処分します)
投稿者 vb素人  (学生) 投稿日時 2017/9/21 13:42:21
Timerコントロールの精度について。

シリアル通信で受信したデータをグラフにプロットしています。

グラフのX軸(横軸)は、 最大1000msec(1秒)に設定。

今、Timerコントロールの設定を次のように変更して、
実際にX軸の最大(1000msec)までデータを表示されてみました。
その時に、実際にデータを表示しきるまでに掛かった時間を記載しています。

Timer10msec設定(データプロット数100): 約6秒
Timer1msec設定(データプロット数1000): 約3分10秒

このように実際の時間の何倍もかかってしまっていますが、
これはシステム(PCの性能やその時の稼働状況)に依存するものでしょうか。

精度をなるべく100%に近づける方法はあるのでしょうか。