本项目主要是基于C++界面开发库QT平台和Opencv软件开发库设计四轴飞行器地面基站系统,主要实现的功能是控制四轴飞行器(包括启动、停机、加油门),实时监测飞行器的四个螺旋桨的油门量程,实时显示飞行器的俯仰角、偏航角和滚转角示数,实时绘画三角度的变化曲线,通过立体坐标系实时显示飞行器的飞行姿态,以便于调整各种飞行参数例如pid参数。
该项目本来源于自己设计的微型四轴飞行器。通过自己设计电路,pcb打板,stm32嵌入式编程,通过蓝牙模块控制飞行器。总体硬件部分已经搭好,接下来的调整飞行器参数却遇到问题,因为无法实时知道飞行器的飞行姿态,也就无法调整飞行参数。因此编写了一个四轴飞行器地面基站软件系统,专门控制飞行器和监测飞行姿态。四轴飞行器通过无线蓝牙模块实时给pc发送数据,pc通过飞行器地面基站软件系统接收数据并发送控制命令。
串口发送和接收数据
四轴飞行器地面基站软件系统首先要接收飞行器发送过来的数据,因此该软件系统本身就是一个串口接收系统。因此必须使得软件系统实现串口接收数据。在Qt中并没有特定的串口控制类,现在大部分人使用的是第三方写的qextserialport类,我们这里也是使用的该类来实现串口接收和发送数据。
把相应的qextserialport类包含在工程中,还要修改一些其他细节,包括设置相关参数例如波特率、校验位等等,这些都通过QT的窗口部件来实现:
ui.cb_ComNumber->insertItem("COM1",0); //串口号的初始化
ui.cb_ComNumber->insertItem("COM2",1);
ui.cb_ComNumber->insertItem("COM3",2);
ui.cb_ComNumber->insertItem("COM4",3);
ui.cb_ComNumber->insertItem("COM5",4);
ui.cb_ComNumber->insertItem("COM6",5);
ui.cb_ComNumber->insertItem("COM7",6);
ui.cb_ComNumber->insertItem("COM8",7);
ui.cb_ComNumber->insertItem("COM9",8);
ui.cb_ComNumber->insertItem("COM10",9);
ui.cb_BaudRate->insertItem("9600",0); //波特率的初始化
ui.cb_BaudRate->insertItem("115200",1);
ui.cb_OddEven->insertItem("NONE",0); //校验位初始化
ui.cb_OddEven->insertItem("ODD",1);
ui.cb_OddEven->insertItem("EVEN",2);
ui.cb_DataBits->insertItem("8",0); //数据位初始化
ui.cb_DataBits->insertItem("7",1);
ui.cb_DataBits->insertItem("6",2);
如上图为系统的调试模块,这部分的功能包括设置串口的参数例如波特率、校验位和数据位,选择对应的串口号。点击打开串口按钮,软件系统即可和飞行器进行通信。上面的数据接收框可以实时显示飞行器发送过来没有经过解码的数据,点击发送数据按钮,在发送数据框中填写相应的指令,也可以给飞行器发送相应的指令例如启动、加油门等等。点击关闭串口按钮即可断开系统与飞行器的通信连接。
控制界面
控制界面主要是为了控制四轴飞行器的状态:
如图所示,点击油门解锁按钮就是给油门解锁,通过无线蓝牙向四轴飞行器发送0x5553指令,也就是设置油门指针,进行油门解锁。然后拖动滑动就是窗体部件逐渐增加油门,同样是发送设置油门指令,然后不该改变油门的值。pid调节框就是为了设置四轴飞行器的飞行姿态参数。有两个按钮包括读取参数和设置参数的功能。点击读取按钮首先向飞行器发送读取命名,然后飞行器会返回当前pid的参数。
void mainwindow::btnClear() //清空接收框
{
Recievestr_Edit = "";
ui.TextEdit_Recieve->setText(Recievestr_Edit);
}
void mainwindow::btnmotor_free() //一键停飞
{
QByteArray ba;
ba[0] = 0x55;
ba[1] = 53; //发送设置油门指令
ba[2] = 0;
ba[3] = 1;
ba[4] = 0;
ba[5] = 1;
ba[6] = 0;
ba[7] = 1;
ba[8] = 0;
ba[9] = 1;
Com->write(ba);
ui.motor_control_Slider->setValue(1);
}
void mainwindow::btnmotor_stop() //一键停飞
{
QByteArray ba;
ba[0] = 0x55;
ba[1] = 53; //发送设置油门指令
ba[2] = 0;
ba[3] = 1;
ba[4] = 0;
ba[5] = 1;
ba[6] = 0;
ba[7] = 1;
ba[8] = 0;
ba[9] = 1;
Com->write(ba);
ui.motor_control_Slider->setValue(1);
}
void mainwindow::motor_slider(int value) //油门控制拖动条
{
QByteArray ba;
ui.lineEdit_motor_ctl->setText(int2str.setNum(value));
ui.motor_progressBar->setValue(value);
ba[0] = 0x55;
ba[1] = 53; //发送设置油门指令
int value_h = (int)value/100;
int value_l = value-value_h*100;
ba[2] = value_h;
ba[3] = value_l;
ba[4] = value_h;
ba[5] = value_l;
ba[6] = value_h;
ba[7] = value_l;
ba[8] = value_h;
ba[9] = value_l;
Com->write(ba);
}
油门显示界面
四轴飞行器会实时返回四个电机当前的油门值,软件系统会实时接收这些数值,并通过精度条来显示数值的大小。
motor1 = rebuf[2]*100.0+rebuf[3];
ui.lineEdit_motor1->setText(int2str.setNum(motor1));
ui.progressBar_1->setValue(motor1);
motor2 = rebuf[4]*100.0+rebuf[5];
ui.lineEdit_motor2->setText(int2str.setNum(motor2));
ui.progressBar_2->setValue(motor2);
motor3 = rebuf[6]*100.0+rebuf[7];
ui.lineEdit_motor3->setText(int2str.setNum(motor3));
ui.progressBar_3->setValue(motor3);
motor4 = rebuf[8]*100.0+rebuf[9];
ui.lineEdit_motor4->setText(int2str.setNum(motor4));
ui.progressBar_4->setValue(motor4);
俯仰角、偏航角和滚转角三轴示数的实时显示
这是地面软件基站系统的一个重要功能,可以实时显示飞行器俯仰角、偏航角和滚转角三轴的飞行姿态的变化情况。
飞行器的六轴姿态传感器会实时采集飞行器俯仰角、偏航角和滚转角三轴的飞行数据,然后将数据进行打包,通过无线蓝牙发送给PC,PC上通过软件系统接收数据,由于是三个轴的数据一起发送,因此软件还用把三个轴的数据进行分离。然后存到特定的数组中。在软件系统中建立画图窗体部件,以一定的频率在画图窗体部件上显示数据点,并用画线进行连续,每个一个单位时间,移动一定个数的像数,重新更新画面,实现示数画线,同时显示俯仰角、偏航角和滚转角三轴的变化曲线。
#include "draw.h"
#include <math.h>
Draw::Draw(QWidget *parent)
{
QPalette p = palette(); //获取调色板信息
p.setColor(QPalette::Window,Qt::white); //设置为白色
setPalette(p); //重新设置调色板
setAutoFillBackground(true); //设置窗体可填充
for(i=0;i<150;i++)
{
points_row[i] = QPoint(i*3, 291-5);
points_pit[i] = QPoint(i*3, 291-4);
points_yaw[i] = QPoint(i*3, 291-3);
}
}
void Draw::paintEvent(QPaintEvent *)
{
QPainter p(this); //创建一个QPainter对象
color = Qt::red;
pen = QPen(color, 1);
p.setPen(pen); //设置QPainter的画笔和画刷
p.setBrush(brush);
for(i=0;i<149;i++)
{
p.drawLine(points_row[i],points_row[i+1]);
}
color = Qt::blue;
pen = QPen(color, 1);
p.setPen(pen); //设置QPainter的画笔和画刷
for(i=0;i<149;i++)
{
p.drawLine(points_pit[i],points_pit[i+1]);
}
color = Qt::green;
pen = QPen(color, 1);
p.setPen(pen); //设置QPainter的画笔和画刷
for(i=0;i<149;i++)
{
p.drawLine(points_yaw[i],points_yaw[i+1]);
}
}
void Draw::setpoint(QPoint pointsx,QPoint pointsy,QPoint pointsz)
{
for(i=149;i>=1;i--)
{
points_row[i] = points_row[i-1];
points_row[i].setX(i*3);
points_pit[i] = points_pit[i-1];
points_pit[i].setX(i*3);
points_yaw[i] = points_yaw[i-1];
points_yaw[i].setX(i*3);
}
points_row[0] = pointsx;
points_pit[0] = pointsy;
points_yaw[0] = pointsz;
update();
}
OPencv三维立体显示飞行器的飞行姿态
使用OPencv显示一个三维立体坐标轴,通过设置三个轴x、y和z的参数,就可改变坐标轴的三维立体的显示效果。因为软件系统可以实时接收俯仰角、偏航角和滚转角三轴的数据,根据三轴的数据来不断设置立体坐标轴的姿态参数,使得坐标轴的姿态变化和四轴飞行器的姿态变化能够同步,实时的反映四轴飞行器的姿态变化情况,让软件系统更加直观,更有利于飞行参数的调整。
#include "qglwidget.h"
qglwidget::qglwidget(QWidget *parent,const char*name)
: QGLWidget(parent,name)
{
rROW = 0.0; //初始化
rPIT = 0.0;
rYAW = 0.0;
}
void qglwidget::initializeGL()
{
glShadeModel( GL_SMOOTH ); //阴影平滑
glClearColor( 0.95, 0.95, 0.95, 0.0 ); //设置清除屏幕时所用的颜色
glClearDepth( 1.0 ); //设置深度缓存
glEnable( GL_DEPTH_TEST ); //启用深度测试
glDepthFunc( GL_LEQUAL ); //所作深度测试的类型
glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );
}
void qglwidget::resizeGL(int width, int height)
{
if ( height == 0 ){ //防止height 为0
height = 1;
}
glViewport( 0, 0, (GLint)width, (GLint)height ); //重置当前的视口
glMatrixMode( GL_PROJECTION ); //选择投影矩阵
glLoadIdentity(); //重置投影矩阵
gluPerspective( 45.0, (GLfloat)width/(GLfloat)height, 0.1, 100.0 ); //建立透视投影矩阵
glMatrixMode( GL_MODELVIEW ); //选择模型观察矩阵
glLoadIdentity(); //重置投影矩阵 //重置投影矩阵
}
void qglwidget::paintGL()
{
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); //清除屏幕和深度缓存
glLoadIdentity(); //重置当前的模型观察矩阵
glTranslatef( 0.0, 0.0, -4.0 ); //移动场景原点
glBegin( GL_QUADS );
glColor3f( 0.75, 0.75, 0.75 );
glVertex3f( -1.1, 1.1, 1.1 );
glVertex3f( -1.1, 1.1, -1.1 );
glVertex3f( -1.1, -1.1, -1.1 );
glVertex3f( -1.1, -1.1, 1.1 );
glColor3f( 0.8, 0.8, 0.8 );
glVertex3f( 1.1, -1.1, 1.1 );
glVertex3f( 1.1, -1.1, -1.1 );
glVertex3f( -1.1, -1.1, -1.1 );
glVertex3f( -1.1, -1.1, 1.1 );
glColor3f( 0.85, 0.85, 0.85 );
glVertex3f( 1.1, 1.1, -1.1 );
glVertex3f( 1.1, -1.1, -1.1 );
glVertex3f( -1.1, -1.1, -1.1 );
glVertex3f( -1.1, 1.1, -1.1 );
glEnd();
glRotatef( rROW, 1.0, 0.0, 0.0 ); //绕着各个轴旋转角度
glRotatef( rPIT, 0.0, 1.0, 0.0 );
glRotatef( rYAW, 0.0, 0.0, 1.0 );
glLineWidth(1.7);
glBegin(GL_LINES) ;
/////////////////////////////////////////////////////////
glColor3f(0.0,0.0,1.0);
glVertex3f(-1.0, 0.0, 0.0);
glVertex3f(1.0, 0.0, 0.0);
glVertex3f(0.93, 0.05, 0.0);
glVertex3f(1.0, 0.0, 0.0);
glVertex3f(0.93, -0.05, 0.0);
glVertex3f(1.0, 0.0, 0.0);
glVertex3f(0.93, 0.0, 0.05);
glVertex3f(1.0, 0.0, 0.0);
glVertex3f(0.93, 0.0, -0.05);
glVertex3f(1.0, 0.0, 0.0);
///////////////////////////////////////////////////////////
glColor3f(0.0,1.0,0.0);
glVertex3f(0.0, -1.0, 0.0);
glVertex3f(0.0, 1.0, 0.0);
glVertex3f(0.05, 0.93, 0.0);
glVertex3f(0.0, 1.0, 0.0);
glVertex3f(-0.05, 0.93, 0.0);
glVertex3f(0.0, 1.0, 0.0);
glVertex3f(0.0, 0.93, 0.05);
glVertex3f(0.0, 1.0, 0.0);
glVertex3f(0.0, 0.93, -0.05);
glVertex3f(0.0, 1.0, 0.0);
///////////////////////////////////////////////////////////
glColor3f(1.0,0.0,0.0);
glVertex3f(0.0, 0.0, -1.0);
glVertex3f(0.0, 0.0, 1.0);
glVertex3f(0.05, 0.0, 0.93);
glVertex3f(0.0, 0.0, 1.0);
glVertex3f(-0.05, 0.0, 0.93);
glVertex3f(0.0, 0.0, 1.0);
glVertex3f(0.0, 0.05, 0.93);
glVertex3f(0.0, 0.0, 1.0);
glVertex3f(0.0, -0.05, 0.93);
glVertex3f(0.0, 0.0, 1.0);
glEnd();
glLoadIdentity();
}