首页 理论教育 上位机软件设计

上位机软件设计

时间:2022-02-12 理论教育 版权反馈
【摘要】:当给定计算机的计算能力和必需的硬件电路后,软件设计的关键就是应用不同的软件实现不同的功能。光电交互式电子白板系统的应用软件主要有以下三个层面:集成的开发环境、与硬件的接口和用户界面。描迹和数据管理模块可以实现电子白板描迹的基本功能,而其他模块则实现电子白板的各种辅助功能。为了保证通信的可靠性,必须依据系统的软硬件设计和系统实际情况以及相关标准制订适合本系统的通信握手协议。

当给定计算机的计算能力和必需的硬件电路后,软件设计的关键就是应用不同的软件实现不同的功能。光电交互式电子白板系统的应用软件(我们将其命名为COETEW)主要有以下三个层面:集成的开发环境、与硬件的接口和用户界面。提高软件编程效率的关键是采用面向对象的编程方式,以便于以后的扩充。

COETEW是基于Windows消息传递机制的多线程程序。建立两个线程,主线程用来维护用户界面,副线程监视串行口,获得从该口传来的数据,并通知主线程进行数据处理。随着研究的细致深入,需要实现更多电子白板的功能,所以在进行软件设计时,必须预留软件接口,使得系统有扩展和升级的可能。

7.6.1 系统软件流程

如同其他基于Windows消息驱动机制的应用程序一样,程序启动,在做完相应的初始化操作(这些初始化包括界面初始化、串口初始化、数据处理模块初始化等)之后,即显示程序主界面,同时进入消息等待循环。当有消息进入队列时,主程序调用相应的消息处理函数来完成不同功能。主程序获得串口数据后交给数据处理模块进行计算,最后交由光标控制函数根据按键信息触发相应的鼠标事件,如果有描迹键信息则调用描迹管理模块进行描迹,然后进入下一轮消息等待。如果获取的消息为存储描迹的控件消息,则主程序调用数据管理模块进行描迹存储。其他消息处理则是为界面提供的一些人机交互功能,如笔型、笔色的选择等,以及系统为扩展功能预留的一些控件。

各个模块分别完成不同的功能,从软件结构上看,用户界面是整个系统程序的基石,其他的底层模块通过程序界面起作用,同时交换所需的数据。串口通信模块是程序与系统硬件交换数据的通道,主程序通过该模块获取下位机解析出来的光点位于CCD像面上的坐标值。而程序的关键模块是数据处理模块,程序通过该模块将获取的坐标值转换为控制光标行为的屏幕坐标,整个程序的性能将取决于该模块对数据转换的精确度和稳定性。描迹和数据管理模块可以实现电子白板描迹的基本功能,而其他模块则实现电子白板的各种辅助功能。

7.6.2 串口通信模块

7.6.2.1 串口功能实现

在COETEW程序中,Windows下串口通信的具体实现使用了一个CSerialPort类,该类声明如下(只列出本系统相关部分):

构造函数主要负责初始化该类几个重要保护成员变量:串口句柄、串口收事件(m_ov.hEvent)、发事件(m_hWriteEvent)、关闭事件(m_hShutdovnEvent)、发送缓冲区大小,析构函数用来释放占用的资源,包括串口资源、释放缓冲区。串口初始化函数[InitPort(…)函数]完成串口的初始化工作,每次只能初始化1个串口。其中,pPortOwner指调用Cwnd类的对象;接下来的5个参数分别是串口号、波特率、校验方式、数据位数、停止位个数;dwCommEvents定义串口的事件,见类定义中函数原型,其缺省值为EV_RXCHAR(系统定义的串门接收事件),如果有字符到达设定的串口的接收缓冲区,则事件被触发,调用相关函数ReceiveChar(…)接收数据。

如前所述,CSeria1Port类建立了一个辅线程来读写串口,所以主线程可以不被阻塞。当串口事件发生时,CSeria1Port类发送一个消息至主窗口,让主线程对串口得来的数据进行处理。该类定义的消息包括(只列出本系统相关的):

串口通信线程函数Comm Thread是CSerialPort类的最重要函数。函数原型:Static UINTComm Thread(LPVOID pParam)。该线程函数将作为工作线程(work_er Thread)被启动,一旦该线程被激活,它将负责监视串门的接收字符事件、发送字符事件和关闭串口事件,并调度与上述事件相关的处理函数,从而完成对串口的操作。为了防止这三个事件同时对同一个对象进行操作,它们之间的同步操作采用Critical Section(排斥体)。

线程函数Comm Thread循环调度WaitCommEvent,这个函数将立即返回。因为我们将串口创建为异步通信口(通过标志FILE_LFAG_OVERLAPPED以及异步结构体m_ov实现),如果串口的接收缓冲区有数据可读,m_ov.hEvent将被设置为Signeled状态,且调用ReceiveChar函数完成字符数据的接收。接收完毕后,如果串口的接收缓冲区没有新的数据到达,系统将会设置m_ov.hEvent事件为Not-Signaled状态,复位该事件,同时开始下一轮的WaitCommEvent。

ReceiveChar函数采用两种方式获得接收缓冲区的数据,ReaFi1e()和GetOverlappedReSults()分别对应于一次读一个字节和一次读多个字节。ReaFile()读完一个字节后将立即返回,并由Comm Thread函数发送消息至主窗口。而GetOverlappedReSults()读完一个字节后,在下一字节到来之前都将循环等待,直到指定字节的数据全部读完后才返回,同样由Comm Thread函数发送消息至主窗口。从系统上下位机数据打包结构来看,一次性发送的数据为5个字节,似乎使用GetOverlappedReSults()函数进行多字节数据读取更加适宜,但是考虑到该系统实时性较高,采用GetOverlappedReSults()函数如果遇到通信阻塞,程序将一直循环等待,这对实时控制是非常不利的,所以采用ReaFile()的方式一个字节一个字节地读取。

7.6.2.2 制订通信协议

为了保证通信的可靠性,必须依据系统的软硬件设计和系统实际情况以及相关标准制订适合本系统的通信握手协议。由于选用的信号传输速率为常用的19200 bps,对信号的采样频率为25 Hz,所以一个数据包可以容纳的字节总数为240,系统要求下位机向上位机发送光点位置在CCD上的二维坐标以及传送光笔的按键信息,并有足够的容量来进行传输。由于传输数据比较简单,采用单向握手的方式来制订通信协议,描述如下:

第一字节发送FF作为单向握手信号,表示以下5个字节属于有效的通信数据。第二字节发送当前按键信息,系统中约定无鼠标事件时控制信号为0xf0,鼠标左键为0xf1,右键为0xZ2,功能切换键为0xf4。之后发送光点CCD坐标信息。

由于系统设计的分辨率容量为X、Y方向各12位,因此总共需要24位表述坐标信息。可用4个字节表示,其每个字节最高两位固定为01,以表示本字节内容为坐标信息,其余的24位则分配给X、Y方向的位置坐标值。如是,则系统异步通信数据包长度为6个字节,具体说明如图7-33所示。串口通信模块在检测到FF信号后,将后面5个字节存入一个结构体,然后调用数据处理模块进行数据处理。

图7-33 异步串口通信数据包解析

7.6.2.3 数据处理模块

数据处理模块的数据参数包括:

(1)两个链表:CList<CCordination,CCordination&>m_cord List和CList<CPoint,CPoint&>m_Sccript Ltst,分别存储CCD坐标值和相应按键控制信息组成的结构体以及所得的屏幕坐标值。

(2)一个BYTE型一维数组:BYTEm_btemp[5],暂存从下位机得到的最新数据。

(3)两个成员变量:CCordination m_Ccord和CPoint m_Script,分别存储当前的CCD坐标值和屏幕坐标值。

数据处理模块的操作函数包括:

(1)数据还原函数int RestoreData(),用于将接收到的数据还原为CCD坐标值以及按键信息;

(2)数据滤波函数int FilterCord(),用于将数据滤波;

(3)坐标转换函数BOOL TransferCord(),用于将CCD坐标值转换为屏幕坐标值;

(4)光标控制函数BOOL CursorControl(),根据不同的按键信息触发相应的光标事件。

1.数据还原功能

数据通信模块首先从上述结构体中获取数据,然后依据通信协议将数据还原为按键信号值和CCD二维位置坐标值,并存入结构体m_Ceodr中。

2.数据滤波功能

使用的测量原理将引入CCD摄像器件上x或者y值的计数误差,在一般情况下x的误差为士1个像素,y㊣的误差为土2个像素。同时由于硬件系统进行行计数时的工作频率即元频是比较高的,在外界干扰的情况下,计数误差将大于这两个值。同时,由于操作者手执光笔在屏幕上移动,手臂的不自觉抖动也会使得光点在CCD摄像器件像面上所成的像抖动,从而使得计数误差增大。而坐标转换是将这个误差从一个小分辨率坐标转换为大分辨率坐标,所以最后得到的屏幕坐标将在一个比较大的范围内波动。这种波动对应到实际操作则表现为当操作者手执光笔停留在屏幕上某个位置时,光标并非静止不动,而是以这个位置为中心抖动,而当操作者手执光笔在屏幕上移动时,光标沿着移动轨迹线抖动。由此可见,必须选取一种适当的滤波算法,使得上述干扰减至最小。从计数结果考虑,这种波动信号可以看作是时域一维信号,采用均值滤波或者中值滤波都可以得到一个较好的结果。

3.光标控制功能

数据处理模块计算好用以控制光标的屏幕坐标后,根据不同的控制信号,调用不同的光标控制函数来进行相应的鼠标控制。当控制信号为0xf0时,只调用SetCursorPos()函数将光标移动至指定的位置;如果有别的键控制信号,调用mouse_event()函数触发相应的鼠标事件。对于描迹键信号,则获取需要描迹的窗口DC,再调用描迹模块进行图形输入。

4.描迹和数据管理模块

(1)基本方法分析。

屏幕描迹功能实际是实现图形的输入以及对其各种属性的改变。下面将具体阐述其设计方法。

对于规则图形,如直线、矩形、椭圆等,采用皮筋式作图方法。若两次在同一位置使用异或方式画同样的图形,则这两次操作的结构相互抵消。对于直线图形采用起点和终点坐标来确定,矩形采用左上角和右下角坐标来确定,圆、椭圆是采用其外切矩形的左上角和右下角坐标来确定的。而绘制图形时,这两点坐标是用户通过拖曳光笔确定的。

对于不规则图形,由于图形的每一点都是在用户的操作过程中随机确定的,点与点之间没有相互联系,所以对作图过程中的每一点都要采集,形成一个组成该图形的点队列。程序中接收到光笔的一个位置就画一条从该点到队列中上一点的线,由此组成不同的不规则图形。

(2)电子白板系统的绘图对象。

在图形输入的实现过程中,使用到抽象基类的概念。C++语言为我们提供了一种语法结构,通过它可以指明,一个虚拟函数只是提供了一个可被子类型改写的接口,但它本身并不能通过虚拟机制被调用,这就是纯虚函数。纯虚函数声明后紧跟着赋值0,如:

包含一个或多个纯虚函数的类被编译器识别为抽象类。一般来说,抽象类所表达的概念涵义太广泛,定义不出实在的对象,因而抽象类的唯一用处是为其他类提供基类,其他类可以从它这里继承共有接口。

在电子白板系统中,所有的数据都是基于对象的方式。CShpae是所有绘图类的抽象基类。在CShape中定义了一些绘图对象通用的基本接口,如:固定绘制virtual void draw(CDC*pDc),应用于打开一个存储文件时直接绘制相应对象;光笔移动时绘制Virtual void draw_move(CDC*pDc),应用于光笔实时控制时绘制对象;光标标点中测试virtual BOOL HtiTest(cPoilltpt),应用于判断光笔是否选中所绘对象。同时还定义了一些绘图对象通用的特性参数,如描述画笔粗细的int m_penWidth以及描述画笔颜色的COLORREF m_color等。

(3)数据管理。

电子白板有一个比较重要的功能就是光笔描迹的编辑、保存与恢复,这要求对图形的输入不能画过了事,而必须以一定的数据结构组织管理起来,以便于操作。

整个电子白板空间由白板页面组成,白板页面中的数据结构采用双向链表来组织,根结点为1号页面,对不同页面用不同的句柄(PID)来进行标识。这种组织形式便于用户进行前后浏览,对任意一页进行定位,用户也可以方便地插入、删除一页等,操作上非常灵活且效率高。而对任意一页中的输入图形对象的主要操作有对对象的删除、恢复、拖动、剪切、复制、选定及对图形对象的颜色、线型、线宽的设置等。同样采用双向链表的组织形式也可获得较高的效率。为保证图形对象的唯一性,对象的句柄可以用一个2元组Hnadle:<PID,OID>来表示。其中PID为页面标识号,其在全局范围内是唯一的;OID为图形对象在本页的标识号,在同一页中不同对象的标识号是不同的。这样,对于每一个增加的图形对象,都可以保持对象句柄的全局一致性。

而对单个图形对象的保存与载入则通过序列化(Seralizie)机制来实现。MFC的序列化(Serialzie)机制又称为连载机制,专用于类对象的存取。当程序保存一个对象时,先记载其类名称,然后再是对象中的数据。由于C++支持对象的动态创建,所以在对象的读取时可以通过记载的类名称动态创建该对象,由此可以实现对象的序列化。而实现序列化的条件包括:首先,类必须直接或者非直接地从CObject派生;其次,类的说明必须包含DECLARE_SERIAL宏调用;最后,类的执行文件必须包含IMPLEMENT_SERIAL宏调用。

由此,将CShpae由CObject派生,在相应位置包含上述两个宏调用,然后重载CShape的序列化函数void CShape::Serialize(CArehive&ar)。而由于CShape是一个抽象基类,其特性函数没有任何意义,所以将该函数定义为虚函数,同时在CShape的各个派生子类中实现各自具体的序列化函数。

免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

我要反馈