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.

System requirements:
- TwinCAT 2.7 or higher
- TCatIoOcx.Ocx
- Delphi 5.0
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.

The process image has the following size:
Input image: 2 bytes;
Output image: 8 bytes;

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.

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:
- TCatIoOcxInputUpdate (the method triggers an update of the input process image (inputs are read));
- TCatIoOcxGetInputData (the method reads the process data from the inputs into the InputImage data buffer)
- ViewData (auxiliary procedure: displays the current values of the inputs in a list box);
- CalculateNewOutputs (auxiliary procedure: generates/alters the values of the outputs (e.g. the running light));
- TCatIoOcxSetOutputData (the method sets the output process data using the data from the OutputImage data buffer)
- TCatIoOcxOutputUpdate (the method triggers an update of the output processed image (outputs are written));
- ViewData (auxiliary procedure: displays the current values of the outputs in a list box);
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.