室内系的上位机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&nbsp;:&nbsp;Form

{

private&nbsp;StringBuilder&nbsp;sb&nbsp;=&nbsp;new&nbsp;StringBuilder();&nbsp;&nbsp;&nbsp;&nbsp;//为了避免在接收处理函数中反复调用,依然声明为一个全局变量

……</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&nbsp;serialPort1_DataReceived(object&nbsp;sender,&nbsp;System.IO.Ports.SerialDataReceivedEventArgs&nbsp;e)

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{

int&nbsp;num&nbsp;=&nbsp;serialPort1.BytesToRead;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//获取接收缓冲区中的字节数

byte[]&nbsp;received_buf&nbsp;=&nbsp;newbyte[num];&nbsp;&nbsp;&nbsp;&nbsp;//声明一个大小为num的字节数据用于存放读出的byte型数据

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;serialPort1.Read(received_buf,&nbsp;0,&nbsp;num);&nbsp;&nbsp;&nbsp;//读取接收缓冲区中num个字节到byte数组中

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sb.Clear();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//防止出错,首先清空字符串构造器

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sb.Append(Encoding.ASCII.GetString(received_buf));&nbsp;&nbsp;//将整个数组解码为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">&nbsp;</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">理解了用方法来对字符串构造类型进行处理之后,就可以对串口文本进行处理,本例中单片机向电脑发送的是&quot;t=12.34-h=56.78&quot;,意为温度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">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;label4.Text&nbsp;=&nbsp;sb.ToString().Split(&#39;=&#39;)[1].Split(&#39;-&#39;)[0];

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;label7.Text&nbsp;=&nbsp;sb.ToString().Split(&#39;=&#39;)[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">即实现将两个标签的文本改为对应数字功能。以提取温度值为例,将接收到的&quot;t=12.34-h=56.78&quot;转化为字符串后,根据=分割,取分割出的第二段(从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">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;timex.Enqueue(DateTime.Now.ToLongTimeString());

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;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">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for&nbsp;(int&nbsp;i&nbsp;=&nbsp;0;&nbsp;i&nbsp;&lt;&nbsp;10&nbsp;;i++)

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;timex.Enqueue(&quot;0&quot;);

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for&nbsp;(int&nbsp;i&nbsp;=&nbsp;0;&nbsp;i&nbsp;&lt;&nbsp;10;&nbsp;i++)

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;temy.Enqueue(&quot;0&quot;);

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for&nbsp;(int&nbsp;i&nbsp;=&nbsp;0;&nbsp;i&nbsp;&lt;&nbsp;10;&nbsp;i++)

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;humy.Enqueue(&quot;0&quot;);

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</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>&nbsp;</p><p><span style="color:black; font-family:新宋体; font-size:9pt"></span></p><pre class="prism-highlight prism-language-csharp">Object[]&nbsp;stime;

Object[]&nbsp;stem;

Object[]&nbsp;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>&nbsp;</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>&nbsp;</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[&quot;温度&quot;].Points.Clear();//清空温度Series中所有点

chart1.Series[&quot;湿度&quot;].Points.Clear();//清空湿度Series中所有点

&nbsp;

stime&nbsp;=&nbsp;timex.ToArray();//将时间队列复制到数组中

stem&nbsp;=&nbsp;temy.ToArray();//将温度队列复制到数组中

shum&nbsp;=&nbsp;humy.ToArray();//将湿度队列复制到数组中

for&nbsp;(int&nbsp;j&nbsp;=&nbsp;0;&nbsp;j&nbsp;&lt;&nbsp;10;&nbsp;j++)

{

&nbsp;&nbsp;&nbsp;&nbsp;chart1.Series[&quot;温度&quot;].Points.AddXY(stime[j],&nbsp;stem[j]);//给温度Series的点赋值

&nbsp;&nbsp;&nbsp;&nbsp;chart1.Series[&quot;湿度&quot;].Points.AddXY(stime[j],&nbsp;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&nbsp;=&nbsp;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.