室内系的上位机Maker(二)
<p>在上篇博文中我们完成了串口接收上位机软件的基本开发,但嵌入式系统给上位机发送的数据格式一般是固定的,有时还可以根据接收到的历史数据查看趋势,这时我们就需要对单片机发来的串口信息进行分割,然后使用chart控件进行绘图展示。</p><h2>文本分割</h2><p>之前我们采用</p><pre class="prism-highlight prism-language-csharp">textBox_receive.AppendText(serialPort1.ReadExisting());</pre><p><span style="color:#383a42; font-family:Consolas"><span style="font-size:10pt; background-color:#fafafa"></span><span style="background-color:#fafafa"></span></span><br/></p><p><span style="color:#383a42"><span style="font-family:宋体; background-color:#fafafa">这一行来给消息接收区文本框一行一行的添加文字,但并不便于我们对接收到的文本进行处理和计数,所以这里运用了字符串构造类型</span><span style="font-family:Consolas; background-color:#fafafa">StringBuilder</span><span style="font-family:宋体; background-color:#fafafa">来创建变量,用作存储接收到的文本。因此,需要在以下位置增加代码</span><span style="font-family:Consolas; background-color:#fafafa"></span></span></p><p><span style="color:blue; font-family:新宋体; font-size:9pt"></span></p><pre class="prism-highlight prism-language-csharp">publicpartialclassForm1 : Form
{
private StringBuilder sb = new StringBuilder(); //为了避免在接收处理函数中反复调用,依然声明为一个全局变量
……</pre><p><span style="color:green; font-size:9pt"><span style="font-family:Calibri"></span><span style="font-family:新宋体"></span></span><br/></p><p><span style="color:#383a42"><span style="font-family:宋体"><span style="font-size:10pt; background-color:#fafafa">这样就</span></span></span><span style="color:#383a42"><span style="font-family: 宋体; font-size: 10pt; background-color: #FAFAFA;">声明好了一个叫sb的StringBuilder类变量。</span></span></p><p><span style="color:#383a42"><span style="font-family: 宋体; font-size: 10pt; background-color: #FAFAFA;">存储文本的容器建好了,还需要将串口缓冲区中的数据放进去,因此串口数据接收事件的写法会有很大不同,代码如下:</span></span></p><pre class="prism-highlight prism-language-csharp">privatevoid serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
int num = serialPort1.BytesToRead; //获取接收缓冲区中的字节数
byte[] received_buf = newbyte[num]; //声明一个大小为num的字节数据用于存放读出的byte型数据
serialPort1.Read(received_buf, 0, num); //读取接收缓冲区中num个字节到byte数组中
sb.Clear(); //防止出错,首先清空字符串构造器
sb.Append(Encoding.ASCII.GetString(received_buf)); //将整个数组解码为ASCII数组</pre><p><span style="color:black; font-family:新宋体; font-size:9pt">这时候需要将文本增加到文本框中使用的代码变为:</span></p><pre class="prism-highlight prism-language-csharp">textBox_receive.AppendText(sb.ToString());</pre><p><span style="color:black; font-family:新宋体; font-size:9pt"></span><br/></p><p><span style="font-size:9pt"><span style="color:black; font-family:新宋体">这里注意到sb使用了ToString方法,可以了解StringBuilder类提供了诸多方法例如Append,Remove</span><span style="font-family:Calibri"> </span><span style="font-family:新宋体">等,具体可以阅读<a href="https://www.cnblogs.com/tonysuen/archive/2010/03/04/1678447.html">https://www.cnblogs.com/tonysuen/archive/2010/03/04/1678447.html</a></span></span></p><p><span style="color:black; font-family:新宋体; font-size:9pt">理解了用方法来对字符串构造类型进行处理之后,就可以对串口文本进行处理,本例中单片机向电脑发送的是"t=12.34-h=56.78",意为温度12.34°,湿度56.78%使用代码如下</span></p><p><span style="color:black; font-family:新宋体; font-size:9pt"> </span></p><pre class="prism-highlight prism-language-csharp"> label4.Text = sb.ToString().Split('=')[1].Split('-')[0];
label7.Text = sb.ToString().Split('=')[2];</pre><p><span style="color:black; font-family:新宋体; font-size:9pt"><span style="color:#a31515"><span style="color:black"></span></span></span><br/></p><p><span style="color:black; font-family:新宋体; font-size:9pt">即实现将两个标签的文本改为对应数字功能。以提取温度值为例,将接收到的"t=12.34-h=56.78"转化为字符串后,根据=分割,取分割出的第二段(从0开始数),再用-分割取第一段。</span></p><p><span style="color:black; font-family:新宋体; font-size:9pt">Split的详细用法可以参阅vs提供的帮助或者以下链接</span></p><p><a href="https://www.cnblogs.com/atom-wangzh/p/8489135.html"><span style="font-family:新宋体; font-size:9pt">https://www.cnblogs.com/atom-wangzh/p/8489135.html</span></a><span style="color:black; font-family:新宋体; font-size:9pt"></span></p><p><span style="color:black; font-family:新宋体; font-size:9pt">最终实现了以下效果,且数字整数位小数位均可变化。</span></p><p><img src="{#ZC_BLOG_HOST#}zb_users/upload/2020/08/5f2ffa3b01d86911655226.png" alt=""/><span style="color:black; font-family:新宋体; font-size:9pt"></span></p><p><span style="color:black; font-family:新宋体; font-size:9pt">接下来讲解本例的折线图绘制,这里一定要参阅制作本例的另一篇笔记</span></p><p><a href="http://www.fcxl9876.xin/index.php/archives/91"><span style="font-family:新宋体; font-size:9pt">http://www.fcxl9876.xin/index.php/archives/91</span></a><span style="color:black; font-family:新宋体; font-size:9pt"></span></p><p><span style="color:black; font-family:新宋体; font-size:9pt">首先主要了解一下chart控件的两个集合属性:</span></p><p><span style="color:black; font-family:新宋体; font-size:9pt">ChartAreas:图表区集合,可以设置图表区域相关属性,如网格颜色,坐标轴名称等。</span></p><p><span style="color:black; font-family:新宋体; font-size:9pt">Series:图表序列集合,可以设置图表序列相关属性,如图表类型,坐标值显示类型,标记图案,图案颜色等。</span></p><p><span style="color:black; font-family:新宋体; font-size:9pt">最重要的是将Series属性中ChartType修改为Line,即折线图。</span></p><p><img src="{#ZC_BLOG_HOST#}zb_users/upload/2020/08/5f2ffa3b32eb7871770677.png" alt=""/><span style="color:black; font-family:新宋体; font-size:9pt"></span></p><p><span style="color:black; font-family:新宋体; font-size:9pt">本图中横竖坐标名称、折线名称、颜色等均可以自定义,其他属性自行查询文档或使用搜索引擎,这里不再一一介绍。</span></p><p><img src="{#ZC_BLOG_HOST#}zb_users/upload/2020/08/5f2ffa3b5caf0536416173.png" alt=""/><span style="color:black; font-family:新宋体; font-size:9pt"></span></p><p><span style="color:black; font-family:新宋体; font-size:9pt">由于数据是不断更新的,这里需要使用队列类(Queue),使用Enqueue方法可以向队列中添加一个数据,使用Dequeue方法可以删除一个最老的数据。因此更新时间队列的写法如下:</span></p><p><span style="color:black; font-family:新宋体; font-size:9pt"> </span></p><pre class="prism-highlight prism-language-csharp"> timex.Enqueue(DateTime.Now.ToLongTimeString());
timex.Dequeue();</pre><p><span style="color:black; font-family:新宋体; font-size:9pt"></span><br/></p><p><span style="color:black; font-family:新宋体; font-size:9pt">这样就准备好了绘图的数据源,但是图表还没准备好,因为我们需要固定让图中始终有10个点,也就是队列中始终有10个数据,所以在Form1_Load事件中加入以下代码:</span></p><p><span style="color:blue; font-family:新宋体; font-size:9pt"></span></p><pre class="prism-highlight prism-language-csharp"> for (int i = 0; i < 10 ;i++)
{
timex.Enqueue("0");
}
for (int i = 0; i < 10; i++)
{
temy.Enqueue("0");
}
for (int i = 0; i < 10; i++)
{
humy.Enqueue("0");
}</pre><p><span style="color:black; font-family:新宋体; font-size:9pt"></span><br/></p><p><span style="color:black; font-family:新宋体; font-size:9pt">这样就差最后一步,把代表数据的点打在折线图上了。以下引用小林文章中的解释。</span></p><p><span style="color:black; font-family:新宋体; font-size:9pt">由于Series中Points集合只能调用AddXY同时添加点的X值和Y值或添加Y值,无法单独添加X值,并且添加Y值后无法修改X值,所以只能同时给X值和Y值赋值。这里由于两个队列使用foreach循环无法实现,所以将队列暂时复制到新的数组中实现对折线图点的赋值。</span></p><p><span style="color:black; font-family:新宋体; font-size:9pt">先在队列定义处(Form1类中)定义数组:</span></p><p> </p><p><span style="color:black; font-family:新宋体; font-size:9pt"></span></p><pre class="prism-highlight prism-language-csharp">Object[] stime;
Object[] stem;
Object[] shum;</pre><p><span style="color:black; font-family:新宋体; font-size:9pt"></span><br/></p><p><span style="color:black; font-family:新宋体; font-size:9pt">之后在需要更新数据的地方(这里为invoke方式,也可使用计时器)执行如下操作:</span></p><p> </p><p><span style="color:black; font-family:新宋体; font-size:9pt">队列入队一个元素,然后出队一个元素完成数据更新;</span></p><p><span style="color:black; font-family:新宋体; font-size:9pt">清空Series中所有点;</span></p><p><span style="color:black; font-family:新宋体; font-size:9pt">将队列元素复制到新的数组中;</span></p><p><span style="color:black; font-family:新宋体; font-size:9pt">使用for循环给Series中的点赋值。</span></p><p><span style="color:black; font-family:新宋体; font-size:9pt">在这个样例中,代码如下:</span></p><p> </p><p><span style="color:black; font-family:新宋体; font-size:9pt"></span></p><pre class="prism-highlight prism-language-csharp">timex.Enqueue(DateTime.Now.ToLongTimeString());//获取系统时间入列
timex.Dequeue();//将队列最前的数据出列
temy.Enqueue(label4.Text);//获取温度入列
temy.Dequeue();//将队列最前的数据出列
humy.Enqueue(label7.Text);//获取湿度入列
humy.Dequeue();//将队列最前的数据出列
chart1.Series["温度"].Points.Clear();//清空温度Series中所有点
chart1.Series["湿度"].Points.Clear();//清空湿度Series中所有点
stime = timex.ToArray();//将时间队列复制到数组中
stem = temy.ToArray();//将温度队列复制到数组中
shum = humy.ToArray();//将湿度队列复制到数组中
for (int j = 0; j < 10; j++)
{
chart1.Series["温度"].Points.AddXY(stime[j], stem[j]);//给温度Series的点赋值
chart1.Series["湿度"].Points.AddXY(stime[j], shum[j]);//给湿度Series的点赋值
}</pre><p><span style="color:black; font-family:新宋体; font-size:9pt"></span><br/></p><p><span style="color:black; font-family:新宋体; font-size:9pt">这样就完成了动态刷新的折线图。</span></p><h3>发送功能</h3><p>本例中只需要串口接收功能,这里附带开发一个简单的发送功能。</p><p>首先拖出文本框和按钮控件,将文本框改为可多行显示,名字命名为textBox_send,将按钮的文本改为发送。</p><p><img src="{#ZC_BLOG_HOST#}zb_users/upload/2020/08/5f2ffa3b867fc094095984.png" alt=""/></p><p>我们不希望软件在串口未打开的情况下发送按钮可用,所以在窗体加载事件<span style="color:black; font-family:新宋体; font-size:9pt">Form1_Load和打开串口按钮事件中加入行</span></p><pre class="prism-highlight prism-language-csharp">button2.Enabled = false;</pre><p><span style="color:black; font-family:新宋体; font-size:9pt"><span style="color:blue"><span style="color:black"></span></span></span><br/></p><p><span style="color:black; font-family:新宋体; font-size:9pt">在关闭的串口被打开后,也需要使发送按钮可用,这一部分代码同理。</span></p><p><span style="color:black; font-family:新宋体; font-size:9pt">如图,串口关闭后发送按钮变为灰色。</span></p><p><img src="{#ZC_BLOG_HOST#}zb_users/upload/2020/08/5f2ffa3bb3223090177970.png" alt=""/></p><p>这一篇有点水,等把Qt学一学再来更新。</p>
Q.E.D.