Search code examples
vb.netaveragetransducer

Rolling average, vb.net


I have a sensor (a Quartzonix pressure transducer, actually) that spits out data via the serial port, around 3 times a second. I'd like to set up some code to give me an average reading based on x-amount of samples.

The output looks something like this:

01+   1.502347091823e01
01+   1.501987234092e01
01+   1.50234524524e01
01+   1.502123412341e01
01+   1.502236234523e01
01+   1.50198345e01
01+   1.502346234523e01

.. and will keep going on forever until the com port is closed or the transducer gets another command.

This is what code I have so far, and the code works to show me what the transducer is actually outputting:

Private Sub btnStart_Click(sender As Object, e As EventArgs) Handles btnStart.Click
    Dim a As String
    a = "$01MC" & Chr(13)
    MyComPort.WriteLine(a)

    Do
        Dim Incoming As String = MyComPort.ReadLine()
        Dim incomingtext As String = Incoming.Remove(0, 3)

        If Incoming Is Nothing Then
            Exit Do
        Else
            txtRawData.Text = Incoming
            boxPSIA.Text = Format(Val(incomingtext), "##0.000")

        End If
        Application.DoEvents()
    Loop

End Sub

The "$01MC" is the command the transducer needs to start spitting out the data. I've got some wierd timeout thing happening when i click the start button, but that's another show (maybe a .readtimeout adjustment is needed, not sure).

I have a text box txtReadingsToAvg for input of how many readings to average.. I'm just not wrapping my head around how to actually get it to caluclate the average (on, say, a button click and then spitting it out into a msgbox, or even into another text box).


Solution

  • Not sure how your code even works. You said you get values @ about 3 Hz? Then a straight up Do...Loop would be too fast. There is an event which is raised when the serial port receives data. Make use of that.

    You will probably need to change this around a little to suit your needs

    ' WithEvents allows events to be handled with "Handles" keyword
    Private WithEvents myComPort As SerialPort
    Private dataQueue As Queue(Of Double)
    Private numReadingsToAvg As Integer = 0
    
    Private Sub btnStart_Click(sender As Object, e As EventArgs) Handles btnStart.Click
        ' make a new queue here to initialize or clear an old queue
        dataQueue = New Queue(Of Double)()
        ' read the num avgs text box. you may want to change on the fly also
        numReadingsToAvg = Integer.Parse(Me.txtReadingsToAvg.Text)
        myComPort.WriteLine("$01MC" & Chr(13))
    End Sub
    
    Private Sub myComPort_DataReceived(sender As Object, e As SerialDataReceivedEventArgs) Handles myComPort.DataReceived
        Dim incomingLine As String = myComPort.ReadLine()
        ' DataReceived event happens on its own thread, not the UI
        ' must invoke call back to UI to change properties of controls
        txtRawData.Invoke(Sub() txtRawData.Text = incomingLine)
        Dim incomingValue As String = incomingLine.Remove(0, 3).Trim()
        If Not String.IsNullOrWhiteSpace(incomingValue) Then
            Exit Sub
        Else
            Dim measurement As Double = Double.Parse(incomingValue)
            boxPSIA.Invoke(Sub() boxPSIA.Text = Format(measurement, "##0.000"))
            dataQueue.Enqueue(measurement)
            ' if there are too many items, remove the last one
            If dataQueue.Count > numReadingsToAvg Then
                dataQueue.Dequeue()
            End If
            Dim average As Double = dataQueue.Average()
            ' you need to add this textbox
            anotherTextBox.Invoke(Sub() anotherTextBox.Text = Format(average, "##0.000"))
        End If
    End Sub
    

    By the way, Application.DoEvents() should rarely (never) be used, as there's always a better way to remedy whatever problem you are bandaging with DoEvents. Your original example suffered from clogging up the UI thread with a never-ending loop running on the UI. If you ever need to run a loop like that, it should almost always run on a different thread than the UI thread. In my example, there is no loop, and the timing is determined by the port itself. No need for any of this to happen on the UI thread.