1 引言
实时性是数控系统一项重要的性能指标。
在IPC(Industrial Personal Computer )+运动控制器构成的开放式数控系统开发平台上,虽然这种主从式结构,确保了运动控制指令在运动控制器内高速、实时的被执行,但在PC机上,仍需要完成诸如实时显示、预处理计算、系统状态监控等许多任务。为了保证系统的实时性能,拟采用多线程技术,通过多任务并行处理的方式,提高系统实时性。
本开发平台采用IPC+运动控制器模式的开放式数控系统,主要的运动控制由固高公司的GT400-SV通用运动控制器完成。它提供C语言函数库GT400sv.lib和Windows动态连接库GT400.dll,能够实现复杂的控制功能[2]。数控系统的开发是将这些控制函数与自己控制系统所需的数据处理、界面显示、用户接口等应用程序模块集成在一起,建造符合特定应用要求的控制系统。
2 进程与线程以及多线程技术
Windows操作系统既支持多进程,又支持多线程。一个进程就是应用程序的一个实例,一次执行过程也就是调入内存准备执行的程序,包括当前执行的应用程序的执行代码和程序执行相关的一些环境信息。每个进程拥有整台计算机的资源,无须知道其他进程在计算机中的信息。通常每个进程至少有一个线程在执行所属地址空间中的代码,该线程称为主线程。如果该主线程运行结束,系统将自动清除进程及其他地址空间。
线程是进程内部执行的路径,是操作系统分配CPU时间的基本实体,是程序运行的最小单位。每个进程都由主线程开始进行应用程序的执行。线程由一个堆栈、CPU寄存器的状态和系统调用列表中的一个入口组成。每个进程可以包含一个以上的线程,这些线程可以同时独立地执行进程地址空间中的代码,共享进程中的所有资源。
Windows系统分配处理器时间的最小单位是线程,系统不停地在各个线程之间切换。在PC机中,同一时间只有一个线程在运行。通常系统为每个线程划分的时间片很小(ms级别),这样快速系统的实时性就有了保障[3]。
要实现多线程编程,可建立辅助线程(worker Thread)和用户界面线程(User Interface Thread)。辅助线程主要用来执行数控程序、坐标显示、动态仿真和数据预处理;用户界面线程用来处理用户的输入,响应用户产生的事件和消息。
3 数控系统实时性分析
3.1 线程的实时性
数控系统需要完成的任务有很多,这些任务中,优先级的要求级别不一样。据此,可以利用Windows系统的多任务、抢占式的特点和多线程技术将各个任务分给不同的线程,并赋予各个线程不同的优先级,当高优先级的线程执行时,即实时性要求高的任务需要执行时,可以自动地终止其他线程的工作转而执行这一线程[4]。通过这一方法,可以实现数控系统所要求的实时性。
3.2 辅助线程创建
本开发系统中所创建的辅助线程可大致划分如下:
(1)坐标显示线程
在手动脉冲面板、电动控制面板和增量控制面板中,可实时显示X、Y、Z三个运动轴的坐标。这样可使操作人员直观看到三轴的实际坐标。实时性要求较低,所以使用最低优先级:Lowest Normal。
(2)图形显示线程
图像显示线程用于在动态仿真面板中执行图形绘制的指令。通过图形显示,操作者可以在动态仿真的同时,对人机界面进行操作。这一线程实时性要求较低,等级为:Blow Normal。
(3)IO状态控制线程
此线程用于检测由系统输入的各个离散量,以及从数控程序得到的指令来输出机床各离散量的状态。此线程优先级比前两线程高,等级为:Normal。
(4)数据预处理线程
数据预处理线程主要负责完成编码形式转换、刀具长度补偿、刀具半径补偿和公英制转换等运动控制数据预处理函数的执行。等级为:Normal。
(5)运动控制线程
此线程主要用于运动控制器执行数控代码函数的运行。负责向缓冲器输入运动控制命令,清空缓冲器和打开关闭缓冲器等操作。等级稍高:Above Normal。
(6)紧急控制线程
此线程处理一些需要机床立即做出反应的时间,如机床的急停等。优先级最高,等级为:Highest。
本系统中所创建的辅助线程可大致划分如下表1所示。
表1 线程的创建及优先级设置
4 多线程的实现
在Windows操作系统中,多线程的实现需要调用一系列的API函数,如CreateThread、ResumeThread等,比较麻烦且容易出错。使用新一代RAD开发工具C++ Builder中的TThread类,可以方便地实现多线程的编程,特别是对于系统开发语言是C的Windows系列操作系统,它具有其它编程语言无可比拟的优势。
4.1 线程的创建
在C++ Builder中虽然用TThread对象说明了线程的概念,但是TThread对象本身并不完整,需要在TThread下新建其子类,并重载Execute来使用线程对象。
在C++ Builder IDE环境下选择菜单File|New,在New栏中选中Thread Object,按OK,在弹出的对话框中输入TThread对象子类的名字CoordinateDisplyThread,自动创建了一个CoordinateDisply的TThread子类。同时在编辑器中创建了一个名为CoordinateDisplyThread单元。
4.2 线程的实现
在创建的代码中Execute()函数就是要在线程中实现的任务的代码所在处。在原Unit1.cpp代码中包含了CoordinateDisplayThread.h文件。使用时,动态创建一个TCoordinateDisplay对象,具体执行的代码就是Execute()方法重载的代码。
由于Execute()中添加的线程运行时所需要执行的函数调用了VCL组件,而VCL对象不具有线程安全性,它们的特性和方法只能在主线程中访问,所以用Synchronize()函数将坐标显示函数进行包装。而坐标显示函数需如下声明:
void_fastcall Function().
下面以坐标显示线程即CoordinateDisplayThread的实现步骤为例,说明线程实现的具体方法。其他线程的实现需根据具体情况,进行修正。
在CoordinateDisplayThread.cpp文件中的CoordinateDisplayThread::Execute()函数里添加如下语句,实现X、Y、Z坐标显示函数调用的一致性。
首先用switch语句判断单轴运动中的哪一轴的坐标位置发生改变:
做好上述准备工作之后,需要在主单元中的适当的位置添加开始线程和挂起线程的命令。代码如下所示:
.3 关于线程同步
线程同步在编程技术中非常重要,当一个线程在访问一个进程对象时,如果另一个线程要改变该对象,可能产生错误的结果。在本例开发应用中,利用API函数,可以直接使用临界或互斥来达到同步的目的。为了提高同步的可靠性和灵活性,同时用到了标志变量和临界机制。只需在程序中声明一个TRTLCriticalSection类型的变量Sect1,并在主线程的构造函数中进行初始化。之后在某个线程中,可以把相应的代码标记为临界部分,当在一个线程中调用EnterCriticalSection()并传递Sect1时,就设置多个数据成员,以表明临界部分进入活动状态。如果另一个线程要调用它自己的临界部分时,函数EnterCriticalSection()将发现有一个临界部分正在使用,就让第二个线程处于休眠状态,直到第一个线程退出临界部分为止。#p#分页标题#e#
5 结束语
本文将C++ builder多线程技术应用于开放式数控系统的软件设计中,有效的解决了线程同步问题,保证了数控软件系统的实时性要求,取得了较好的运用效果。
转载请注明出处。