Profile - CN046


CN046

四轴飞行器及其UAV飞控系统

桂林电子科技大学信息科技学院
电子工程系


Finals


[print]


Project

Name of Project:四轴飞行器及其UAV飞控系统

Contact Information

Name:夏克儒
E-Mail:sonireolxxx@163.com
Telephone:03162206592
Mobile Phone:15295892628
Mailing Address:广西壮族自治区桂林市桂林电子科技大学信息科技学院花江校区 541004

Contest Advisor

Name:江国强
E-Mail:hmjgq@guet.edu.cn
Telephone:13977393225

Members

No.NameE-MailEnglish Name
1夏克儒sonireolxxx@163.comXiaKeru
2苏雪526881500@qq.comSuXue
3葛友杰543053056@qq.comGeYoujie

Project Paper - view as Preliminary(2011/08/29), Final(2011/08/29), Draft, Latest

1. 设计概述 (Preliminary Paper)

        当前世界上主流的飞行器,基本分为两大类:固定翼飞行器和旋翼飞行器,大家熟知的直升机即属于旋翼飞行器的一种.其最大特色是能完成空中悬停的动作.但直升机为保持平衡和完成操控着要求的动作,需要非常复杂的旋翼机械结构,同时要通过在尾部加装额外的旋翼防止主桨的反作用力造成自旋.这极大地增加了其设计制造难度和维护成本。

          上图为一种直升机主桨旋翼头结构的示意图,从图上可以看出,即便没有副翼系统,旋翼头的机械结构依然非常复杂,其制造和维护难度可想而知。因此在电子技术高度发达的今天,有必要设计一种新型飞行装置,依靠机载电子设备取代部分机械结构,在以不损失飞行器性能为前提下降低生产和维护成本.因此我们选择了四轴飞行器,以其为基础开发专用的飞行控制系统。四轴飞行器具有类似直升机的飞行性能,可以在空中悬停.但机械机构相对直升机要简单很多.其维持机身平衡的任务全部由机载电子设备完成,因此极大的降低了整机的生产成本和维护复杂度.四轴飞行器有用四个螺旋桨,每个螺旋桨的桨距都是固定不变的,因此省去了复杂的旋翼头结构.需调整四个臂中某一臂的升力时,只需相应改变其电机转速即可。下图为我们设计的四轴飞行器实物。

 

     为维持机身平衡稳定,电子控制部分采用3轴陀螺仪和3轴加速度传感器组成捷联惯性导航系统(IMU),通过Altera可编程逻辑器件强大的并行处理能力和灵活的IP组合方式,加之NiosII软核的有力支持,飞行器可实时计算出姿态变化并自动作出调整.结合上位机给出的用户指令,系统可出色完成飞行器的控制任务。因控制过程全部由电子系统根据实际情况自动调整,在制作过程中,对飞行器的中心,对称性,螺旋桨及电机性能一致性都没有很高要求.这对降低设计难度和量产品质控制有很大意义.

    四轴飞行器作为一种稳定的飞行平台,可搭载航拍系统,空投系统等等,在军用领域,四轴飞行器可用作战地无人侦查、定点轰炸.民用领域可用作抢险救灾、航拍、代替人完成危险工程等用途。因其很容易小型化和定制,在诸多领域皆有较大的发展前景.

(Revision: 22 / 2011-08-28 23:15:14)

2. 功能描述 (Final Project Paper)

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)

3. 性能参数 (Final Project Paper)

最大留空时间: 采用12v2200mAh锂离子电池在8min左右。该时间受飞行动作、扩展模块及气温条件影响变化较大。

自重:1.5kg左右(没有电子称无法得出准确数据,该重量不包含图像传输扩展模块)

载荷:500g以上(500ml瓶装饮料测试结果)

最高飞行高度:程序限制为1000m

最大通讯距离:在20mW输出功率 @2400bps波特率情况下,无障碍直线距离最小1000M

数传电台频率:418-455MHz(1KHz步进)

图像传输距离:在800mW输出功率情况下,无障碍空距最小800M

图传电台工作频率:1.2GHz

导航系统冷启动时间: <29秒

导航系统热启动时间(安装GPS备用纽扣电池): <1秒

航向精度: 高于1°

待机功耗:1.32W(未连接扩展模块,一代核心板,二代电调板,二代主控板)

以上各参数部分取自器件手册目前飞行器还在不断地完善中,各项指标还将继续提高,敬请期待。对于Altera器件的评价请参考第6章。

 

(Revision: 9 / 2011-08-28 23:40:18)

4. 设计结构 (Preliminary Paper)

1.硬件部分

        飞控系统硬件主要包括四个部分,下面分别介绍他们的结构框图

  1.1飞行器机械结构

        四轴飞行器机架为两根刚性支架,成90度正交组成。在每个角安置一颗电机,其中对角线位置的电机转动方向相同,相邻电机转动方向相反。根据下左图的受力分析可知,四轴飞行器依靠四只旋翼对机体的反作用力两两抵消来防止自旋。飞行器升空要求电机有足够的推力,同时需要尽量降低飞行器自重延长留空时间.提高载荷能力.同级别无刷电机推重比较传统有刷电机大得多,因此本设计采用的是新西达2212KV1000无刷电机.如下右图。


  1.2 无刷电机电子调速器

        无刷电机的驱动相较有刷电机也稍复杂.航模用无刷电机多为三线,不含霍尔传感器.需要利用电机转动时内部线圈切割磁场产生的反向感生电动势确定电机换相时机.三根线上都需要能接通12v,地或产生高阻.因此采用全桥驱动并通过PWM方式控制转速

  1.3 主控板

        主控板负责为四轴飞行器进行姿态计算,并接收上位机指令,控制电子调速器的工作,其结构框图如下

  1.4地面站

        地面站作为飞行器的控制平台,以PC机为主,DE2开发板为辅助组成。完成指令的处理和发送,并向用户提供飞行器的当前状态。

2.软件部分

  2.1无刷电机驱动

  2.2主控控制流程

  2.3上位机软件

        四轴飞行器的上位机软件设计包括主要包括如下几个模块,它们分别为串口通信模块,3D加速度传感器模块,陀螺仪传感器模块,图像显示模块,GPS导航模块。

2.3.1  串口通信模块功能

    该模块主要负责接收飞机回传的数据和发送指令给飞机,是所有模块与飞行器交换数据的基础。在这里,用户可以变更通讯端口的各项参数设置。

2.3.2  3D加速度传感器模块和陀螺仪传感器模块

        该模块主要负责实时显示飞机发送回来的加速度传感器和陀螺仪传感器数据,并将其数据显示在坐标轴中,以便我们更直观地了解飞行器状态的变化情况。

2.3.3  图像显示模块

        该模块主要负责实时显示飞行器的图像传输模块从高空回传的视频流,从而使我们可以在地面站了解飞行器视角的周围环境。这个部分可继续扩展出各种有趣的功能,例如利用图像处理算法完成跟踪移动物体,辨识特定目标等等。

2.3.4  GPS导航模块

        该部分是无人机自动导航功能的核心,此部分要完成的主要功能是对飞机实现定点,定位飞行,通过提取用户输入的GPS的坐标信息并与机载主控板通讯,从而达到控制飞机的航迹。

2.3.5 模块的设计基础

      该软件的设计是基于MFC的对话框设计,在此设计中,我们将使用微软的软件开发平台Visual C++软件开发工具,MFC中包含的控件系统函数以及微软所提供的API函数来进行各个模块的设计。 

(Revision: 12 / 2011-08-28 23:41:15)

5. 设计方法 (Final Project Paper)

        SOPC系统虽然灵活强大,但整个构建过程涉及诸多的工具和环境,要掌握基本的方法还是需要努力的。在这个过程中我们也遇到了很多问题和困惑,有的在网络上和老师那里都没有得到明确的答案,直到最后尝试摸索出一些门路才得以解决。下面这一章节着重介绍我们在开发过程中走了很多弯路的地方,希望能给有需要的同学提供一些参考。

1.姿态解算系统的构建

        在构建NIOSII外部的硬件模块时,我们的方法是先用HDL语言对模块功能作行为描述,并使用相关的工具进行验证,后生成元件符号在顶层设计中完成走线并测试功能。

        首先建立工程,名称不能过于随意,否则随着系统越来越庞大可能会造成混乱与误解。我们的方法是属于同一功能部分的模块全部采用相同的名称打头,后面用下划线做间隔加上描述具体功能的名称。所有该模块相关的文件全部保存在同一目录,并生成模块顶层设计的元件符号,并在总的顶层设计中用库包含的方式进行调用。

        下图是我们完成HDL编写后的状态。

        然后生成元件符号,并在模块顶层设计中将各个小部分按功能连接,然后生成该模块顶层设计的元件符号。

        必不可少的,我们使用Signal-Tap内嵌式逻辑分析仪验证我们的逻辑设计。顺便赞一个,Signal-Tap工具真的是非常方便,不论是内部还是外部节点,只要加入到监测节点列表中都可以实时了解其输入输出情况,可以说它是调试复杂系统时不可或缺的利器。该工具还支持增量编译,做一些很小的调整时极大的节省了编译综合的时间。下图是调试姿态处理模块时,PC端的输出情况。(截图中未开始运行)

        完成模块设计后,我们回到总顶层工程中,以包含库的方式包含功能模块所在的目录,便可以在器件列表中找到并例化模块,在一个工程中还可以放置多个相同的模块。

        至此,我们定制的功能模块就加入到了总顶层设计中。

 

2.Wishbone 总线信号与 Avalon-MM总线信号的转换

        我们在设计中采用了开源的I2C核,该IP核原设计为Wishbone总线兼容,为将该IP加入到我们的SOPC系统中,我们需要将Wishbone总线信号转化为Avalon-MM总线信号,这个过程中某些端口需要一些逻辑上的处理,为此我们需要先了解两种总线信号间的对应关系。

        通过查阅资料我们了解到如下的对应关系。

      这里有几个特殊信号需要注意,在转换过程中我们需要对它们进行逻辑转换。

Wishbone                   Avalon


                         we_i            =        !write_n_i & read_n_i


                            stb_i         =        !write_n_i | !read_n_i;

 

   waitreq_o        =               !ack_o;

      我们为原IP编写了一个顶层HDL,将原IP核例化并设置连线。这个过程必须使用HDL,否则无法导入SOPC系统,下图是相关语句。

        在SOPC BUILDER工具中新建一个user logic,顶层文件处选择我们刚才写好的文件,并将其他文件包含进去。

      继续配置信号类型和各自的详细设置。对于我们采用的I2C核,各个信号的类型描述如下图所示。

 

        最后将这个新的user logic加入到我们的SOPC系统中,就完成了两种总线信号的IP核转化。从下图中可以看出,这个I2C核没有错误,实际使用中也完全正常。

 

3.关于NIOSII的DataCache(数据缓存)

        在NIOSII程序的编写过程中,我们需要对一些寄存器进行与等于(&=)、或等于(|=)的操作,API提供的IOWR和IORD宏不能满足要求。在没有了解到更好方法的情况下,我们采用了指针指向寄存器地址,并直接对该地址内容进行操作的方法。

        但随后我们发现了问题,我们明明声明了volatile类型也执行了写语句,可寄存器内容并没有发生变化。这个写过程好像“蒸发”了一样。为此我们苦恼了很久,才终于在官方的Software Developer’s Handbook中发现了端倪,请看下图中高亮的部分。

        原来我们的指令确实没有被完整的执行,因为它被送入了NIOSII核的DataCache,也就是数据缓存。这个数据缓存是NIOSII / f所特有的。

        那么为什么系统提供的IOWR宏可以正常读写寄存器呢?手册中同样有答案,这个宏本来就可以对数据缓存进行“Bypass”,所以采用IOWR自然没有问题。

        随后我们在SOPC BUILDER中关闭了数据缓存,问题解决。但是我们还是希望了解,有没有什么方法可以不使用API也能Bypass这个数据缓存呢。答案还是在数据手册中,看来遇到问题,查阅手册还是最好的方法!NIOSII提供了32位理论上共4G的地址空间,但实际上只有2G可用,也就是说少了一位,这一位就是bit-31。它的作用是在数据缓存打开时,使能Bypass功能。也就是说如果我们不希望指令通过数据缓存,只需要在寻址的时候把地址的最高位设为1,那么指令就不会通过数据缓存了。

        至此问题解决,但最后还是要提一下,官方并不建议用户直接对寄存器进行操作,除非是底层驱动开发,否则最好采用HAL层提供的API完成需要的操作。因此我们盼望,如果哪位高人了解如何使用系统函数完成与等赋值和或等赋值,还请不吝赐教!

(Revision: 9 / 2011-08-29 16:02:12)

6. 设计特点 (Preliminary Paper)

       飞行器对于电子系统的要求是非常高的:高性能,高可靠性,高集成度。同时要求也是非常低的:低功耗,低干扰,低开发门槛。在使用Altera公司的器件和环境进行开发时,以上要求均可以满足。

        高性能:大运算量的姿态处理算法本来需要强劲的MCU支持,但在FPGA上全部由硬件完成。这样基本上不需要考虑算法对于NIOSII处理器的压力,可以最大限度的实现最高性能。

        高可靠性和高集成度:本设计中包含各种功能的大小模块,SOPC技术将它们全部集成在了一片IC中,这极大地降低了布线的复杂度,同时提高了可靠性缩短了验证周期。我们不需要考虑过多的工艺和EMC方面的问题,只需要专心开发逻辑和程序即可。对于一个入门者来说,这种设计方式的变革带来的好处是不言而喻的。

        低功耗:当前就FPGA器件本身来说,相对于其他器件的功耗不能算是绝对优势,但对于复杂系统来说就不同了。采用FPGA器件可以最大程度的缩减外围电路,以更低的主频获得更好的性能,这些优势完全可以弥补FPGA本身的功耗问题。

        低干扰:由于FPGA的工作原理,不论其中用户的设计多么复杂,走线多么不符合EMC规范,由FPGA造成的干扰问题基本可以忽略,这使我们在开发过程中倍感轻松。

        低开发门槛:Altera为开发者提供了QuartusII集成开发环境。在熟悉开发流程后,我们感觉各种按钮、窗口、快捷键的安排还都比较合理,也比较舒服。与此同时QuartusII环境中包含了各种实用的工具,在前后仿真,调试过程中我们很少被工具的问题所困扰,对于入门者来说,一个好的环境也许比一个强大的器件更为重要。Altera在官方网站上还提供了超长超全面的视频教程供大家学习,在开发门槛上,我们认为Altera做得非常友好。

(Revision: 10 / 2011-08-28 23:47:42)

7. 总结 (Final Project Paper)

         “去吧学弟,在比赛中成长与进步”

        只有当真正全身心的投入一次后,你才会真的体会到,一场大赛给你带来的不仅是成就与满足感,更多的是知识的突飞猛进和各种人生感悟。

        在比赛过程中我们通过交流,学习合作精神,学习与人沟通的方式。总有人说IT工程师越优秀,性格就越古怪,不懂合作。我想说一个优秀的项目必然是一群优秀的工程师合作的结果,一个优秀的工程师不会是一名“独行侠”。想做好工作,先学会融入一个团队。

        在技术上,我们接触了很多SOPC相关的新鲜内容,也深刻体会了SOPC相较于传统开发方式的巨大优越。在学习的过程中,我们经历了很多失败与困惑,面对了各种各样千奇百怪的问题,我们走了弯路,但是我们没有停下脚步,这些弯路会为我们每一个人积累宝贵的经验。
       
技术是没有止境的,我们在众多高手中只是几个微不足道的小角色。但不论我们多么渺小,作为准电子从业者,我们有志为中国的电子行业,尤其是FPGA器件及相关技术的发展而努力。

        另外,网络给了我们跨越地域“取经”的机会,在国内外各个论坛上,总有许多高手无私的分享着自己的知识,开展着各种各样的开源项目。没有他们的付出,也就没有我们的快速进步,在这里,请容许我向你们表达崇高的敬意。

        还有很多感悟,不能一言而尽。大赛即将截稿,但是不论结果如何,这些经历都留在我们全体队员的心里,它会支持着我们走的更远。

        感谢 Innovate Asia ... 希望有一天,我们有能力托起这个地球。

  一起加油。

 

 

(Revision: 14 / 2011-08-28 23:49:41)