1.无刷电机电子调速器设计
1.1什么是无感无刷电机
本设计中采用三相无感无刷电机,无感无刷电机是一种交流电动机,内部绕组作为定子,采用三相对称星形接法,引出三根线。如下左图所示


(摘自《无刷电机之电调设计全攻略》)
外部转子的内侧固定了多组永磁体,在内部绕组某两项分别接通源与地时,产生的磁场会将转子拉至一个稳定的位置,如上右图。 因此简单地说,想让无刷电机转起来,就是在这三根线上不停地切换通电状态,产生一个“旋转”的磁场,并带动外转子转动。这个过程需要一个驱动电路。这个电路就是无刷电机电子调速器,简称无刷电调。
1.2驱动臂构建
经过上面介绍,我们知道无刷电机引出的三根线都需要能接通源、地或浮空,因此我们采用常见的六臂全桥驱动,下图展示的是连接一根线的两只臂,其余四只相同。

驱动桥上臂MOS管选用IRFR5305,这是IR公司生产的一种PMOS管,它具有导通电阻小、开关速度快、可通过电流大等特点。下臂采用该公司的IRFR1205 NMOS管与之搭配,值得说明的是,IRFR5305和IRFR1205都内置了反向续流二极管,因此在驱动电机等感性负载时可减少电路的复杂程度,提高可靠性。

根据芯片手册,PMOS开启电压Vgs应达到-2V到-4V,在供电电压为12V时,考虑到MCU驱动能力,上臂栅极连接了一个NPN三极管作为驱动。而下臂开启Vgs为2V到4V,直接连接MCU引脚即可。上述电路中。其中R27可以减小下臂NMOS栅极电容充放电电流,同时防止MOS管关断时被瞬间增大的Vds击穿。R30与R33分别为PMOS栅极上拉和NMOS栅极下拉电阻,它们的作用是防止在系统上电时,瞬态的上下臂导通,提高系统稳定性。R9为三极管基极限流电阻
1.3电机换向和启动
当然,仅仅能够产生“旋转“磁场是不够的。为了让无刷电机高效率的转起来,我们还需要确定最佳的切换磁场时机,也就是换相时机。如果能在外转子刚刚转过稳定位置时进行换相,让它被新的磁场牵引继续转动,而不是被上一步的磁场锁定住,便可以达到最高效率。传统有刷电机通过碳刷换相,这是一个机械过程不需要过多考虑。而无感无刷电机没有换相刷,也没有霍尔传感器用来确定转子位置,因此我们的目光锁定在浮空的那一根线上。下图为无刷电机的感应电动势采集网络,无刷电机的换相时机就是通过该网络的输出情况确定的。

图中A,B,C三点直接接入电机三项,那么假设在某一刻A项上臂导通,B项下臂导通,C项浮空,因为电机转子上的永磁体位移,C项位于电机内部的绕组切割永磁体磁场产生了正向感应电动势,叠加在AB项中点电压6V之上必然大于6V,通过仿真可知此时BIJIM电势略小于EC点电势.随着电机转动,永磁体的位置也在改变,当磁场渐渐变弱,绕组切割永磁体磁场产生的正向感应电动势变小,并最终达到临界时,感应电动势为0V时,BIJIM电势等于EC电势.此后电机由于惯性还在转动,但转子上下一块永磁体的极性是相反的,因此C项开始产生反向感应电动势,叠加后上图电路内C位置电势将小于6V.此时BIJIM电势将会大于EC电势.由此可见,当BIJIM电势等于浮空项电势时,即是比较合适的换相时机.此时把B项变为高阻,C项下臂导通接地.下一个周期用同样方法比较EB与BIJIM电势关系即可.在ATMEGA8单片机内部提供了模拟比较器,将BIJIM和EA,EB,EC接入相应引脚,通过程序实现过零点捕捉.即可完成电机换相。
通过上面分析我们可以看出,当电机正在运转时,换相时机是根据浮空相的反馈确定的。但是在启动时,电机并没有转动或者转速非常慢,这种情况下浮空相上的电势基本没有参考价值,因此,我们还需要一套启动算法,大致的思路是,先不管反馈,把无刷电机当成步进电机,通过强制换相产生一个不断加速的”旋转”磁场,并希望转子跟随这个磁场转动,当转子达到足够产生反馈的速度时,使能上述比较策略便可切入闭环控制。
1.4堵转保护和开机自检
电机的换相是个自动过程,而调速是通过改变驱动臂的PWM占空比完成的。当电机启动失败或是其他原因导致堵转时,高占空比的输出对于驱动臂来说是毁灭性的。为了避免这种情况的发生,我们加入了堵转保护策略:当电机应当在运转,却迟迟没有进入换相中断时,电调便关断驱动臂,并根据情况判断是否需要重新启动。
同样基于保护目的我们编写了一套基于电压采样的自检算法,保证起飞前各MOS管工作正常,这套算法可以实现电调板自动测试每只MOS管的各项性能、锁定故障点并上报主控板,所用到的检测电路依旧为感应电动势采集网络。这套算法可以检测的错误几乎包括了所有将导致严重后果的MOS管故障:
*上臂短路
*上臂断路
*上臂导通不佳
*上臂关断不佳
*下臂短路
*下臂短路或导通不佳
2.飞行器姿态解算
2.1陀螺仪、加速度传感器是什么
四轴飞行器仅有动力系统还只是无头苍蝇,它需要一套姿态解算与处理系统协调各电机的转速,以保持平衡、完成飞行动作。

我们搭建了一套惯性导航系统,该系统由三颗单轴陀螺仪和一颗三轴加速度传感器组成,陀螺仪输出与角速度呈线性关系的模拟量,加速度传感器则输出与加速度呈线性关系的模拟量。因地球表面有1G左右的重力加速度,当飞行器悬停时,加速度传感器在Z轴上会有1G读数,X轴和Y轴为0G。陀螺仪各轴读数也为0。当发生倾斜时,陀螺仪会反映出倾斜的快慢情况,而加速度传感器则直接输出叠加在该轴上的重力加速度,就物理意义来说,这个加速度数据就是倾角数据。
2.2加速度传感器的不足
这样看似只要有加速度传感器就可以知道倾角了,但是加速度传感器有些很不利的特性,比如对震动非常敏感,另外当存在侧向加速度时,读数会大幅偏移,而很不幸这两种情况在飞行器上都存在。加入简单的滤波算法可在一定程度上缓解该问题,但又严重影响数据的实时性,因此仅使用加速度传感器作为四轴飞行器的姿态采集系统是不够的。下图为轻微振动时(手抖传感器),加速度传感器某轴的输出情况,在峰值1.94V的情况下纹波高达600mV。很容易得出结论,这样的数据是不能使用的。

2.3陀螺仪的局限
那么我们能否利用陀螺仪的积分数据呢,角速度积分就是角度,它的物理意义与重力传感器是相同的而且基本不受震动和侧向加速度影响,但是同样有问题——积分会产生累积误差,这个误差会随积分过程不断扩大,使输出严重偏离真实值。于此同时,出于成本考虑我们没有选用昂贵的高精度陀螺仪,根据飞行器的特性也不能加入高通滤波器,因此温漂的问题也导致了积分中立点的飘移,进而使积分数据越来越不可靠,如下图绿色线所示,在正向旋转传感器后,再回转相同角度,输出却没有回归X轴。

(摘自百度文库,jiayufei2010《四轴DIY小结》)
2.4融合算法与ALTERA SOPC系统的优势
为解决这些问题,我们的方法是将二者按一定算法进行融合,加速度传感器有绝对参考系——地球重力加速度,它不会产生累积误差,温漂极小。就一段较长的时间来看它的输出和倾角的关系还是稳定的。而陀螺仪积分抗干扰,反应灵敏,在较短的时间内比较可靠。因此角度输出短时间内倾向于依赖陀螺仪的积分数据,加速度传感器只进行小幅修正,在较长的时间后,加速度传感器的修正将会使角度输出最终偏向加速度传感器。这样,陀螺仪的高速反应和抗干扰特性得以保留,而积分累积误差和温飘较大等缺点也将被加速度传感器修正,二者优势互补,便能够在高实时性前提下给出一个较为理想的角度值数据。

上述过程的计算量还是非常可观的,采用普通MCU来做会占用非常多的CPU时间,而我们的核心控制IC采用了Altera的FPGA,构建了专用SOPC系统,姿态处理过程用HDL编写并生成相应的硬件,最后作为用户IP连接Nios2软核处理器。上图就是这套姿态解算系统的框图,左端输入陀螺仪与加速度传感器的采样值,右端直接输出角度等信息,这种较为复杂的运算用硬件加速的方式大大节省了CPU时间,提高了运算速度,其优势不言自明。
下图为采用融合算法后的示意图。

(摘自百度文库,jiayufei2010《四轴DIY小结》)
3.无人机导航系统
3.1.GPS卫星定位模块
导航系统采用了一颗ublox的GPS模块,通过UART口与核心相连。

ALTERA的SOPC BUILDER工具中提供了UART的IP核,可以轻易实现UART通讯。另外该模块支持ubx协议输出,其中可见卫星,UTC,经纬信息等等都通过2进制表示,而不是NMEA协议中的ASCLL编码方式,为后期的数据处理过程提供了不少方便。

ubx协议有完整的数据帧协议,其中包含两个包头字节u和b,后紧跟一个字节的消息类代码,指示这个包的数据类别,后跟一字节消息代码,后包含两字节数据长度信息,后跟数据部分和两个校验位。在不需要直接输出GPS信息到ASCLL输出设备的场合,采用ubx协议明显比较合适。
该gps模块手册说明支持4hz输出(实测5hz也是有效的),启动后GPS默认为NMEA协议1hz刷新率,只需要发送一组指令到GPS完成初始化,便可调整到更高的波特率并以4hz输出信息。对于飞行器来说,高刷新率意味着算法部分有更大的空间获得及时的、稳定的位置信息,本设计对于GPS部分的数据处理方案是,先对经纬坐标做均值处理,并在中间值上下各容许一段空间的漂移以防止GPS数据的不稳定性导致飞行器过度调整。同样的,由于GPS输出的坐标信息很难与上位机要求的航迹点坐标完全重合,因此对于目标航迹点的到达判断也是采用类似方案,即到达以目标点为中心的“小方块”以内,即认为到达,准备前往下一个航迹点。
3.2磁阻传感器
由于慢速飞行时,GPS所提供的航向以及速度等信息并不准确,而自动导航功能要求飞行器实时了解机头的朝向,因此我们在主控板上另外安装了一颗磁阻传感器(琥珀中的就是它)

磁阻传感器用以检测地磁场,并通过反三角函数计算与地球磁北夹角,从而得出航向数据。它通过I2C总线与核心FPGA通讯,磁阻传感器作为从机有固定的读写地址,每次需要数据时只需要按手册说明发送SLA+R便可获知全部3轴的磁场强信息,供算法处理。
3.3气压计
飞行器定高航行功能主要通过气压传感器实现。随着海拔高度的上升,气压呈下降趋势,通过查阅相关方面高手的研究资料了解到,在1500米范围内,气压与高度的关系可认为大致呈线性。高度每上升10米,气压下降1.1mbar。这是一个很小的数值,因此我们将气压计输出经过运放高倍率放大以获得更高的分辨率。同时需要说明的是,大气压强随天气等原因变化比较明显,所以即便在同一地点不同时间测试,气压计的输出也是不同的,因此气压计只能在一个相对较短的时间内提供相对高度信息,每次起飞前,需要根据地面气压校准相对0高度,才能获得比较合理的输出数据。

(摘自豆丁网,文献家园的《大气压于海拔高度的关系》)
4.数传电台及电调通讯
4.1电调板通讯(I2C核移植)
主控板与电调板通讯采用I2C协议,i2c协议是一种简单的多主从半双工通讯协议,硬件上只需要两根线,便可连接主控端和四只电机驱动,在标准模式下通讯速率为100kbps,快速模式通讯速率更是高达400kbps,完全可以满足电调板的控制需要。这里我们采用了标准模式。
电调板的控制单片机内置了i2c(twi)总线控制器,只需在程序中操作相应寄存器组,即可产生符合I2c标准的通讯时序。而网络上很容易获得基于 Wishbone总线的开源IP核,因此我们只需要将其转换为符合Avalon——MM总线标准的IP核,并通过到定制IP加入到我们的SOPC系统,即可轻易实现二者的通讯,SOPC这一可按需定制外围的特性,无疑大大提高了整个系统的灵活性,也减少了工程人员的重复性劳动,这是采用SOPC系统的一又大优势。
下面介绍一下我们定制的专用数据帧格式。考虑到电调的工作环境,各种干扰比较严重,为保证数据的正确传输,我们制定了一套数据帧格式,其中S表示开始信号,SLA表示从机地址,W表示写,R表示读,DATA表示数据,ACK表示应答信号,NACK表示无应答信号,P表示停止。
主机写从机:
主机发送:S-------SLA+W---------DATA--------DATA的反码------------P
从机发送: -------------------- ACK ---------ACK-------------------NACK----
主机快速读取从机:
主机发送:S------SLA+R-------------------NACK------P
从机发送:--------------------ACK-DATA------------------
主机标准读取从机:
主机发送:S------SLA+R-------------------------ACK----------------------NACK------P
从机发送:--------------------ACK-DATA-----------------DATA的反码-------------------
加入反码的发送,并供接收端校验二者按位与和按位或的结果是否是全1和全0,就可以知道数据是否被干扰而出错。而校验的结果,可以在下一个读取过程中,发送给主控板,这样收发端就都可以了解出错情况,从而进行下一步的处理。
4.2数传电台

主控板与遥控器和上位机通讯是采用无线UART模块实现的,其无线传输过程对外部透明,主控部分只需要把它当成普通UART端口进行通讯即可,不过由于原理限制,无线UART不能实现普通UART一样的全双工通讯,因此数据上行过程全部由上位机发起以避免堵塞通讯通道,下行数据以0x00--0x1f做为帧头和帧尾,供主控识别数据帧类别并检验内容是否正确。
5.扩展组件
5.1空投系统(PPM波形发生)
空投系统由一个舵机控制释放和锁紧空投挂钩,因此空投系统控制就是舵机控制。对于模型用模拟舵机来说,其控制信号是频率为50Hz,占空比范围5%-10%的PPM信号,因此只需要用HDL写一个带有的PWM功能的,同时可并行接收NIOSII核指令来调整占空比的分频器即可。

5.2视频传输与头部传感器
作为一种稳定的飞行平台,四轴飞行器可以搭载视频传输系统向上位机或操纵者传输高质量的实时视频。

对于视频显示我们提供了两种方案:
1:使用上位机软件的视频接收窗口查看。(已在演示光盘中演示)
2:使用视频眼镜,以第一人称观看 。

当使用方式1时,只需将图传系统的A/V输出端接入视频采集卡,便可作为标准USB视频输入设备被上位机软件识别并显示出来。
当使用方式2时,可追加头部传感器模块,实现摄像头随操控者头部移动而移动,达到虚拟驾驶舱的效果!
为实现以上效果,头部传感器需要捕捉控制者头部两轴的动作——水平旋转和俯仰,因此拟采用上文介绍的磁阻传感器和加速度传感器组合,通过磁阻传感器读取地磁场信息判断用户头部水平朝向的变化,通过加速度传感器读取俯仰的变化,再通过遥控器采集,无线串口模块发送至飞行器,最终采用空投部分使用的舵机驱动逻辑,驱动扩展接口上的两路舵机即可控制摄像头云台跟随操控着头部运动。

(用户佩戴视频眼镜进行控制的效果)
6.上位机软件设计
上位机软件是无人机飞行模式下的控制平台,用户在这里可以了解到飞行器的各项参数,下达指令,编制飞行计划以及监视图像系统回传的视频流。下面是上位机软件的整体界面。

6.1串口通信模块的设计
6.1.1方案的选择
目前串口通信方案的实现比较常用的有3种:
第一种是利用VC自带的串口控件CMSComm来实现,这种方案的实现比较简单,但受限制比较多,同时实现的灵活度比较低;
第二种方案是利用微软所提供的API函数来实现,这种方案实现的灵活度比较高,但实现的过程比较复杂些;第3种方案是利用第3方商家提供的串口函数来实现,这种方案的实现与第一种方案的优缺点是一样的。
由于我们的四轴飞行器与电脑的通信比较复杂,且实现的灵活度比较大,因此我们选择了第2种方案来进行实现。
6.1.2流程框图

6.1.3 代码的实现过程
利用API的CreateFile()函数打开一个串口,该函数实现完后返回一个句柄,当函数调用成功时,则返回一个不为零的句柄,表明此时串口已经打开了,可对串口进行下一步的设置;当函数调用失败,则返回一个为零的句柄,表明串口打开失败。
my_hcom=CreateFile(port,GENERIC_WRITE|GENERIC_READ,0,NULL,OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,NULL);//打开串口
串口成功打开后,需要对串口通信协议中的一些参数进行设置,在VC中,串口以DCB的数据结构类型存在,我们只需对DCB里面的BaudRate(波特率),fBinary(允许二进制接收),ByteSize(数据位),Parity(奇偶校验位),StopBits(停止位)等成员变量进行设置,利用 GetCommState()函数可以获得DCB数据结构中的成员变量,再利用SetCommState()函数和 SetupComm()函数来实现串口参数的设置和串口缓存区的大小。
//设置串口的配置参数
GetCommState(my_hcom,&dcb);
dcb.BaudRate=set_data;
dcb.fBinary=true;
dcb.ByteSize=8;
dcb.Parity=true;
dcb.StopBits=ONESTOPBIT;
SetCommState(my_hcom,&dcb);
SetupComm(my_hcom,1024,1024);
串口的写操作用到了API函数的WriteFile();WriteFile()的函数模型及参数设置如下:
BOOL WriteFile(
HANDLE hFile, // 串口句柄
LPCVOID lpBuffer, // 数据缓存区指针
DWORD nNumberOfBytesToWrite, // 要写的字节数
LPDWORD lpNumberOfBytesWritten, // 用于保存实际写入字节数的存储区域的指针
LPOVERLAPPED lpOverlapped // OVERLAPPED结构体指针 );
先定义一个BYTE类型的数组,再用strlen()函数可以得到写入串口数据的字节数,但此时我们得到的是一个CString 类型的的串口数据,还要将其转化为16进制的形式进行发送,转化实现的代码如下:
int CYazhuoDlg::String2hex(CString s1, BYTE *senddate)
{
int hexleng=0;
int s5,s6;
int len=s1.GetLength(); //获得所需转化的字符长度
for(int i = 0; i
{
char s2,s4;
char s3=s1[i];
if(s3==' ')
{
i++;
continue;
}
if(i>=len)
break;
s4=s1[++i];
s5=hex(s3); //将对应的字符转化为16进制对应的“0--F”
s6=hex(s4);
if((s5==16)||(s6==16))
break;
else s5=s5*16+s6; //获得具体的16进制数
senddate[hexleng++]=(char)s5;//得到16进制的长度
}
return hexleng;
}
char CYazhuoDlg::hex(char ch)
{
if((ch>='0')&&(ch<='9'))
return (ch-0x30);
else if((ch>='A')&&(ch<='Z'))
return (ch-'A'+10);
else if((ch>='a')&&(ch<='z'))
return (ch-'a'+10);
else return (-1);
}
利用API函数中的ReadFile()来实现串口的读取操作,ReadFile()的函数模型如下:
BOOL ReadFile(
HANDLE hFile, //串口的句柄
LPVOID lpBuffer, //用于保存读入数据的一个缓冲区
DWORD nNumberOfBytesToRead, //要读入的字符数
LPDWORD lpNumberOfBytesRead, //指向实际读取字节数的指针
LPOVERLAPPED lpOverlapped //如串口打开时指定了FILE_FLAG_OVERLAPPED,那么
必须,用这个参数引用一个特殊的结构。该结构定义了一次异步读取操作。否则,应将这个
参数设为NULL );
读取串口的数据时,还需要用到VC中的定时器,这样可以更合理的控制串口的读取时间,VC中要使用定时器时,则必须先添加WM_TIME的消息响应机制,然后再用SetTimer()来设置定时器的各个参数,最后当不用定时器时,要用 KillTimer()来释放定时器的资源,利用ClearCommError()函数能清楚串口的缓存区或着串口的错误标记。代码如下:
memset(input,0,50);//清空数据区
ClearCommError(my_hcom,&derror,&constat);
length=min(constat.cbInQue,50);
ReadFile(my_hcom,input,length,&length,NULL);
if(length>0)
{
for(unsigned int i=0;is1.Format("%X",input[i]);//将数据以16进制的方式存入临时变量S1中
}
串口的关闭比较简单,利用API函数CloseHandle();可停止串口的工作
6.2传感器数据显示的方案和代码的实现
通过图形化的数据显示窗口,用户可以非常直观的了解到机载传感器的数据变化情况,下图为陀螺仪传感器数据显示窗。

6.2.1传感器数据显示模块初始化
传感器数据的显示使用了图形坐标的形式,这样可以更加直观地反映出飞行器的传感器数据在空中的变化情况,以便了解飞机的变化情况。图形坐标的显示,使用了VC中的CNTGraph类,在类的头文件中,定义2个CNTGraph的对象分别代表速度传感器m_sudugrap和陀螺仪传感器m_tlgrap的坐标图初始化代码如下:
//加速度传感器坐标图设置
m_sudugrap.ClearGraph();
m_sudugrap.SetElementLineColor(RGB(0,0,255));//加速度X轴曲线
m_sudugrap.AddElement();//在控件增加一个元素
m_sudugrap.SetElementLineColor(RGB(255,0,0)); //加速度Y轴曲线
m_sudugrap.AddElement();//在控件增加一个元素
m_sudugrap.SetElementLineColor(RGB(0,255,0)); //加速度Z轴曲线
m_sudugrap.SetElementIdentify(FALSE);
m_sudugrap.SetShowGrid(TRUE);
//命名方式
m_sudugrap.SetCaption("速度传感器");
m_sudugrap.SetXLabel("X轴");
m_sudugrap.SetYLabel("Y轴");
//等量划分X,Y轴的值
m_sudugrap.SetXGridNumber(10);
m_sudugrap.SetYGridNumber(10);
//设置X,Y轴的范围
m_sudugrap.SetRange (0,50,-300,300);
//陀螺仪传感器坐标图设置
m_tlgrap.ClearGraph();
m_tlgrap.SetElementLineColor(RGB(255,0,0));//陀螺仪X轴曲线
m_tlgrap.AddElement();//在控件增加一个元素
m_tlgrap.SetElementLineColor(RGB(0,255,0)); //陀螺仪Y轴曲线
m_tlgrap.AddElement();//在控件增加一个元素
m_tlgrap.SetElementLineColor(RGB(0,0,255)); //陀螺仪Z轴曲线
m_tlgrap.SetElementIdentify(FALSE);
m_tlgrap.SetShowGrid(TRUE);
//命名方式
m_tlgrap.SetCaption("陀螺仪传感器");
m_tlgrap.SetXLabel("X轴");
m_tlgrap.SetYLabel("Y轴");
//等量划分X,Y轴的值
m_tlgrap.SetXGridNumber(10);
m_tlgrap.SetYGridNumber(10);
//设置X,Y轴的范围
m_tlgrap.SetRange (0,50,-300,300);
四轴飞行器通过串口传给电脑的传感器数据暂时存放在一个BYTE型的inputde临时变量数组,因此只需从该临时变量取出数据,我们就可以对传感器的数据进行处理了,但由于存在数组中的传感器数据是属于字符型变量,因此处理前我们需要用Format()函数将其转化为16进制的形式,再利用函数PlotXY()以及函数相关变量的设置,就可以在图形坐标上画出曲线,代码的实现如下:
switch(i)
{
case 1:{s3.Format("%X",input[i]);m_sudugrap.PlotXY(x,sin(x)*(double)input[i],0); break;}//速度传感器X轴的数据
case 2:{s4.Format("%X",input[i]);m_sudugrap.PlotXY(x,sin(x)*(double)input[i],1);break;}//速度传感器Y轴的数据
case 3:{s5.Format("%X",input[i]);m_sudugrap.PlotXY(x,sin(x)*(double)input[i],2);break;}//速度传感器Z轴的数据
case 4:{s6.Format("%X",input[i]);m_tlgrap.PlotXY(x,sin(x)*(double)input[i],0);break;}//陀螺仪传感器X轴的数据
case 5:{s7.Format("%X",input[i]);m_tlgrap.PlotXY(x,sin(x)*(double)input[i],1);break;}//陀螺仪传感器Y轴的数据
case 6:{s8.Format("%X",input[i]);m_tlgrap.PlotXY(x,sin(x)*(double)input[i],2);form_dis=1memset(input,0,50);//清空数据区
PurgeComm(my_hcom,PURGE_RXCLEAR);//清空接收缓存break;}//陀螺仪传感器Z轴的数据
}
6.2.2传送传感器数据的通信协议约定
PC机先发送一个准备信号STARE"aa16",从机进行初始化准备,准备完毕后给主机发送一个aCK-"aa"信号,若主机收到则做出应答信号ACK--"aafc"; 若主机没收到从机的响应信号,则发送ACK1-"aafa"要求从机重发。代码的实现如下:
memset(input,0,50);//清空数据区
ClearCommError(my_hcom,&derror,&constat);
length=min(constat.cbInQue,50);
ReadFile(my_hcom,input,length,&length,NULL);
if(length>0)
{
for(unsigned int i=0;i
{
if(m_revecheck.GetCheck())
{
//下位机响应信号
if(star==1)
{
s1.Format("%X",input[i]);
if(s1=="AA")
{ //收到响应信号
star=0;
star_dis=1;
memset(senddata1,0,50);//清空内存数据
int len1=String2hex(ACK,senddata1); //获得发送字符的长度
WriteFile(my_hcom,senddata1,len1,&dw2,NULL); //写串口数据
}
else
{
//要求重发响应
memset(senddata1,0,50);//清空内存数据
int len1=String2hex(ACK1,senddata1); //获得发送字符的长度
WriteFile(my_hcom,senddata1,len1,&dw1,NULL); //写串口数据
trainform=0;
}
}
}
}
6.3 GPS信号的提取和处理
6.3.1 GPS坐标提取
GPS的信号提取和处理与传感器的处理方式差不多,也是通过串口发送给PC机,同时将数据存放在一个临时变量的数组,但GPS的提取必须遵循一定的协议---NEMA0183协议,当串口接收到数据的时候,应先先判断第一个字符是否为“$”,如果是,则表示这一帧数据是正确的GPS信号,如果不是,则表示错误的GPS信号,其次,由于GPS信号的每一个数据包后面都有“,”为标记,因此我们可以利用这一特征来提取GPS信号中有用的信息,并对其进行处理等等!提取的代码如下:
memset(input1,0,100);//清空数据区
ClearCommError(my_hcom,&derror,&constat);
length1=min(constat.cbInQue,100);
ReadFile(my_hcom,input1,length1,&length1,NULL);//获取GPS的数据
if(length>0){
for(unsigned int j=0;j
{
gps.Format("%c",input1[j]);
// m_revedata1+=gps;
//判断帧头
if(j == 0) {
if('$' == input1[j])
{
//SetDlgItemText(IDC_EDIT2,"$");
count = 0;
}
else
{
memset(input1,0,100);
PurgeComm(my_hcom,PURGE_TXCLEAR| PURGE_RXCLEAR);//清除BUFF
}
}
//判断具体的帧节数,并计算帧节数
if(input1[j] == ',') count++;
else
{
if('/' != input1[j])
{
switch(count)
{
case 0 : { m_infor+=input1[j]; SetDlgItemText(IDC_EDIT17,m_infor);break;}
//时间的处理和提取
case 1 : { m_time+=input1[j]; /*SetDlgItemText(IDC_EDIT4,m_time);*/ break;}
case 2 : { m_flag=input1[j]; //SetDlgItemText(IDC_EDIT5,m_flag);
if(m_flag == 'A')
SetDlgItemText(IDC_EDIT16,"有效定位");
if(m_flag == 'V')
SetDlgItemText(IDC_EDIT16,"无效定位");
break;}
case 3: {m_wei+=input1[j];//SetDlgItemText(IDC_EDIT12,m_wei);
break;}
case 4: {m_NS=input1[j];//SetDlgItemText(IDC_EDIT12,m_NS);
if(m_NS == 'N')
SetDlgItemText(IDC_EDIT13,"北半球");
if(m_NS == 'S')
SetDlgItemText(IDC_EDIT13,"南半球");
break;}
case 5: {m_jing+=input1[j];//SetDlgItemText(IDC_EDIT14,m_jing);
break;}
case 6: {m_EW=input1[j];//SetDlgItemText(IDC_EDIT16,m_EW);
if(m_EW == 'E')
SetDlgItemText(IDC_EDIT15,"东半球");
if(m_EW == 'W')
SetDlgItemText(IDC_EDIT15,"西半球");
break;}
case 7: { break;}
case 8: { break;}
case 9: { m_date+=input1[j];//SetDlgItemText(IDC_EDIT19,m_date);
memset(input,0,100);
PurgeComm(my_hcom,PURGE_TXCLEAR| PURGE_RXCLEAR);//清除BUFF
break;}
default :{ //SetDlgItemText(IDC_EDIT6,"asfrgg");
break;}
}
}
}
}
6.3.2 GPS数据处理
GPS信号的信息都是格林制的时间,同时各个地方的经纬度也是不同的,因此要对其信息进行处理,处理的方法为先把数组中的信息存放到一个临时的CString变量,再利用atoi()函数转化为int型,再进行时差的运算,代码如下:
if('/' == input1[j])
{
//GPS信息提取变量
CString str1,str2,str3,str4,str5,str6;
//日期的变量
CString riqi,riqi_clock1,riqi_clock2;
CString riqi_hour,riqi_min,riqi_sec,riqi_day,riqi_month,riqi_year;
CString wei_d,wei_m,wei_d1,wei_m1,weidu; //GPS纬度的变量
CString jingdu,jing_d,jing_m,jing_d1,jing_m1;//GPS经度的变量
//GPS时间的处理和提取
::strcpy(data,m_time);
str1.Format("%c%c",data[0],data[1]);//小时的处理
GPS.hour = (atoi(str1)+8)%24;
str2.Format("%c%c",data[3],data[4]);//分钟的处理
GPS.min = atoi(str2);
str3.Format("%c%c",data[5],data[6]);//秒处理
GPS.sec = atoi(str3);
::strcpy(data,m_date);
str4.Format("%c%c",data[0],data[1]);//日处理
GPS.day = atoi(str4);
str5.Format("%c%c",data[2],data[3]);//月处理
GPS.month = atoi(str5);
str6.Format("%c%c",data[4],data[5]);//年处理
GPS.year = atoi(str6);
riqi_hour.Format("%d",GPS.hour);
riqi_min.Format("%d",GPS.min);
riqi_sec.Format("%d",GPS.sec);
riqi_clock1 = riqi_hour+":" + riqi_min+ ":"+riqi_sec+":";
riqi_year.Format("%d",GPS.year);
riqi_month.Format("%d",GPS.month);
riqi_day.Format("%d",GPS.day);
riqi_clock2 = riqi_year+":"+riqi_month+":"+riqi_day+":";
riqi = riqi_clock2+ riqi_clock1;
SetDlgItemText(IDC_EDIT11,riqi);
//纬度的处理和提取
::strcpy(data1,m_wei);
wei_d.Format("%c%c",data1[0],data1[1]);
wei_m.Format("%c%c%c%c%c%c%c",data1[2],data1[3],data[4],data1[5],data[6],data1[7],data1[8]);
GPS.weid = atoi(wei_d);
GPS.weim = atof(wei_m);
wei_d1.Format("%d", GPS.weid);
wei_m1.Format("%f",GPS.weim);
weidu = atoi(wei_d1)+atof(wei_m1);
SetDlgItemText(IDC_EDIT12,weidu);//显示纬度信息
//经度的处理和提取
::strcpy(data2,m_jing);
if(m_jing.GetLength() == 9) //经度未超过90度
{
jing_d.Format("%c%c",data2[1],data2[2]);
}
if(m_jing.GetLength() == 10)//经度超过90度
{
jing_d.Format("%c%c%c",data2[0],data2[1],data2[2]);
}
jing_m.Format("%c%c%c%c%c%c%c",data2[3],data2[4],data2[5],data2[6],data2[7],data2[8],data2[9]);
GPS.jingd = atoi(jing_d);
GPS.jingm = atof(jing_m);
jing_d1.Format("%d",GPS.jingd);
jing_m1.Format("%f",GPS.jingm);
jingdu = atoi(jing_d1)+atof(jing_m1);
SetDlgItemText(IDC_EDIT14,jingdu);//显示经度信息
}
}
6.4摄像头图像的采集
6.4.1摄像头采集流程图

6.4.2代码实现
摄像头的采集利用了OPENCV的方案进行了处理,先对其摄像头进行初始化,代码如下:
capture = cvCreateCameraCapture(0);//capture是cvCapture类型
if(!capture)
{
AfxMessageBox("设备打开失败!");
}
SetTimer(1,50,NULL);//开启摄像头定时器 每50采集一帧图像。
对摄像头的采集到的视频流处理,我们使用了定时器的方式,即每隔50MS从视频流中捉取一帧的图像,从而达到视频的播放效果!在定时器中的响应函数加入如下的代码:
mage = cvQueryFrame(capture);//从视频流中捉取一帧的图像
m_Cimage.CopyOf(mage,1); //保存一帧图像到m_Cimage
CDC *dc = GetDlgItem(IDC_video)->GetDC();//得到一个显示图像区域的句柄
HDC hDC = dc->GetSafeHdc();
GetDlgItem(IDC_video)->GetClientRect(&rect);//得到显示图像区域的坐标信息
m_Cimage.DrawToHDC(hDC,&rect);//把图像显示到指定的位置
ReleaseDC( dc );//释放环境设备量
即可完成功能要求。
(Revision: 20 / 2011-08-29 12:53:19)