TwinCAT I/O Ring 3 OCX Delphi Application.

 

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

TwinCAT I/O Ring 3 OCX 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/O Ring 3 OCX Delphi Application. 2:

The process image has the following size:

Input image: 2 bytes;

Output image: 8 bytes;

TwinCAT I/O Ring 3 OCX Delphi Application. 3:

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

 

Linking the TCatIoOcx ActiveX Components

In order to be able to use the TCatIoOcx ActiveX components in Delphi applications, it must be linked into the component palette. ActiveX components can be linked using the menu command: Component->Import ActiveX Control ... . Select the TCatIoOcx ActiveX Control Module from the list of installed components, and confirm with Install... Then confirm the dialogue window that follows. When successful, the TCatIoOcx will be found in the palette of ActiveX components.

TwinCAT I/O Ring 3 OCX Delphi Application. 4:

The Application

The event function FormCreate calls the method TCatIoOcxOpen. 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 method TCatIoOcxClose. Once the connection has been successfully established, two other methods are called: TCatIoOcxGetInputPtr and TCatIoOcxGetOutputPtr. These methods return pointers to the process images for the inputs and outputs. These pointers can be used to obtain read access to the inputs and write access to the outputs. In our example, however, these pointers are not used to provide access to the process data. Two data buffers are used instead: InputImage and OutputImage. 4-byte alignment must be observed when defining the data buffers.

This means that 4 bytes must be reserved in the data buffer for every DWord of process data, whether complete or partial. In our example, the size of the data buffer for the inputs is 4 bytes (the actual size is 2 bytes) and for the outputs it is 12 bytes (the actual size is 8 bytes). The data buffers can be larger, but must not be smaller.

 

unit TCatIoOcxDelphiUnit;
interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ExtCtrls, Grids, OleCtrls, TCATIOOCXLib_TLB;
type
  TForm1 = class(TForm)
    ListBox1: TListBox;
    Timer1: TTimer;
    StartButton: TButton;
    StopButton: TButton;
    Label1: TLabel;
    Button1: TButton;
    Label2: TLabel;
    Label3: TLabel;
    ListBox2: TListBox;
    ListBox3: TListBox;
    TCatIoOcx1: TTCatIoOcx;
    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;

const
 TASK_1_PORTNUMBER = 301;
    MAX_INPUT_IMAGE_BYTESIZE = 2;
    MAX_OUTPUT_IMAGE_BYTESIZE = 8;

type
    TInputImage       = ARRAY[ 0..MAX_INPUT_IMAGE_BYTESIZE DIV 4 ] Of Integer;
    TOutputImage      = ARRAY[ 0..MAX_OUTPUT_IMAGE_BYTESIZE DIV 4 ] Of Integer;

var
    Form1: TForm1;
InputImage    :TInputImage;
OutputImage       :TOutputImage;

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

    Result := TCatIoOcx1.TCatIoOcxOpen();
    if ( Result <> 0 ) then
    ListBox1.Items.Insert(0, Format('TCatIoOcxOpen() error:%d', [ Result ] ) )
    else
    begin
    {get/initialize pointer to the input image}
    Result := TCatIoOcx1.TCatIoOcxGetInputPtr( TASK_1_PORTNUMBER, InPtr, MAX_INPUT_IMAGE_BYTESIZE );
    if (  Result <> 0 ) then
        ListBox1.Items.Insert(0, Format('TCatIoOcxGetInputPtr() error:%d', [ Result ] ) );
    {get/initialize pointer to the output image}
    Result := TCatIoOcx1.TCatIoOcxGetOutputPtr( TASK_1_PORTNUMBER, OutPtr, MAX_OUTPUT_IMAGE_BYTESIZE );
    if (  Result <> 0 ) Then
        ListBox1.Items.Insert(0, Format('TCatIoOcxGetOutputPtr() error:%d', [ Result ] ) );
    end;
end;

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

 

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

procedure TForm1.Timer1Timer(Sender: TObject);
var Result : integer;
begin
    try
    {Update input image}
      Result := TCatIoOcx1.TCatIoOcxInputUpdate( TASK_1_PORTNUMBER );
    if ( Result <> 0 ) then
        ListBox1.Items.Insert(0, Format('TCatIoOcxInputUpdate() error:%d', [ Result ] ) );
    {read input values}
      Result := TCatIoOcx1.TCatIoOcxGetInputData( TASK_1_PORTNUMBER,  InputImage[0], MAX_INPUT_IMAGE_BYTESIZE );
    if ( Result <> 0 ) then
        ListBox1.Items.Insert(0, Format('TCatIoOcxGetInputData() error:%d', [ Result ] ) );
    {View inputs}
    ViewData( ListBox2, @InputImage, MAX_INPUT_IMAGE_BYTESIZE );
    {Calculate new output values (running light)}
    CalculateNewOutputs( @OutputImage, MAX_OUTPUT_IMAGE_BYTESIZE );
    {write output values}
      Result := TCatIoOcx1.TCatIoOcxSetOutputData( TASK_1_PORTNUMBER,  OutputImage[0], MAX_OUTPUT_IMAGE_BYTESIZE );
    if ( Result <> 0 ) then
        ListBox1.Items.Insert(0, Format('TCatIoOcxSetOutputData() error:%d', [ Result ] ) );
    {Update output image}
      Result := TCatIoOcx1.TCatIoOcxOutputUpdate( TASK_1_PORTNUMBER );
    if ( Result <> 0 ) then
        ListBox1.Items.Insert(0, Format('TCatIoOcxOutputUpdate() error:%d', [ Result ] ) );
    {View outputs}
    ViewData( ListBox3, @OutputImage, MAX_OUTPUT_IMAGE_BYTESIZE );
    except
    Timer1.Enabled := false;
    ListBox1.Items.Insert(0, 'TCatIoOcx exception error:%d' );
    end;
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 := TCatIoOcx1.TCatIoOcxReset();
    if ( Result <> 0 ) Then
    ListBox1.Items.Insert( 0, Format('TCatIoOcxReset() 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/0 Ring 3 Ocx: Delphi Application.