速度参数实时数据采集的软件实现
1 实时数据采集的要求及软件平台
---- 数据采集一般是通过软件或硬件的定时中断通过A/D来读取外界传感器的数据。因此实时数据采集的首要的基本要求是定时准确,即采样间隔具有较好的一致性。
---- 实时数据采集系统过去一般是在DOS操作系统下应用汇编语言开发的。Windows操作系统的普及应用,尤其是可视化开发软件Visual C++ 的出现,为软件开发提供了强大的图形界面功能,使得开发出来的应用程序具有良好的人机交互功能。汇编语言的特点是难调试,而高级语言具有良好的可读性及方便的调试手段。
多媒体---- 本文采用美国微软公司推出的Visual C++为软件开发工具,采样间隔采用多媒体定时器进行精确定时,并采用Visual C++ 提供的端口操作的操作台函数进行硬件I/O编程。
2 多媒体定时器和硬件接口函数
---- Visual C ++ 提供了两种定时器。一般常用的是系统计时器,它使用函数SetTimer进行初始化,应用程序响应SetTimer函数发送来的消息WM_TIMER。这个定时器是IBM PC硬件和ROM BIOS构造的定时器逻辑的一个相当简单的扩展。PCROM初始化Intel8259定时器芯片来产生硬件中断08H。这个中断有时称为"定时器滴答"中断。中断08H每隔54.925毫秒产生
一次,或大约每秒18.2次。BIOS使用中断08H更新存于BIOS数据区的"时间"值。因此,这个定时器在Windows中的最大缺点是计时器的最大分辨率是55毫秒,也就是说应用程序每秒只能接收到18个消息。此外,这个计时器消息的优先权太低,只有在所有的消息(除了消息WM_PAINT)被处理后才能被处理。因此函数SetTimer只能用于一般的定时,如扉屏显示时间定时等,它远远不能满足实时数据采集的要求。本文重点介绍的是多媒体定时器(Multimedia Timer)。它使用自己单独的线程(Thread),来调用一个自己的回调函数(Callback Function)。它的优先级很高,它每隔一定时间就发送一个消息而不管其它消息是否执行完。此外,对于现在的Intel CPU来说,它的最小定时精度通常都可以达到1毫秒,足够满足实时数据采集的定时精度。第4节将详细阐明Visual C++ 5.0 中多媒体定时器使用的详细过程。
---- Visual C++ 5.0 作为C++的可视化编程工具,具有C语言对硬件操作的能力。它提供了大量的操作台函数(可参阅Visual C++提供的帮助)。例如:从端口地址读取数据的函数_inp(读字节), _inpw(读字), _inpd(读双字)和向端口写操作字和赋初值的函数_outp(写字节), _outpw(写字), _outpd(写双字)。_inp, _inpw, _inpd三个函数的参数均为地址变量,返回的是该地址口读取的数据。_outp, _outpw, _outpd三个函数的第一个参数
是地址,第二个参数是须写入地址的数据。
---- 读端口地址的三个函数原型分别是:
int _inp( unsigned short port );
unsigned short _inpw( unsigned short port );
unsigned long _inpd( unsigned short port );
向端口地址写数据或命令字的三个函数原型分别如下:
int _outp( unsigned short port, int databyte );
unsigned short _outpw( unsigned short port, unsigned short dataword );
unsigned long _outpd( unsigned short port, unsigned long dataword );
---- 举例来说,对端口地址0xAddress写入字节数据0xData_outp(0xAddress,0xData),而若从该地址读取字节数据,则用_inp(0xAddress)。第5节将以一实例介绍硬件操作的全过程。
3 Visual C++多媒体定时器的编程实现
---- 3.1设定Windows 95多媒体定时器[1][2]
---- 多媒体定时器可直接用Component Gallery在项目中插入Windows Multimedia组件,此
时多媒体定时器所需的头文件和库将自动插入工程的Stdafx.h中,或用手直接将以下语句添入Stdafx.h,即:
#include
// CG: The following line was added
by the Windows Multimedia component.
#pragma comment(lib, "winmm.lib")
---- 3.2 多媒体定时器的应用 1)定义定时器参数
#define TEN_MILLI_SECOND 200 //定时器间隔
#define TIMER_ACCURACY 1//定时器精度
UINTTimer_ID;//定时器句柄
UINTwAccuracy; //定时器精度参数
2)通过多媒体定时器设备函数
timeGetDeviceCaps获得本微机的最大分辨率。
TIMECAPS tc; //定时器分辨率的结构
If(timeGetDeviceCaps(&tc,sizeof(TIMECAPS))
= = TIMERR_NOERROR)
{
//获得本系统的最小定时器分辨率,
所有应用必须大于等于该分辨率
wAccuracy=min(max(tc.wPeriodMin,
TIMER_ACCURACY),tc.wPeriodMax);
//设定本应用的所需的定时器分辨率,
本例为微机的所允许的最大分辨率
timeBeginPeriod(wAccuracy);
}
3)应用多媒体定时器的timeSetEvent
数设定事件的触发方式,它的函数原形是:
MMRESULT timeSetEvent( UINT uDelay,
UINT uResolution,
LPTIMECALLBACK lpTimeProc,
DWORD dwUser, UINT fuEvent);
uDelay用于设定事件触发间隔;
uResolution用于设定程序所需的最小分辨率;
lpTimeProc 调用回调函数;
dwUser 用户提供的回调数据;
fuEvent 事件触发方式,
Visual C++中有两种方式:
TIME_ONESHOT:事件仅触发一次
TIME_PERIODIC:每隔一定时间触发一次
TimeSetEvent函数返回定时器句柄,
具体应用是:
Timer_ID=timeSetEvent
(TEN_MILLI_SECOND,wAccuracy,
( LPTIMECALLBACK)CatchMMTimer,
(DWORD)hWnd,TIME_PERIODIC);
4)声明一个全局的回调(Callback)函数
void CALLBACK TwoHundredMilliSecondProc
(UINT wTimerID,UINT nMsg,DWORD
dwUser,DWORD dw1,DWORD dw2)
---- 在回调函数中调用事件触发消息且在回调函数中语句尽量简单,不要在回调函数内做一些耗时的操作;
---- 5)添加用户消息CatchMMTimer函数,用来接收多媒体定时器的事件通知。其过程是首先在类的头文件定义:#define MYMSG_TIMER WM_USER+101,然后在类头文件的AFX_MSG块中说明消息处理函数:afx_msg LRESULT OnMymsgTimer(WPARAM wParam, LPARAM lParam); 在类实现的消息映射块中,使用ON_MESSAGE宏指令将消息映射到消息处理函数中:ON_MESSAGE (MYMSG_TIMER, OnMymsgTimer)。最后在相应类中实现消息处理函数。关于用户自定义消息具体可参考Visual C++ s书籍。如:PostMessage(HWNDdwUser,MYMSG_TIMER,0,0); //PostMessage发送消息
---- 6)定时器的任务完成后,要及时删除,否则占用太多内存,系统会越来越慢。删除定时器分两步,首先调用timeKillEvent函数删除定时器句柄,然后用timeEndPeriod函数删除
定时器的分辨率。具体应用如下:
---- timeKillEvent(Timer_ID);
---- timeEndPeriod(wAccuracy);
---- 本节所用所有函数的使用可参阅Visual C++提供的在线帮助。
4 Visual C++硬件I/O操作的编程实现
---- 在对硬件的操作中,除了应用本文2节中的函数外还必须在相关类的实现文件中添加操作台的头文件#include "conio.h"。本文以康拓研制的IPC5387计数板中的82C54计数器芯片u24的第一个计数器通道为例进行较为系统的说明。该板有482C54芯片,具有1216位计数。它直接插在PC总线插槽中。下面例中命令字的含义及读写操作顺序可参考82C54的有关资料,其中所用的地址值可参考IPC5387计数板的说明书。
---- 1)定义控制口地址变量和计数器0高低字节变量
unsigned short U24CtrlPort;
unsigned int i1L, i1H;
//计数器0高低字节变量
并在类的构造函数中赋命令口地址值及初值
usCtrlPort=0x163;
i1L=0;
i1H=0;
---- 2) 初始化82C54时,给本芯片的控制口地址赋操作命令字并赋初值
Initialize82C54()
{
//在本文中芯片的读写操作均为先读低字节,
再读高字节
_outp(usCtrlPort,0x30);//写命令字
_outp(0x160,0x00); //计数器0赋初值
_outp(0x160,0x00);
}
---- 3) _inp函数读出各地址的值,读数之前先锁存计数值再读数
void Read82C54Data()
{
/
/锁存82C54计数器的通道
_outp(CtrlPort1,0x00); //锁存82C54计数器0
//读出计数值,先读取低字节,再读取高字节
//读取82C54计数器0
i1L=_inp(0x160);
i1H=_inp(0x160);
_outp(0x160,0x00); //计数器0重赋初值
_outp(0x160,0x00);
}
5 防滑器速度参数实时数据采集的编程实现
---- 防滑器速度参数数据的采集过程如图1所示。计数板(本文采用康拓公司的IPC5387,计数芯片是82C54)插在PC机主板的ISA插槽,车辆四轴的脉冲速度传感器安装在轴头。并采用"飞读"方式,计算机按一定的时间间隔 读取计数板内的脉冲数,将脉冲数按公式(1)转换为速度值。

1 防滑器速度参数采集过程

---- 式中: :轴切线速度, :脉冲数, :采集周期。
---- 根据45两节所阐述的内容,我们在Visual C++的工作台上用New建立一个新的MFC AppWizard(exe) 项目Project,确保建立一个单文档程序并选择中文字库。在Resource View资源MenuIDR_MAINFRAME中添加一个弹出式菜单, Caption中填入"数据采集",然后在两个菜单项的Caption中填入"开始""结束"并分别定义IDID_DATA_STARTID_DATA_STOP。用鼠标右击菜单,并在弹出的浮动菜单上选择ClassWizard进入MFC ClassWizard中的Message Maps中定义消息处理函数,在Class Name中选择文档类,在Object Ids中分别选择ID_DATA_STARTID_DATA_STOP,在Messages中双击Command并接受缺省的函数名OnDataStartOnDataStop。点击Edit Code按钮,VC将光标自动定位到所定义的函数。
---- 在文档类的头文件中定义一个全局回调函数和一些地址和数据变量,并按第4节的过程设置定时器参数,在OnDataStart()函数中启动多媒体定时器,完成对硬件的初始化。多媒体定时器每隔一定的时间间隔在回调函数中调用自定义的Read82C54Data函数,实现计数
值的连续实时采集。 OnDataStop()函数完成定时器的删除工作。具体的实现按45节的编程过程操作。
---- 所有操作完成后,通过编译连接并生成执行文件后,该程序将按用户设定的时间间隔从计数器I/O端口读取传感器所采集的脉冲数数据。
6 结论
---- Visual C++ 实现实时数据采集系统的主要关键是保证定时器的定时精度和对数据采集卡等硬件I/O接口地址数据的读写操作。本文利用了Visual C++ 5.0的多媒体定时器和硬件操作函数大大提高了实时数据的采集精度。本文的多媒体定时器的定时精度可达到1毫秒,可以满足绝大部分实时数据采集系统的定时要求。

This article was contributed by Simon Wood.
This simple class encapsulates the multimedia timers. To use the class include mmTimers.cpp and mmTimers.h in your project. Link with winmm.lib. To use a timer derive a class from CMMTimers and override member timerProc. timerProc will be called when the timer goes off. Instantiate a variable of the new class. The parameter to the con
structor is the timer resolution in ms. To start a timer call startTimer. The first parameter specifies the period of the timer in ms. The second parameter specifies whether the timer is a one shot or periodic timer. To stop a periodic timer call stopTimer.
This code was developed with Visual C++ 5.0 and has been tested on NT 4.0.
The source follows:
The header file, mmTimers.h
#ifndef ___multimedia_timers___
#define ___multimedia_timers___

#include
class CMMTimers
{
public:
CMMTimers(UINT resolution);
virtual ~CMMTimers();
UINT getTimerRes() { return timerRes; };
bool startTimer(UINT period,bool oneShot);
bool stopTimer();
virtual void timerProc() {};
protected:
UINT timerRes;
UINT timerId;
};

#endif
The source file, mmTimers.cpp

#include "StdAfx.h"
#include "mmTimers.h"

CMMTimers::CMMTimers(UINT resolution) : timerRes(0), timerId(0)
{
TIMECAPS tc;
if (TIMERR_NOERROR == timeGetDevCaps(&tc,sizeof(TIMECAPS)))
{
timerRes = min(max(tc.wPeriodMin,resolution),tc.wPeriodMax);
timeBeginPeriod(timerRes);
}
}

CMMTimers::~CMMTimers()
{
stopTimer();
if (0 != timerRes)
{
timeEndPeriod(timerRes);
timerRes = 0;
}
}

extern "C"
void CALLBACK internalTimerProc(UINT id, UINT msg,
DWORD dwUser, DWORD dw1, DWORD dw2)
{
CMMTimers * timer = (CMMTimers *)dwUser;
timer->timerProc();
}

bool CMMTimers::startTimer(UINT period,bool oneShot)
{
bool res = false;
MMRESULT result;
result = timeSetEvent(period, timerRes, internalTimerProc,
(DWORD)this,oneShot ? TIME_ONESHOT : TIME_PERIODIC);
if (NULL != result)
{
timerId = (UINT)result;
res = true;
}
return res;
}

bool CMMTimers::stopTimer()
{
MMRESULT result;
result = timeKillEvent(timerId);
if (TIMERR_NOERROR == result)
timerId = 0;
return TIMERR_NOERROR == result;