三维姿态显示上位机 C#+WPF+HID+Unity3D技术

  • Post author:
  • Post category:其他




三维姿态显示上位机 C#+WPF+HID+Unity3D技术

在毕业设计中做了一个基于AHRS的设计,涉及了姿态融合算法,为了调试算法参数性能,需要用到姿态显示上位机用来显示验证算法的效果。在收集了很多资料后,决定自己做一个三维姿态显示上位机。由于本人学识浅薄,程序中可能有很多目前没有发现的BUG,如有纰漏,敬请指正。


下面介绍一下我开发的大致方法:


1、HID数据传输,在开发的过程中,采用无线传输的方式将单片机的数据传输到电脑的USB端,再采用HID协议将USB端点的数据上传到电脑。

2、在电脑上位机中检测上传到电脑的数据,当检测到数据后,通过TCP协议与Unity3D通讯,Unity3D作为服务器,根据通信内容执行相关操作。

3、上位机UI采用开源的PanuonUI进行开发。


该上位机的优劣:


优点:开发简单,不用使用OpenGL开发,仅使用C#语言就可以完成开发;界面美观,贴图和背景相比OpenGL开发效果好很多;运行中Unity3D使用了GPU渲染,相比软渲染流畅度更好。

缺点:Unity3D的EXE文件较大,比采用C++开发的上位机体量大很多;可以作为学习和实验使用,距离商用还有很大差距。

由于整个上位机的开发过程叙述起来比较麻烦,这里直接放出源码供需要的人下载使用。




源码下载



关键代码:


HID对接收数据进行转换:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
using System.Windows.Interop;
using System.Windows.Threading;
using HID;
using System.Runtime.InteropServices;
using Panuon.UI.Silver;

namespace CubeAttitudeShow
{
    public partial class MainWindow : WindowX
    {
        #region HID_协议定义与实现
        /*定义HID数传的内容*/
        public Hid myHid = new Hid();
        public IntPtr myHidPtr = new IntPtr();
        string IMUKeyNum;
        int temp;
        public bool BiaoDingFlag = false;
        public bool BiaoDingFlag2 = false;

        double MagX,MagY,MagZ;

        string SmagX, SmagY, SmagZ;

        public string Dis1;

        public string Dis2;

        public string Yaw;

        public string Pitch;

        public string Roll;

        /***************************************************************************/
        /// <summary>
        ///  数据到达事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void myhid_DataReceived(object sender, report e)
        {
            if (lianjieflag)
            {
                RecDataBuffer = e.reportBuff;

                for (int a = 0; a < 6; a++)
                    TransformData[a] = RecDataBuffer[a + 5];
                WpfServer.SendMessage(TransformData);
                /*把数据显示到列表框*/
                IMUKeyNum = RecDataBuffer[0].ToString();//按键信息

                temp = 0;
                if (RecDataBuffer[1] >= 0x80) //判定是否是负数
                    temp = 0 - ((RecDataBuffer[1] & 0x7f) * 256 + RecDataBuffer[2]);
                else
                    temp = (RecDataBuffer[1]) * 256 + RecDataBuffer[2];
                Dis1 = temp.ToString();//基站 1 距离 

                temp = 0;
                if (RecDataBuffer[3] >= 0x80) //判定是否是负数
                    temp = 0 - ((RecDataBuffer[3] & 0x7f) * 256 + RecDataBuffer[4]);
                else
                    temp = (RecDataBuffer[3]) * 256 + RecDataBuffer[4];
                Dis2 = temp.ToString();//基站 2 距离 

                temp = 0;
                if (RecDataBuffer[5] >= 0x80)
                    temp = 0 - ((RecDataBuffer[5] & 0x7f) * 256 + RecDataBuffer[6]) / 10;
                else
                    temp = ((RecDataBuffer[5]) * 256 + (RecDataBuffer[6])) / 10;
                Yaw = temp.ToString();//偏航角
                MagX = temp * 0.00256;
                SmagX = MagX.ToString();

                temp = 0;
                if (RecDataBuffer[7] >= 0x80)
                    temp = 0 - ((RecDataBuffer[7] & 0x7f) * 256 + RecDataBuffer[8]) / 10;
                else
                    temp = ((RecDataBuffer[7]) * 256 + (RecDataBuffer[8])) / 10;
                Pitch = temp.ToString();//俯仰角 
                MagY = temp * 0.00256;
                SmagY = MagY.ToString();

                temp = 0;
                if (RecDataBuffer[9] >= 0x80)
                    temp = 0 - ((RecDataBuffer[9] & 0x7f) * 256 + RecDataBuffer[10]) / 10;
                else
                    temp = ((RecDataBuffer[9]) * 256 + (RecDataBuffer[10])) / 10;
                Roll = temp.ToString();//滚转角 
                MagZ = temp * 0.00256;
                SmagZ = MagZ.ToString();
                this.Dispatcher.Invoke(new Action(delegate { KeyCommand.Text = IMUKeyNum; }));
                this.Dispatcher.Invoke(new Action(delegate { Station1.Text = Dis1; }));
                this.Dispatcher.Invoke(new Action(delegate { Station2.Text = Dis2; }));
                this.Dispatcher.Invoke(new Action(delegate { PitchAngle.Text = Pitch; }));
                this.Dispatcher.Invoke(new Action(delegate { YawAngle.Text = Yaw; }));
                this.Dispatcher.Invoke(new Action(delegate { RollAngle.Text = Roll; }));
            }
        }

        /// <summary>
        /// 设备移除事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void myhid_DeviceRemoved(object sender, EventArgs e)
        {
            CloseDevice();
            this.Dispatcher.Invoke(new Action(delegate { LianJie.Content = tubiao + " 失去连接 "; }));
            lianjieflag = false;
        }
        /***************************************************************************/

        Byte[] RecDataBuffer = new byte[90];


        /// <summary>
        /// 打开设备
        /// </summary>
        protected bool OpenDevice()
        {
            if (myHid.Opened == false)
            {
                UInt16 myVendorID = 0x3333;
                UInt16 myProductID = 0x4444;
                if ((int)(myHidPtr = myHid.OpenDevice(myVendorID, myProductID)) != -1)//调用开启HID函数来开启HID口
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
            else
            {
                return true;
            }
        }
        /// <summary>
        /// 设备关闭
        /// </summary>
        protected void CloseDevice()
        {
            myHid.CloseDevice(myHidPtr);
        }

        /// <summary>
        /// 窗口加载时完成事件说明
        /// </summary>
        protected void HID_Loaded()
        {
            myHid.DataReceived += new EventHandler<HID.report>(myhid_DataReceived); //订阅DataRec事件
            myHid.DeviceRemoved += new EventHandler(myhid_DeviceRemoved);
        }
        #endregion       
    }
}


TCP客户端:

using System.Net.Sockets;
using System.Net;
using System.Threading;
using System.Diagnostics;
using System.Text;
using System;

namespace CubeAttitudeShow
{
    class TcpServer
    {
        //私有成员
        private static byte[] result = new byte[1024];
        private int myProt = 500;   //端口  
        static Socket serverSocket;//服务器接口
        static Socket clientSocket;//客户接口
        Thread myThread;
        static Thread receiveThread;
        //属性
        public int port { get; set; }
        //方法     
        internal void StartServer()
        {
            //服务器IP地址  
            IPAddress ip = IPAddress.Parse("127.0.0.1");
            serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            serverSocket.Bind(new IPEndPoint(ip, myProt));  //绑定IP地址:端口  
            serverSocket.Listen(10);    //设定最多10个排队连接请求  
            myThread = new Thread(ListenClientConnect);
            myThread.Start();
            
        }

        internal void QuitServer()
        {
            serverSocket.Close();
            clientSocket.Close();
            myThread.Abort();
            receiveThread.Abort();
        }


        internal void SendMessage(byte[] msg)
        {
                clientSocket.Send(msg);
        }



        /// <summary>  
        /// 监听客户端连接  
        /// </summary>  
        private static void ListenClientConnect()
        {
            while (true)
            {
                try
                {
                    clientSocket = serverSocket.Accept();
                    //clientSocket.Send(Encoding.ASCII.GetBytes("Server Say Hello"));
                    receiveThread = new Thread(ReceiveMessage);
                    receiveThread.Start(clientSocket);
                }
                catch (Exception)
                {
                }
            }
        }

        /// <summary>  
        /// 接收消息  
        /// </summary>  
        /// <param name="clientSocket"></param>  
        private static void ReceiveMessage(object clientSocket)
        {
            Socket myClientSocket = (Socket)clientSocket;
            while (true)
            {
                try
                {
                    //通过clientSocket接收数据  
                    int receiveNumber = myClientSocket.Receive(result);
                    //Debug.WriteLine("接收客户端{0}消息{1}", myClientSocket.RemoteEndPoint.ToString(), Encoding.ASCII.GetString(result, 0, receiveNumber));
                }
                catch (Exception ex)
                {
                    try
                    {
                        Debug.WriteLine(ex.Message);
                        myClientSocket.Shutdown(SocketShutdown.Both);
                        myClientSocket.Close();
                        break;
                    }
                    catch (Exception)
                    {
                    }

                }
            }
        }
    }
}  


Unity嵌套:

using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Threading;

namespace CubeAttitudeShow
{
    public partial class UnityControl : UserControl
    {
        [DllImport("User32.dll")]
        static extern bool MoveWindow(IntPtr handle, int x, int y, int width, int height, bool redraw);

        internal delegate int WindowEnumProc(IntPtr hwnd, IntPtr lparam);
        [DllImport("user32.dll")]
        internal static extern bool EnumChildWindows(IntPtr hwnd, WindowEnumProc func, IntPtr lParam);

        [DllImport("user32.dll")]
        static extern int SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);

        private Process process;
        private IntPtr unityHWND = IntPtr.Zero;

        private const int WM_ACTIVATE = 0x0006;
        private readonly IntPtr WA_ACTIVE = new IntPtr(1);
        private readonly IntPtr WA_INACTIVE = new IntPtr(0);

        public UnityControl()
        {
            InitializeComponent();
            this.Load += UnityControl_Load;
            panel1.Resize += panel1_Resize;
        }

        private void UnityControl_Load(object sender, EventArgs e)
        {
            try
            {
                process = new Process();
                //process.StartInfo.FileName = @"D:\毕业设计\毕业设计\电脑软件设计\CubeAttitude\CubeAttitude3DShow\EXE\CubeAttitude3DShow.exe";
                process.StartInfo.FileName = Application.StartupPath + @"\Cube Attitude Exe\CubeAttitude3DShow.exe";
                process.StartInfo.Arguments = "-parentHWND " + panel1.Handle.ToInt32() + " " + Environment.CommandLine;
                process.StartInfo.UseShellExecute = true;
                process.StartInfo.CreateNoWindow = true;

                process.Start();

                process.WaitForInputIdle();
                // Doesn't work for some reason ?!
                //unityHWND = process.MainWindowHandle;
                EnumChildWindows(panel1.Handle, WindowEnum, IntPtr.Zero);

                //unityHWNDLabel.Text = "Unity HWND: 0x" + unityHWND.ToString("X8");
            }
            catch (Exception ex)
            {
                //unityHWNDLabel.Text = ex.Message;
                //MessageBox.Show(ex.Message);
            }
        }

        internal void ActivateUnityWindow()
        {
            SendMessage(unityHWND, WM_ACTIVATE, WA_ACTIVE, IntPtr.Zero);
        }

        internal void DeactivateUnityWindow()
        {
            SendMessage(unityHWND, WM_ACTIVATE, WA_INACTIVE, IntPtr.Zero);
        }

        private int WindowEnum(IntPtr hwnd, IntPtr lparam)
        {
            unityHWND = hwnd;
            ActivateUnityWindow();
            return 0;
        }

        private void panel1_Resize(object sender, EventArgs e)
        {
            MoveWindow(unityHWND, 0, 0, panel1.Width, panel1.Height, true);
            ActivateUnityWindow();
        }

        // Close Unity application
        internal void Form1_FormClosed()
        {
            try
            {
                process.CloseMainWindow();

                Thread.Sleep(1000);
                while (process.HasExited == false)
                    process.Kill();
            }
            catch (Exception)
            {

            }
        }

        internal void Form1_Activated()
        {
            ActivateUnityWindow();
        }

        internal void Form1_Deactivate()
        {
            DeactivateUnityWindow();
        }
    }
}



效果图:

在这里插入图片描述



点击这里下载



版权声明:本文为qq_42348833原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。