疲劳检测系统关键技术研究
2019-05-25张利利马进沈超惠铎铎高敏超
张利利, 马进, 沈超, 惠铎铎, 高敏超
(1.空军军医大学 航空航天医学教育部重点实验室, 西安 710032; 2.长安大学 信息工程学院, 西安 710064)
0 引言
航天飞行是一项强度大、风险高的技术性工作。它不仅要求飞行员要有过硬的飞行技能,还应有强健的身体素质和良好的心理品质。但由于多方面的原因,如机舱的噪声和振动、气压变化、长时间飞行、高应激状态、昼夜节律以及睡眠不足等[1],飞行员经常处于疲劳或者半疲劳状态,导致飞行员工作能力下降,容易引起判断失误或者飞行错觉,更为严重的是引发飞行事故。不仅如此,机动车驾驶、机场航班调度、航天测控以及军事系统值班等,人员疲劳都会产生重大隐患。因此,研究人体疲劳的检测技术对遏制疲劳工作,提高社会多领域交通生产安全具有重大意义[2]。
现有一套自主研发的、可以直观、间接判断疲劳的系统。它采用具有4个压力传感器的二维重心板,通过计算机对采集的信号进行转换处理,从而判断受试者是否处于疲劳状态。本文主要介绍该系统开发的两个关键技术。
1 关键技术简介
1.1 线程
在Windows操作系统中,进程是应用程序运行的一个实例,每个进程可以拥有一个或多个独立的线程。一个进程只拥有一个线程称为单线程,单线程是按照程序的先后顺序执行的。一个进程拥有多个线程称为多线程,多线程允许多个任务同时执行[3]。
对于疲劳检测系统而言,利用多线程技术实现对不同功能的同步处理,只需解决线程间的协调同步[4],就可达到多个线程的并发执行,有效地提高了程序的处理速度好效率。而且如果某单个线程崩溃也不会影响其他线程的正常运行,从而有效提高了系统的稳定性和可靠性。
多线程虽然有很多的优点,但是如果对线程处理不当,则会造成数据采集缓慢,效率低下,甚至会由于线程间的死锁而导致程序崩溃。因此,必须设计好线程间的通信、同步和调度的方法:
1.1.1 线程间通信
通常不同的线程为了协作实现某种功能,相互之间就必须进行通信。我们可以采用全局变量、使用消息或者使用事件对象。
1.1.2 线程的同步
同步是多线程必须要解决的问题,使一个进程的各个线程协调一致地工作即为线程 同步[5],通过同步技术可以防止同一时间有多个线程对同一数据进行操作,以免数据的完整性遭到破坏。
1.1.3 线程的死锁
任何时候当一段代码需要两个或两个以上的资源时,都有可能造成死锁,这时就破坏了线程的独立性,整个线程就会陷入互相等待的死循环中。避免死锁的最有效的方法是强迫线程将资源释放,即如果线程所需要的资源没有完全获得,就在等待一段时间后把自己已经获得的不完全的资源释放掉,从而避免因互相等待对方的资源而发生死锁。
1.2 动态链接库(DLL)的调用
DLL(Dynamic Link Library)文件即动态链接库是一种文件类型。使用DLL的好处是可以减少磁盘和内存的占用。比较常用的开发工具,都可以编译生成DLL文件。这种文件具有通用性,各种语言平台都能支持调用。
目前有两种方法可以实现动态链接库的调用,一种是静态调用,另一种就是动态调用。静态调用需要同时加载LIB库文件和DLL文件,而动态调用只需要加载DLL文件。
两种调用方式的比较:
1.2.1 静态调用:
优点:
① 代码装载速度快,执行速度比动态链接库稍快;
② 只需保证在本计算机开发的系统中有正确的.LIB文件,在发布程序时不需考虑在其他计算机上是否存在.LIB文件以及文件版本是否兼容,可避免DLL地狱等问题。
缺点:
使用静态链接生成的可执行文件体积较大,包含相同的公共代码,容易造成内存和磁盘空间的浪费。
1.2.2 动态调用:
优点:
① 更加节省内存并减少页面交换;
② DLL文件与EXE文件独立,只要输出接口不变,更换DLL文件不会对EXE文件造成任何影响,因而极大地提高了可维护性和可扩展性;
③ 不同编程语言编写的程序只要按照函数调用约定就可以调用同一个DLL函数;
④ 适用于大规模的软件开发,使开发过程独立、耦合度小,便于不同开发者和开发组织之间进行开发和测试。
缺点:
使用动态链接库的应用程序不是自完备的,它依赖的DLL模块也要存在,如果使用载入时动态链接,程序启动时发现DLL不存在,系统将终止程序并给出错误信息。而使用运行时动态链接,系统不会终止,但由于DLL中的导出函数不可用,程序会加载失败,而且其运行速度比静态链接要慢。
2 关键技术的实现
本系统采用C++ Builder设计开发主程序,而采用VS开发环境较方便实现与单片机的通信,所以我们首先在VS环境中编写与之交互的动态链接库文件,而C++ Builder则通过调用动态链接库myUSBClass.dll文件实现与单片机的通信。本系统的测试时间为32秒,单片机每32毫秒采集一次数据,并将它发送给主程序,即共有1024组数据需要处理。主程序将这些数据通过复杂的变换、处理之后将转换为人体的重心坐标,而且还需要将人体重心的移动轨迹实时绘制并显示在系统运行的界面中。
2.1 多线程的实现
2.1.1 程序框架介绍
为了提高系统的执行速度和效率,我们需要采用多线程技术。根据系统功能的要求把程序各功能模块放在几个独立的线程中执行,他们分别是数据采集线程,数据处理线程和与交互线程。
数据采集线程负责读取采集卡的模拟和数字信息;数据处理线程负责对采集到的数据进行保存和处理;交互线程负责显示测试界面以及响应用户操作。线程间交互及逻辑关系图如图1所示。
图1 程序框架图
2.1.2 具体实现步骤
利用C++ Builder提供的Thread对象,多线程的编程变得非常简便易用[6]。在应用程序中,对线程的常用操作包含以下步骤:(1)生成一个线程类;(2)实例化一个线程对象;(3)开启一个线程,利用函数Execute()完成线程所需要实现的功能;(4)在需要阻塞线程的位置调用Sleep()函数使线程进入休眠状态;(5)线程执行完后,程序自动销毁线程并且释放线程所占用的资源。下面给出部分的实现代码:
class TMyReadThread:public TThread
{
public:
__fastcall TMyReadThread(void);
private:
void __fastcall Execute(void);
}; //生成一个读取采集数据的线程类
……
TMyReadThread*MyThread1;
……
MyThread1=new TMyReadThread(); //实例化一个线程对象
void __fastcall TMyReadThread::Execute() //线程要实现的功能
{
……
while(iLengthReceive>0)
{
iLengthReceive--;
UsbReadData((int)(m_hdevice),chFrameReceive,65,40);
memcpy(&CommBytes[iFrameCount*13],&chFrameReceive[1],13);
iFrameCount=iFrameCount+1;
……
}
Sleep(2000);
}
2.2 调用动态链接库的实现
本系统的主程序和动态链接库并非是在同一个编程环境下开发的,所以我们采用动态调用dll的方式更为简单。其具体实现步骤如下:
1) 将 myUSBClass.dll文件拷贝至工程目录下。
2) 在对应的头文件中定义函数;这里的函数为地址转换函数。
HANDLE (_stdcall*UsbGetDevice)(unsigned int vendorid, unsigned int productid);
bool (_stdcall*UsbSendData)(int usbcomHandle, unsigned char data[]);
DWORD (_stdcall*UsbReadData)(int usbComHandle, unsigned char*buffer, unsigned int readlength, unsigned int waiting);
3) 定义模块句柄,全局变量,它是载入DLL文件后的实例;
HINSTANCE m_hdll;
4) 装载DLL并得到模块句柄;
在主程序的构造函数中利用LoadLibrary动态加载库文件
AnsiString str= ExtractFilePath(Application->ExeName) + "myUSBClass.dll";
m_hdll=LoadLibrary(str.c_str());
5) 定义函数地址;
FARPROC getdevice;
FARPROC senddata;
FARPROC readdata;
6) 调用GetProcAddress函数获取指定导出函数的指针;
getdevice=GetProcAddress(m_hdll,"UsbGetDevice");
senddata=GetProcAddress(m_hdll,"UsbSendData");
readdata=GetProcAddress(m_hdll,"UsbReadData");
7) 强制类型转换,即将所获取的函数地址强制转换为函数;
UsbGetDevice=(HANDLE __stdcall(_cdecl*) (unsigned int,unsigned int))getdevice;
UsbSendData =(bool __stdcall (__cdecl*)(int, unsigned char*))senddata;
UsbReadData=(DWORD __stdcall (__cdecl*)(int, unsigned char*, unsigned int , unsigned int))readdata;
8) 函数调用;
UsbReadData((int)(m_hdevice),chFrameReceive,65,40);
9) 使用完毕后,用FreeLibrary释放DLL。
在主程序的FormDestroy函数中释放库文件
FreeLibrary(m_hdll)。
3 总结
本文主要介绍了在C++ Builder中利用多线程技术提高系统资源的利用率从而提升软件的整体执行效率,这种解决问题的思路有一定的通用性和参考价值。通过动态链接库将软件划分为多个独立的模块,由不同的开发人员实现不同的功能,增加了系统的可重用性和可移植性。