Serial Port Glitch

Hey Guys/Gals,
I am having issues with serial ports having glitches when fetching data from them. I end up receiving partial data from them.

An example would be if I wrote a fetch code to the serial port, it would respond with 05.34 when it should send 105.34. This pops up randomly when retrieving data and has expressed itself over multiple serial port communications I have done.

What is the fix? Am I fetching data too fast?

Code:

[code]
CurrentPressure = me.ReadAll(Encodings.ISOLatin1).val - ZeroValue

if readpressure = true then //Readpressure property is a boolean given by a timer. This simply puts the pressure into a listbox with the time
  
  dim Pressure as Variant = CurrentPressure
  
  dim Time as string = timelabel1.Text // calls to time
  
  PressureDataListbox.AddRow(Time, Pressure) // Add row with time in cell 1, Pressure in cell 2
  
  readpressure = false
end

end

dim s as string = “!001:SYS?” + chr(13)
serialPort1.write s[/code]

Would there be a sleep command for this or what? Thank you so much!

Are you fetching the data on a timer? Or through DataAvailable?

I think you should use the DataAvailable event to accumulate whole lines
( until chr(13) is met, assuming that is your end-of-line character )
and then parse out the number, something like this
( This is off the top of my head, untested, so beware )…

[code]Form property: input_buffer as string

DataAvailable event:-

// Add available data to our buffer…
input_buffer = input_buffer + me.readall(Encodings.ISOLatin1)

// Look for end-of-line by splitting the line up…
// ( This also copes with multiple values having been accumulated )

dim items(-1) as string
items = input_buffer.split( chr(13 )

dim last_item as integer = items.ubound

if last_item > 0 then

// last item can be empty, or part-value
input_buffer = item( last_item )

last_item = last_item - 1 // last full value

for at as integer = 0 to last_item
call process_value( item(at).trim )
next at

end if

Function: process_value( value_string as string )

readpressure = false [/code]

Note: Also: If a value is only sent in response to the string you are sending,
then would it might be better to do this from your 1 sec timer, like…

[code]In timer code:
if NOT readpressure then
// Only do this if we are not already waiting for a reading

readpressure = true
dim s as string = “!001:SYS?” + chr(13)
serialPort1.write s
end if [/code]

@Chris Carter is right, use the dataavailable event

use the lookahead function.
it returns whats in the buffer without removing it.

so you can do something like

eolPos = serial1.lookahead.instr(endofline)

if eolpos > 0 then
thiscmd = serial.read(eolpos)
process(thiscmd)
end if

which will let the dataavailableevents fire, but only read the data once it contains the endofline character

much cleaner and less error prone i find

I am using the code I posted on the DataAvailable Event.

[quote=187299:@Chris Carter]I think you should use the DataAvailable event to accumulate whole lines
( until chr(13) is met, assuming that is your end-of-line character )
and then parse out the number, something like this
( This is off the top of my head, untested, so beware )…

[code]Form property: input_buffer as string

DataAvailable event:-

// Add available data to our buffer…
input_buffer = input_buffer + me.readall(Encodings.ISOLatin1)

// Look for end-of-line by splitting the line up…
// ( This also copes with multiple values having been accumulated )

dim items(-1) as string
items = input_buffer.split( chr(13 )

dim last_item as integer = items.ubound

if last_item > 0 then

// last item can be empty, or part-value
input_buffer = item( last_item )

last_item = last_item - 1 // last full value

for at as integer = 0 to last_item
call process_value( item(at).trim )
next at

end if

Function: process_value( value_string as string )

readpressure = false [/code]

Note: Also: If a value is only sent in response to the string you are sending,
then would it might be better to do this from your 1 sec timer, like…

[code]In timer code:
if NOT readpressure then
// Only do this if we are not already waiting for a reading

readpressure = true
dim s as string = “!001:SYS?” + chr(13)
serialPort1.write s
end if [/code][/quote]

The readpressure property is a boolean that sets it to input the data into a listbox to be charted.

Shouldn’t I not use a timer to run the serial port?

[quote=187301:@Russ Lunn]@Chris Carter is right, use the dataavailable event

use the lookahead function.
it returns whats in the buffer without removing it.

so you can do something like

eolPos = serial1.lookahead.instr(endofline)

if eolpos > 0 then
thiscmd = serial.read(eolpos)
process(thiscmd)
end if

which will let the dataavailableevents fire, but only read the data once it contains the endofline character

much cleaner and less error prone i find[/quote]

So does that look for a quantifiable amount or for the end line character?

The problem isn’t zeros, it is partial numbers. The carts will look like this

[quote]15005.12
15005.12
15005.12
15005.12
15
5.12
15005.12[/quote]

So the end line character is a great idea. How would y’all edit the code I started with to look ahead for that?

Obviously a LookAhead code is needed, but what would it be?

The code Chris or Russ posted should work for you. Both are valid approaches that I have used at various times for socket or serial code. Look those over and try to understand what they are doing. You should have no trouble modifying your code once you understand those snippets.

My code:-

items = input_buffer.split( chr(13 ) )

is looking or chr(13) as end-of-line code

and Ross’s line:-

eolPos = serial1.lookahead.instr(endofline)

is looking for the Xojo endofline code.

Personally I would use chr(13) as endofline depends on the platform you are compiling on ( chr(10) on Linux and Mac but the two bytes chr(13)+chr(10) on Windows ) and does not necessarily reflect the end-of-line code sent by the device attached to your serial port - ( I have used chr(13) as that is what you use in the data you send to your device - if chr(10) is being sent as well, it can be stripped off using .trim - as in my example ).

Your original code does a readall and then throws away the data if your readpressure flag is false, so perhaps this is where it goes missing.

[quote=187369:@Chris Carter]My code:-
items = input_buffer.split( chr(13 ) )
is looking or chr(13) as end-of-line code

and Ross’s line:-
eolPos = serial1.lookahead.instr(endofline)
is looking for the Xojo endofline code.

Personally I would use chr(13) as endofline depends on the platform you are compiling on ( chr(10) on Linux and Mac but the two bytes chr(13)+chr(10) on Windows ) and does not necessarily reflect the end-of-line code sent by the device attached to your serial port - ( I have used chr(13) as that is what you use in the data you send to your device - if chr(10) is being sent as well, it can be stripped off using .trim - as in my example ).[/quote]

I am using a mac to write the software and would like to be able to test it locally to that. The software is for windows computers primarily though. Which would be better to fit both of those?

It actually doesn’t go missing. It is turned into the CurrentPressure property which is read by many other parts of the software. I only need the chart updated when the X axis (time) also gets more information (new second counts).

But, because you do not check for end-of-line, CurrentPressure can contain a part reading, and you overwrite this in the next ReadAll(…)

You need to really know what is being sent by your device as the end of line character and look for that. Likely it is 13 but not necessarily.

Absolutley.

You also need to check for all occurances or the end of line character/sequence, as more than one reading may end up in the incoming buffer.

From the Xojo Blog: http://www.xojo.com/blog/en/2015/03/guest-post-serial-communications-with-xojo.php

Sub DataAvailable()

Dim PacketSize As Integer?

Dim Terminator As String = ChrB(26) // This is the data terminator, in this case it is an EndOfFile, but could be multiple byte like CRLF (#0D0A)

 PacketSize = InStrB(0, me.Lookahead, Terminator) - 1 // Check the buffer for at least one full packet.  Instr returns the position of the first byte of the Terminator, so we subtract 1 to get the actual packet size
 If PacketSize < 0 Then // Wait for at least one full packet
    Return // Exit this routine
 End If
 Do
     Dim DataPacket As String = me.Read(PacketSize) // Read the data packet without the terminator
     Call me.Read(LenB(Terminator)) // Remove the terminator from the buffer
    // Deal with the Data (DataPacket)
    PacketSize = InStrB(0, me.Lookahead, Terminator) // Recheck the buffer for a full packet
  Loop Until PacketSize < 0
End Sub

Chris is right about endofline, it will change depending on the machine its run on

you can also use endofline.windows, endofline.macintosh, endofline.unix and these are then fixed.

more typing than chr(13) as well!

The data terminator is set by the remote device, not the OS that is hosting the app. Read the specs of the device, or inspect the bytes in the buffer.

One of the devices is something I built on an Arduino board. It sends chr(13) after every line. The second device is not of my creation, but still ends with chr(13).

So how would such a data terminator be used? Is that not the same as chr(13)?

Correct me if I am wrong, but the .split action would work to read the line for the chr(13). If no chr(13) is found, throw out the reading.

That is your error. If no chr(13) is found, you may have part of the next message, so you need to keep it around, not throw it out. You are not guaranteed to get a complete send in DataAvailable. You may get the first few bytes in one DataAvailable event, and then later get the rest of the message in another DataAvailable event. Either leave the partial message in the buffer, or buffer it yourself in your own property.

So how would you suggest writing this so that it keeps the partial message around?

Just use Wayne’s code, as suggested by Antonio, but change the line starting…

Dim Terminator As String = ChrB(26)

with

Dim Terminator As String = ChrB(13)

[quote=187586:@Antonio Rinaldi]From the Xojo Blog: http://www.xojo.com/blog/en/2015/03/guest-post-serial-communications-with-xojo.php

[code]
Sub DataAvailable()

Dim PacketSize As Integer?

Dim Terminator As String = ChrB(26) // This is the data terminator, in this case it is an EndOfFile, but could be multiple byte like CRLF (#0D0A)

PacketSize = InStrB(0, me.Lookahead, Terminator) - 1 // Check the buffer for at least one full packet. Instr returns the position of the first byte of the Terminator, so we subtract 1 to get the actual packet size
If PacketSize < 0 Then // Wait for at least one full packet
Return // Exit this routine
End If
Do
Dim DataPacket As String = me.Read(PacketSize) // Read the data packet without the terminator
Call me.Read(LenB(Terminator)) // Remove the terminator from the buffer
// Deal with the Data (DataPacket)
PacketSize = InStrB(0, me.Lookahead, Terminator) // Recheck the buffer for a full packet
Loop Until PacketSize < 0
End Sub
[/code][/quote]

[quote=188024:@Chris Carter]Just use Wayne’s code, as suggested by Antonio, but change the line starting…

Dim Terminator As String = ChrB(26)

with

Dim Terminator As String = ChrB(13)

After doing some research, I found that one of the devices I am using ends lines on chr(10), so I adjusted the code accordingly. My code is as follows:

DataAvailable

[code] Dim PacketSize as Integer

Dim Terminator as string = ChrB(10)

PacketSize = InStrB(0, me.LookAhead, Terminator) -1 // Check the buffer for at least one full packet.

If PacketSize < 0 then
return // exit routine
end if

Do
Dim DataPacket as string = me.Read(PacketSize) //Read the data packet without the terminator
call me.Read(LenB(Terminator))

//my code

Sound = DataPacket
TextField1.Text = Sound

dim time as string = label1.text

Listbox1.AddRow(time, Sound)

//end my code

PacketSize = InStrB(0, me.Lookahead, Terminator) // Recheck the buffer for a full packet

Loop Until PacketSize < 0[/code]

This seems to have some critical error in that the application does not respond. What have I done wrong? Thank you so much for your help.

Wow! I have to say there’s no better example of how wonderful the Xojo community is than this thread!