串口通信--控制舵机运动

界面部分使用krytonToolKit开发,整个软件实现了串口通信,并且控制四个自由度的舵机运动。

整个软件使用多线程开发,采集舵机指令保存到TXT文档中,然后调用TXT文档再现舵机运动过程,软件界面如下:


优化之后的界面 如下:



using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.IO.Ports;
using System.Windows.Forms;
using System.Text.RegularExpressions;
using System.Threading;
using System.IO;
//DotNetMagic
using Crownwood.DotNetMagic;
using Crownwood.DotNetMagic.Common;
using Crownwood.DotNetMagic.Controls;
using Crownwood.DotNetMagic.Docking;
using Crownwood.DotNetMagic.Forms;
using System.Runtime.InteropServices;
using System.Collections;
 
namespace ComAssistant
{
    public partial class ComAssistantForm : DotNetMagicForm
    {
        private int received_count = 0;//接收计数  
        private long send_count = 0;//发送计数
        private bool Listening = false;//是否没有执行完invoke相关操作  
        //private bool Closing = false;//是否正在关闭串口,执行Application.DoEvents,并阻止再次invoke  
        private bool Closing = false;//是否正在关闭串口,执行Application.DoEvents,并阻止再次invoke  
        private bool Query = false;//查询状态 
        private List<byte> buffer = new List<byte>(4096);//默认分配1页内存,并始终限制不允许超过  
        private byte[] binary_data_1 = new byte[10];//FF FF 01 06 00 B1 0A 00 00 3D 
        List<Array> dataList = new List<Array>();  //数据再现
        string FILE_INFO_NAME = "FileInfo.txt";
        private bool kaiguan = true;
        private bool openClose = true;
 
 
        string strShow = "";
 
        public ComAssistantForm()
        {
            InitializeComponent();
        }
 
        private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            //返回指令格式:FF FF 01 06 00 B1 0A 00 00 3D   ,   其中B1 0A为ID为1的舵机当前位置,00 00为其当前速度
            // 第2位是舵机,5、6位是位置,7、8位为速度
 
            if (Closing) return;//如果正在关闭,忽略操作,直接返回,尽快的完成串口监听线程的一次循环
            try
            {
                Listening = true;//设置标记,说明我已经开始处理数据,一会儿要使用系统UI的。
                int n = serialPort1.BytesToRead;//先记录下来,避免某种原因,人为的原因,操作几次之间时间长,缓存不一致
                byte[] buf = new byte[n];//声明一个临时数组存储当前来的串口数据
                serialPort1.Read(buf, 0, n);//读取缓冲数据
 
                /////////////////////////////////////////////////////////////////////////////////////////////////////////////
                //<协议解析>
                //1.缓存数据
                buffer.AddRange(buf);
                //2.完整性判断
                while (buffer.Count >= 10)//至少要包含头(2字节)+长度(7字节)+校验(1字节)
                {
                    if (buffer[0] == 0xFF && buffer[1] == 0xFF && buffer[2] == 0xFF)
                    {
                        buffer.RemoveRange(0, 10);//从缓存中移除数据。
                        continue;
                    }
 
                        //2.1 查找数据头
                        if (buffer[0] == 0xFF && buffer[1] == 0xFF&& buffer[2] != 0xFF)
                        {
                            received_count++;
                            //至此,已经被找到了一条完整数据。我们将数据直接分析,或是缓存起来一起分析
                            buffer.CopyTo(0, binary_data_1, 0, 9);//复制一条完整数据到具体的数据缓存
                            //3.分析数据
 
                            //根据舵机编号分类并存入TXT文件
                            string zhuanbian = binary_data_1[7].ToString("X2");
                            if (zhuanbian == "00")
                            {
                                zhuanbian = "01";
                            }
 
                            //我们的数据都是定好格式的,所以当我们找到分析出的数据1,就知道固定位置一定是这些数据,我们只要显示就可以了
                            string data = binary_data_1[2].ToString("X2") + " " +
                                binary_data_1[5].ToString("X2") + " " + binary_data_1[6].ToString("X2") + " " +
                                zhuanbian + " " + binary_data_1[8].ToString("X2");
 
                            //更新界面
                            this.Invoke((EventHandler)(delegate
                            {
                                kryptonRichTextBox1.Text += data + "**";
                                WriteFile(data);
 
                            }));
                            if (received_count == 4)
                            {
                                WriteFileLine();
                                received_count = 0;
                                //更新界面
                                this.Invoke((EventHandler)(delegate
                                {
                                    kryptonRichTextBox1.Text += "\r\n";
                                }));
                            }
                            buffer.RemoveRange(0, 10);//正确分析一条数据,从缓存中移除数据。
                        }
                        else
                        {
                            //这里是很重要的,如果数据开始不是头,则删除数据
                            buffer.RemoveAt(0);
                        }
                }
            }
            catch (Exception)
            {
 
                MessageBox.Show("串口接收数据出错,请查看串口接收指令!", "软件提醒", MessageBoxButtons.OKCancel, MessageBoxIcon.Error);
            }
            finally
            {
                Listening = false;//我用完了,ui可以关闭串口了。
            }
        }
 
        //初始化
        private void ComAssistantForm_Load(object sender, EventArgs e)
        {
            int ii = 0;
            foreach (string s in SerialPort.GetPortNames())
            {
                if (ii >= 1)
                {
                    ktComboBoxCom.Items.Add(s);
                }
                ii++;
            }
 
            if (SerialPort.GetPortNames().Length != 0)
            {
                ktComboBoxCom.Text = (string)ktComboBoxCom.Items[0];
            }
 
            //string[] strCom = new string[] { "COM1", "COM2", "COM3", "COM4" };
            //ktComboBoxCom.DataSource = strCom;
 
 
            string[] ss = new string[] {"2000000" , "9600", "19200", "57600" };
            ktComboBoxBount.DataSource = ss;
 
            ktComboBoxJiaoyan.DataSource = Enum.GetNames(typeof(Parity));
 
            ss = new string[] { "5", "6", "7", "8" };
            ktComboBoxData.DataSource = ss;
            ktComboBoxData.Text = "8";
 
            ktComboBoxStop.DataSource = Enum.GetNames(typeof(StopBits));
            ktComboBoxStop.Text = Enum.Format(typeof(StopBits), StopBits.One, "G");
            kryptonTextBox1.Text = "FF FF 04 04 03 18 00 DC";
 
            try
            {
                if (!serialPort1.IsOpen)
                {
                    serialPort1.PortName = ktComboBoxCom.Text;
                    serialPort1.BaudRate = Convert.ToInt32(ktComboBoxBount.Text);
                    serialPort1.Parity = (Parity)Enum.Parse(typeof(Parity), ktComboBoxJiaoyan.Text);
                    serialPort1.DataBits = Int32.Parse(ktComboBoxData.Text);
                    serialPort1.StopBits = (StopBits)Enum.Parse(typeof(StopBits), ktComboBoxStop.Text);
                    serialPort1.Encoding = Encoding.GetEncoding("Gb2312");
                    serialPort1.Open();
                    pictureBox1.Image = Image.FromFile(Application.StartupPath + "\\kai.jpg");
                }
            }
            catch (Exception)
            {
 
                MessageBox.Show("初始化出问题,请检查串口设备", "软件提醒", MessageBoxButtons.OKCancel, MessageBoxIcon.Error);
            }
        }
 
        //连接串口
        private void ktBtnLink_Click(object sender, EventArgs e)
        {
            try
            {
                if (!serialPort1.IsOpen)
                {
                    serialPort1.PortName = ktComboBoxCom.Text;
                    serialPort1.BaudRate = Convert.ToInt32(ktComboBoxBount.Text);
                    serialPort1.Parity = (Parity)Enum.Parse(typeof(Parity), ktComboBoxJiaoyan.Text);
                    serialPort1.DataBits = Int32.Parse(ktComboBoxData.Text);
                    serialPort1.StopBits = (StopBits)Enum.Parse(typeof(StopBits), ktComboBoxStop.Text);
                    serialPort1.Encoding = Encoding.GetEncoding("Gb2312");
                    serialPort1.Open();
                    pictureBox1.Image = Image.FromFile(Application.StartupPath + "\\kai.jpg");
                }
            }
            catch (Exception)
            {
 
                MessageBox.Show("初始化出问题,请检查串口设备", "软件提醒", MessageBoxButtons.OKCancel, MessageBoxIcon.Error);
            }
        }
 
        //关闭串口
        private void ktBtnClose_Click(object sender, EventArgs e)
        {
            try
            {
                if (serialPort1.IsOpen)
                {
                    Closing = true;
                    while (Listening) Application.DoEvents();
                    serialPort1.Close();
                    pictureBox1.Image = Image.FromFile(Application.StartupPath + "\\guan.jpg");
                    //打开时点击,则关闭串口  
                    Closing = false;
                }
            }
            catch (System.Exception ex)
            {
                MessageBox.Show("串口关闭出错,请检查串口连接!", "软件提醒", MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation);
            }
        }
 
        //发送数据
        private void ktBtnSend_Click(object sender, EventArgs e)
        {
            if (!serialPort1.IsOpen)
            {
                MessageBox.Show("请打开串口!", "软件提醒", MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation);
            }
            else
            {
                string content = kryptonTextBox1.Text;
                byte[] bytes = MyClass.StringToHex(content);
                serialPort1.Write(bytes, 0, bytes.Length);
            }
        }
 
        //接收数据
        private void ktBtnAccept_Click(object sender, EventArgs e)
        {
            //Closing = false;
 
            int n = serialPort1.BytesToRead;
            byte[] dbs = new byte[n];
            int len = serialPort1.Read(dbs, 0, n);
            int[] num = new int[len];
 
            string show = "";
            show = MyClass.byteToHexStr(dbs);
            kryptonRichTextBox1.Text += show;
        }
 
        //清空数据
        private void ktBtnClear_Click(object sender, EventArgs e)
        {
            strShow = "";
            kryptonRichTextBox1.Text = "";
        }
 
        //急停指令
        private void ktBtnStop_Click(object sender, EventArgs e)
        {
            try
            {
                if (serialPort1.IsOpen)
                {
                    Closing = true;
                    while (Listening) Application.DoEvents();
                    serialPort1.Close();
                    pictureBox1.Image = Image.FromFile(Application.StartupPath + "\\guan.jpg");
                    //打开时点击,则关闭串口  
                    Closing = false;
                }
            }
            catch (System.Exception ex)
            {
                MessageBox.Show("串口紧急关闭!", "软件提醒", MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation);
            }
        }
 
        //查询舵机状态
        private void ktBtnQuery_Click(object sender, EventArgs e)
        {
 
            try
            {
                FileStream stream2 = File.Open(FILE_INFO_NAME, FileMode.OpenOrCreate, FileAccess.Write);
                stream2.Seek(0, SeekOrigin.Begin);
                stream2.SetLength(0); //清空txt文件
                stream2.Close();
            }
            catch (System.Exception ex)
            {
           
            }
            finally
            {
                MessageBox.Show("缓存数据已经清空!", "软件提醒", MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation);
            }
 
 
            //if (!Query)
            //{
            //    ktBtnQuery.Text = "停止查询舵机";
            //    Query = true;
 
            //    if (!serialPort1.IsOpen)
            //    {
            //        MessageBox.Show("请打开串口!", "软件提醒", MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation);
            //    }
            //    else
            //    {
            //        for (int i = 0; i < 10; i++)
            //        {
            //            //这是舵机返回指令,发送指令没有加入
            //            //舵机1
            //            byte[] bytes = MyClass.StringToHex("FF FF 01 06 00 B1 0A 01 01 3D");
            //            serialPort1.Write(bytes, 0, bytes.Length);
 
            //            //舵机2
            //            bytes = MyClass.StringToHex("FF FF 02 06 00 B1 0A 02 02 3D");
            //            serialPort1.Write(bytes, 0, bytes.Length);
 
            //            //舵机3
            //            bytes = MyClass.StringToHex("FF FF 03 06 00 B1 03 03 03 3D");
            //            serialPort1.Write(bytes, 0, bytes.Length);
 
            //            //舵机4
            //            bytes = MyClass.StringToHex("FF FF 04 06 00 B1 04 04 04 3D");
            //            serialPort1.Write(bytes, 0, bytes.Length);
            //            //Thread.Sleep(5000);
 
            //        }
 
            //    }
 
            //}
            //else
            //{
            //    ktBtnQuery.Text = "查询舵机状态";
            //    Query = false;
            //}
        }
 
        /// <summary>
        /// 写入舵机位置速度信息
        /// </summary>
        private void WriteFile(string input)
        {
            using (StreamWriter sw = File.AppendText("FileInfo.txt"))
            {
                sw.Write(input + " ** ");
            }
 
            ///文件目录
            //FileStream fs = new FileStream("FileInfo.txt", FileMode.Create);
            //StreamWriter sw = new StreamWriter(fs);
            //sw.WriteLine(input + "\n");
            ////清空缓冲区
            //sw.Flush();
            ////关闭流
            //sw.Close();
            //fs.Close();
            ///写入内容并换行
        }
 
        /// <summary>
        /// 换行
        /// </summary>
        private void WriteFileLine()
        {
            using (StreamWriter sw = File.AppendText(FILE_INFO_NAME))
            {
                sw.WriteLine();
            }
        }
 
 
 
 
 
 
 
        //采集指令
        private void ktBtnCollection_Click(object sender, EventArgs e)
        {
            if (kaiguan)
            {
                 Closing = false;
                //这里创建一个线程,使之执行Alpha类的Beta()方法
                Thread newThread = new Thread(new ThreadStart(caijiThread));
                newThread.Start();
                ktBtnCollection.Text = "停  止";
                kaiguan = false;
 
            }
            else
            {
                kaiguan = true;
                ktBtnCollection.Text = "采  集";
 
            }
        }
 
        //创建采集新线程
        public void caijiThread()
        {
            if (!serialPort1.IsOpen)
            {
                MessageBox.Show("请打开串口!", "软件提醒", MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation);
            }
            else
            {
                try
                {
 
                    while (true)
                    {
                        if (kaiguan == true)
                        {
                            break;
                        }
 
                        //采集[指令固定]
                        byte[] bytes = MyClass.StringToHex("FF FF FE 14 83 18 01 01 00 02 00 03 00 04 00 05 00 06 00 07 00 08 00 2D");
                        serialPort1.Write(bytes, 0, bytes.Length);
 
                        //舵机01的指令FF FF 01 04 02 24 04 D0
                        byte checksum = 0;
                        for (int QueryNum = 1; QueryNum < 5; QueryNum++)
                        {
                            //求校验和,方法一
                            checksum = ((byte)(QueryNum + 0X04 + 0X02 + 0X24 + 0X04));
                            byte[] ab = new byte[1];
                            ab[0] = (byte)~(0xff & checksum);
                            string mySum = MyClass.byteToHexStr(ab);
 
                            //////求校验和,方法二
                            //byte[] tempab = new byte[] { 0X01, 0X04, 0X02, 0X24, 0X04 };
                            //byte strab = MyClass.CheckSum(tempab);
 
                            byte[] bytesCaiji = MyClass.StringToHex("FF FF " + QueryNum.ToString("X2") + " 04 02 24 04 " + mySum.Trim());
                            serialPort1.Write(bytesCaiji, 0, bytesCaiji.Length);
                            Thread.Sleep(1 / 50);
                        }
                        Thread.Sleep(50);
                    }
                }
                catch (System.Exception ex)
                {
                    MessageBox.Show("采集过程中出错!", "软件提醒", MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation);
               
                }
            }
        }
 
 
        //再现过程
        private void ktBtnAgain_Click(object sender, EventArgs e)
        {
            if (openClose)
            {
                //这里创建一个线程,使之执行Alpha类的Beta()方法
                Thread newThread = new Thread(new ThreadStart(zaixianThread));
                newThread.Start();
                ktBtnAgain.Text = "执行中 • • •";
                openClose = false;
 
            }
            else
            {
                openClose = true;
                ktBtnAgain.Text = "再  现";
 
            }
 
        }
 
 
        //创建再现新线程
        public void zaixianThread()
        {
            if (!serialPort1.IsOpen)
            {
                MessageBox.Show("请打开串口!", "软件提醒", MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation);
            }
            else
            {
                try
                {
                    //获取数据
                    dataList = ReadFile();
 
                    //FF FF FE 18 83 1E 04 01 00 00 11 11 02 22 22 33 33 03 44 44 55 55 04 66 66 77 99 18 
                    foreach (Array ar in dataList) //获取一条四个舵机数据
                    {
 
                        string zuhe = "";
                        byte[] xiafa = new byte[28];
                        foreach (string str in ar)   //获取一个舵机数据
                        {
                            zuhe += str.Trim() + " ";
                        }
                        string hh = zuhe.Trim();
                        byte[] temp = MyClass.StringToHex(hh);
 
                        byte[] tempOne = new byte[26];
                        for (int i = 0; i < 20; i++)
                        {
                            xiafa[i + 7] = temp[i];
                        }
                        xiafa[0] = 0XFF;
                        xiafa[1] = 0XFF;
                        xiafa[2] = 0XFE;
                        xiafa[3] = 0X18;
                        xiafa[4] = 0X83;
                        xiafa[5] = 0X1E;
                        xiafa[6] = 0X04;
 
                        Array.Copy(xiafa, 2, tempOne, 0, 26);//复制数组
                        xiafa[27] = MyClass.CheckSum(tempOne);
                        serialPort1.Write(xiafa, 0, xiafa.Length);
                        Thread.Sleep(65);
                    }
                    if (openClose == false)
                    {
                        openClose = true;
                        ktBtnAgain.Text = "再  现";
                    }
                }
                catch (System.Exception ex)
                {
                    MessageBox.Show("串口紧急关闭!", "软件提醒", MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation);
                }
 
 
            }
        }
 
        //读取文件
        private List<Array> ReadFile()
        {
            List<Array> myArray = new List<Array>();
            if (File.Exists(FILE_INFO_NAME))
            {
                FileStream myFile = new FileStream(FILE_INFO_NAME, FileMode.Open, FileAccess.Read);
 
   
 
 
                using (StreamReader sr = new StreamReader(FILE_INFO_NAME, UnicodeEncoding.GetEncoding("GB2312")))
                {
                    string Line = null;
                    string[] duoji = new string[4];  //存储一条数据 
                    for (Line = sr.ReadLine(); Line != null; Line = sr.ReadLine())
                    {
                        if (!(Line.Trim() == ""))
                        {
                            duoji = Line.Trim().Split(new string[] { "**" }, StringSplitOptions.RemoveEmptyEntries);
                            myArray.Add(duoji);
                        }
                    }
 
                }
            }
            return myArray;
            #region
            //备份读取文件的执行过程
                //using (StreamReader sr = new StreamReader(FILE_INFO_NAME, UnicodeEncoding.GetEncoding("GB2312")))
                //{
                //    string Line = null;
                //    string[] duoji = new string[4];  //存储一条数据 
                //    for (Line = sr.ReadLine(); Line != null; Line = sr.ReadLine())
                //    {
                //        if (!(Line.Trim() == ""))
                //        {
                //            duoji = Line.Trim().Split(new string[] { "**" }, StringSplitOptions.RemoveEmptyEntries);
                //            myArray.Add(duoji);
                //        }
                //    }
 
                //}
 
 
                //使用每次读取固定长度的字节,遇到 问题是加车符号的处理
                //int FileSize = 172;
 
                //if (FileSize < myFile.Length)
                //{
                //    byte[] buffer = new byte[FileSize];
                //    int copy = 0;
                //    string[] duoji = new string[4];  //存储一条数据 
                //    string temp1;
                //    ArrayList myAL = new ArrayList();
 
                //    while (copy <= myFile.Length)
                //    {
                //        myFile.Read(buffer, 0, FileSize);
                //        myFile.Flush();
                //        copy += FileSize;
                //        myFile.Position = copy;
                //        temp1 = System.Text.Encoding.Default.GetString(buffer);
                //        //myAL = MyClass.Str16ToArrayList(temp1);
                //        //duoji = MyClass.byteToHexStr(buffer).Trim().Split(new string[] { "**" }, StringSplitOptions.RemoveEmptyEntries);
                //        string jiequ = temp1.Replace("\r\n", "");
                //        duoji = jiequ.Trim().Split(new string[] { "**" }, StringSplitOptions.RemoveEmptyEntries);
                //        myArray.Add(duoji);
                //    }
            //}
            #endregion
        }
 
 
        //复位选择
        private void ktBtnFuwei_Click(object sender, EventArgs e)
        {
            if (!serialPort1.IsOpen)
            {
                MessageBox.Show("请打开串口!", "软件提醒", MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation);
            }
            else
            {
                Closing = true;
                byte[] bytes = MyClass.StringToHex("FF FF FE 18 83 1E 04 01 00 08 0A 00 02 00 08 0A 00 03 00 08 0A 00 04 00 08 0A 00 F2");
                serialPort1.Write(bytes, 0, bytes.Length);
            }
        }
    }
}

********************************************************************************************************************************************************
 using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Collections;
namespace System
{
    class MyClass
    {
        public MyClass()
        {
        }
 
        /// <summary>
        /// 字符串转16进制字节数组
        /// </summary>
        /// <param name="hexString"></param>
        /// <returns></returns>
        public static byte[] StringToHex(string str)
        {
            string[] strs = str.Split(' ');
            byte[] bytes = new byte[strs.Length];
            int index = 0;
            foreach (string s in strs)
            {
                int hex = Convert.ToInt32(s, 16);
                bytes[index++] = (byte)hex;
            }
            return bytes;
        }
 
        /// <summary>
        /// 字节数组转16进制字符串
        /// </summary>
        /// <param name="bytes"></param>
        /// <returns></returns>
        public static string byteToHexStr(byte[] bytes)
        {
            string returnStr = "";
            if (bytes != null)
            {
                for (int i = 0; i < bytes.Length; i++)
                {
                    //ToString("X2") 为C#中的字符串格式控制符
                    //假设有两个数10和26,正常情况十六进制显示0xA、0x1A,这样看起来不整齐,为了好看,可以指定"X2",这样显示出来就是:0x0A、0x1A。
                    returnStr += bytes[i].ToString("X2");
                    returnStr += " ";
                }
            }
            return returnStr;
        }
 
        public static byte CheckSum(byte[] temp)
        {
            byte sum;
            byte jia=0X00;
            for (int i = 0; i < temp.Length;i++ )
            {
                jia += temp[i];
            }
            sum= (byte)~(0xff & jia);
            return sum;
        }
 
        public static ArrayList Str16ToArrayList(string strIn)
        {
            string sParse = "";
            ArrayList myAL = new ArrayList();
 
            int i = 0;
            foreach (char cc in strIn)
            {
                i++;
                if (cc == ' ' || sParse.Length == 2)
                {
                    myAL.Add(sParse);
                    if (sParse.Length == 2 && cc != ' ')
                    {
                        sParse = Convert.ToString(cc);
                    }
                    else
                    {
                        sParse = "";
                    }
                }
                else
                {
                    sParse += Convert.ToString(cc);
                    if (i == strIn.Length && cc != ' ')
                    {
                        myAL.Add(sParse);
                    }
                }
            }
            return myAL;
        }
 
 
    }
 
}
******************************************************************************************************************************************************** 



第一次软件界面:





软件由主程序 和myClass类两部分组成,其中myClass 类主要处理字符与字节之间转换及求取校验位。

主程序:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.IO.Ports;
using System.Windows.Forms;
using System.Text.RegularExpressions;
using System.Threading;
using System.IO;
//DotNetMagic
using Crownwood.DotNetMagic;
using Crownwood.DotNetMagic.Common;
using Crownwood.DotNetMagic.Controls;
using Crownwood.DotNetMagic.Docking;
using Crownwood.DotNetMagic.Forms;
using System.Runtime.InteropServices;
 
namespace ComAssistant
{
    public partial class ComAssistantForm : DotNetMagicForm
    {
        private StringBuilder builder = new StringBuilder();//避免在事件处理方法中反复的创建,定义到外面。  
        private int received_count = 0;//接收计数  
        private long send_count = 0;//发送计数
        private bool Listening = false;//是否没有执行完invoke相关操作  
        private bool Closing = false;//是否正在关闭串口,执行Application.DoEvents,并阻止再次invoke  
        private bool Query = false;//查询状态 
        private List<byte> buffer = new List<byte>(4096);//默认分配1页内存,并始终限制不允许超过  
        private byte[] binary_data_1 = new byte[10];//FF FF 01 06 00 B1 0A 00 00 3D 
        List<Array> dataList = new List<Array>();  //数据再现
        string FILE_INFO_NAME = "FileInfo.txt";
        private bool kaiguan = true;
        private bool openClose = true;
 
 
        string strShow = "";
 
        public ComAssistantForm()
        {
            InitializeComponent();
        }
 
        private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            //返回指令格式:FF FF 01 06 00 B1 0A 00 00 3D   ,   其中B1 0A为ID为1的舵机当前位置,00 00为其当前速度
            // 第2位是舵机,5、6位是位置,7、8位为速度
 
            if (Closing) return;//如果正在关闭,忽略操作,直接返回,尽快的完成串口监听线程的一次循环
            try
            {
                Listening = true;//设置标记,说明我已经开始处理数据,一会儿要使用系统UI的。
                int n = serialPort1.BytesToRead;//先记录下来,避免某种原因,人为的原因,操作几次之间时间长,缓存不一致
                byte[] buf = new byte[n];//声明一个临时数组存储当前来的串口数据
                serialPort1.Read(buf, 0, n);//读取缓冲数据
 
                /////////////////////////////////////////////////////////////////////////////////////////////////////////////
                //<协议解析>
                //1.缓存数据
                buffer.AddRange(buf);
                //2.完整性判断
                while (buffer.Count >= 10)//至少要包含头(2字节)+长度(7字节)+校验(1字节)
                {
                    //2.1 查找数据头
                    if (buffer[0] == 0xFF && buffer[1] == 0xFF)
                    {
                        received_count++;
                        /*
                        //校验
                        byte checksum = 0;
                        for (int i = 0; i < len + 3; i++)//len+3表示校验之前的位置
                        {
                            checksum ^= buffer[i];
                        }
                        if (checksum != buffer[len + 3]) //如果数据校验失败,丢弃这一包数据
                        {
                            buffer.RemoveRange(0, len + 4);//从缓存中删除错误数据
                            continue;//继续下一次循环
                        }
                        */
 
                        //至此,已经被找到了一条完整数据。我们将数据直接分析,或是缓存起来一起分析
                        buffer.CopyTo(0, binary_data_1, 0, 9);//复制一条完整数据到具体的数据缓存
                        //3.分析数据
 
                        //根据舵机编号分类并存入TXT文件
                        string zhuanbian = binary_data_1[7].ToString("X2");
                        if (zhuanbian == "00")
                        {
                            zhuanbian = "01";
                        }
 
                        //我们的数据都是定好格式的,所以当我们找到分析出的数据1,就知道固定位置一定是这些数据,我们只要显示就可以了
                        string data = binary_data_1[2].ToString("X2") + " " +
                            binary_data_1[5].ToString("X2") + " " + binary_data_1[6].ToString("X2") + " " +
                            zhuanbian + " " + binary_data_1[8].ToString("X2");
 
 
 
                        //更新界面
                        this.Invoke((EventHandler)(delegate
                        {
                            kryptonRichTextBox1.Text += data + "**";
                            WriteFile(data);
 
                        }));
                        if (received_count == 4)
                        {
                            WriteFileLine();
                            received_count = 0;
                            //更新界面
                            this.Invoke((EventHandler)(delegate
                            {
                                kryptonRichTextBox1.Text += "\r\n";
                            }));
                        }
                        buffer.RemoveRange(0, 10);//正确分析一条数据,从缓存中移除数据。
                    }
                    else
                    {
                        //这里是很重要的,如果数据开始不是头,则删除数据
                        buffer.RemoveAt(0);
                    }
                }
            }
            finally
            {
                //写入文档
                Listening = false;//我用完了,ui可以关闭串口了。
            }
 
 
 
 
            #region
            /*
            //接收指令函数
            int n = serialPort1.BytesToRead;
            byte[] dbs = new byte[n];
            int len = serialPort1.Read(dbs, 0, n);
            int[] num = new int[len];
            string show = "";
            show = MyClass.byteToHexStr(dbs);
            strShow += show + "\r\n";
            this.Invoke((EventHandler)delegate { kryptonRichTextBox1.Text = strShow; });
            */
 
 
            /*
            //查询01舵机
            //try
            //{
            Listening = true;//设置标记,说明我已经开始处理数据,一会儿要使用系统UI的。  
            int nn = serialPort1.BytesToRead;//先记录下来,避免某种原因,人为的原因,操作几次之间时间长,缓存不一致  
            byte[] buf = new byte[nn];//声明一个临时数组存储当前来的串口数据  
            received_count += nn;//增加接收计数  
            serialPort1.Read(buf, 0, nn);//读取缓冲数据  
            /////////////////////////////////////////////////////////////////////////////////////////////////////////////  
            //<协议解析>  
            bool data_1_catched = false;//缓存记录数据是否捕获到  
            //1.缓存数据  
            buffer.AddRange(buf);
            //2.完整性判断  
            //while (buffer.Count >= 5)//至少要包含头(2字节)+长度(1字节)+校验(1字节)  
            //{
            //请不要担心使用>=,因为>=已经和>,<,=一样,是独立操作符,并不是解析成>和=2个符号  
            //2.1 查找数据头  
            if (buf[0] == 0xFF && buf[1] == 0xFF)
            {
                int len = buf[2];//数据长度  
 
                if (1 == len)
                {
                    binary_data_1[0] = buffer[5];
                    binary_data_1[1] = buffer[6];
                    binary_data_1[2] = buffer[7];
                    binary_data_1[3] = buffer[8];
                }
 
                //更新界面   
                this.Invoke((EventHandler)delegate { kryptonRichTextBox1.Text = MyClass.byteToHexStr(binary_data_1); });
            }
 
 
            */
 
 
 
            // this.Invoke((EventHandler)(delegate { txData.Text = data; }));  
            //strShow = ;
            //strShow = "舵机1接收到的指令:" + binary_data_1[0].ToString() + " " + binary_data_1[1].ToString() + " " + binary_data_1[2].ToString() + " " + binary_data_1[3].ToString();
            //    buffer.CopyTo(0,binary_data_1,0,4);//复制一条完整数据到具体的数据缓存  
            //    buffer.CopyTo(0, binary_data_1, 5, 4);//复制一条完整数据到具体的数据缓存  
            //data_1_catched = true;
            //buffer.RemoveRange(0, len + 4);//正确分析一条数据,从缓存中移除数据。  
 
            //                   }
            //                   else
            //                   {
            //                       //这里是很重要的,如果数据开始不是头,则删除数据  
            //                       buffer.RemoveAt(0);
            //                   }
            //               }
            //               //分析数据  
            //               if (data_1_catched)
            //               {
            //                   //我们的数据都是定好格式的,所以当我们找到分析出的数据1,就知道固定位置一定是这些数据,我们只要显示就可以了  
            //                   string data = binary_data_1[5].ToString("X2") + " " + binary_data_1[6].ToString("X2") + " " +
            //                       binary_data_1[7].ToString("X2") + " " + binary_data_1[8].ToString("X2");
            //                   //更新界面  
            //                   this.Invoke((EventHandler)(delegate { kryptonRichTextBox1.Text = data; }));
            //               }
            //               //如果需要别的协议,只要扩展这个data_n_catched就可以了。往往我们协议多的情况下,还会包含数据编号,给来的数据进行  
            //               //编号,协议优化后就是: 头+编号+长度+数据+校验  
            //               //</协议解析>  
            //               /////////////////////////////////////////////////////////////////////////////////////////////////////////////  
            //               //因为要访问ui资源,所以需要使用invoke方式同步ui。  
            //               this.Invoke((EventHandler)(delegate
            //               {
            //                   //判断是否是显示为16禁止  
            ////依次的拼接出16进制字符串  
            //                       foreach (byte b in buf)
            //                       {
            //                           builder.Append(b.ToString("X2") + " ");
            //                       }
 
            //                   //追加的形式添加到文本框末端,并滚动到最后。  
            //                   this.kryptonRichTextBox1.AppendText(builder.ToString());
            //                   //修改接收计数  
            //               }));
            //           }
            //           finally
            //           {
            //               Listening = false;//我用完了,ui可以关闭串口了。  
            //           }  
            #endregion
 
        }
 
        //初始化
        private void ComAssistantForm_Load(object sender, EventArgs e)
        {
            int ii = 0;
            foreach (string s in SerialPort.GetPortNames())
            {
                if (ii >= 1)
                {
                    ktComboBoxCom.Items.Add(s);
                }
                ii++;
            }
 
            if (SerialPort.GetPortNames().Length != 0)
            {
                ktComboBoxCom.Text = (string)ktComboBoxCom.Items[0];
            }
 
            //string[] strCom = new string[] { "COM1", "COM2", "COM3", "COM4" };
            //ktComboBoxCom.DataSource = strCom;
 
 
            string[] ss = new string[] {"1000000" , "9600", "19200", "57600" };
            ktComboBoxBount.DataSource = ss;
 
            ktComboBoxJiaoyan.DataSource = Enum.GetNames(typeof(Parity));
 
            ss = new string[] { "5", "6", "7", "8" };
            ktComboBoxData.DataSource = ss;
            ktComboBoxData.Text = "8";
 
            ktComboBoxStop.DataSource = Enum.GetNames(typeof(StopBits));
            ktComboBoxStop.Text = Enum.Format(typeof(StopBits), StopBits.One, "G");
            kryptonTextBox1.Text = "FF FF 04 04 03 18 00 DC";
 
            //打开串口
            try
            {
                if (!serialPort1.IsOpen)
                {
                    serialPort1.PortName = ktComboBoxCom.Text;
                    serialPort1.BaudRate = Convert.ToInt32(ktComboBoxBount.Text);
                    serialPort1.Parity = (Parity)Enum.Parse(typeof(Parity), ktComboBoxJiaoyan.Text);
                    serialPort1.DataBits = Int32.Parse(ktComboBoxData.Text);
                    serialPort1.StopBits = (StopBits)Enum.Parse(typeof(StopBits), ktComboBoxStop.Text);
                    serialPort1.Encoding = Encoding.GetEncoding("Gb2312");
                    serialPort1.Open();
                    ktLabMessage.Text = "当前连接状态: 已经连接";
                }
            }
            catch (Exception)
            {
 
                MessageBox.Show("初始化出问题,请检查串口设备", "软件提醒", MessageBoxButtons.OKCancel, MessageBoxIcon.Error);
            }
 
 
 
        }
 
        //连接串口
        private void ktBtnLink_Click(object sender, EventArgs e)
        {
            try
            {
                if (!serialPort1.IsOpen)
                {
                    serialPort1.PortName = ktComboBoxCom.Text;
                    serialPort1.BaudRate = Convert.ToInt32(ktComboBoxBount.Text);
                    serialPort1.Parity = (Parity)Enum.Parse(typeof(Parity), ktComboBoxJiaoyan.Text);
                    serialPort1.DataBits = Int32.Parse(ktComboBoxData.Text);
                    serialPort1.StopBits = (StopBits)Enum.Parse(typeof(StopBits), ktComboBoxStop.Text);
                    serialPort1.Encoding = Encoding.GetEncoding("Gb2312");
                    serialPort1.Open();
                    ktLabMessage.Text = "当前连接状态: 已经连接";
                }
            }
            catch (Exception)
            {
 
                MessageBox.Show("初始化出问题,请检查串口设备", "软件提醒", MessageBoxButtons.OKCancel, MessageBoxIcon.Error);
            }
        }
 
        //关闭串口
        private void ktBtnClose_Click(object sender, EventArgs e)
        {
            if (serialPort1.IsOpen)
            {
                serialPort1.Close();
                ktLabMessage.Text = "当前连接状态: 已经关闭";
            }
        }
 
        //发送数据
        private void ktBtnSend_Click(object sender, EventArgs e)
        {
            if (!serialPort1.IsOpen)
            {
                MessageBox.Show("请打开串口!", "软件提醒", MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation);
            }
            else
            {
                string content = kryptonTextBox1.Text;
                byte[] bytes = MyClass.StringToHex(content);
                serialPort1.Write(bytes, 0, bytes.Length);
 
            }
        }
 
        //接收数据
        private void ktBtnAccept_Click(object sender, EventArgs e)
        {
            int n = serialPort1.BytesToRead;
            byte[] dbs = new byte[n];
            int len = serialPort1.Read(dbs, 0, n);
            int[] num = new int[len];
 
            string show = "";
            show = MyClass.byteToHexStr(dbs);
            kryptonRichTextBox1.Text += show;
        }
 
        //清空数据
        private void ktBtnClear_Click(object sender, EventArgs e)
        {
            strShow = "";
            kryptonRichTextBox1.Text = "";
        }
 
 
 
        private void timer1_Tick(object sender, EventArgs e)
        {
            kryptonRichTextBox1.Text = strShow;
        }
 
 
 
        //急停指令
        private void ktBtnStop_Click(object sender, EventArgs e)
        {
            if (!serialPort1.IsOpen)
            {
                MessageBox.Show("请打开串口!", "软件提醒", MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation);
            }
            else
            {
                byte[] bytes = MyClass.StringToHex("FF FF FE 1C 83 20 02 01 01 00 02 01 00 03 01 00 04 01 00 05 01 00 06 01 00 07 01 00 08 01 00 14");
                serialPort1.Write(bytes, 0, bytes.Length);
            }
        }
 
        //查询舵机状态
        private void ktBtnQuery_Click(object sender, EventArgs e)
        {
 
            try
            {
                FileStream stream2 = File.Open(FILE_INFO_NAME, FileMode.OpenOrCreate, FileAccess.Write);
                stream2.Seek(0, SeekOrigin.Begin);
                stream2.SetLength(0); //清空txt文件
                stream2.Close();
            }
            catch (System.Exception ex)
            {
           
            }
            finally
            {
                MessageBox.Show("缓存数据已经清空!", "软件提醒", MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation);
            }
 
 
            //if (!Query)
            //{
            //    ktBtnQuery.Text = "停止查询舵机";
            //    Query = true;
 
            //    if (!serialPort1.IsOpen)
            //    {
            //        MessageBox.Show("请打开串口!", "软件提醒", MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation);
            //    }
            //    else
            //    {
            //        for (int i = 0; i < 10; i++)
            //        {
            //            //这是舵机返回指令,发送指令没有加入
            //            //舵机1
            //            byte[] bytes = MyClass.StringToHex("FF FF 01 06 00 B1 0A 01 01 3D");
            //            serialPort1.Write(bytes, 0, bytes.Length);
 
            //            //舵机2
            //            bytes = MyClass.StringToHex("FF FF 02 06 00 B1 0A 02 02 3D");
            //            serialPort1.Write(bytes, 0, bytes.Length);
 
            //            //舵机3
            //            bytes = MyClass.StringToHex("FF FF 03 06 00 B1 03 03 03 3D");
            //            serialPort1.Write(bytes, 0, bytes.Length);
 
            //            //舵机4
            //            bytes = MyClass.StringToHex("FF FF 04 06 00 B1 04 04 04 3D");
            //            serialPort1.Write(bytes, 0, bytes.Length);
            //            //Thread.Sleep(5000);
 
            //        }
 
            //    }
 
            //}
            //else
            //{
            //    ktBtnQuery.Text = "查询舵机状态";
            //    Query = false;
            //}
        }
 
        /// <summary>
        /// 写入舵机位置速度信息
        /// </summary>
        private void WriteFile(string input)
        {
            using (StreamWriter sw = File.AppendText("FileInfo.txt"))
            {
                sw.Write(input + " ** ");
            }
 
            ///文件目录
            //FileStream fs = new FileStream("FileInfo.txt", FileMode.Create);
            //StreamWriter sw = new StreamWriter(fs);
            //sw.WriteLine(input + "\n");
            ////清空缓冲区
            //sw.Flush();
            ////关闭流
            //sw.Close();
            //fs.Close();
            ///写入内容并换行
        }
 
        /// <summary>
        /// 换行
        /// </summary>
        private void WriteFileLine()
        {
            using (StreamWriter sw = File.AppendText(FILE_INFO_NAME))
            {
                sw.WriteLine();
            }
        }
 
        //读取文件
        private List<Array> ReadFile()
        {
            List<Array> myArray = new List<Array>();
            if (File.Exists(FILE_INFO_NAME))
            {
                using (StreamReader sr = new StreamReader(FILE_INFO_NAME, UnicodeEncoding.GetEncoding("GB2312")))
                {
                    string Line = null;
                    string[] duoji = new string[4];  //存储一条数据 
                    for (Line = sr.ReadLine(); Line != null; Line = sr.ReadLine())
                    {
                        if (!(Line.Trim() == ""))
                        {
                            duoji = Line.Trim().Split(new string[] { "**" }, StringSplitOptions.RemoveEmptyEntries);
                            myArray.Add(duoji);
                        }
                    }
                    
                }
 
            }
 
            return myArray;
        }
 
 
 
        //再现过程
        private void ktBtnAgain_Click(object sender, EventArgs e)
        {
            if (openClose)
            {
                //这里创建一个线程,使之执行Alpha类的Beta()方法
                Thread newThread = new Thread(new ThreadStart(zaixianThread));
                newThread.Start();
                ktBtnAgain.Text = "停  止";
                openClose = false;
 
            }
            else
            {
                openClose = true;
                ktBtnAgain.Text = "再  现";
 
            }
        }
 
 
        //创建再现新线程
        public void zaixianThread()
        {
            if (!serialPort1.IsOpen)
            {
                MessageBox.Show("请打开串口!", "软件提醒", MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation);
            }
            else
            {
                while (true)
                {
                    if (openClose == true)
                    {
                        break;
                    }
 
 
                //获取数据
                dataList = ReadFile();
 
                //FF FF FE 18 83 1E 04 01 00 00 11 11 02 22 22 33 33 03 44 44 55 55 04 66 66 77 99 18 
                foreach (Array ar in dataList) //获取一条四个舵机数据
                {
                    
                    string zuhe = "";
                    byte[] xiafa = new byte[28];
                    foreach (string str in ar)   //获取一个舵机数据
                    {
                        zuhe += str.Trim() + " ";
                    }
                    string hh = zuhe.Trim();
                    byte[] temp = MyClass.StringToHex(hh);
 
                    byte[] tempOne=new byte[26];
                    for (int i = 0; i < 20; i++)
                    {
                        xiafa[i + 7] = temp[i];
                        //tempOne[i+5]=temp[i];
                    }
                    xiafa[0] = 0XFF;
                    xiafa[1] = 0XFF;
                    xiafa[2] = 0XFE;
                    xiafa[3] = 0X18;
                    xiafa[4] = 0X83;
                    xiafa[5] = 0X1E;
                    xiafa[6] = 0X04;
 
                    Array.Copy(xiafa, 2, tempOne, 0, 26);//复制数组
                    xiafa[27] = MyClass.CheckSum(tempOne);   
                    serialPort1.Write(xiafa, 0, xiafa.Length);
                    Thread.Sleep(100);
 
                }
 
                }
            }
        }
 
 
        //采集指令
        private void ktBtnCollection_Click(object sender, EventArgs e)
        {
            if (kaiguan)
            {
                //这里创建一个线程,使之执行Alpha类的Beta()方法
                Thread newThread = new Thread(new ThreadStart(caijiThread));
                newThread.Start();
                ktBtnCollection.Text = "停  止";
                kaiguan = false;
 
            }
            else
            {
                kaiguan = true;
                ktBtnCollection.Text = "采  集";
 
            }
        }
 
        //创建采集新线程
        public void caijiThread()
        {
            if (!serialPort1.IsOpen)
            {
                MessageBox.Show("请打开串口!", "软件提醒", MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation);
            }
            else
            {
                while (true)
                {
                    if (kaiguan == true)
                    {
                        break;
                    }
 
                    //采集[指令固定]
                    byte[] bytes = MyClass.StringToHex("FF FF FE 14 83 18 01 01 00 02 00 03 00 04 00 05 00 06 00 07 00 08 00 2D");
                    serialPort1.Write(bytes, 0, bytes.Length);
 
                    //舵机01的指令FF FF 01 04 02 24 04 D0
                    byte checksum = 0;
                    for (int QueryNum = 1; QueryNum < 5; QueryNum++)
                    {
                        //求校验和,方法一
                        checksum = ((byte)(QueryNum + 0X04 + 0X02 + 0X24 + 0X04));
                        byte[] ab = new byte[1];
                        ab[0] = (byte)~(0xff & checksum);
                        string mySum = MyClass.byteToHexStr(ab);
 
                        //////求校验和,方法二
                        //byte[] tempab = new byte[] { 0X01, 0X04, 0X02, 0X24, 0X04 };
                        //byte strab = MyClass.CheckSum(tempab);
 
                        byte[] bytesCaiji = MyClass.StringToHex("FF FF " + QueryNum.ToString("X2") + " 04 02 24 04 " + mySum.Trim());
                        serialPort1.Write(bytesCaiji, 0, bytesCaiji.Length);
                        Thread.Sleep(6);
                    }
                    Thread.Sleep(100);
                }
            }
        }
    }
}
 


*******************************************************************************************************************************************************
myClass类:

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Collections;
namespace System
{
    class MyClass
    {
        public MyClass()
        {
        }
 
        /// <summary>
        /// 字符串转16进制字节数组
        /// </summary>
        /// <param name="hexString"></param>
        /// <returns></returns>
        public static byte[] StringToHex(string str)
        {
            string[] strs = str.Split(' ');
            byte[] bytes = new byte[strs.Length];
            int index = 0;
            foreach (string s in strs)
            {
                int hex = Convert.ToInt32(s, 16);
                bytes[index++] = (byte)hex;
            }
            return bytes;
        }
 
        /// <summary>
        /// 字节数组转16进制字符串
        /// </summary>
        /// <param name="bytes"></param>
        /// <returns></returns>
        public static string byteToHexStr(byte[] bytes)
        {
            string returnStr = "";
            if (bytes != null)
            {
                for (int i = 0; i < bytes.Length; i++)
                {
                    //ToString("X2") 为C#中的字符串格式控制符
                    //假设有两个数10和26,正常情况十六进制显示0xA、0x1A,这样看起来不整齐,为了好看,可以指定"X2",这样显示出来就是:0x0A、0x1A。
                    returnStr += bytes[i].ToString("X2");
                    returnStr += " ";
                }
            }
            return returnStr;
        }
 
        public static byte CheckSum(byte[] temp)
        {
            byte sum;
            byte jia=0X00;
            for (int i = 0; i < temp.Length;i++ )
            {
                jia += temp[i];
            }
            sum= (byte)~(0xff & jia);
            return sum;
        }
 
        public static ArrayList Str16ToArrayList(string strIn)
        {
            string sParse = "";
            ArrayList myAL = new ArrayList();
 
            int i = 0;
            foreach (char cc in strIn)
            {
                i++;
                if (cc == ' ' || sParse.Length == 2)
                {
                    myAL.Add(sParse);
                    if (sParse.Length == 2 && cc != ' ')
                    {
                        sParse = Convert.ToString(cc);
                    }
                    else
                    {
                        sParse = "";
                    }
                }
                else
                {
                    sParse += Convert.ToString(cc);
                    if (i == strIn.Length && cc != ' ')
                    {
                        myAL.Add(sParse);
                    }
                }
            }
            return myAL;
        }
 
 
    }
 
}
 

 

posted @ 2014-04-25 14:33  ZhongJieKing  阅读(3582)  评论(0)    收藏  举报