zoukankan      html  css  js  c++  java
  • C#串口通信及数据表格存储

    1.开发环境

    系统:win10

    开发工具:Visual Studio 2017

    程序下载地址:https://download.csdn.net/download/wang0huan/11145500

    分不够的可留言邮箱。

    2.界面设计

    串口通信的界面大致如此,在此基础上添加项目所需的调试指令与数据存储功能,界面排布方面可参考其他教程。

    3.串口通信的实现

    本文串口通信主要使用的技术点有:队列、多线程、List等,相比传统单线程方案更加稳定,适合大数据收发。

    • 串口基础参数设置

      

            #region 设置串口的属性SetPortProperty
            private void SetPortProperty()//设置串口的属性
            {
                sp = new SerialPort
                {
                    PortName = cbPortName.Text.Trim(), //设置串口名
                    //BaudRate = Convert.ToInt32(cbBaudRate.Text.Trim()) //设置串口波特率
                    BaudRate = int.Parse(cbBaudRate.Text.Trim())
                };
                float f = Convert.ToSingle(cbStop.Text.Trim());//设置停止位
                if (f == 0)
                {
                    sp.StopBits = StopBits.None;
                }
                else if (f == 1.5)
                {
                    sp.StopBits = StopBits.OnePointFive;
                }
                else if (f == 1)
                {
                    sp.StopBits = StopBits.One;
                }
                else if (f == 2)
                {
                    sp.StopBits = StopBits.Two;
                }
                else
                {
                    sp.StopBits = StopBits.One;
                }
                sp.DataBits = Convert.ToInt16(cbDataBits.Text.Trim());//设置数据位
    
                string s = cbParity.Text.Trim();//设置奇偶校验位
                if (s.CompareTo("") == 0)
                {
                    sp.Parity = Parity.None;
                }
                else if (s.CompareTo("奇校验") == 0)
                {
                    sp.Parity = Parity.Odd;
                }
                else if (s.CompareTo("偶校验") == 0)
                {
                    sp.Parity = Parity.Even;
                }
                else
                {
                    sp.Parity = Parity.None;
                }
                sp.ReadTimeout = -1; //设置超市读取时间
                //sp.NewLine = "/r/n"; //根据实际情况吧,当使用ReadLine()时需要定义一下
                //sp.RtsEnable = true; //启用RTS发送请求信号,根据实际情况吧
    
                //定义串口DataReceived事件,当串口接受到数据后触发事件
                sp.DataReceived += Sp_DataReceived; //添加事件注册
            }
            #endregion

    下面重点说明串口的发送和接收的实现方法:

    • 串口接收实现

      串口接收事件Sp_DataReceived,串口组件的接收触发事件,接收数据并在接收文本框显示(便于调试),注意多线程访问UI资源要用invoke方式来同步,同时将接收的数据添加到SerialRevList中,同时加锁保护List。同时用一个独立线程来进行接收数据的处理,线程之间数据加锁同步。

            #region 串口接受事件Sp_DataReceived
            private void Sp_DataReceived(object sender, SerialDataReceivedEventArgs e)//串口接受事件
            {
                try
                {
                    if (this.sp.BytesToRead > 0)
                    {
                        byte[] buffer = new byte[this.sp.BytesToRead];
                        this.sp.Read(buffer, 0, buffer.Length);
                        received_count += buffer.Length;//增加接收计数
                        Rev_builder.Clear();//清除字符串构造器的内容
                        //因为要访问ui资源,所以需要使用invoke方式同步ui,串口接收事件会自动创建线程,多线程访问控件需要使用invoke来委托
                        this.Invoke((EventHandler)(delegate
                        {
                            if (rbRcvHex.Checked == false)//接受数据字符串显示
                            {
                                //tbxRcvData.Text += sp.ReadLine(); //一直读取到输入缓冲区中的 NewLine 值,使用这个需要注意换行符
                                //直接按ASCII规则转换成字符串  
                                Rev_builder.Append(Encoding.Default.GetString(buffer));
                            }
                            else//接受数据Hex显示
                            {
                                //依次的拼接出16进制字符串  
                                foreach (byte b in buffer)
                                {
                                    Rev_builder.Append(b.ToString("X2") + " ");
                                }
                            }
                            this.tbxRcvData.AppendText(Rev_builder.ToString());//接受数据显示在文本框
                            labelRcvCount.Text = "接收字节数:" + received_count.ToString();//修改接收计数
                            sp.DiscardInBuffer();//丢弃接受缓冲区数据
                        }));
    
                        Monitor.Enter(this.SerialRevList);   //数据保护
                        this.SerialRevList.AddRange(buffer); //交由接收处理线程处理
                        Monitor.Exit(this.SerialRevList);
                    }
                }
                catch (Exception ex)
                {
                    throw ex;
                }
            }
            #endregion

    单独开一个线程来进行串口接收数据的处理,下面给出了常用帧处理给出的实例,充分利用了List集合带来的数据处理的便捷;

            #region 串口接受缓存数据处理线程函数
            /// <summary>
            /// 串口接受缓存数据处理线程函数
            /// </summary>
            /// <param name="obj"></param>
            private void SerialRev(object obj)
            {
                while (true)
                {
                    try
                    {
                        this.SerialRevWaiter.WaitOne();
                        Monitor.Enter(this.SerialRevList);
    
                        //AWGM.AWGHandle(SerialRevData);
                        //至少包含帧头(1字节)+长度(2字节)+数据 + 结束位(1字节)
                        if (this.SerialRevList.Count > 0) //接收缓存有数据
                        {
                            //AWG接收字符串协议处理方式
                            string str = Encoding.Default.GetString(SerialRevList.ToArray());
                            if (str.Substring(0, 1) == "*")  //起始位
                            {
                                int len = Convert.ToInt32(str.Substring(1, 2));
                                if (SerialRevList.Count < len + 4)//未接收完整
                                {
                                    break;
                                }
                                else
                                {
                                    if (str.Substring(len + 3, 1) == "^")//结束位
                                    {
                                        //lineyValue1 = Convert.ToDouble(str.Substring(3, 5));
                                        //lineyValue2 = Convert.ToDouble(str.Substring(8, 5));
    
                                        //多线程访问控件使用invoke来委托
                                        this.Invoke((EventHandler)(delegate
                                        {
                                            this.tbxRcvData.AppendText(str);
                                            //tbxAWGMTem.Text = str.Substring(3, 5);
                                        }));
                                    }
    
                                }
                                SerialRevList.RemoveRange(0, len + 4);//去掉处理一帧的数据
                            }
                            else
                            {
                                SerialRevList.RemoveAt(0);//帧头不正确时,记得清除
                            }
    
                            //IODH接收16进制处理方式
    
                        }
                        Monitor.Exit(this.SerialRevList);
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show(ex.Message, "串口接受线程处理错误!", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    }
                    Thread.Sleep(50);
                }
            }
    
            #endregion
    
    • 串口发送

      考虑到UI界面可能触发多个串口发送需求,此时也需要用到多线程来保证数据发生的安全;

      串口发送采用队列的方式来存储发送数据,然后利用一个线程来一次发送队列中的数据;

      串口发送实例:

    private void btnTECPIDSet_Click(object sender, EventArgs e)
            {
                Monitor.Enter(this.SerialSendQueue);
                this.SerialSendQueue.Enqueue("pidsettem" + string.Format("{0:0.000}", Convert.ToSingle(tbxTecKp.Text)) + string.Format("{0:0.000}", 
           Convert.ToSingle(tbxTecKi.Text)) + string.Format("{0:0.000}", Convert.ToSingle(tbxTecKd.Text))); Monitor.Exit(this.SerialSendQueue); }

      串口发送线程:

    #region 串口发送数据线程函数
            /// <summary>
            /// 这个线程函数负责发送串口命令
            /// </summary>
            /// <param name="obj"></param>
            private void SerialSend(object obj)
            {
                while (true)
                {
                    try
                    {
                        this.SerialSendWaiter.WaitOne();
                        Monitor.Enter(this.SerialSendQueue); //队列排他锁,实现同步访问
                        string buf = null;
                        if (this.SerialSendQueue.Count > 0) //有命令
                        {
                            buf = this.SerialSendQueue.Dequeue(); //取出发送队列第一个命令
                        }
                        if (buf != null)
                        {
                            byte[] buffer = Encoding.Default.GetBytes(buf);
                            this.sp.DiscardInBuffer();
                            this.sp.Write(buffer, 0, buffer.Length);
    
                            tbxSendData.AppendText(buf + "
    ");
                            send_count += buf.Length;
                            labelSendCount.Text = "发送字节数:" + send_count.ToString();//更新发送计数
                        }
                        Monitor.Exit(this.SerialSendQueue); //释放锁
                        Thread.Sleep(100);
                    }
                    catch (Exception ex)
                    {
                        throw ex;
                    }
                }
            }
            #endregion

    4.表格数据存储EXCEL

    常用测试中经常用到测试数据的自动化记录,这样由串口采集输出到excel中功能就变得非常实用;

    下面介绍实现方法:结合的技术是dataGridView控件和SaveFileDialog;

    由于电脑不一定都安装有office,直接使用office组件功能缺乏一定的通用性,因此本实例采用存储为csv格式表格数据文件,然后根据设置的格式转化为EXCEL文件即可。

    代码参考如下:

    #region 测试数据存储(DataGridView和DataTable的使用)
            /// <summary>
            /// 初始化DataTable,并将datatable绑定到DataGridView的数据源,新建列标题
            /// </summary>
            private void InitDatable()
            {
                //新建列  
                DataColumn col1 = new DataColumn("休眠", typeof(string));
                DataColumn col2 = new DataColumn("门磁A-12V", typeof(string));
                DataColumn col3 = new DataColumn("门磁A-5V", typeof(string));
                DataColumn col4 = new DataColumn("门A-LED", typeof(string));
                DataColumn col5 = new DataColumn("门磁B-12V", typeof(string));
                DataColumn col6 = new DataColumn("门磁B-5V", typeof(string));
                DataColumn col7 = new DataColumn("门B-LED", typeof(string));
                //添加列  
                dt.Columns.Add(col1);
                dt.Columns.Add(col2);
                dt.Columns.Add(col3);
                dt.Columns.Add(col4);
                dt.Columns.Add(col5);
                dt.Columns.Add(col6);
                dt.Columns.Add(col7);
    
                this.dataGridView1.DataSource = dt.DefaultView;
            }
    
            /// <summary>
            /// 按键触发记录测试数据,即添加行数据
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void btnTestRecord_Click(object sender, EventArgs e)
            {
                //dt.Rows.Clear();//清空数据  
                DataRow dr = dt.NewRow();//新增行 
                dr[0] = "OK";
                dr[1] = "OK";
                dr[2] = "OK";
                dr[3] = "OK";
                dr[4] = "OK";
                dr[5] = "OK";
                dr[6] = "F";
                this.dt.Rows.Add(dr);//增加行
            }
    
            /// <summary>
            /// 清除表格记录内容
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void btnCleanRecord_Click(object sender, EventArgs e)
            {
                dt.Rows.Clear();//清空数据
            }
    
            /// <summary>
            /// 测试记录导出
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void btnExport_Click(object sender, EventArgs e)
            {
                IODM_TEST.DataGridViewToExcel(dataGridView1);
            }
            #endregion
    DataGridViewToExcel实现如下:
    #region DateGridView导出到csv格式的Excel     
            /// <summary>     
            /// 常用方法,列之间加/,一行一行输出,此文件其实是csv文件,不过默认可以当成Excel打开。     
            /// </summary>     
            /// <remarks>     
            /// using System.IO;     
            /// </remarks>     
            /// <param name="dgv">表格数据控件</param>     
            public static void DataGridViewToExcel(DataGridView dgv)
            {
                SaveFileDialog dlg = new SaveFileDialog();
                dlg.Filter = "Execl files (*.csv)|*.csv";
                dlg.FilterIndex = 0;
                dlg.RestoreDirectory = true;
                dlg.CreatePrompt = true;
                dlg.Title = "保存为Excel文件";
    
                if (dlg.ShowDialog() == DialogResult.OK)
                {
                    Stream myStream;
                    myStream = dlg.OpenFile();
                    StreamWriter sw = new StreamWriter(myStream, System.Text.Encoding.GetEncoding(-0));
                    string columnTitle = "";
                    try
                    {
                        //写入列标题     
                        for (int i = 0; i < dgv.ColumnCount; i++)
                        {
                            if (i > 0)
                            {
                                columnTitle += "/";
                            }
                            columnTitle += dgv.Columns[i].HeaderText;
                        }
                        sw.WriteLine(columnTitle);
    
                        //写入列内容     
                        for (int j = 0; j < dgv.Rows.Count; j++)
                        {
                            string columnValue = "";
                            for (int k = 0; k < dgv.Columns.Count; k++)
                            {
                                if (k > 0)
                                {
                                    columnValue += "/";
                                }
                                if (dgv.Rows[j].Cells[k].Value == null)
                                    columnValue += "";
                                else
                                    columnValue += dgv.Rows[j].Cells[k].Value.ToString().Trim();
                            }
                            sw.WriteLine(columnValue);
                        }
                        sw.Close();
                        myStream.Close();
                    }
                    catch (Exception e)
                    {
                        MessageBox.Show(e.ToString());
                    }
                    finally
                    {
                        sw.Close();
                        myStream.Close();
                    }
                }
            }
            #endregion
     
  • 相关阅读:
    HDU 4970 Killing Monsters(树状数组)
    HDU 1541 Stars(树状数组)
    HDU 1541 Stars(树状数组)
    POJ 1990 MooFest(树状数组)
    POJ 1990 MooFest(树状数组)
    关于论坛数据库的设计(分表分库等-转)
    struts2零配置參考演示样例
    [ATL/WTL]_[中级]_[保存CBitmap到文件-保存屏幕内容到文件]
    转【翻译】怎样在Ubuntu 12.04上配置Apache SSL证书
    《简单的飞机大战》事实上不简单(1)
  • 原文地址:https://www.cnblogs.com/silencehuan/p/9045991.html
Copyright © 2011-2022 走看看