1.功能描述
接收串口数据,并将收到的十六进制数据用坐标系的方式将数据波形展示出来
2.代码部分
步骤1:定义链表,用于数据保存
//数据结构-线性链表
private List<byte> DataList = new List<byte>();
步骤2:定义波形颜色
//定义波形颜色
private Pen LinesPen = new Pen(Color.FromArgb(0xFF,0x00,0x00));//FF 00 00 为红色
步骤3:绘制接收数据的波形
//如果数据量大于可容纳的数据量,则删除最左数据
if (DataList.Count >= (this.ClientRectangle.Width - StartPrint) / DrawStep)
{
DataList.RemoveRange(0, DataList.Count - (this.ClientRectangle.Width - StartPrint) / DrawStep);
}
//绘制波形
for(int i = 0; i < DataList.Count - 1; i++)
{
//点与点之间做直线连接
e.Graphics.DrawLine(LinesPen, StartPrint + i * DrawStep, StartPrint + Unit_length * 16 - DataList[i] * (Unit_length / 16), StartPrint + (i + 1) * DrawStep, StartPrint + Unit_length * 16 - DataList[i + 1] * (Unit_length / 16));
}
步骤4:定义数据添加函数
//定义链表尾部添加数据
public void AddDataToWaveList(byte[] Data)
{
for (int i = 0; i < Data.Length; i++)
DataList.Add(Data[i]);
this.Invalidate();//刷新显示
}
步骤5:串口接收函数中添加代码
//更新波形显示窗体的链表数据
if(WaveForm != null)
{
WaveForm.AddDataToWaveList(data);
}
完整接收函数
//串口接收事件
private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
//接收格式为ASCII码
if (!checkBox16.Checked)//复用框没有被选择时
{
try
{
textBox1.AppendText("[" + DateTime.Now.ToString("HH:mm:ss") + "]" + "->");
string str = serialPort1.ReadExisting();//将接收到的数据存在自定义的字符串变量中
textBox1.AppendText(str + "\r\n");
//统计接收字节数
UInt32 RBytes = Convert.ToUInt32(textBox7.Text, 10);//定义接收字节数变量,并初始化为已接收字节数
RBytes += (UInt32)str.Length;//加ASCII码字节数
textBox7.Text = Convert.ToString(RBytes, 10);//显示总接收字节数
}
catch
{
textBox1.AppendText("[" + DateTime.Now.ToString("HH:mm:ss") + "]" + "->");
textBox1.AppendText("ASCII格式接收错误!\r\n");
}
}
//接收格式为HEX
else
{
try
{
//断帧功能
if (Timer4_Flag == true)
{
Timer4_Flag = false;
textBox1.AppendText("\r\n");
textBox1.AppendText("[" + DateTime.Now.ToString("HH:mm:ss") + "]" + "->");
}
//textBox1.AppendText("[" + DateTime.Now.ToString("HH:mm:ss") + "]" + "->"); //此处被断帧功能替代换行
byte[] data = new byte[serialPort1.BytesToRead];//定义接收缓冲区,长度为串口接收的数据长度
serialPort1.Read(data, 0, data.Length);//形参,起始位置,终止位置,将读取的数据存放在缓冲区
//遍历用法
foreach (byte Member in data)//循环函数
{
string str = Convert.ToString(Member, 16).ToUpper();//转化为十六进制大写
textBox1.AppendText((str.Length == 1 ? "0" + str : str) + " ");//数据+空格“”
}
//textBox1.AppendText("\r\n"); //此处被断帧功能替代换行
//统计接收字节数
UInt32 RBytes = Convert.ToUInt32(textBox7.Text, 10);//定义接收字节数变量,并初始化为已接收字节数
RBytes += (UInt32)data.Length;//加HEX字节数
textBox7.Text = Convert.ToString(RBytes, 10);//显示总接收字节数
//更新波形显示窗体的链表数据
if(WaveForm != null)
{
WaveForm.AddDataToWaveList(data);
}
}
catch
{
textBox1.AppendText("[" + DateTime.Now.ToString("HH:mm:ss") + "]" + "->");
textBox1.AppendText("HEX格式接收错误!\r\n");
}
}
}
步骤6:接收HEX数据波形显示时,子窗体出现抖动现象原因:缓存不足,启动双缓存显示,缓存1用于窗体显示,缓存2用于波形更新
public WaveForm()
{
//波形稳定刷新
//开启双缓冲
this.SetStyle(ControlStyles.DoubleBuffer |
ControlStyles.UserPaint |
ControlStyles.AllPaintingInWmPaint,
true);
this.UpdateStyles();
InitializeComponent();
}
#加入此代码后波形显示不再抖动
步骤7:打开波形显示时同步打开串口及HEX勾选
方便再重复两次操作
这里参考了博客CheckBox控件的用法
//打开波形显示按钮时同步打开串口
if (!serialPort1.IsOpen)
{
button2.PerformClick();//打开串口按钮单击事件
this.checkBox16.Checked = true; //打开HEX勾选
}
3.完整代码
//主窗体程序
//串口接收事件
private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
//接收格式为ASCII码
if (!checkBox16.Checked)//复用框没有被选择时
{
try
{
textBox1.AppendText("[" + DateTime.Now.ToString("HH:mm:ss") + "]" + "->");
string str = serialPort1.ReadExisting();//将接收到的数据存在自定义的字符串变量中
textBox1.AppendText(str + "\r\n");
//统计接收字节数
UInt32 RBytes = Convert.ToUInt32(textBox7.Text, 10);//定义接收字节数变量,并初始化为已接收字节数
RBytes += (UInt32)str.Length;//加ASCII码字节数
textBox7.Text = Convert.ToString(RBytes, 10);//显示总接收字节数
}
catch
{
textBox1.AppendText("[" + DateTime.Now.ToString("HH:mm:ss") + "]" + "->");
textBox1.AppendText("ASCII格式接收错误!\r\n");
}
}
//接收格式为HEX
else
{
try
{
//断帧功能
if (Timer4_Flag == true)
{
Timer4_Flag = false;
textBox1.AppendText("\r\n");
textBox1.AppendText("[" + DateTime.Now.ToString("HH:mm:ss") + "]" + "->");
}
//textBox1.AppendText("[" + DateTime.Now.ToString("HH:mm:ss") + "]" + "->"); //此处被断帧功能替代换行
byte[] data = new byte[serialPort1.BytesToRead];//定义接收缓冲区,长度为串口接收的数据长度
serialPort1.Read(data, 0, data.Length);//形参,起始位置,终止位置,将读取的数据存放在缓冲区
//遍历用法
foreach (byte Member in data)//循环函数
{
string str = Convert.ToString(Member, 16).ToUpper();//转化为十六进制大写
textBox1.AppendText((str.Length == 1 ? "0" + str : str) + " ");//数据+空格“”
}
//textBox1.AppendText("\r\n"); //此处被断帧功能替代换行
//统计接收字节数
UInt32 RBytes = Convert.ToUInt32(textBox7.Text, 10);//定义接收字节数变量,并初始化为已接收字节数
RBytes += (UInt32)data.Length;//加HEX字节数
textBox7.Text = Convert.ToString(RBytes, 10);//显示总接收字节数
//更新波形显示窗体的链表数据
if(WaveForm != null)
{
WaveForm.AddDataToWaveList(data);
}
}
catch
{
textBox1.AppendText("[" + DateTime.Now.ToString("HH:mm:ss") + "]" + "->");
textBox1.AppendText("HEX格式接收错误!\r\n");
}
}
}
//波形显示按钮事件
private void button30_Click(object sender, EventArgs e)
{
//第一次创建WaveForm实体
if (WaveForm == null)
{
//创建新窗体
WaveForm = new WaveForm();
}
else
{
//多次创建通过判断IsDisposed确定窗口是否已经关闭,避免同窗口多开
if (WaveForm.IsDisposed == true)//判断该控件有无释放,若释放则重新创建窗体
{
//如果窗体已经关闭,需要重新创新
WaveForm = new WaveForm();
}
}
//新建窗体
//WaveForm = new WaveForm();//发现不用if语句判断直接创建窗体也能实现一样的功能
//窗体展示
WaveForm.Show();
//设置波形窗体紧靠主窗体
this.Left = 0;//主窗体左边的坐标为0
WaveForm.Location = this.Location;//主窗体坐标赋给子窗体
WaveForm.Left = this.Right;//主窗体的右显示坐标赋给子窗体左显示坐标
//打开波形显示按钮时同步打开串口
if (!serialPort1.IsOpen)
{
button2.PerformClick();//打开串口按钮单击事件
this.checkBox16.Checked = true; //打开HEX勾选
}
//子窗体程序
namespace 上位机初始界面
{
public partial class WaveForm : Form
{
//点坐标偏移量
private const int StartPrint = 40;
//单位格大小
private const int Unit_length = 32;
//默认绘制单位
//脚距越大容纳数据越小,脚距越小容纳数据越大
private int DrawStep = 8;//每一单元格的成分格子长度为8,影响横轴数字宽度,此处参数设置是4倍
//绘制单位最大值
private const int MaxStep = 32;
//绘制单位最小值
private const int MinStep = 1;
//定义轴线颜色
private Pen TablePen = new Pen(Color.FromArgb(0x00,0x00,0x00));//00 00 00 为黑色
//用于波形显示的变量声明
//数据结构-线性链表
private List<byte> DataList = new List<byte>();
//定义波形颜色
private Pen LinesPen = new Pen(Color.FromArgb(0xFF,0x00,0x00));//FF 00 00 为红色
public WaveForm()
{
//波形稳定刷新
//开启双缓冲
this.SetStyle(ControlStyles.DoubleBuffer |
ControlStyles.UserPaint |
ControlStyles.AllPaintingInWmPaint,
true);
this.UpdateStyles();
InitializeComponent();
}
//加载子窗体事件
private void WaveForm_Load(object sender, EventArgs e)
{
//重新设定波形显示窗体尺寸
int Width = Screen.GetWorkingArea(this).Width - Form1.MainForm.Width;//显示宽度为屏幕尺寸-主窗体尺寸
int Heigth = this.Height - this.ClientRectangle.Height;//显示整个高度 - 工作区矩形 = 只剩下菜单栏
Heigth += Unit_length * 16;//32*16
Heigth += StartPrint * 2;
this.Size = new Size(Width, Heigth);
}
//定义链表尾部添加数据-数据从串口输入
public void AddDataToWaveList(byte[] Data)
{
for (int i = 0; i < Data.Length; i++)
DataList.Add(Data[i]);
this.Invalidate();//刷新窗体显示
}
//子窗体绘制事件
private void WaveForm_Paint(object sender, PaintEventArgs e)
{
String Str = "";
System.Drawing.Drawing2D.GraphicsPath gp = new System.Drawing.Drawing2D.GraphicsPath();
//绘制横轴线
for(int i = 0; i < (this.ClientRectangle.Height - StartPrint) / Unit_length; i++)
{
//画横线-----画笔颜色,起始坐标,终点坐标
e.Graphics.DrawLine(TablePen, StartPrint, StartPrint + i * Unit_length, this.ClientRectangle.Width, StartPrint + i * Unit_length);
//绘制纵坐标
Str = ((16 - i) * 16).ToString("X");
Str = "0x" + (Str.Length == 1 ? Str + "0" : Str);
if(i == 0)
Str = "0xFF";
//添加文字
gp.AddString(Str, this.Font.FontFamily, (int)FontStyle.Regular, 13, new RectangleF(0, StartPrint + i * Unit_length - 8, 400, 50), null);
}
//绘制纵轴线
for(int i = 0; i <=(this.ClientRectangle.Width - StartPrint) / Unit_length; i++)
{
//画纵线
e.Graphics.DrawLine(TablePen, StartPrint + i * Unit_length, StartPrint, StartPrint + i * Unit_length, StartPrint + Unit_length * 16);
//绘制横坐标
gp.AddString((i * (Unit_length / DrawStep)).ToString(), this.Font.FontFamily,(int)FontStyle.Regular,13,new RectangleF(StartPrint + i * Unit_length - 7,this.ClientRectangle.Height - StartPrint + 4, 400, 50),null);
}
//绘制文字
e.Graphics.DrawPath(Pens.Black, gp);
//如果数据量大于可容纳的数据量,则删除最左数据
if (DataList.Count >= (this.ClientRectangle.Width - StartPrint) / DrawStep)
{
DataList.RemoveRange(0, DataList.Count - (this.ClientRectangle.Width - StartPrint) / DrawStep);
}
//绘制波形
for(int i = 0; i < DataList.Count - 1; i++)
{
//点与点之间做直线连接
e.Graphics.DrawLine(LinesPen, StartPrint + i * DrawStep, StartPrint + Unit_length * 16 - DataList[i] * (Unit_length / 16), StartPrint + (i + 1) * DrawStep, StartPrint + Unit_length * 16 - DataList[i + 1] * (Unit_length / 16));
}
}
}
}
4.测试结果
接收下位机十六进制数据并显示在子窗体的坐标系中,数据波形显示
参考自B站硬件家园