实现过程:
关于六轴:
线下有一个带有六轴姿态传感器的硬件设备,将采集到的三轴加速度和角速度的值每隔1秒通过串口发送给电脑,电脑上位机使用的是pyqt5,在python中调用serial模块进行串口数据的接收,接收程序如下:
class scsThread(QThread):
update_ui_signal = pyqtSignal(int)
def __init__(self, window):
super(scsThread, self).__init__()
self.CONTROLLER_COM = None
self.window = window
def run(self):
com_list = run.get_COM()
if com_list != None:
self.CONTROLLER_COM = com_list[1]
timeout = 3 * 10 # 3秒超时
ctr_serial = serial.Serial(self.CONTROLLER_COM, 115200)
while timeout > 0: # 判断是否接收超时
# print(timeout)
timeout = timeout - 1
time.sleep(0.1)
n = ctr_serial.inWaiting()
if n:
data = ctr_serial.read(n)
data = data.decode('utf-8')
if data != '':
# print(data)
data_num = data.find("test data is:")
if data_num >= 0:
date_list = re.split(r'[,\s]+', data[data_num-28:])
self.data["euler"] = date_list[16] + " " + date_list[17] + " " + date_list[18] # 欧拉角
timeout = 3 * 10
self.window.STL_Thread_object.update_ui_signal.emit(self.data["euler"]) # 姿态
从帧中解析出原始数据,将其转换为欧拉角,转换程序如下:
// 定义全局变量
double pitch, roll, yaw;
double q0 = 1.0;
double q1 = 0.0;
double q2 = 0.0;
double q3 = 0.0;
double exInt = 0.0;
double eyInt = 0.0;
double ezInt = 0.0;
// 定义常量
const double halfT 0.001; // half of sample period, in seconds
const double Kp = 100;
const double Ki = 0.002;
//欧拉角计算
void update_IMU(double ax, double ay, double az, double gx, double gy, double gz, double* pitch, double* roll, double* yaw) {
// 测量正常化
double norm = sqrt(ax * ax + ay * ay + az * az);
// 单元化
ax = ax / norm;
ay = ay / norm;
az = az / norm;
// 估计方向的重力
double vx = 2 * (q1 * q3 - q0 * q2);
double vy = 2 * (q0 * q1 + q2 * q3);
double vz = q0 * q0 - q1 * q1 - q2 * q2 + q3 * q3;
// 错误的领域和方向传感器测量参考方向之间的交叉乘积的总和
double ex = (ay * vz - az * vy);
double ey = (az * vx - ax * vz);
double ez = (ax * vy - ay * vx);
// 积分误差比例积分增益
exInt = exInt + ex * Ki;
eyInt = eyInt + ey * Ki;
ezInt = ezInt + ez * Ki;
// 调整后的陀螺仪测量
gx = gx + Kp * ex + exInt;
gy = gy + Kp * ey + eyInt;
gz = gz + Kp * ez + ezInt;
// 整合四元数
q0 = q0 + (-q1 * gx - q2 * gy - q3 * gz) * halfT;
q1 = q1 + (q0 * gx + q2 * gz - q3 * gy) * halfT;
q2 = q2 + (q0 * gy - q1 * gz + q3 * gx) * halfT;
q3 = q3 + (q0 * gz + q1 * gy - q2 * gx) * halfT;
// 正常化四元数
norm = sqrt(q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3);
q0 = q0 / norm;
q1 = q1 / norm;
q2 = q2 / norm;
q3 = q3 / norm;
// 获取欧拉角 pitch、roll、yaw
*pitch = asin(-2 * q1 * q3 + 2 * q0 * q2) * 57.3;
*roll = atan2(2 * q2 * q3 + 2 * q0 * q1, -2 * q1 * q1 - 2 * q2 * q2 + 1) * 57.3;
*yaw = atan2(2 * (q1 * q2 + q0 * q3), q0 * q0 + q1 * q1 - q2 * q2 - q3 * q3) * 57.3;
}
将欧拉角发送给VTK的修改模型角度的函数接口(单独一个线程,见下面核心程序)。
关于3D模型:
电路板使用Altium Designer的话可以导出step格式的3D模型文件,然后用FreeCAD将其转换为STL格式文件,VTK库直接加载STL文件即可。
关于VTK:
下载方式是:pip install VTK-8.1.2-cp37-cp37m-win_amd64.whl
下载链接:https://vtk.org/download/
实例参考:https://github.com/lorensen/VTKExamples
DEMO:https://github.com/kimtaikee/QVTKDemo
导入的时候如果使用import vtk,在调试的时候没啥问题,但是在封装为exe的时候,会报no named moudle vtkmodules.all或vtk.py not found vtkmodules.all或的错误,修改方法是修改为import vtkmodules.all as vtk
报ModuleNotFoundError: No module named 'vtk.qt’的解决方法是将from vtk.qt import QVTKRenderWindowInteractor修改为from vtkmodules.qt.QVTKRenderWindowInteractor import QVTKRenderWindowInteractor
关于程序:
程序环境是PyCharm Community Edition 2021.1.1 x64,用插件QtDesigner和PyUIC配置窗口步骤的略过。核心程序如下:
class stlThread(QThread):
update_ui_signal = pyqtSignal(str)
def __init__(self, window):
super(stlThread, self).__init__()
# 信号绑定槽函数
self.update_ui_signal.connect(self.display_stl)
self.window = window
def display_stl(self, stl_str): # accel + gyro
stl_data = stl_str.split()
# print(stl_data)
display_data = "pitch\r\n" + stl_data[0] + "\r\n" + "roll\r\n" + stl_data[1] + "\r\n" + "yaw\r\n" + stl_data[2]
self.window.label_stl.setText(display_data)
self.window.actor.SetOrientation(float(stl_data[0]), float(stl_data[1]), float(stl_data[2]))
self.window.ren.AddActor(self.window.actor)
self.window.ren.ResetCamera()
self.window.iren.Initialize()
self.window.iren.Start()
def run(self):
while True: # 让子线程一直运行,等待主线程(ui线程)下发的任务
time.sleep(1)
# print("clock thread")
class MyInsideWindow(QMainWindow, Ui_SCS_INSIDE):
def __init__(self):
super(MyInsideWindow, self).__init__()
self.setupUi(self)
self.pushButton.clicked.connect(self.inside_pro) # 内部测试窗口操作
#### 模型相关
self.vtkWidget = QVTKRenderWindowInteractor(self.frame)
self.vl = Qt.QVBoxLayout()
self.vl.addWidget(self.vtkWidget)
self.ren = vtk.vtkRenderer()
self.vtkWidget.GetRenderWindow().AddRenderer(self.ren)
self.iren = self.vtkWidget.GetRenderWindow().GetInteractor()
source = vtk.vtkSTLReader() # 从文件加载STL模型数据源
source.SetFileName("PCB.stl")
# Create a mapper
mapper = vtk.vtkPolyDataMapper()
mapper.SetInputConnection(source.GetOutputPort())
# Create an actor
self.actor = vtk.vtkActor()
self.actor.SetMapper(mapper)
self.actor.SetOrientation(0, 180, 0)
self.ren.AddActor(self.actor)
self.ren.ResetCamera()
self.frame.setLayout(self.vl)
self.STL_Thread_object = None
def inside_init(self):
self.show()
self.iren.Initialize()
self.iren.Start()
if self.STL_Thread_object != None:
self.STL_Thread_object = None
def inside_pro(self): # 内部窗口操作
self.STL_Thread_object = stlThread(self)
self.STL_Thread_object.start()