zoukankan      html  css  js  c++  java
  • [自娱自乐] 4、超声波测距模块DIY笔记(四)——终结篇·基于C#上位机软件开发

    前言

    上一节我们已经基本上把超声波硬件的发射和接收模块全部做好了,接下来我们着手开发一个软硬结合的基于C#的平面定位软件!


    目录

    一、整体思路

    二、效果提前展示

           2-1、软件部分展示

           2-2、硬件部分展示

    三、基于C#的客户端软件说明

      3-1、整体框架介绍:

      3-2、部分技术细节介绍

       3-2-1、串口操作

       3-2-2、JiSuan函数说明及核心算法介绍

    四、阶段小结

    五、相关链接 


    一、整体思路

    >_<" 如下图,利用我们上三节开发的超声波发射与接收设备构成一个:2固定接收头+1可移动超声波发射头的平面定位硬件设备。由上一节我们知道:在一个采样周期内硬件定位装置完成2次测距,即图中的x和y的长度。又因为我们双接收模块部分两个接收头的距离是固定的L,所以我们可以根据数学公式求出移动头的具体坐标,然后在PC上进行仿真。

    二、效果提前展示

    2-1、软件部分展示:

    >_<" 模式一:用来动态获取下位机测得x和y数据经过几何运算将硬件结构映射到FORM窗口中,实现动态仿真。这里:COM3处表示接收模块所连接的串口为COM3;右边的四个数据分别表示所测实时的x,y的长度,以及串口缓冲区的有效数据剩余q(用栈来实现),和所有采集到的有效点转换为FORM中坐标点的队列大小(用队列来实现)。

    >_<" 模式二:每次获得新的有效点时连接上一次的点和这一次的点形成活动端的移动路径展示效果。(这里由于从原始数据到FORM上坐标转换时放大了7倍,所以波动比较大,其实精度是在1cm左右波动的。

    2-2、硬件部分展示:

    >_<" 1个发送头(即可移动头)+2个接收头[在这节中要把两个接收头并排固定间距为14cm,即L的长度]

     

    >_<" 超声波发送模块集成电路+超声波接收模块集成电路

     

    >_<" 硬件整体图(注意这里再次强调接收头要并排固定相距14cm!嘿嘿,由于晚上写的,俺的手机又太渣,30万像素Java机,也称“学霸机”,就不能把最新的固定好的图发给大家看了!)

    三、基于C#的客户端软件说明

    3-1、整体框架介绍:

    >_<" 如下图:上位机程序构成很简单,初始化函数DrawHS()进行获得当前可用串口、实例化并挂起用于数据处理的线程、启动定时器用于周期性刷新屏幕。①当用户选中超声波接收模块所对应的串口并成功连接后,原始数据的到来会触发串口数据接收函数执行来接收数据,这里接收来的数据保存在Q的栈里;②然后JiSuan线程会提取栈内的最新数据进行处理,并把得出的有效转化为FORM平面的点放入P的点集队列里;③定时器的定时刷新周期到来会根据上面的计算来重绘当前界面。这里模式一和模式二按钮决定采用Draw1()函数还是Draw2()函数进行绘图。

    3-2、部分技术细节介绍

    3-2-1、串口操作

    >_<" 首先,你得在FORM中加入一个ComboBox,命名为PortList,即:下拉COM口选择框。

    >_<" 其次,你得有个SerialPort控件,命名为serialPort1,即:C#提供的串口对象。

    >_<" 那么,在初始化函数中写入如下代码获得当前可用串口并加入ComboBox中,注意最后一句:表示如果有可用串口,默认选择第一个。

     1 //Get all port list for selection
     2 //获得所有的端口列表,并显示在列表内
     3 PortList.Items.Clear();
     4 string[] Ports = SerialPort.GetPortNames();
     5 for (int i = 0; i < Ports.Length; i++)
     6 {
     7      string s = Ports[i].ToUpper();
     8      Regex reg = new Regex("[^COM\d]", RegexOptions.IgnoreCase | RegexOptions.Multiline);//正则表达式
     9      s = reg.Replace(s, "");
    10      PortList.Items.Add(s);
    11 }
    12 if (Ports.Length > 1) PortList.SelectedIndex = 1;

    >_<" 接下来,对于串口的连接就比较简单了。如本工程,当你从ComboBox中选择一个串口时,然后点击连接按钮进行连接,那么连接按钮就是负责串口连接功能:这里根据连接按钮的状态判断当前连接情况,当有连接时,点击连接按钮表示断开连接;当没有连接时,点击连接按钮,则会连接从ComboBox中选中的串口。这里要注意(20行):由于串口数据接收属于触发事件,有数据到缓冲区才会触发并接收数据,这里实例化一个串口数据接收句柄就是负责响应触发接收数据的函数,即:PortDataReceived~

     1 SerialPort Connection = new SerialPort();
     2         private void 链接ToolStripMenuItem_Click(object sender, EventArgs e)
     3         {
     4             if (链接ToolStripMenuItem.Text == "关闭")//通过控件名字判断当前状态
     5             {   
     6                 Connection.Close();
     7                 链接ToolStripMenuItem.Text = "链接";//改变控件名字
     8                 PortList.Enabled = true;
     9                 JS.Suspend();//挂起线程
    10             }
    11             else
    12             {
    13                 if (!Connection.IsOpen)
    14                 {
    15                     JS.Resume();//继续已挂起的线程
    16                     Connection = new SerialPort();
    17                     Connection.PortName = PortList.SelectedItem.ToString();
    18                     Connection.Open();
    19                     Connection.ReadTimeout = 10000;
    20                     Connection.DataReceived += new SerialDataReceivedEventHandler(PortDataReceived);
    21                     链接ToolStripMenuItem.Text = "关闭";
    22                     PortList.Enabled = false;
    23                 }
    24             } 
    25         }

    >_<" 如上面介绍,每当串口有数据传来就会触发该函数执行来接收串口缓冲区数据。这里的第6行即是从串口缓冲区读取length长的数据放入data数组中,0表示放入data[0]开始。下面8、9两行即把原始数据压入栈给JiSuan线程处理。

     1 private byte[] data;//接收的数据数组
     2 private int length = 2;//一次从缓冲区取出数据长度
     3 private void PortDataReceived(object sender, SerialDataReceivedEventArgs e)
     4 {
     5      data = new byte[length];
     6      if (Connection.Read(data, 0, length) != length) return;
     7      Connection.DiscardInBuffer();
     8      Connection.DiscardOutBuffer();
     9      Q.Push(data[0]);//压入栈
    10      Q.Push(data[1]);
    11 }

    3-2-2、JiSuan函数说明及核心算法介绍

    >_<" 如下图,原始数据转换为FORM坐标系下移动端的坐标(x,y)的求法:首先利用三角函数根据原始数据计算出a,b的值,然后根据一定比例放大a,b值再根据坐标关系求出FORM坐标系下的移动点(x,y)的坐标。

    >_<" 当理解上述计算转换过程,下面的代码便不难理解。这个JiSuan的函数其实是个数据处理的线程,一直处于while循环下,当栈Q中存在待处理原始数据时,则取出原始数据,判断原始数据是否正确合理,如果合理则分别找出data_x和data_y的值,即原始值(这里,由于下位机发送过来data_x和data_y的顺序先后不定,于是在下位机中将data_y的数据取相反数发送过来,所以在这里能够巧妙地区分)。接下来就是采用上述所述方法进行计算移动点的FORM坐标系下的坐标了。这里的Show字符串是为了保存最初您看到的显示的x:23 y:21 q:0 p:123信息。

     1 private string Show="";
     2 private void JiSuan()
     3 {
     4     while (true)
     5     {
     6         if (Q.Count()<2) continue;
     7         data[0] = Q.Pop();
     8         data[1] = Q.Pop();
     9         //从串口读原始数据并判断是否合法
    10         //这里串口的数据位发送到x、y接收头的距离,并且只保留1m内的距离
    11         //为了分辨清晰,把y的真实距离取相反数经串口发送
    12         //当上位机串口收到2个距离放到data[2]中,当负的强制转换为int类型时要用225-接收的数据才能转化为原数
    13         int data_x = 0, data_y = 0;
    14         if ((int)data[0] >= 100 && (int)data[1] <= 100)
    15         {
    16             data_y = 255 - data[0];
    17             data_x = data[1];
    18         }
    19         else if ((int)data[0] <= 100 && (int)data[1] >= 100)
    20         {
    21             data_x = data[0];
    22             data_y = 255 - data[1];
    23         }
    24         else continue;
    25         //下面是根据收到的发射到两个接收头的距离计算相应的平面坐标位置
    26         //a,b为原始偏移,x,y为坐标平面的对应点坐标
    27         double cos8 = (14.0 * 14.0 + data_x * data_x * 1.0 - data_y * data_y * 1.0) / (2.0 * 14.0 * data_x);
    28         double a = 1.0 * data_x * Math.Sin(Math.Acos(cos8));
    29         double b = 1.0 * data_x * cos8;
    30         x = ((int)(7 * a));
    31         y = ((int)(7 * b)) + 100;
    32         if (x < 0) continue;
    33         p3.X = x;
    34         p3.Y = y;
    35         P.Add(p3);
    36         rect.X = x - 2;
    37         rect.Y = y - 2;
    38         //用于显示收到数据
    39         Show = "x:";
    40         Show += data_x;
    41         Show += " y:";
    42         Show += data_y;
    43         Show += " q:";
    44         Show += Q.Count();
    45         Show += " p:";
    46         Show += P.Count();
    47         canShow = true;//可以显示
    48     }
    49 }

    四、阶段小结

    >_<" 经过这四小节的学习,从最初的想法到如今的实现,收获颇丰!对于模拟电路有了更深刻的认识,对于51单片机的应用有个进一步的提高,对于PC上位机的开发也有了新的了解和体会。可以说,硬件的难在于其变化性,即使是绝美的方案也会出现真实世界莫名因素的影响,让你头大;软件的难在于逻辑之难,本项目虽小,但是用了多线程的思维,里面隐隐涉及到线程同步和死锁的问题,只是有些错误在调试阶段已经被淘汰,大家在这个最终的方案下也就无法想象原初的各种死锁、运行时中断、迟缓等问题了。此外,可能是前三篇过于偏向于硬件,所以大家可能不会理睬,但是如果您真的对该篇文章感兴趣的话,还是建议您仔细阅读下前三篇的介绍,毕竟这个过程中硬件几乎占了大半个江山呢~其实,本来的想法是:第一阶段把这个做成个平面定位的类似于电子白板的触控模块,第二阶段把这个做成三接收头的空间三维定位装置,结合当前体感游戏、手势识别等开发出更有意义的产品,但是在研究的过程中发现超声波定位其本身存在不可忽视的缺点,容易反射相互干扰等,接下来的想法造成了本质性的阻碍。由于最近学业较忙,所以这个超声波的研究就暂且为止!最后,感谢大家的支持!

    五、相关链接 

    [自娱自乐] 1、超声波测距模块DIY笔记(一)链接:http://www.cnblogs.com/zjutlitao/p/4014855.html

    [自娱自乐] 2、超声波测距模块DIY笔记(二)链接:http://www.cnblogs.com/zjutlitao/p/4029937.html

    [自娱自乐] 3、超声波测距模块DIY笔记(三)链接:http://www.cnblogs.com/zjutlitao/p/4069272.html

    [源码]该项目C#工程VS2012平台:http://pan.baidu.com/s/1ntqkxNb

    [源码]该项目超声波接收模块51单片机代码(和第三篇的不同):http://pan.baidu.com/s/1jGA9vqI

    [打击盗版]请进入博主主页查看原版博文:http://www.cnblogs.com/zjutlitao/

  • 相关阅读:
    TCP 的那些事儿(转载)
    3. 对象在内存中的布局
    GO语言学习之数据类型-->基本类型(字符串)
    GO语言学习之变量and常量
    wrk
    为什么显示消息“错误:您所在国家/地区是禁运国,无法下载 Java”?
    raw.githubusercontent.com 访问不了
    Windows Terminal
    vue:无法加载文件C:UsersAppDataRoaming pmvue.ps1, 在此系统上无法加载脚本
    vue使用过滤改变el-switch开关的状态
  • 原文地址:https://www.cnblogs.com/zjutlitao/p/4086237.html
Copyright © 2011-2022 走看看