I have a serialport dataRecived event with 2 methods inside it. The LogFile is loging data and drawSetpoint is drawing a graph.
public void serialPort1_DataRecived (object sender, SerialDataReceivedEventArgs e)
{
DateTime time = DateTime.Now;
string Rdata = serialPort1.ReadLine();
LogFile(Rdata, (int)numericSetpoint.Value);
drawSetpoint(time,numericSetpoint.Value.ToString());
}
Both methods take the second argument from a numericUpDown control which looks like this
public void numericSetpoint_KeyPress(object sender, KeyPressEventArgs e)
{
if (e.KeyChar == 13)
{
if (serialPort1.IsOpen)
{
int setpoint = (int)numericSetpoint.Value;
//send to serial port .....
}
The problem is both methods in my data recived event read digits as I type them. For example, if I type 150, LogFile will show 1,15,150 and the draw function will start drawing 1,15,150. I would like both functions to take the value from the numericSetpoint control after the enter key is pressed so i get the whole value. How could I do that ?
You are using the KeyPress event. Instead consider using the ValueChanged event which only fires when enter is pressed or the user leaves the control https://msdn.microsoft.com/en-us/library/system.windows.forms.numericupdown.valuechanged%28v=vs.110%29.aspx
It sounds like your data rx event is firing while your typing. I would try filling a buffer in your data rx event and hold off on any logging or plotting until you've sent your data. There are bunch of "safe" ways to do this but the core logic is as follows:
byte[] buffer = new byte[MAX_BUFFER];
private volatile bool _ready = false;
private Object _lock = new Object();
public void serialPort1_DataRecived (object sender, SerialDataReceivedEventArgs e)
{
DateTime time = DateTime.Now;
// Either read as bytes or convert string to bytes, add to your buffer
// string Rdata = serialPort1.ReadLine();
lock(_lock )
{
if(_ready)
{
_ready = false;
var myData = your buffer as string
// clear buffer
LogFile(myData, (int)numericSetpoint.Value);
drawSetpoint(time,numericSetpoint.Value.ToString());
}
}
}
...
public void numericSetpoint_KeyPress(object sender, KeyPressEventArgs e)
{
if (e.KeyChar == 13)
{
if (serialPort1.IsOpen)
{
int setpoint = (int)numericSetpoint.Value;
//send to serial port .....
lock(_lock ) { _ready = true; }
}
}
}
Related
I am trying to perform a control where I receive information sent by Arduino.
This information is of type string and format VX 1.987 or VY 0.123 and are sent at high speed. After they are filtered and treated, they update the textbox in my program, and in this way what I see in the TextBox is the last information received.
What happens with my code is that at a certain point, for example, were sent 1000 lines by the Arduino, my program will update up to 600 and then stop showing the updates. I know that in the receive buffer are the other 400 rows, but they were not shown.
Summarizing how do I make memo time be receiving the information by serial and reading and putting in the textbox? Could this be a processing time issue? How to solve?
namespace ControleCaseiro
{
private void Form1_Load(object sender, EventArgs e)
{
serialPort1.DataReceived += new SerialDataReceivedEventHandler(serialPort1_DataReceived);
}
private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
try
{
DataIn = serialPort1.ReadLine();
this.Invoke(new EventHandler(MostraDados));
}
catch(Exception ex)
{ }
}
private void MostraDados(object sender, EventArgs e)
{
switch (DataIn)
{
case "19\r":
textcontrole.Text = "XOFF";
break;
case "17\r":
textcontrole.Text = "XON";
if (botãoEnviaGcode)
{
LinhaArquivo++;
EnviaGcode();
}
break;
default:
FiltroDadosRecebidos(DataIn);
break;
}
}
private void FiltroDadosRecebidos(string valorRecebido)
{
string eixo="";
int tamanho=0;
string valorEixoAtual="";
if (valorRecebido.Length > 1)
{
tamanho = valorRecebido.Length;
eixo = valorRecebido.Substring(0, 2);
switch (eixo)
{
case "VX":
valorEixoAtual = valorRecebido.Substring(2, tamanho - 3);
txtPosAtualX.Text=valorEixoAtual;
break;
case "VY":
valorEixoAtual = valorRecebido.Substring(2, tamanho - 3);
txtPosAtualY.Text=valorEixoAtual;
break;
case "VZ":
valorEixoAtual = valorRecebido.Substring(2, tamanho - 3);
txtPosAtualZ.Text=valorEixoAtual;
break;
default :
DadosRecebidos.Items.Add(DataIn);// se não for dados referentes aos eixos, vai mostrar na listbox “DadosRecebidos”
break;
}
}
}
}
As I have already described in the beginning, what it is that the Arduino is sending the information and after a while, everything to and the information is not shown in the TextBox. What it looks like is that while OA SerialPort is receiving data, the event "SerialDataReceivedEventArgs " causes the program to show the information, when it is out of receive, no longer shows and the information is accumulated in the serialport buffer.
The issue is likely that you're not handling all the data in the receive buffer. You're only extracting one line of the receive buffer even though there may be multiple lines waiting to be processed. You could try changing your handler to something like this:
private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
try
{
while (serialPort1.BytesToRead > 0)
{
DataIn = serialPort1.ReadLine();
this.Invoke(new EventHandler(MostraDados));
}
}
catch (Exception ex)
{ }
}
So I am sending from an Arduino Uno an int from potentiometer(0 - 1023) and when I am reading it and print it in a label, there are no numbers.And I read somewhere that I need to read the bytes, how I am going to do that?
namespace Receiver
{
public partial class Form1 : Form
{
SerialPort port;
UITimer _timer = new UITimer();
public Form1()
{
InitializeComponent();
if (port == null)
{
port = new SerialPort("COM11", 9600);//Set your board COM
port.Open();
}
}
private void Form1_Load(object sender, EventArgs e)
{
_timer.Interval = 200;
_timer.Tick += _timer_Tick;
_timer.Enabled = true;
_timer.Start();
}
private void _timer_Tick(object sender, EventArgs e)
{
string a = port.ReadExisting();
afisare.Text = a;
}
void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
_timer.Stop();
if (port != null && port.IsOpen)
{
port.Close();
}
}
}
}
First, make sure that you are using the correct Baud rate for your serial communication or otherwise you will receive unreadable data.
Your code is only missing a correct interpretation of the incoming data. On top of that I would recommend removing the timer and using the built-in DataReceived event. That means you can delete all your timer related code and add an event handler to your SerialPort initialization:
if (port == null)
{
port = new SerialPort("COM11", 9600); //Set your board COM
port.DataReceived += DataReceivedEvent;
port.Open();
}
Then you of course have to declare your DataReceivedEvent handler. Since you said that your potentiometer can contain values ranging from 0-1023 and you didn't provide your Arduino code, I'm assuming that that's the only thing being send over the port.
This would mean you are sending 2 bytes every cycle which need to be parsed back to an integer.
This works by performing a left shift of your two received bytes.
private void DataReceivedEvent(object sender, SerialDataReceivedEventArgs e)
{
SerialPort senderPort = (SerialPort)sender;
byte[] buffer = new byte[2];
if (senderPort.Read(buffer, 0, 2) != 0)
{
int data = (int)buffer[0] << 8 | buffer[1];
Console.WriteLine("Received data: {0}", data);
}
}
If you want to use div's Answer on the C# side, you have to send those two bytes as well.
That gives you a bit more precision than in your comment (dividing by 4 and multiply by 4.015 --why??--)
Using the corresponding shift operation:
void loop() {
int a= analogRead(A0);
Serial.write(a>>8);
Serial.write(a & 0xFF);
delay(200);
}
You must be sure that you use the c# DataReceivedEvent trigger, when both bytes are available:
https://learn.microsoft.com/de-de/dotnet/api/system.io.ports.serialport.bytestoread
Created a simple window form to receive and process data from serial port
also logging the data in textfile and displaying in the rich-text box.
For some reason, it is going into unresponsive state and not able to execute user action and freeze.
According to my understanding, earlier code was using two threads one UI and another one for data received event. Is this correct
But now as i am using background worker, it should create anther thread to process and append in the log and richtext box. Is this correct?
This is my first project in c# so pardon me if it is already answered somewhere else as i am unable to correlate this situation with the given answers and not able to implement them.
Is it a good idea to background worker if not then how can i solve this problem.
Thanks in advance.
Earlier the entire application was running on UI thread. Now i have tried to use background to create a new thread to store data receiving from serial port and processing inside that thread
//earlier code
void DataReceived_Event(object sender, SerialDataReceivedEventArgs e)
{
if (Port.IsOpen)
{
int bytes = Port.BytesToRead;
byte[] buffer = new byte[bytes];
Port.Read(buffer, 0, bytes);
receivedBytes.AddRange(buffer);
ProcessRecievedBytes(null);
}
}
//latest code with background worker
void DataReceived_Event(object sender, SerialDataReceivedEventArgs e)
{
if (Port.IsOpen)
{
int bytes = Port.BytesToRead;
byte[] buffer = new byte[bytes];
Port.Read(buffer, 0, bytes);
receivingBytes.AddRange(buffer);
if (!essentialBgWorker.IsBusy)
{
receivedBytes.AddRange(receivingBytes);
receivingBytes.Clear();
essentialBgWorker.RunWorkerAsync();
}
}
}
private void essentialBgWorker_DoWork(object sender, DoWorkEventArgs e)
{
foreach (byte hexByte in receivedBytes)
{
//color = Color.Gray;
if ((Config.Data.Serial_DisplayLevel == GLOBAL.HEX_LEVEL_NONE))
{
if ((hexByte == '\n') || ((hexByte >= 0x20) && (hexByte <= 0x7E)))
{
String tmpString = new String((char)hexByte, 1);
//essentialBgWorker.ReportProgress(0, tmpString);
//in here i am putting in the log file and appending in the richtext box
PreprocessAppend(tmpString, Color.Black, false);
}
}
}
process.ProcessData(receivedBytes);//in here i am processing the data
receivedBytes.Clear();
}
private void essentialBgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
string str = e.UserState.ToString();
logWriter.Write(str);
logWriter.Flush();
if (e.ProgressPercentage == 0)
{
AppendSerial(str, Color.Black, false);
}
else if (e.ProgressPercentage == 1)
{
AppendSerial(str, Color.Black, false);
}
}
SerialTab.SerialPort.AppendSerial += delegate (string data, Color txtColor, bool newLineCheck)
{
this.BeginInvoke((MethodInvoker)(delegate ()
{
if (newLineCheck && (serialTextBox.Text != "") && (serialTextBox.Text[serialTextBox.TextLength - 1] != '\r') && (serialTextBox.Text[serialTextBox.TextLength - 1] != '\n'))
{
data = "\n" + data;
}
AppendTextbox(serialTextBox, data, txtColor);
}));
};
void AppendTextbox(RichTextBox tb, string data, Color txtColor)
{
if (data == null)
{
return;
}
int start = tb.TextLength;
tb.AppendText(data);
int end = tb.TextLength;
// Textbox may transform chars, so (end-start) != text.Length
tb.Select(start, end - start);
tb.SelectionColor = txtColor;
//reset color to defaults
tb.SelectionLength = 0;
tb.SelectionColor = serialTextBox.ForeColor;
//move caret to bottom of page
ScrollToBottom(tb);
//ensure text buffer stays below 15000 characters
checkTextBoxLength(tb);
}
void checkTextBoxLength(RichTextBox box)
{
//ensure text buffer in text box gets too large
if (box.Text.Length > 15000)
{
box.ReadOnly = false;
box.SelectionStart = 0;
box.SelectionLength = box.TextLength - 10000;
box.SelectedText = "";
box.ReadOnly = true;
}
}
Don't access windows controls from any thread other than the one that created them. When using a BackgroundWorker:
add the backgroundworker to the form designer
set its ReportsProgress property to true
attach an event handler to DoWork and another to ProgressChanged
in the DoWork event, do your background work such as reading from the serial port. Any time you want to update a windows control, call the backgroundworker ReportProgress() method with an object argument for the user state that contains some data you want to use in the control
I tend to use the progress int to also specify something, such as running a switch statement to chose one of several text boxes to update
The background worker will correctly ensure the ProgressChanged event is executed by the UI thread, meaning you can update controls from within it
Looking at your code, it seems you were going along these lines because I can see a commented out ReportProgress call. Show the contents of your ProgressChanged event handler. Make absolutely sure the only time you ever access any windows control (read or write any property or call any method) is in ProgressChanged, NOT DoWork
Edit/Update
Now I can see more of your code, it looks like you've embarked on a path of making it all way too complicated. I'll make another answer
I see what you are trying to do and you kind of have the right idea, I think you are just having a threading issue. Do you have the code for your PreprocessAppend method? You probably need to use BeginInvoke in that function and that should fix your issue.
Personally, I don't think you need a backgroundworker, as the Serial DataReceived event is threaded. I would recommend what the other answer said and use ReadExisting as that will grab all the characters in the received buffer and you can determine what to do from there. Just be careful because the DataReceived event can fire whenever it determines is a good time, so you could only get a partial line back. If you go this route you'll have to build your string until you know you have the whole thing and then process it.
In my example below, I'm expecting a Line Feed as my terminator so I build a string until I have the whole line then I process it.
//private variables
private SerialPort sp;
private StringBuilder sb;
private char LF = (char)10;
//function to initialize all my objects
public void init_state_machine()
{
sp = new SerialPort(currentSettings.ComPort, currentSettings.BaudRate);
sp.DataReceived += new SerialDataReceivedEventHandler(sp_DataReceived);
sb = new StringBuilder();
sb.Clear();
}
private void sp_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
string currentLine = "";
string Data = sp.ReadExisting();
foreach (char c in Data)
{
if (c == LF)
{
sb.Append(c);
//because it's threaded its possible to enter this code while processing so we will
//clear our string building immediately
currentLine = sb.ToString();
sb.Clear();
//process your line here or whatever
processReceivedData(currentLine);
}
else
{
sb.Append(c);
}
}
}
//this is where you process the response. For a simple example I just append the string
//to our textbox, but you could do any computations in here.
private void processReceivedData(string s)
{
this.BeginInvoke((MethodInvoker)delegate
{
serialTextBox.Text += s;
});
}
I recommend you junk all of your existing code, and proceed with using something like this, derived from the example from MSDN
namespace WindowsFormsApp3
{
public partial class Form1 : Form
{
SerialPort mySerialPort = new SerialPort("COM1");
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
mySerialPort.BaudRate = 9600;
mySerialPort.Parity = Parity.None;
mySerialPort.StopBits = StopBits.One;
mySerialPort.DataBits = 8;
mySerialPort.Handshake = Handshake.None;
mySerialPort.RtsEnable = true;
mySerialPort.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);
mySerialPort.Open();
}
private void DataReceivedHandler(
object sender,
SerialDataReceivedEventArgs e)
{
SerialPort sp = (SerialPort)sender;
string indata = sp.ReadExisting();
textBox1.InvokeIfRequired(ctrl => ctrl.AppendText(indata));
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
mySerialPort.Close();
}
}
public static class ControlHelpers
{
public static void InvokeIfRequired<T>(this T control, Action<T> action) where T : ISynchronizeInvoke
{
if (control.InvokeRequired)
{
control.Invoke(new Action(() => action(control)), null);
}
else
{
action(control);
}
}
}
}
Your DataReceived handler is quite complex. The MSDN example uses a far simpler interface where it takes care internally of the reading and buffering etc with ReadExisting. You might want to attach to it and just keep appending the data you received into some kind of buffer (stringbuilder?), and regularly inspect the buffer to see if it contains a full message that you want to process
I want to change BackColor of a Button for a fixed time when a certain value comes from a serial port. I set the Timer object here:
public formLabel()
{
InitializeComponent();
// ...
timerColor.Tick += timerColor_Tick;
timerColor.Interval = 3000;
}
Then, when I receive signal from a serial port:
private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
{
SerialPort sp = (SerialPort)sender;
string indata = sp.ReadExisting();
MessageBox.Show("Dati Ricevuti: " + indata);
if (indata.CompareTo("K") == 0)
{
timerColor.Enabled = true;
btnEsito.BackColor = Color.Green;
btnEsito.Text = "GOOD";
// Do something
}
if (indata.CompareTo("O") == 0)
{
timerColor.Enabled = true;
btnEsito.BackColor = Color.Red;
btnEsito.Text = "NO GOOD";
}
}
and here is the method to stop the timer:
private void timerColor_Tick(object sender, EventArgs e)
{
MessageBox.Show("HERE!");
timerColor.Enabled = false;
btnEsito.BackColor = Color.White;
}
BackColor of btnEsito becomes Green or Red based on the type of signal I receive but the program doesn't show the message "HERE!", and the button doesn't come back White.
Could anyone help me?
The System.Windows.Forms.Timer is designed for single threaded use & needs to be started & stopped on the UI thread. You are trying to start the timer on a separate thread, so you need to ensure modifying the timer Enabled property is done on the UI thread. You could do this by creating a delegate method & invoking that from your event handlers - possibly something like this :
delegate void TimerDelegate(bool Enable);
private void ControlTimer(bool Enable)
{
timerColor.Enabled = Enable;
}
private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
{
SerialPort sp = (SerialPort)sender;
string indata = sp.ReadExisting();
MessageBox.Show("Dati Ricevuti: " + indata);
if (indata.CompareTo("K") == 0)
{
Invoke((TimerDelegate)ControlTimer, true);
btnEsito.BackColor = Color.Green;
btnEsito.Text = "GOOD";
// Do something
}
if (indata.CompareTo("O") == 0)
{
Invoke((TimerDelegate)ControlTimer, true);
btnEsito.BackColor = Color.Red;
btnEsito.Text = "NO GOOD";
}
}
private void timerColor_Tick(object sender, EventArgs e)
{
MessageBox.Show("HERE!");
Invoke((TimerDelegate)ControlTimer, false);
btnEsito.BackColor = Color.White;
}
I am receiving data on serial port 250 packets per second where each packet is of size 23 bytes. I am using following code handling the data received on serial port.
private SerialPort connectComPort = new SerialPort();
List<byte> receivedBytes1 = new List<byte>();
public Form1()
{
InitializeComponent();
connectComPort.DataReceived += new SerialDataReceivedEventHandler(receiveData);
//Background worker for parsing packet
m_oWorker = new BackgroundWorker();
m_oWorker.DoWork += new DoWorkEventHandler(m_oWorker_DoWork);
m_oWorker.ProgressChanged += new ProgressChangedEventHandler(m_oWorker_ProgressChanged);
m_oWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(m_oWorker_RunWorkerCompleted);
m_oWorker.WorkerReportsProgress = true;
m_oWorker.WorkerSupportsCancellation = true;
}
private void buttton_Click(object sender, EventArgs e)
{
m_oWorker.RunWorkerAsync();
}
void m_oWorker_DoWork(object sender, DoWorkEventArgs e)
{
modprocessReceivedBuffer();
m_oWorker.ReportProgress(100);
}
private void receiveData(object sender, SerialDataReceivedEventArgs e)
{
while (connectComPort.BytesToRead > 0)
receivedBytes1.Add((byte)connectComPort.ReadByte());
}
private void modprocessReceivedBuffer()
{
while (1 == 1)
{
if (receivedBytes1.Count() != 0)
{
var tiff = receivedBytes1.GetRange(0, (int)receivedBytes1[4]).ToList<byte>();
receivedBytes1.RemoveRange(0, (int)receivedBytes1[4]);
modifiedProcess(tiff);
}
else
{
Thread.Sleep(100);
}
}
}
Thus I am just queuing the data received on serial port in a list and I am running a process on background thread whose job is to parse packet. My question is their any better method than this to handle such large data. Currently its 250 packets/sec but this rate can be increased to 16000 packets/sec.
This (VB converted to C#) is what I would do. This will eliminate the issue you were going to eventually have with two threads accessing the list. I also changed the code to read all the bytes at once. There are comments in the code that point at areas to be addressed.
System.Threading.AutoResetEvent dataRcvd = new System.Threading.AutoResetEvent(false);
private void receiveData(object sender, SerialDataReceivedEventArgs e)
{
dataRcvd.Set();
}
private void modprocessReceivedBuffer()
{
while (1 == 1) {
dataRcvd.WaitOne();
while (connectComPort.BytesToRead > 0) {
byte[] buf = new byte[connectComPort.BytesToRead];
int bytsRead = connectComPort.Read(buf, 0, buf.Length);
if (buf.Length != bytsRead) {
Array.Resize(ref buf, bytsRead);
}
//what if there is more than one message in receivedBytes1
if (receivedBytes1.Count() != 0) {
//I think a check is needed for enoung bytes in receivedBytes1????????
var tiff = receivedBytes1.GetRange(0, Convert.ToInt32(receivedBytes1(4))).ToList<byte>();
receivedBytes1.RemoveRange(0, Convert.ToInt32(receivedBytes1(4)));
modifiedProcess(tiff);
}
}
}
}