小生使用delphi编写工控界面程序,为了实现1ms内读和写(硬件满足要求),将timer的interval属性设置为1。但是发现Timer这个过程执行时间不是1ms执行一次,而且误差很大。请问各位大神们在做工业控制时有没有遇到这种情况,该如何解决?
    另外一个问题是假如Timer实际可以达到1ms,那么Timer中的程序如果执行到中间的时候,1ms到了,是继续执行程序还是重新开始执行Timer?
    感激不尽!

解决方案 »

  1.   

    使用timer是单线程的,如果程序中其它部分在执行,比如数据库操作,或者其它的串口、并口在读写。  那么你timer里边的程序就要等着。所以你要写多线程的程序,你看看多线程就行了。
      

  2.   

    无语了……
    window上实现1MS控制,神了
      

  3.   

    另外一个问题是假如Timer实际可以达到1ms,那么Timer中的程序如果执行到中间的时候,1ms到了,是继续执行程序还是重新开始执行Timer?继续执行,执行完后,取出时钟消息并处理(消息中包含Timer的消息),也就是说是排队执行.送一段代码,自行修改procedure __TimeProc2(uTimerID, uMessage: UINT; dwUser: PInteger; dw1, dw2: DWORD) stdcall;
    begin
      inc(dwUser^);
    end;procedure TForm1.Button1Click(Sender: TObject);
    var
      nID : DWORD;
      i : integer;
    begin
      i := 0;
      nID := timeSetEvent(1 , 0 , @__TimeProc2 , integer(@i) , TIME_PERIODIC or TIME_CALLBACK_FUNCTION);
      Sleep(1000);
      timeKillEvent(nID);
      CloseHandle(nID);
      Memo1.Lines.Add('1秒内执行次数:' + IntToStr(i));
    end;基本上是1MS执行一次,引用MMSystem单元,注意TimeProc执行体非主线程.
      

  4.   

    读CPU时间周期,若没有高级别线程打扰,精度可达1ms,你上网查一下
      

  5.   

    谢谢各位捧场!小菜鸟万分感激!!!
    小的下去自学一下timeSetEvent和timeKillEvent的用法。
    另外微弱的问一句,4楼代码中@i是取i的地址,integer(@i)是啥意思?将i的地址取整吗?
    小菜鸟十分菜鸟,大神见谅~
    再次感谢!!!
      

  6.   

    lz,你别往这方面钻了,这些家伙都是说的理论的上的。基本是1ms是不可能的,即使是用cpu周期,操作系统不可能只有你一个线程在执行,做工控要求在毫秒级的,即使你是用板卡级采集和控制,也是不能考虑!毫秒级响应基本是要类似DCU来处理的。时间放大100-500左右,也就是100MS以上你再想这问题吧。
      

  7.   

    两位前辈不要吵小菜鸟第一次发帖,罪过罪过!
    我的实验设备中:
    1.模拟输出卡的建立输出电压时间为130us;
    2.伺服放大器和伺服电机的响应时间为5ms;
    3.编码器的输出频率不大于150k(响应时间不小于6.7us);
    4.运动控制卡的编码器输入频率为3.2MHz(最小采样时间为0.3us);
    5.工控机与板卡的PCI总线频率为33MHz(延时0.3us)。
    一、其中伺服放大器和伺服电机与机械部分作为控制对象,可以将5ms看作其最小周期,根据香农采样定理可知系统采样时间不能大于2.5ms。
    二、由1345控制系统的延时可得采样时间不能小于0.14毫秒。
    综上我选取1ms或2ms的采样时间,个人觉得比较合理,就是不知道能不能实现。老师说这套工控设备已经是很快的了。另外4楼前辈kiboisme的代码中,函数timeSetEvent调用时integer(@i)是啥意思?是不是指指向i的整形指针?
      

  8.   


    多媒体定时器的精度是ms级的,对于要1ms精确定时肯定不够,你只能用CPU的“CLICK”来定时,但是,对WINDOWS这种非实时多任务操作系统来说,时间响应要求1ms太高了。从I/O口得到数据,你的程序检测到数据,处理,再从I/O口输出数据(控制命令),这里不知道经过了多少层,即使现在CPU的频率再高,1ms也来不及响应的。何况你还得刷新界面,如果还得保存数据,这就更杯具了。对于这种高实时实响应系统,只能用真实时操作系统,也就是实时嵌入式操作系统,比如ucos,FreeRtos,等等,wondow系统只能做为后台历史数据处理(或者时间响应远大于1ms,比如100MS以上的控制的操作)。
    我们原来就客户要求做个时间精度1ms的波形分析,只是纯粹的板卡采集数据,没有控制命令,只是实时波形界面显示,还是不理想。定时器用QueryPerformanceFrequency()来读取CPU的“CLICK”,自己做底层驱动,尽量绕过API,等等
      

  9.   

    一般不会用TIMER做1MS定时,不可能的,采样卡里面有采样频率这个参数,或者在类似卡的内部写入程序,让卡本身做采集,然后读到一定数量后,再去采集,
      

  10.   

    使用多媒体计时器是可以的(按照你的最低要求“可以将5ms看作其最小周期,根据香农采样定理可知系统采样时间不能大于2.5ms”),它在自己的线程中运行,计时周期到了之后调用用户的代码,不用担心你的线程此时是否得到执行。如果还不行的话,在PC上可以使用CMOS RTC中断,中断率可以保证1KHz,高到8KHz都没有问题(理论上可以高到32KHz,但是中断处理必须非常快),缺点是在Windows下你必须写核心驱动。
      

  11.   

    感谢大家关注菜鸟!利用kiboisme的代码写了个程序试了试timeSetEvent,点击按钮1次可以顺利运行,结果显示‘执行了999次’,关闭exe的时候不报错。点击两次次按钮之后,结果显示正确,但是关闭exe时会弹窗报错:Project c:\...\Project1.exe faulted with message:'system exception(code 0xc0000008) at 0x76e5616f'.
    Process Stopped. Use Step or Run to continue.代码如下:unit Unit1;
     
     interface
     
     uses
       Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
       Dialogs, MMSystem, StdCtrls;
     
     type
       TForm1 = class(TForm)
         Memo1: TMemo;
         Button1: TButton;
         procedure Button1Click(Sender: TObject);
       private
         { Private declarations }
       public
         { Public declarations }
       end;
     var
       Form1: TForm1;
     
     implementation
     
     {$R *.dfm}
     procedure my_TimeProc(uTimerID, uMessage: UINT; dwUser: PInteger; dw1, dw2: DWORD) stdcall;
     begin
       inc(dwUser^);           
     end;
     
     
     
     procedure TForm1.Button1Click(Sender: TObject);
     var
       nID : DWORD;
       i : integer;
     begin
       i := 0;
       nID := timeSetEvent(1 , 0 , @my_TimeProc, integer(@i) , TIME_PERIODIC or TIME_CALLBACK_FUNCTION);
       Sleep(1000);
       timeKillEvent(nID);
       CloseHandle(nID);
       Memo1.Lines.Add('1秒内执行次数:' + IntToStr(i));
     end;
     
     end.再次感谢大家的回复!感激!
      

  12.   

    回复7楼我也是做工控的,也用delphi,实现1MS定时很简单话说你连界面刷新都考虑上,我提醒你,如果界面刷新也放在定时器里,必然是要出错的,WINDOWS消息只支持17MS以上的刷新间隔,不然很容易出问题,但是你就没想过用另一个线程去管界面么?界面和底层分离这是最基本的
      

  13.   

    一般我都是直接用delphiX组件的高精度timer,都是直接设1ms的。可以去看看它的源码如何实现的。
      

  14.   

      {  TCustomDXTimer  }  TDXTimerEvent = procedure(Sender: TObject; LagCount: Integer) of object;  TCustomDXTimer = class(TComponent)
      private
        FActiveOnly: Boolean;
        FEnabled: Boolean;
        FFrameRate: Integer;
        FInitialized: Boolean;
        FInterval: Cardinal;
        FInterval2: Cardinal;
        FNowFrameRate: Integer;
        FOldTime: DWORD;
        FOldTime2: DWORD;
        FOnActivate: TNotifyEvent;
        FOnDeactivate: TNotifyEvent;
        FOnTimer: TDXTimerEvent;
        procedure AppIdle(Sender: TObject; var Done: Boolean);
        function AppProc(var Message: TMessage): Boolean;
        procedure Finalize;
        procedure Initialize;
        procedure Resume;
        procedure SetActiveOnly(Value: Boolean);
        procedure SetEnabled(Value: Boolean);
        procedure SetInterval(Value: Cardinal);
        procedure Suspend;
      protected
        procedure DoActivate; virtual;
        procedure DoDeactivate; virtual;
        procedure DoTimer(LagCount: Integer); virtual;
        procedure Loaded; override;
      public
        constructor Create(AOwner: TComponent); override;
        destructor Destroy; override;
        property ActiveOnly: Boolean read FActiveOnly write SetActiveOnly;
        property Enabled: Boolean read FEnabled write SetEnabled;
        property FrameRate: Integer read FFrameRate;
        property Interval: Cardinal read FInterval write SetInterval;
        property OnActivate: TNotifyEvent read FOnActivate write FOnActivate;
        property OnDeactivate: TNotifyEvent read FOnDeactivate write FOnDeactivate;
        property OnTimer: TDXTimerEvent read FOnTimer write FOnTimer;
      end;  {  TDXTimer  }  TDXTimer = class(TCustomDXTimer)
      published
        property ActiveOnly;
        property Enabled;
        property Interval;
        property OnActivate;
        property OnDeactivate;
        property OnTimer;
      end;
      
      
      {  TCustomDXTimer  }constructor TCustomDXTimer.Create(AOwner: TComponent);
    begin
      inherited Create(AOwner);
      FActiveOnly := True;
      FEnabled := True;
      Interval := 1000;
      Application.HookMainWindow(AppProc);
    end;destructor TCustomDXTimer.Destroy;
    begin
      Finalize;
      Application.UnHookMainWindow(AppProc);
      inherited Destroy;
    end;procedure TCustomDXTimer.AppIdle(Sender: TObject; var Done: Boolean);
    var
      t, t2: DWORD;
      LagCount, i: Integer;
    begin
      Done := False;  t := TimeGetTime;
      t2 := t-FOldTime;
      if t2>=FInterval then
      begin
        FOldTime := t;    LagCount := t2 div FInterval2;
        if LagCount<1 then LagCount := 1;    Inc(FNowFrameRate);    i := Max(t-FOldTime2, 1);
        if i>=1000 then
        begin
          FFrameRate := Round(FNowFrameRate*1000/i);
          FNowFrameRate := 0;
          FOldTime2 := t;
        end;    DoTimer(LagCount);
      end;
    end;function TCustomDXTimer.AppProc(var Message: TMessage): Boolean;
    begin
      Result := False;
      case Message.Msg of
        CM_ACTIVATE:
            begin
              DoActivate;
              if FInitialized and FActiveOnly then Resume;
            end;
        CM_DEACTIVATE:
            begin
              DoDeactivate;
              if FInitialized and FActiveOnly then Suspend;
            end;
      end;
    end;procedure TCustomDXTimer.DoActivate;
    begin
      if Assigned(FOnActivate) then FOnActivate(Self);
    end;procedure TCustomDXTimer.DoDeactivate;
    begin
      if Assigned(FOnDeactivate) then FOnDeactivate(Self);
    end;procedure TCustomDXTimer.DoTimer(LagCount: Integer);
    begin
      if Assigned(FOnTimer) then FOnTimer(Self, LagCount);
    end;procedure TCustomDXTimer.Finalize;
    begin
      if FInitialized then
      begin
        Suspend;
        FInitialized := False;
      end;
    end;procedure TCustomDXTimer.Initialize;
    begin
      Finalize;  if ActiveOnly then
      begin
        if Application.Active then
          Resume;
      end else
        Resume;
      FInitialized := True;
    end;procedure TCustomDXTimer.Loaded;
    begin
      inherited Loaded;
      if (not (csDesigning in ComponentState)) and FEnabled then
        Initialize;
    end;procedure TCustomDXTimer.Resume;
    begin
      FOldTime := TimeGetTime;
      FOldTime2 := TimeGetTime;
      Application.OnIdle := AppIdle;
    end;procedure TCustomDXTimer.SetActiveOnly(Value: Boolean);
    begin
      if FActiveOnly<>Value then
      begin
        FActiveOnly := Value;    if Application.Active and FActiveOnly then
          if FInitialized and FActiveOnly then Suspend;
      end;
    end;procedure TCustomDXTimer.SetEnabled(Value: Boolean);
    begin
      if FEnabled<>Value then
      begin
        FEnabled := Value;
        if ComponentState*[csReading, csLoading]=[] then
          if FEnabled then Initialize else Finalize;
      end;
    end;procedure TCustomDXTimer.SetInterval(Value: Cardinal);
    begin
      if FInterval<>Value then
      begin
        FInterval := Max(Value, 0);
        FInterval2 := Max(Value, 1);
      end;
    end;procedure TCustomDXTimer.Suspend;
    begin
      Application.OnIdle := nil;
    end;
      

  15.   

    Delphi深度历险有介绍定时器,不过想达到1ms比较难的