TwinCAT I/0 Ring 3 DLL: Delphi-Applikation

 

Für dieses Beispiel wurden die Funktionen der TCatIoDrv-DLL nach Pascal portiert und in einer Unit TCatIoDrv.pas zusammengefasst.

Die Sourcen zu diesem Beispiel können sie hier entpacken: TwinCAT I/O Ring 3 DLL Delphi-Applikation.

TwinCAT I/0 Ring 3 DLL: Delphi-Applikation 1:

Systemvoraussetzungen:

Beschreibung

Aus der Delphi-Applikation soll das Mapping von dem Prozessabbild der Ein-/Ausgänge einer zusätzlichen Task im TwinCAT System Manager getriggert werden. Die Zykluszeit beträgt 100ms und wird mit Hilfe eines Multimedia-Timers erzeugt. Die Onlinewerte der Prozessabbilder werden in zwei ListBoxen  angezeigt. Über den IO Reset-Button kann ein Reset der TwinCAT IO-Geräte durchgeführt werden. Das Mapping der Eingänge und Ausgänge kann über die Buttons Start IO cycle und Stop IO cycle gestartet bzw. gestoppt werden. Eventuelle Fehlermeldungen werden in einer weiteren ListBox ausgegeben. 

Die Task-Konfiguration in TwinCAT System Manager

Für die zusätzliche Task wurde im TwinCAT System Manager die Portnummer 301 konfiguriert.

TwinCAT I/0 Ring 3 DLL: Delphi-Applikation 2:

Das Prozessabbild hat folgende Größe:

Eingangsabbild:        2 Byte;

Ausgangsabbild:       8 Byte;     

TwinCAT I/0 Ring 3 DLL: Delphi-Applikation 3:

Die Portnummer und die Bytegröße des Prozessabbildes wurden in der TCatIoDrv.pas-Unit als Konstanten definiert und müssen bei anderen Werten entsprechend geändert werden.

Einbinden der TCatIoDrv-Unit

Die Deklarationen der benutzten TCatIoDrv.DLL-Funktionen befinden sich in der Pascal-Unit TCatIoDrv.pas. Diese wurde über eine Uses-Klausel in das Projekt eingebunden.

unit TCatIoDrvDelphiUnit;

interface
uses
TCatIoDrv,
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs,
StdCtrls, ExtCtrls, Grids;

...

Unit TCatIoDrv.pas

unit TCatIoDrv;

interface
uses sysutils,windows;
{$A-}
const
    // TwinCAT® System Manager->Additional Tasks->Task 1 tab:
    TASK_1_PORTNUMBER       = 301;
    // TwinCAT® System Manager->Additional Tasks->Task 1-Image->Size/Offset tab:
    MAX_INPUT_IMAGE_BYTESIZE    = 2;
    MAX_OUTPUT_IMAGE_BYTESIZE   = 8;
type
    TInputImage     = ARRAY[ 0 .. MAX_INPUT_IMAGE_BYTESIZE - 1 ] OF Byte;
    TOutputImage    = ARRAY[ 0 .. MAX_OUTPUT_IMAGE_BYTESIZE - 1 ] OF Byte;
    PInputImage     = ^TInputImage;
    POutputImage    = ^TOutputImage;
    function TCatIoOpen:longint; stdcall; external 'TCatIoDrv.dll' name '_TCatIoOpen@0';
    function TCatIoClose:longint;stdcall; external 'TCatIoDrv.dll' name '_TCatIoClose@0';
    function TCatIoInputUpdate( nPort : WORD ):longint;  stdcall; external 'TCatIoDrv.dll' name '_TCatIoInputUpdate@4';
    function TCatIoOutputUpdate( nPort : WORD ):longint;  stdcall; external 'TCatIoDrv.dll' name  '_TCatIoOutputUpdate@4';
    function TCatIoGetInputPtr( nPort : WORD; var pInput :Pointer; nSize :longint ):longint;   stdcall; external 'TCatIoDrv.dll' name  '_TCatIoGetInputPtr@12';
    function TCatIoGetOutputPtr( nPort : WORD; var pOutput :Pointer; nSize :longint ):longint;  stdcall; external 'TCatIoDrv.dll' name  '_TCatIoGetOutputPtr@12';
    function TCatIoReset():longint;  stdcall; external 'TCatIoDrv.dll' name   '_TCatIoReset@0';
    function TCatIoGetCpuTime( var  pCpuTime : TFileTime ):longint;  stdcall; external 'TCatIoDrv.dll' name  '_TCatIoGetCpuTime@4';
    function TCatIoGetCpuCounter( var pCpuCount : int64 ):longint;  stdcall; external 'TCatIoDrv.dll' name  '_TCatIoGetCpuCounter@4';

implementation
initialization
finalization
end.

Die Applikation

In der Event-Funktion FormCreate wird die DLL-Funktion TCatIoOpen aufgerufen. Beim Erfolg liefert diese Funktion eine Null und es wird eine Verbindung zum TwinCAT I/O Subsystem aufgebaut. Ein Fehler wird in der ListBox an den Benutzer ausgegeben.  Beim Beenden der Anwendung muß die Verbindung zum TwinCAT I/O Subsystem abgebaut werden. Dies geschieht in der  Event-Funktion FormDestroy, dabei wird die DLL-Funktion TCatIotClose aufgerufen. Wurde die Verbindung erfolgreich aufgebaut, dann werden zwei weitere Funktionen aufgerufen: TCatIoGetInputPtr und TCatIoGetoutputPtr. Diese Funktionen liefern Zeiger auf die Prozessabbilder der Ein-/Ausgänge. Über diese Zeiger können die Prozessdaten der Eingänge gelesen oder der Ausgänge geschrieben werden.

unit TCatIoDrvDelphiUnit;
interface
usesTCatIoDrv,
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ExtCtrls, Grids;
type
  TForm1 = class(TForm)
    ListBox1: TListBox;
    Timer1: TTimer;
    StartButton: TButton;
    StopButton: TButton;
    Label1: TLabel;
    Button1: TButton;
    Label2: TLabel;
    Label3: TLabel;
    Label4: TLabel;
    Label5: TLabel;
    ListBox2: TListBox;
    ListBox3: TListBox;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
    procedure StartButtonClick(Sender: TObject);
    procedure StopButtonClick(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
    procedure InitControls();
    procedure CalculateNewOutputs(pData   : Pointer; cbSize   :integer);
    procedure ViewData( var ListBox : TListBox; pData   : Pointer; cbSize   :integer);
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
  InputImagePtr      :PInputImage;
  OutputImagePtr     :POutputImage;

implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
var Result  : integer;
    InPtr, OutPtr :Pointer;
begin
    InitControls();

    Result := TCatIoOpen();
    if ( Result <> 0 ) then
    ListBox1.Items.Insert(0, Format('TCatIoOpen() error:%d', [ Result ] ) )
    else
    begin
    {get/initialize pointer to the input image}
    InPtr := NIL;
    Result := TCatIoGetInputPtr( TASK_1_PORTNUMBER, InPtr, MAX_INPUT_IMAGE_BYTESIZE );
    if (  Result = 0 ) And ( InPtr <> NIL ) then
        InputImagePtr := InPtr
    else
        ListBox1.Items.Insert(0, Format('TCatIoGetInputPtr() error:%d', [ Result ] ) );

    {get/initialize pointer to the output image}
    OutPtr := NIL;
    Result := TCatIoGetOutputPtr( TASK_1_PORTNUMBER, OutPtr, MAX_OUTPUT_IMAGE_BYTESIZE );
    if (  Result = 0 ) And ( OutPtr <> NIL ) Then
        OutputImagePtr := OutPtr
    else
        ListBox1.Items.Insert(0, Format('TCatIoGetOutputPtr() error:%d', [ Result ] ) );
    end;
end;

procedure TForm1.FormDestroy(Sender: TObject);
var Result : integer;
begin
    Result := TCatIoClose();
    if ( Result <> 0 ) then
    MessageBox(0, 'TCatIoClose() error!', 'Error', MB_OK);
end;

 

Wurde der Multimedia-Timer aktiviert, dann wird zyklisch die Timer1Timer-Ereignisroutine aufgerufen. In dieser Routine werden jedes mal folgende Funktionen aufgerufen:

 

procedure TForm1.Timer1Timer(Sender: TObject);
var Result : integer;
    CpuCounter  :int64;
    FileTime    :TFileTime;
    SystemTime  :TSystemTime;
begin
    Result := 0;
    try
    {Update input image}
    Result := TCatIoInputUpdate( TASK_1_PORTNUMBER );
    if ( Result <> 0 ) then
        ListBox1.Items.Insert(0, Format('TCatIoInputUpdate() error:%d', [ Result ] ) );
    {View inputs}
    ViewData( ListBox2, InputImagePtr, sizeof(TInputImage) );
    {Calculate new output values (running light)}
    CalculateNewOutputs( OutputImagePtr, sizeof(TOutputImage));
    {Update output image}
    Result := TCatIoOutputUpdate( TASK_1_PORTNUMBER );
    if ( Result <> 0 ) then
        ListBox1.Items.Insert(0, Format('TCatIoOutputUpdate() error:%d', [ Result ] ) );
    {View outputs}
    ViewData( ListBox3, OutputImagePtr, sizeof(TOutputImage) );
    except
    Timer1.Enabled := false;
    ListBox1.Items.Insert(0, Format('TCatIoDrv exception error:%d', [ Result ] ) );
    end;
    Result := TCatIoGetCpuCounter(CpuCounter);
    Label4.Caption := Format('TCatIoGetCpuCounter() result:%d, CPU counter:0x%x', [Result,CpuCounter] );
    Result := TCatIoGetCpuTime(FileTime);
    FileTimeToSystemTime( FileTime, SystemTime );
    Label5.Caption := Format('TCatIoGetCpuTime() result:%d, CPU time: %d:%d:%d:%d',
    [Result,SystemTime.wHour, SystemTime.wMinute, SystemTime.wSecond, SystemTime.wMilliseconds]);
end;

 

Die Routinen zum aktivieren/deaktivieren des Timers:

procedure TForm1.StartButtonClick(Sender: TObject);
begin
    StartButton.Enabled := false;
    StopButton.Enabled := true;
    Timer1.Enabled := true;
end;
procedure TForm1.StopButtonClick(Sender: TObject);
begin
    StartButton.Enabled := true;
    StopButton.Enabled := false;
    Timer1.Enabled := false;
end;

Die Routine für den IO-Reset:

procedure TForm1.Button1Click(Sender: TObject);
var Result : integer;
begin
    Result := TCatIoReset();
    if ( Result <> 0 ) Then
    ListBox1.Items.Insert( 0, Format('TCatIoReset() error:%d', [ Result ] ) );
end;
procedure TForm1.InitControls();
var Row   :integer;
begin
    StartButton.Enabled := true;
    StopButton.Enabled := false;

    Timer1.Enabled := false;
    Timer1.Interval := 100; {100 ms}

    for Row:= 0 To MAX_INPUT_IMAGE_BYTESIZE - 1 do
    ListBox2.Items.Add( Format( 'Byte[%d]', [Row] ) );

    for Row:= 0 To MAX_OUTPUT_IMAGE_BYTESIZE - 1 do
    ListBox3.Items.Add( Format( 'Byte[%d]', [Row] ) );
end;

procedure TForm1.CalculateNewOutputs(pData   : Pointer; cbSize   :integer);
var i:integer;
    pByte : ^Byte;
begin
    if ( pData <> NIL ) And (cbSize > 0) then
    begin
    for i:= 0 to  cbSize - 1 do
    begin
        pByte := Pointer(Integer(pData) + i);
        if ( pByte^ = 0 ) then pByte^ := 1
        else  pByte^ := pByte^ shl 1;
    end;
    end;
end;

procedure TForm1.ViewData( var ListBox : TListBox; pData   : Pointer; cbSize   :integer );
var
  ByteOff    : Integer;
  pByte      : ^Byte;
begin
    if ( pData <> NIL ) And (cbSize > 0) then
    begin
    for ByteOff := 0 to cbSize - 1 do
    begin
        pByte:=  Pointer( integer(pData) + ByteOff );
        ListBox.Items.Strings[ByteOff] := Format('Byte[%d] Value:0x%x',[ ByteOff, pByte^ ] );
    end;
    end;
end;
end.

Die Sourcen zu diesem Beispiel können sie hier entpacken: TwinCAT I/O Ring 3 DLL Delphi-Applikation.