C# Serial Data Loss? - c#

i study C# Serial.
i write this code to receive Data.
when i run this code and another device sent data only once, but the program receives the data twice or more than three times.
how can i fix this problem?
There's still a lot I don't know. Please explain it easily.
I spent a week because I couldn't solve this problem.... :(
private void MainForm_Load(object sender, EventArgs e)//main form
{
serialPort1.DataReceived += new SerialDataReceivedEventHandler(EventDataReceived);
CheckForIllegalCrossThreadCalls = false;
........
........
........
}
........
........
........
void EventDataReceived(object sender, SerialDataReceivedEventArgs e)//this is receiving data method
{
int size = serialPort1.BytesToRead;// assign size of receive data to 'int size'
byte[] buff = new byte[size];// array who assign receiving data(size = int size)
serialPort1.Read(buff, 0, size);//assign receive data to 'buff'
string hexData = BitConverter.ToString(buff).Replace("-", " ");//Convert the received data into hexadecimal numbers and store to 'string hexdata'
for (int i = 0; i < size; i++)
{
tb_rx.Text = tb_rx.Text + " \r\n " + hexData;
Thread.Sleep(1000);//When I first encountered the problem, I added it because I thought the interval was too short. But I don't think this is the solution.
}
}

This code is fishy:
string hexData = BitConverter.ToString(buff).Replace("-", " ");//Convert the received data into hexadecimal numbers and store to 'string hexdata'
for (int i = 0; i < size; i++)
{
tb_rx.Text = tb_rx.Text + " \r\n " + hexData;
Thread.Sleep(1000);//When I first encountered the problem, I added it because I thought the interval was too short. But I don't think this is the solution.
}
BitConverter.ToString(byte[]) already converts the byte array to a sequence of hex strings. In the for loop, you then add this hexData string to tb_rx (probably a textbox) for each byte received. So depending on the number of bytes you receive at once, you do get duplicate output. Just change that to:
string hexData = BitConverter.ToString(buff).Replace("-", " ");//Convert the received data into hexadecimal numbers and store to 'string hexdata'
tb_rx.Text = tb_rx.Text + " \r\n " + hexData;

Related

Data loss during Serial.Write method

Now I would like to use WinForms to develop a GUI to control a embedded board.
Between WinForm and the embedded board, serial communication is utilized.
Currently, result of reading data is well.
However, writing data with Serial.Write method did not perform well.
https://learn.microsoft.com/zh-tw/dotnet/api/system.io.ports.serialport.write?view=netframework-4.8
Some characters were not received by the board.
the result is show in the following:
private void ButtonSendCommand_Click(object sender, EventArgs e)
{
byte[] txdata = Encoding.UTF8.GetBytes(TXTBoxCommand.Text);
OBCSerialPort.Write(txdata, 0, txdata.Length);
OBCSerialPort.Write(Environment.NewLine);
}
in my embedded board, I use scanf to get command and decode them
scanf("%[^\n]%*c", cStr);
And decode function:
void decode_context(char *str)
{
uint8_t i = 0;
uint8_t argc = 0; //number of argument
char *p = strtok(str, " ");
char *cBuf[MAX_INPUT_LEN];
command_t* pCase = 0;
struct cmd_context ctx;
// Split input string with space
while (p != NULL){
cBuf[i++] = p;
p = strtok (NULL, " ");
argc++;
}
if(argc < Max_ARGC){
// Duplicate arg & argc to command context
memcpy(ctx.arg, cBuf, sizeof(cBuf[0])*argc);
memcpy(&ctx.argc, &argc, sizeof(uint8_t));
// Linear serach for key of instruction
for(pCase = command
; pCase != command + sizeof(command) / sizeof(command[0])
; pCase++)
{
if(0 == strcmp(pCase->name, ctx.arg[0])) {
(*pCase->handler)(&ctx);
break;
}
if(pCase == command-1 + sizeof(command)/sizeof(command[0]))
printf("\n\r\t Not defined command:'%s' Please refer instruction set!\n", ctx.arg[0]);
}
}
else
printf("\n\r\t Error: too many arguments ! \n\r");
}
P.S.Before the development of this GUI, I have used Tera Term to communicate with the board and both reading and writing performed well.
Configuration is :
Receiving: CR, encoding with UTF8
Transmitting: LF, encoding with UTF8
Does anyone figure out why it happened just like that?
Update
I have solved this issue after I modified writing process:
int index = 0;
byte[] txdata = Encoding.UTF8.GetBytes(TXTBoxCommand.Text);
while (index < txdata.Length)
{
OBCSerialPort.Write(txdata, index, 1);
index++;
if (index == txdata.Length)
OBCSerialPort.Write("\n");
}
It seems that for my embedded board using OBCSerialPort.Write to transmit multiple bytes will cause data loss.
However, it does not make sense...

C# Serial port randomly return all 0 when overloaded

I am making a C# User Interface for a project using MCU communicate with the PC using UART. The string sent from the MCU in this format(3 numbers per group, 3 groups):
<number>,<number>,<number>,.<number>,<number>,<number>,.<number>,<number>,<number>,.\n
With each number can change depend on the MCU sending. I have written a program which work fine for this configuration. In general, it take in the data from COM port as a string, filter out any characters that is not a number, then save them separately into a List.
However, when we expand the format to use 7 numbers per group (21 in total):
<number>,<number>,<number>,<number>,<number>,<number>,<number>,.<number>,<number>,<number>,<number>,<number>,<number>,<number>,.<number>,<number>,<number>,<number>,<number>,<number>,<number>,.\n
When I try the same approach, there is some sort of "stagger" in the data in. When the data is stable, it is fine. When there is any rapid change in the number coming in, the graph of the data look like this:
The sharp drop in the middle of the graph is not how the data change; the UART port just "freeze up" and output 0 for all number in the receiving List, I have checked the data from Hercules myself.
I think the problem come from overload since there is no differences in any other aspect between the 3 and 7 numbers datastream, but can StackOverflow users think of any other solution?
EDIT: Should it helps, here is the code I used:
// Initialization
static SerialPort mySerialPort;
string receiveMessage = "";
public delegate void AddDataDelegate(string text);
public AddDataDelegate DELEGATE_WRITE;
DELEGATE_WRITE = new AddDataDelegate(writeConsole);
// com and baud are defined variables
mySerialPort = new SerialPort(com, baud);
mySerialPort.DataReceived += new SerialDataReceivedEventHandler(mySerialPort_datareceived);
mySerialPort.Open();
//Supporting functions
private void mySerialPort_datareceived(object sender, SerialDataReceivedEventArgs e)
{
string input = mySerialPort.ReadExisting();
textBox.BeginInvoke(method: DELEGATE_WRITE, args: new Object[] { input });
}
public void writeConsole(string text)
{
receiveMessage += text;
// check if the receiving data steam is valid: end with '\n' and not longer than 64 characters (21 value of maybe 2-3 digits each)
if (receiveMessage.Length > 64)
{
receiveMessage = "";
}
if (receiveMessage.Length > 0)
if (receiveMessage[receiveMessage.Length - 1] == '\n')
{
updateInterface(receiveMessage);
receiveMessage = "";
}
return;
}
void updateInterface(string input)
{
try
{
int[] result = calculate(input);
if (result == null)
{
MessageBox.Show("Error", caption: "Algorithm fail to extract data", buttons: MessageBoxButtons.OK, icon: MessageBoxIcon.Error);
return;
}
//Save the data to List
}
static int[] calculate(string input)
{
int[] outputs = new int[21];
int index = 0;
string s = "";
for (int i = 0; i < input.Length; i++)
{
char c = input[i];
if (c == '.')
{
int output = int.Parse(s);
outputs[index] = output;
index++;
s = "";
}
else if (c >= '0' && c <= '9'|| c == '-')
{
s += c;
}
}
return outputs;
}
Always the devil is in the details
In the writeConsole function of my code, I make a check for valid string by checking if they end in '\n' (since the data will be coming in real time) and if they are less than 64 characters long (21 value of maybe 2-3 digits each). Turn out, that is not a correct assumption at all when the data could easily reach twice as long when the data changed quickly enough (in my examples, the change could reach 100). So just delete the length check and the program work as usual.
PS: Come on guys, this is the second time I have to answer my own question already

Serial Communication with Arduino in C#

I am repeatedly reading a serial port that i created using C# (which is easy).
I created a loop to read the serial port (say 50 times after a delay of 50 milliseconds) and read 10 bytes of the serial port after i click the start button on the form and write the read values to console.
private void buttonStart_Click(object sender, EventArgs e)
{
serialPort1.PortName = "COM3";
serialPort1.BaudRate = 115200;
serialPort1.Open();
if (serialPort1.IsOpen)
{
buttonStart.Enabled = false;
buttonStop.Enabled = true;
//textBox1.ReadOnly = false;
}
for (int i = 0; i < 50; i++)
{
string input = "AN\n"; // Analog read command for WildThumper (No problems here)
if (!serialPort1.IsOpen) return;
serialPort1.Write(input);
Application.DoEvents();
System.Threading.Thread.Sleep(50);
}
}
the last byte is '*' which is end of string delimiter in my case.
Then i combine the read 10 bytes into the 5 values by the following piece of code
private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
int n = serialPort1.BytesToRead; // gives me n = 11 (the last one is '*' for end of string)
byte[] data = new byte[n];
serialPort1.Read(data, 0, data.Length);
int[] Values = new int [5];
for(int i = 0;i<5;i++)
{
int value1 = data[0+i*2];
int value2 = data[1+i*2];
int value = value1 + value2;
Values[i] = value;
}
string RxString = string.Join(" ", Values.Select(i => i.ToString()).ToArray());
Console.WriteLine(RxString + Environment.NewLine);
}
My problem is that when I run the code it does the required task (reading the serial port and displaying the five Values[] ) a few times (5,6 times), then i get an exception which makes my program crash.
the exception points to this line in the code.
int value1 = data[0+i*2];
the debugger says says something like "make sure that data index is not negative, the maximum index is less that index of list size etc etc."
The following exception is shown in the console window:
Unhandled Exception: System.IndexOutOfRange Exception: Index was outside the bounds of array.
I don't know what the problem is.
Can you have a look at it and tell me what am I doing wrong. I am an electronics major so my idea of programming is minimal.
You need to check n is always 11. There is a chance that it sometimes returns less than 11, in which case you won't have got 10 bytes to read. You'll need to buffer them and call read again until you get all 10 of them.
I would also recommend you check the return value of serialPort1.Read(data, 0, data.Length); because it returns the number of bytes it actually read, rather than the number of bytes you asked it to read.
See the docs here.

SerialPort not writing data with long strings - FIXED

I am writing a program which writes channel urls and channel names to a device. I'm trying to write channel urls with a length of 254. Shorter urls (I haven't found the threshold yet) do work.
What makes it more strange is that the channel names are only 16 characters long, and these do also only write when the channel urls are shorter.
I have the following code which works:
for (int i = 0; i < numberOfChannels; i++)
{
string hexString = String.Format("{0:X1}", i);
Serialport.WriteLine("channel 2 " + hexString + " " + channelURLs[i]);
System.Threading.Thread.Sleep(1000);
Serialport.WriteLine("channel 1 " + hexString + " " + channelnames[i]);
System.Threading.Thread.Sleep(1000);
}
however, I do not want to have my code sleep 2seconds for writing 1 channel. QUESTION: How do I get those long strings written through the serialport without waiting 2 seconds?
This does not work:
for (int i = 0; i < numberOfChannels; i++)
{
string hexString = String.Format("{0:X1}", i);
if (Serialport.WriteLine("channel 2 " + hexString + " " + channelURLs[i]))
{
Debug.WriteLine("this should happen before the string is written");
}
if(Serialport.WriteLine("channel 1 " + hexString + " " + channelnames[i]))
{
Debug.WriteLine("this should happen before the string is written");
}
}
the debug lines are written, and the string is not written through the comport.
my Serialport.WriteLine function looks like this:
public static bool WriteLine(string data)
{
if (SerPort.IsOpen)
{
Debug.WriteLine("PORT OPEN, WRITING "+ data);
SerPort.WriteLine(data);
return true;
}
else return false;
}
UPDATE: I found that when I disconnect and reconnect, the first URL is written. Apparently it has to do with some buffer?
UPDATE2: This is how I set up my serialport:
SerPort.BaudRate = 115200;
SerPort.DataBits = 8;
SerPort.StopBits = StopBits.One;
SerPort.PortName = comnr;
SerPort.Handshake = Handshake.None;
Serialport.SerPort.ReadTimeout = 1000;
Serialport.SerPort.WriteTimeout = 1000;
SerPort.DataReceived += SerPort_Datareceived;
SerPort.Open();
SerPort.DiscardInBuffer();
SerPort.DiscardOutBuffer();
UPDATE 3: Fixed the issue
I've found where my issue was. Before sending the commands shown here, I sent a command which took some time to process. If the device recieved data during this process it would stop receiving data. I updated my code with a timer to check for an OK respond from the device before sending the next commands.
This depends on the device you are working on. If you send too many bytes to it, there's a chance that it cannot process them all.
You can try to decrease the waiting time from 1000 ms to, for example, 200 ms.
You can also try to send packages of data... for example, send the long string in packages of 50 characters, waiting an amount of time between them (example, 100 ms).
UPDATE 1:
Also try to decrease the BaudRate from 115200 to 9600.

Circular log TextBox for raw serial characters

The problem
From the serial port I receive a stream of characters. The stream I receive will be terminated by \n. I receive the serial port data in parts like this:
Serial port Event 1: "123\n456\n789"
Serial port Event 2: "\n0123\n456\n789\n"
Serial port Event 3: "0123\n456\n789\n"
As you can see my stream fluctuates, and this is very normal since I read the serial port with "what is available currently".
My problem is that I want to log this to the UI with a RichTextBox. I cannot use ListBox because I need color and font formatting.
First approach
Before I tried this below and that works very well till the time the messages exceed around 30.000 lines of text. The UI gets unresponsive. This was the code:
uiTextActivity.SelectionFont = new Font(uiTextActivity.SelectionFont, FontStyle.Bold);
uiTextActivity.SelectionColor = LogMsgTypeColor[(int)msgtype];
uiTextActivity.AppendText(msg);
uiTextActivity.ScrollToCaret();
Later on I used this as a quick fix that clears the box after 2000 lines:
if ((int.Parse(uiLabelCounterActivity.Text) + 1) > 2000)
uiTextBoxResetActivity();
I want to keep a history of around 500lines.
But with this quick fix above I lose my log completely when the counter hits 2000.
I think what I need is a circular FIFO TextBox. So I can remove after 500 lines the oldest logs and append the new one.
Second approach
I also tried this (note that in this case below the newest logs entries are on top and the oldest below in the textbox)
msg = msgToPasteWhenComplete + msg;
int c = 0; // c=zero if no newline, 1 for single newline, 2 for 2times newline and so on
int latestTermination = 0;// store the point where \n is found
for (int e = 0; e < msg.Length; e++)
{
if (msg[e] == '\n')
{
c++;
latestTermination = e+1;
}
}
// contains strings all terminated with \n
string msgToPaste = msg.Substring(0, latestTermination);
// contains string that is not complete (not yet terminated with \n)
msgToPasteWhenComplete = msg.Substring(latestTermination, msg.Length - latestTermination);
string previous = msgToPaste + uiTextActivity.Text;
if (previous.Length > maxDisplayTextLength)
{
string debugInfo3 = previous.Substring(0, maxDisplayTextLength);
uiTextActivity.Text = debugInfo3;
}
else
uiTextActivity.Text = previous;
This works almost very well. The problem with this approach is that the line that comes from the serial port will not be pasted to the UI until the \n is received. This means that whenever communication is slow, I will have to wait for the serial stream to complete the whole line including \n before I can see it... What I want is to see every character directly.
info about serialport
The serial data am I getting from the SerialDataReceivedEvent, in that event I use comport.ReadExisting() to have a non-blocking event.
The serial data comes from my embedded programming board that has analog readings. The analog readings supply me with 20 lines per second, each line containing 20 chars.
I need the raw data to be read in a user interface where it has to be filtered to other textboxes depending on the prefix of the serial message (like err goes to error , warn goes to warning textbox, but they all go to the activity log. The activity log is what this question is about.
I wish I could give you a specific chunk of code to work off of, but I haven't had to do this kind of input processing in a while.
That being said, you might get better performance by buffering your inputs into a Queue (MSDN reference) object, and then polling the queue on a timer or by responding to some other event (maybe OnChanged?).
I found a solution that works, here is my code:
private const int maxDisplayTextLength = 5000;
private string splitString = "";
private int activityCount = 0;
private string errorMessageBox = "";
private bool errorMessageBoxNeedUpdate = true;
private int errorMessageBoxCount = 0;
private string filteredMessageBox = "";
private int filteredMessageCount = 0;
private bool filteredMessageBoxNeedUpdate = true;
private string activityLogFilterKeyword = "Warning";
string logHelperStringMaxSizeLimiter(string input)
{
// check if our buffer isn't getting bigger than our specified max. length
if (input.Length > maxDisplayTextLength)
{
// discard the oldest data in our buffer (FIFO) so we have room for our newest values
input = input.Substring(input.Length - maxDisplayTextLength);
}
return input;
}
private void logHelperIncoming(string msg)
{
msg = msg.Replace('\0', '\n'); // remove \0 NULL characters as they have special meanings in C# RichTextBox
// add the new received string to our buffer
splitString += msg;
// filter out special strings
int searchIndexStart = splitString.Length - msg.Length;
for (int e = searchIndexStart; e < splitString.Length; e++)
{
if (splitString[e] == '\n')
{
activityCount++;
string subString = splitString.Substring(searchIndexStart, e - searchIndexStart) + "\n";
searchIndexStart += e - searchIndexStart + 1; // update searchindex for next search
string filterError = "error";
// filter messages that start with error
if (subString.Length > filterError.Length) // for this filter, the length should be at least length of error
{
if (String.Compare(subString, 0, filterError, 0, 4, true) == 0)
{
errorMessageBox += subString;
errorMessageBoxNeedUpdate = true;
errorMessageBoxCount++;
}
}
// filter messages that start with XXX
if (subString.Length > activityLogFilterKeyword.Length && activityLogFilterKeyword.Length != 0) // for this filter, the length should be at least length of error
{
if (String.Compare(subString, 0, activityLogFilterKeyword, 0, activityLogFilterKeyword.Length, true) == 0)
{
filteredMessageBox += subString;
filteredMessageBoxNeedUpdate = true;
filteredMessageCount++;
}
}
}
}
}
// outputs to main activity textbox
private void Log(LogMsgType msgtype, string msg)
{
if (msgtype == LogMsgType.Incoming || msgtype == LogMsgType.Normal)
{
logHelperIncoming(msg);
}
else if (msgtype == LogMsgType.Outgoing)
{
}
splitString = logHelperStringMaxSizeLimiter(splitString);
filteredMessageBox = logHelperStringMaxSizeLimiter(filteredMessageBox);
errorMessageBox = logHelperStringMaxSizeLimiter(errorMessageBox);
uiTextActivity.Invoke(new EventHandler(delegate
{
// time to post our updated buffer to the UI
uiTextActivity.Text = splitString;
uiTextActivity.Update();
// scroll to the newest data only if user has no focus on the
uiTextActivity.SelectionStart = uiTextActivity.TextLength; // make sure view is to the latest
uiTextActivity.ScrollToCaret();
uiLabelCounterActivity.Text = activityCount.ToString();
if (errorMessageBoxNeedUpdate)
{
errorMessageBoxNeedUpdate = false;
uiTextActivity.SelectionColor = Color.Red;
// time to post our updated buffer to the UI
uiTextboxErrorLog.Text = errorMessageBox;
// scroll to the newest data only if user has no focus on the
uiTextboxErrorLog.SelectionStart = uiTextboxErrorLog.TextLength; // make sure view is to the latest
uiTextboxErrorLog.ScrollToCaret();
uiLabelCounterError.Text = errorMessageBoxCount.ToString();
}
if (filteredMessageBoxNeedUpdate)
{
filteredMessageBoxNeedUpdate = false;
// time to post our updated buffer to the UI
uiTextboxFilteredLog.Text = filteredMessageBox;
// scroll to the newest data only if user has no focus on the
uiTextboxFilteredLog.SelectionStart = uiTextboxFilteredLog.TextLength; // make sure view is to the latest
uiTextboxFilteredLog.ScrollToCaret();
uiLabelCounterFiltered.Text = filteredMessageCount.ToString();
}
// extract value from filter and store to filter
activityLogFilterKeyword = uiTextboxFilterKeyword.Text;
}));
}

Categories