三维姿态显示上位机 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 版权协议,转载请附上原文出处链接和本声明。