TwinCAT I/0 Ring 3 DLL: Delphi Application

 

For this example, the functions of the TcatIoDrv DLL are ported to Pascal and assembled in a unit named TCatIoDrv.pas.

The source for this example can be unpacked from here: TwinCAT I/O Ring 3 DLL Delphi Application.

TwinCAT I/0 Ring 3 DLL: Delphi Application 1:

System requirements:

Description

The mapping of the process image of the inputs and outputs of an additional task in the TwinCAT System Manager is to be triggered from the Delphi application. The cycle time is 100 ms, and is generated with the aid of a multi-media timer. The online values of the process images are displayed in two list boxes. The TwinCAT I/O devices can be reset by means of the I/O Reset button. Mapping the inputs and outputs can be started or stopped using the Start I/O cycle and Stop I/O cycle buttons. If there are any error messages they are output in another list box.

Task Configuration in the TwinCAT System Manager

Port number 301 is configured for the additional task in the TwinCAT System Manager.

TwinCAT I/0 Ring 3 DLL: Delphi Application 2:

The process image has the following size:

Input image: 2 bytes;

Output image: 8 bytes;

TwinCAT I/0 Ring 3 DLL: Delphi Application 3:

The port number and the byte size of the process image are defined as constants in the TCatIoDrv.pas unit, and must be appropriately changed if they have any other value.

Linking the TCatIoDrv unit

The declarations of the TCatIoDrv.DLL functions used are found in the TCatIoDrv.pas Pascal unit. This is linked into the project by means of a "uses" clause.

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.

The Application

The event function FormCreate calls the DLL function TcatIoOpen. If successful, the function returns a null, and a connection to the TwinCAT I/O sub-system is established. The user is informed of any errors in the list box. When the application is closed, the connection to the TwinCAT I/O sub-system must be removed. This is done in the FormDestroy event function, which calls the DLL function TCatIotClose. Once the connection has been successfully established, two other functions are called: TCatIoGetInputPtr and TCatIoGetoutputPtr. These functions return pointers to the process images for the inputs and outputs. These pointers can be used to read the input process data or to write to the outputs.

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;

 

The Timer1Timer event routine is called cyclically when the multimedia timer is active. The following functions are called every time by this routine:

 

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;

 

The routines for activating/deactivating the timer:

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;

 

The routine for the I/O 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.

The source for this example can be unpacked from here: TwinCAT I/O Ring 3 DLL Delphi Application.