C# winform解析串口数据
C# winform parsing serial port data
我是一个新手,使用复制粘贴试图从旧的 Avery-Weightronix 7820 体重秤获取数据。我通过 RJ-232 电缆将其连接起来,并成功地从秤上获得了响应。我按照这个教程 https://www.studentcompanion.co.za/creating-a-serial-port-interface-with-c/ 起床 运行.
一旦我开始使用它,我想简化它,基本上将我的计算机用作远程显示器。我对串行连接参数进行了硬编码,并设置了每秒发送一次重量请求的代码,这样我就可以在 winform 中显示它。
以后我打算扩展它,这样我就可以得到多个项目的总重量。我计划称量一件物品,将重量复制到 运行 总重量,将一件新物品放在秤上并重复。
我现在被卡住了,因为我无法始终如一地解析来自秤的 return 数据,并且因为循环锁定了 UI。
当我发送 W<CR>
请求当前重量时,示例响应是:
<LF>
0001.10lb<CR>
<LF>
00<CR><ETX>
其中 <ETX>
= 文本结束符(Ø3 十六进制),<LF>
= 换行符(ØA 十六进制),<CR>
= 回车 return 字符(ØD十六进制)。
秤的响应是固定长度的,但是当我进入调试模式时,在代码的前几个循环中没有收到响应。然后它会晚点到达。当我将它输出到富文本字段时这没问题,但是当我尝试提取子字符串时,如果没有数据,我会收到错误消息。
另外,UI 锁,我唯一能做的就是停止执行。通过阅读,我似乎应该实现线程,但我不确定如何重新组织我的代码来做到这一点。
对于如何解决这些问题的任何指示或建议,我将不胜感激。
这是我的代码:
using System;
using System.Drawing;
using System.Windows.Forms;
using System.IO.Ports;
namespace ScaleView
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void groupBox1_Enter(object sender, EventArgs e)
{
}
private void Form1_Load(object sender, EventArgs e)
{
//updatePorts(); //Call this function everytime the page load
//to update port names
CheckForIllegalCrossThreadCalls = false;
}
private SerialPort ComPort = new SerialPort(); //Initialise ComPort Variable as SerialPort
private void connect()
{
bool error = false;
ComPort.PortName = "COM3";
ComPort.BaudRate = int.Parse("9600"); //convert Text to Integer
ComPort.Parity = (Parity)Enum.Parse(typeof(Parity), "Even"); //convert Text to Parity
ComPort.DataBits = int.Parse("7"); //convert Text to Integer
ComPort.StopBits = (StopBits)Enum.Parse(typeof(StopBits), "1"); //convert Text to stop bits
try //always try to use this try and catch method to open your port.
//if there is an error your program will not display a message instead of freezing.
{
//Open Port
ComPort.Open();
ComPort.DataReceived += SerialPortDataReceived; //Check for received data. When there is data in the receive buffer,
//it will raise this event, we need to subscribe to it to know when there is data
//MessageBox.Show(this, "Connected", MessageBoxButtons.OK);
}
catch (UnauthorizedAccessException) { error = true; }
catch (System.IO.IOException) { error = true; }
catch (ArgumentException) { error = true; }
if (error) MessageBox.Show(this, "Could not open the COM port. Most likely it is already in use, has been removed, or is unavailable.", "COM Port unavailable", MessageBoxButtons.OK, MessageBoxIcon.Stop);
//if the port is open, Change the Connect button to disconnect, enable the send button.
//and disable the groupBox to prevent changing configuration of an open port.
if (ComPort.IsOpen)
{
btnConnect.Text = "Disconnect";
}
}
// Call this function to close the port.
private void disconnect()
{
ComPort.Close();
btnConnect.Text = "Connect";
}
//whenever the connect button is clicked, it will check if the port is already open, call the disconnect function.
// if the port is closed, call the connect function.
private void btnConnect_Click_1(object sender, EventArgs e)
{
if (ComPort.IsOpen)
{
disconnect();
}
else
{
connect();
rtxtDataArea.AppendText("Connected\n");
sendData();
}
}
private void btnClear_Click(object sender, EventArgs e)
{
//Clear the screen
rtxtDataArea.Clear();
}
// Function to send data to the serial port
private void sendData()
{
bool error = false;
while(ComPort.IsOpen) //if text mode is selected, send data as tex
{
try
{
// Convert string of hex digits (in this case representing W<CR>) to a byte array
string hextext = "57 0D";
byte[] data = HexStringToByteArray(hextext);
// Send the binary data out the port
ComPort.Write(data, 0, data.Length);
System.Threading.Thread.Sleep(3000);
rtxtDataArea.ForeColor = Color.Blue; //write Hex data in Blue
string response = ComPort.ReadLine();
int charfrom = 1;
int charto = 9;
//string weight = response.Substring(charfrom, charto - charfrom);
rtxtDataArea.AppendText(response + "TEST\n");
}
catch (FormatException) { error = true; }
// Inform the user if the hex string was not properly formatted
catch (ArgumentException) { error = true; }
if (error) MessageBox.Show(this, "Not properly formatted hex string: \n" + "example: E1 FF 1B", "Format Error", MessageBoxButtons.OK, MessageBoxIcon.Stop);
}
}
//Convert a string of hex digits (example: E1 FF 1B) to a byte array.
//The string containing the hex digits (with or without spaces)
//Returns an array of bytes. </returns>
private byte[] HexStringToByteArray(string s)
{
s = s.Replace(" ", "");
byte[] buffer = new byte[s.Length / 2];
for (int i = 0; i < s.Length; i += 2)
buffer[i / 2] = (byte)Convert.ToByte(s.Substring(i, 2), 16);
return buffer;
}
private void btnSend_Click(object sender, EventArgs e)
{
sendData();
}
//This event will be raised when the form is closing.
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (ComPort.IsOpen) ComPort.Close(); //close the port if open when exiting the application.
}
//Data recived from the serial port is coming from another thread context than the UI thread.
//Instead of reading the content directly in the SerialPortDataReceived, we need to use a delegate.
delegate void SetTextCallback(string text);
private void SetText(string text)
{
//invokeRequired required compares the thread ID of the calling thread to the thread of the creating thread.
// if these threads are different, it returns true
if (this.rtxtDataArea.InvokeRequired)
{
rtxtDataArea.ForeColor = Color.Green; //write text data in Green colour
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
else
{
this.rtxtDataArea.AppendText(text);
}
}
private void SerialPortDataReceived(object sender, SerialDataReceivedEventArgs e)
{
var serialPort = (SerialPort)sender;
var data = serialPort.ReadExisting();
SetText(data);
}
private void rtxtDataArea_TextChanged(object sender, EventArgs e)
{
}
}
}
删除try-catch
块,connect()
和sendData()
方法中的MessageBox.Show(...)
。 new Thread(() => {
块中的 try-catch
将捕获任何异常并将它们发送到 UI 线程。
删除CheckForIllegalCrossThreadCalls = false;
行,因为它绕过了built-in保护。 serialPort.ReadLine();
方法可能比 serialPort.ReadExisting();
更好,因为 ReadLine()
将等待整行数据。
这应该让您了解代码的外观:
Thread dataThread = null;
private void btnConnect_Click_1(object sender, EventArgs e) {
if (ComPort.IsOpen) {
disconnect();
}
else {
btnConnect.Enabled = false; // user has to wait for connection to succeed or fail. Prevents clicking the button twice.
Form form = this;
dataThread = new Thread(() => {
try {
connect();
// if you get here then connect succeeded
form.BeginInvoke((Action) delegate {
btnConnect.Enabled = true; // now becomes the Disconnect button
rtxtDataArea.AppendText("Connected\n");
});
// start sending and receiving data on the thread
sendData();
} catch (Exception ex) {
form.BeginInvoke((Action) delegate {
btnConnect.Enabled = true; // remains as the Connect button, user can try again
MessageBox.Show(form, ex.GetType().Name + ": " + ex.Message, "COM Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
});
}
});
dataThread.IsBackground = true;
dataThread.Start();
}
}
delegate void SetTextCallback(string text);
private SetTextCallback callSetText = new SetTextCallback(SetText); // don't create new delegates each time
private void SetText(string text) {
if (this.rtxtDataArea.InvokeRequired) {
rtxtDataArea.ForeColor = Color.Green; //write text data in Green colour
this.BeginInvoke(d, new object[] { text }); // use BeginInvoke instead
}
else {
this.rtxtDataArea.AppendText(text);
}
}
我是一个新手,使用复制粘贴试图从旧的 Avery-Weightronix 7820 体重秤获取数据。我通过 RJ-232 电缆将其连接起来,并成功地从秤上获得了响应。我按照这个教程 https://www.studentcompanion.co.za/creating-a-serial-port-interface-with-c/ 起床 运行.
一旦我开始使用它,我想简化它,基本上将我的计算机用作远程显示器。我对串行连接参数进行了硬编码,并设置了每秒发送一次重量请求的代码,这样我就可以在 winform 中显示它。
以后我打算扩展它,这样我就可以得到多个项目的总重量。我计划称量一件物品,将重量复制到 运行 总重量,将一件新物品放在秤上并重复。
我现在被卡住了,因为我无法始终如一地解析来自秤的 return 数据,并且因为循环锁定了 UI。
当我发送 W<CR>
请求当前重量时,示例响应是:
<LF>
0001.10lb<CR>
<LF>
00<CR><ETX>
其中 <ETX>
= 文本结束符(Ø3 十六进制),<LF>
= 换行符(ØA 十六进制),<CR>
= 回车 return 字符(ØD十六进制)。
秤的响应是固定长度的,但是当我进入调试模式时,在代码的前几个循环中没有收到响应。然后它会晚点到达。当我将它输出到富文本字段时这没问题,但是当我尝试提取子字符串时,如果没有数据,我会收到错误消息。
另外,UI 锁,我唯一能做的就是停止执行。通过阅读,我似乎应该实现线程,但我不确定如何重新组织我的代码来做到这一点。
对于如何解决这些问题的任何指示或建议,我将不胜感激。
这是我的代码:
using System;
using System.Drawing;
using System.Windows.Forms;
using System.IO.Ports;
namespace ScaleView
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void groupBox1_Enter(object sender, EventArgs e)
{
}
private void Form1_Load(object sender, EventArgs e)
{
//updatePorts(); //Call this function everytime the page load
//to update port names
CheckForIllegalCrossThreadCalls = false;
}
private SerialPort ComPort = new SerialPort(); //Initialise ComPort Variable as SerialPort
private void connect()
{
bool error = false;
ComPort.PortName = "COM3";
ComPort.BaudRate = int.Parse("9600"); //convert Text to Integer
ComPort.Parity = (Parity)Enum.Parse(typeof(Parity), "Even"); //convert Text to Parity
ComPort.DataBits = int.Parse("7"); //convert Text to Integer
ComPort.StopBits = (StopBits)Enum.Parse(typeof(StopBits), "1"); //convert Text to stop bits
try //always try to use this try and catch method to open your port.
//if there is an error your program will not display a message instead of freezing.
{
//Open Port
ComPort.Open();
ComPort.DataReceived += SerialPortDataReceived; //Check for received data. When there is data in the receive buffer,
//it will raise this event, we need to subscribe to it to know when there is data
//MessageBox.Show(this, "Connected", MessageBoxButtons.OK);
}
catch (UnauthorizedAccessException) { error = true; }
catch (System.IO.IOException) { error = true; }
catch (ArgumentException) { error = true; }
if (error) MessageBox.Show(this, "Could not open the COM port. Most likely it is already in use, has been removed, or is unavailable.", "COM Port unavailable", MessageBoxButtons.OK, MessageBoxIcon.Stop);
//if the port is open, Change the Connect button to disconnect, enable the send button.
//and disable the groupBox to prevent changing configuration of an open port.
if (ComPort.IsOpen)
{
btnConnect.Text = "Disconnect";
}
}
// Call this function to close the port.
private void disconnect()
{
ComPort.Close();
btnConnect.Text = "Connect";
}
//whenever the connect button is clicked, it will check if the port is already open, call the disconnect function.
// if the port is closed, call the connect function.
private void btnConnect_Click_1(object sender, EventArgs e)
{
if (ComPort.IsOpen)
{
disconnect();
}
else
{
connect();
rtxtDataArea.AppendText("Connected\n");
sendData();
}
}
private void btnClear_Click(object sender, EventArgs e)
{
//Clear the screen
rtxtDataArea.Clear();
}
// Function to send data to the serial port
private void sendData()
{
bool error = false;
while(ComPort.IsOpen) //if text mode is selected, send data as tex
{
try
{
// Convert string of hex digits (in this case representing W<CR>) to a byte array
string hextext = "57 0D";
byte[] data = HexStringToByteArray(hextext);
// Send the binary data out the port
ComPort.Write(data, 0, data.Length);
System.Threading.Thread.Sleep(3000);
rtxtDataArea.ForeColor = Color.Blue; //write Hex data in Blue
string response = ComPort.ReadLine();
int charfrom = 1;
int charto = 9;
//string weight = response.Substring(charfrom, charto - charfrom);
rtxtDataArea.AppendText(response + "TEST\n");
}
catch (FormatException) { error = true; }
// Inform the user if the hex string was not properly formatted
catch (ArgumentException) { error = true; }
if (error) MessageBox.Show(this, "Not properly formatted hex string: \n" + "example: E1 FF 1B", "Format Error", MessageBoxButtons.OK, MessageBoxIcon.Stop);
}
}
//Convert a string of hex digits (example: E1 FF 1B) to a byte array.
//The string containing the hex digits (with or without spaces)
//Returns an array of bytes. </returns>
private byte[] HexStringToByteArray(string s)
{
s = s.Replace(" ", "");
byte[] buffer = new byte[s.Length / 2];
for (int i = 0; i < s.Length; i += 2)
buffer[i / 2] = (byte)Convert.ToByte(s.Substring(i, 2), 16);
return buffer;
}
private void btnSend_Click(object sender, EventArgs e)
{
sendData();
}
//This event will be raised when the form is closing.
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
if (ComPort.IsOpen) ComPort.Close(); //close the port if open when exiting the application.
}
//Data recived from the serial port is coming from another thread context than the UI thread.
//Instead of reading the content directly in the SerialPortDataReceived, we need to use a delegate.
delegate void SetTextCallback(string text);
private void SetText(string text)
{
//invokeRequired required compares the thread ID of the calling thread to the thread of the creating thread.
// if these threads are different, it returns true
if (this.rtxtDataArea.InvokeRequired)
{
rtxtDataArea.ForeColor = Color.Green; //write text data in Green colour
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
else
{
this.rtxtDataArea.AppendText(text);
}
}
private void SerialPortDataReceived(object sender, SerialDataReceivedEventArgs e)
{
var serialPort = (SerialPort)sender;
var data = serialPort.ReadExisting();
SetText(data);
}
private void rtxtDataArea_TextChanged(object sender, EventArgs e)
{
}
}
}
删除try-catch
块,connect()
和sendData()
方法中的MessageBox.Show(...)
。 new Thread(() => {
块中的 try-catch
将捕获任何异常并将它们发送到 UI 线程。
删除CheckForIllegalCrossThreadCalls = false;
行,因为它绕过了built-in保护。 serialPort.ReadLine();
方法可能比 serialPort.ReadExisting();
更好,因为 ReadLine()
将等待整行数据。
这应该让您了解代码的外观:
Thread dataThread = null;
private void btnConnect_Click_1(object sender, EventArgs e) {
if (ComPort.IsOpen) {
disconnect();
}
else {
btnConnect.Enabled = false; // user has to wait for connection to succeed or fail. Prevents clicking the button twice.
Form form = this;
dataThread = new Thread(() => {
try {
connect();
// if you get here then connect succeeded
form.BeginInvoke((Action) delegate {
btnConnect.Enabled = true; // now becomes the Disconnect button
rtxtDataArea.AppendText("Connected\n");
});
// start sending and receiving data on the thread
sendData();
} catch (Exception ex) {
form.BeginInvoke((Action) delegate {
btnConnect.Enabled = true; // remains as the Connect button, user can try again
MessageBox.Show(form, ex.GetType().Name + ": " + ex.Message, "COM Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
});
}
});
dataThread.IsBackground = true;
dataThread.Start();
}
}
delegate void SetTextCallback(string text);
private SetTextCallback callSetText = new SetTextCallback(SetText); // don't create new delegates each time
private void SetText(string text) {
if (this.rtxtDataArea.InvokeRequired) {
rtxtDataArea.ForeColor = Color.Green; //write text data in Green colour
this.BeginInvoke(d, new object[] { text }); // use BeginInvoke instead
}
else {
this.rtxtDataArea.AppendText(text);
}
}