做项目的时候需要检测发送给步进马达的脉冲数和编码器实际接收到的脉冲数的差值,以用于判断步进马达是否失步。为了能方便调用log以及实时监控脉冲计数的变化,因此写了这个软件。测试下来还是很实用的。下位机的数据发送是不定位数整数,每发送一个会有一个换行,体现在c#里就是\r\n
源码可以在这里下载
c#串口通讯log存储及实时波形绘制源代码
废话不多说,直接讲部分重点
//查询主机上存在的串口
comboBox_Port.Items.AddRange(SerialPort.GetPortNames());
if (comboBox_Port.Items.Count > 0)
{
comboBox_Port.SelectedIndex = 0;
}
else
{
comboBox_Port.Text = "未检测到串口";
}
//向ComDevice.DataReceived(是一个事件)注册一个方法Com_DataReceived,当端口类接收到信息时时会自动调用Com_DataReceived方法
ComDevice.DataReceived += new SerialDataReceivedEventHandler(Com_DataReceived);
private void Com_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
//开辟接收缓冲区,这个读取按字节读取,长度是不定的
byte[] Buffer = new byte[ComDevice.BytesToRead];
//从串口读取数据
ComDevice.Read(Buffer, 0, Buffer.Length);
//实现数据的解码与显示
AddData(Buffer);
}
public void AddData(byte[] data)
{
if (radioButton_Hex.Checked)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < data.Length; i++)
{
sb.AppendFormat("{0:x2}" + " ", data[i]);
}
AddContent(sb.ToString().ToUpper());
}
else if (radioButton_ASCII.Checked)
{
AddContent(new ASCIIEncoding().GetString(data));
}
else if (radioButton_UTF8.Checked)
{
AddContent(new UTF8Encoding().GetString(data));
}
else if (radioButton_Unicode.Checked)
{
AddContent(new UnicodeEncoding().GetString(data));
}
else
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < data.Length; i++)
{
sb.AppendFormat("{0:x2}" + " ", data[i]);
}
AddContent(sb.ToString().ToUpper());
}
}
private void AddContent(string content)
{
BeginInvoke(new MethodInvoker(delegate
{
textBox_Receive.AppendText(content);
}));
}
///
/// 将消息编码并发送
///
///
///
private void button_Send_Click_1(object sender, EventArgs e)
{
if (textBox_Receive.Text.Length > 0)
{
textBox_Receive.AppendText("\n");
}
byte[] sendData = null;
if (radioButton_Hex.Checked)
{
sendData = strToHexByte(textBox_Send.Text.Trim());
}
else if (radioButton_ASCII.Checked)
{
sendData = Encoding.ASCII.GetBytes(textBox_Send.Text.Trim());
}
else if (radioButton_UTF8.Checked)
{
sendData = Encoding.UTF8.GetBytes(textBox_Send.Text.Trim());
}
else if (radioButton_Unicode.Checked)
{
sendData = Encoding.Unicode.GetBytes(textBox_Send.Text.Trim());
}
else
{
sendData = strToHexByte(textBox_Send.Text.Trim());
}
SendData(sendData);
}
///
/// 此函数将编码后的消息传递给串口
///
///
///
public bool SendData(byte[] data)
{
if (ComDevice.IsOpen)
{
try
{
//将消息传递给串口
ComDevice.Write(data, 0, data.Length);
return true;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "发送失败", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
else
{
MessageBox.Show("串口未开启", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
return false;
}
log的存储比较简单,注意一下每次向文件中写完数据后,记得关闭数据流。
private void AddContent(string content)
{
BeginInvoke(new MethodInvoker(delegate
{
textBox_Receive.AppendText(content);
//接受数据并将数据存储到log文件中
if (File.Exists(wetlog))
{
StreamWriter sw = new StreamWriter(wetlog, true);
sw.Write(content);
sw.Close();
}
else
{
File.Create(wetlog).Dispose();
StreamWriter sw = new StreamWriter(wetlog, true);
sw.Write(content);
sw.Close();
}
fileSystemWatcher_Changed();
}));
}
由于数据的长度不定,之前考虑写正则表达式来将数据解析出来,但是发现太难写了,就放弃了。。。
所以采用的方法是先将数据存储到log文件中,然后每次访问log文件取出最后一行的数据,添加到绘图队列中。
图表的初始化就不多说了,网上一搜一大把。这里将数据曲线名称改为com口,如:
this.chart1.Series.Clear();
Series series1 = new Series(comboBox_Port.Text);
series1.ChartArea = "C1";
this.chart1.Series.Add(series1);
还有上下限的直线和折线样式,颜色等的设置可百度
这个函数在每次log存储完后进行调用,每次从log中选择最新的数据,这里的arrayStr.Length - 2是因为最后一行是空行,所以倒数第二行才是最新的数据。
String wetlog = "D:/VS_Files/WindowsFormsApplication1/WindowsFormsApplication1/bin/arduinoLog/wetrobotLog.txt";
private void fileSystemWatcher_Changed()
{
string path = @"D:/VS_Files/WindowsFormsApplication1/WindowsFormsApplication1/bin/arduinoLog/wetrobotLog.txt";
StreamReader sr = new StreamReader(path);
string str = sr.ReadToEnd();
string[] arrayStr = Regex.Split(str, "\r\n");
string Mess = arrayStr[arrayStr.Length - 2];
double p = double.Parse(Mess);
label7.Text = p.ToString();
UpdateQueueValue(p);
sr.Close();
return;
}
首先建立一个大小为100的double类型的队列
private Queue<double> dataQueue = new Queue<double>(100);
private int num = 5;
数据更新,这里有一个bug暂时没解决,即我每次在log存储后进行数据抓取和更新队列,但是发现有的数据偶尔会重复一次写入到队列中,经过多次Debug没解决掉。因此便将队列中相邻重复数据进行跳过一个进行绘制。
private void UpdateQueueValue(double p)
{
if (dataQueue.Count > 100)
{
for (int i = 0; i < num; i++)
{
dataQueue.Dequeue();
}
}
dataQueue.Enqueue(p);
this.chart1.Series[0].Points.Clear();
for (int i = 0; i < dataQueue.Count-1; i++)
{
if (dataQueue.Count>=2&&dataQueue.ElementAt(i)==dataQueue.ElementAt(i+1))
{
i++;
}
this.chart1.Series[0].Points.AddXY((i+1), dataQueue.ElementAt(i));
}
}
以上是所有主要代码,虽然还有一点不足,但是工具还是很实用的。希望以后多积累,能写出更好工具软件。
非常欢迎大神提出宝贵的改进建议