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.
Systemvoraussetzungen:
- TwinCAT 2.5 oder höher
- TCatIoDrv.Dll
- Delphi 5.0
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.
Das Prozessabbild hat folgende Größe:
Eingangsabbild: 2 Byte;
Ausgangsabbild: 8 Byte;
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:
- TCatIoInputUpdate ( DLL-Funktion: Triggert die Aktualisierung des Prozessabbildes der Eingänge ( Eingänge werden gelesen ) );
- ViewData ( Hilfsprozedur: Zeigt die aktuellen Werte der Eingänge in einer ListBox an );
- CalculateNewOutputs ( Hilfsprozedur: Generiert/Verändert die Werte der Ausgänge ( z.B. Lauflicht ) );
- TCatIoOutputUpdate ( DLL-Funktion: Triggert die Aktualisierung des Prozessabbildes der Ausgänge ( Ausgänge werden geschrieben ) );
- ViewData ( Hilfsprozedur: Zeigt die aktuellen Werte der Ausgänge in einer ListBox an );
- TCatIoGetCpuCounter ( DLL-Funktion: Liest den aktuellen Wert des CPU-Zählers );
- TCatIoGetCpuTime ( DLL-Funktion: Liest den Zählerstand der Pentium CPU );
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.