Reading and writing of DT/DATE/TOD/TIME variables
Requirements:
- TwinCAT v2.11 Build >= 2034;
- Delphi 7.0 + + update 7.1 or higher;
- TcAdsDLL.DLL;
- TcAdsDEF.pas and TcAdsAPI.pas, contained in the file delphi_adsdll_api_units.zip, if you want to compile the source code yourself;
The task
The date and/or time and a duration should be written from the Delphi application to the TwinCAT PLC or read from the TwinCAT PLC into the Delphi application. When the values are written to the PLC, the time and/or date set in the visual component TDateTimePicker is first converted to the appropriate PLC format and then written into the appropriate PLC variable. The values read from the TwinCAT PLC are converted to the appropriate Delphi format and indicated by the TDateTimePicker component. The time duration should be indicated in milliseconds and editable via TEdit control.
The data types available in the Delphi application include the following:
- TDateTime: 64-bit floating point number. Contains the date (integer part) and the time (fractional part). The integer part represents the number of days elapsed since 1899-12-30. The fraction indicates the time (fraction of a 24-hour day). Through coding of the floating point number the resolution is smaller for very large values (smaller number of decimal places available).
- TDate: 64-bit floating point number, TDateTime alias type, contains only the date (integer part). Number of days past since 1899-12-30;
- TTime: 64-bit floating point number, TDateTime alias type, contains only the time (fractional part of a 24-hour day);
- Longword, 32-bit count value, in our application it should contain the time duration with millisecond resolution (no time);
Using the TDateTime data type allows to use the available Delphi time conversion functions. In particular the two conversion functions DateTimeToUnix() and UnixToDateTime(). Instead, a 32-bit integer value or another data type can be used as well, the time and date suitably encoded, converted and then written to the PLC. The type conversion/mapping of data types described below should not be regarded as mandatory.
The following data types are available in the TwinCAT PLC:
- DT or DATE_AND_TIME: unsigned 32-bit count value, contains the date and time, number of seconds past since 1970-01-01, resolution in seconds;
- DATE: unsigned 32-bit count value, contains the date only, number of seconds past since 1970-01-01. Resolution in seconds;
- TOD or TIME_OF_DAY: unsigned 32-bit count value, contains only the time. Number of milliseconds past since the beginning of the day (00:00), resolution in milliseconds;
- TIME, unsigned 32-bit count value, contains the time duration (not the time), resolution in milliseconds;
During writing of the values into the TwinCAT PLC the following type conversion/mapping is carried out:
- TDateTimePicker.DateTime property (TDateTime) -> DT or DATE_AND_TIME
- TDateTimePicker.Date property (TDate) -> DATE
- TDateTimePicker.Time property (TTime) -> TOD or TIME_OF_DAY
- TEdit.text property (long word) -> time
During reading of values from the TwinCAT PLC the type conversion takes place in the other direction.
Depending on whether the Pick->Date or Pick->Time option was selected, the date or time can be set in the visual TDateTimePicker component.
The time duration in milliseconds can be set via TEdit control in the lower section.
A mouse click on the corresponding command button causes the time and/or date or the time duration to be written into the TwinCAT PLC or read from the PLC and displayed.
The PLC program
In MAIN a PLC variable was declared for each PLC type (date/time). The Delphi application should write or read the values.
PROGRAM MAIN
VAR
vDateAndTime : DATE_AND_TIME;
vDate : DATE;
vTimeOfDay : TIME_OF_DAY;
vTime : TIME;
END_VAR
;
Delphi 7 program
The declarations for the TcAdsDLL functions used are located in the Pascal units TcAdsDEF.pas and TcAdsAPI.pas. They were linked in the project via a Uses clause. The unit DateUtils contains the required time conversion functions and must also be linked.
unit Unit1;
interface
uses
TcAdsDEF, TcAdsAPI, Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ComCtrls, Grids, Calendar, StdCtrls, DateUtils, ExtCtrls;
type
TForm1 = class(TForm)
Button2: TButton;
Button3: TButton;
StatusBar1: TStatusBar;
Button4: TButton;
Button5: TButton;
Button6: TButton;
Button7: TButton;
Button9: TButton;
Button8: TButton;
Edit1: TEdit;
UpDown1: TUpDown;
RadioGroup1: TRadioGroup;
DateTimePicker1: TDateTimePicker;
Label2: TLabel;
procedure Button2Click(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure Button4Click(Sender: TObject);
procedure Button5Click(Sender: TObject);
procedure Button6Click(Sender: TObject);
procedure Button7Click(Sender: TObject);
procedure Button8Click(Sender: TObject);
procedure Button9Click(Sender: TObject);
procedure RadioGroup1Click(Sender: TObject);
private
{ Private declarations }
LocalAddr : TAmsAddr;
ServerAddr : TAmsAddr;
adsResult : Longint;
hDateAndTime : Longword;
hDate : Longword;
hTimeOfDay : Longword;
hTime : Longword;
function AdsCreateVarHandle( const name : AnsiString ) : Longword;
function AdsReleaseVarHandle( hVar : Longword ) : boolean;
function AdsWriteUI32( hVar : Longword; value : Longword ) : boolean;
function AdsReadUI32( hVar : Longword; var value : Longword ) : boolean;
public
{ Public declarations }
end;
var
Form1: TForm1;
Establishing the Ads client connection to TwinCAT PLC
During startup the Delphi application establishes an Ads client connection to the TwinCAT PLC (first runtime system, port: 801) and fetches handles for the 4 PLC variables. When the application is terminated the handles are released and the Ads client connection is closed. The code for fetching and releasing the variable handles is encapsulated in two internal help functions: AdsCreateVarHandle() and AdsReleaseVarHandle().
//////////////////////////////////////////////////////////////////
// Create ads client connection, create PLC variable handles
procedure TForm1.FormCreate(Sender: TObject);
var ClientPort:Word;
begin
StatusBar1.SimplePanel := true;
ClientPort:= AdsPortOpen();
if ClientPort = 0 then {error!}
ShowMessage( 'AdsPortOpen() error!' )
else {OK}
begin
adsResult:=AdsGetLocalAddress( @LocalAddr );
if adsResult = 0 then {OK}
begin
ServerAddr.netId:=LocalAddr.netId;{connect to the PLC on the local PC}
ServerAddr.port:=801; {first RTS}
StatusBar1.SimpleText:=Format('Client NetId:%d.%d.%d.%d.%d.%d Client Port:%d, Server Port:%d',[
LocalAddr.netId.b[0], LocalAddr.netId.b[1], LocalAddr.netId.b[2],
LocalAddr.netId.b[3], LocalAddr.netId.b[4], LocalAddr.netId.b[5],
ClientPort, ServerAddr.port]);
hDateAndTime := AdsCreateVarHandle( 'MAIN.vDateAndTime' );
hDate := AdsCreateVarHandle( 'MAIN.vDate' );
hTimeOfDay := AdsCreateVarHandle( 'MAIN.vTimeOfDay' );
hTime := AdsCreateVarHandle( 'MAIN.vTime' );
end
else
ShowMessageFmt('AdsGetLocalAddress() error:%d', [adsResult]);
end;
end;
Separating the Ads client connection to TwinCAT PLC
//////////////////////////////////////////////////////////////////
// Release PLC variable handles, close ads connection
procedure TForm1.FormDestroy(Sender: TObject);
begin
AdsReleaseVarHandle( hDateAndTime );
AdsReleaseVarHandle( hDate );
AdsReleaseVarHandle( hTimeOfDay );
AdsReleaseVarHandle( hTime );
adsResult := AdsPortClose();
if AdsResult > 0 then
ShowMessageFmt('AdsPortClose() error:%d', [adsResult]);
end;
Fetch handle for the PLC variable
/////////////////////////////////////////////////////////////////
// Create PLC variable handle
function TForm1.AdsCreateVarHandle( const name : AnsiString ) : Longword;
var hVar : Longword;
begin
adsResult := AdsSyncReadWriteReq( @serverAddr, ADSIGRP_SYM_HNDBYNAME, 0, sizeof(hVar), @hVar, Length(name)+1, @name[1] );
if adsResult <> 0 then
begin
ShowMessageFmt( 'AdsSyncReadWriteReq(ADSIGRP_SYM_HNDBYNAME) error: %d', [adsResult] );
AdsCreateVarHandle := 0;
end
else
AdsCreateVarHandle := hVar;
end;
Releasing the PLC variable handle
//////////////////////////////////////////////////////////////////
// Release PLC variable handle
function TForm1.AdsReleaseVarHandle( hVar : Longword ) : boolean;
begin
adsResult := AdsSyncWriteReq( @serverAddr, ADSIGRP_RELEASE_SYMHND, hVar, 0, Nil );
if adsResult <> 0 then
ShowMessageFmt( 'AdsSyncReadWriteReq(ADSIGRP_RELEASE_SYMHND) error: %d', [adsResult] );
AdsReleaseVarHandle := adsResult = 0;
end;
Writing value into the PLC (32-bit unsigned integer)
In the TwinCAT PLC the time data types are stored/mapped as unsigned 32-bit count values. After conversion into the time format of the PLC the values should be written to the PLC as unsigned 32-bit numbers. The code for writing and reading of the 32-bit values is encapsulated in two internal help functions: AdsWriteUI32() and AdsReadUI32().
//////////////////////////////////////////////////////////////////
// Write unsigned 32 bit integer value to the PLC
function TForm1.AdsWriteUI32( hVar : Longword; value : Longword ) : boolean;
begin
adsResult := AdsSyncWriteReq( @ServerAddr, ADSIGRP_SYM_VALBYHND, hVar, sizeof(value), @value );
if adsResult <> 0 then
ShowMessageFmt('AdsSyncWriteReq(ADSIGRP_SYM_VALBYHND) error:%d', [adsResult]);
AdsWriteUI32 := adsResult = 0;
end;
Reading value from the PLC (32-bit unsigned integer)
//////////////////////////////////////////////////////////////////
// Read unsigned 32 bit integer value from the PLC
function TForm1.AdsReadUI32( hVar : Longword; var value : Longword ) : boolean;
begin
adsResult := AdsSyncReadReq( @ServerAddr, ADSIGRP_SYM_VALBYHND, hVar, sizeof(value), @value );
if adsResult <> 0 then
ShowMessageFmt('AdsSyncReadReq(ADSIGRP_SYM_VALBYHND) error:%d', [adsResult]);
AdsReadUI32 := adsResult = 0;
end;
Writing DATE_AND_TIME to the PLC
//////////////////////////////////////////////////////////////////
// Convert TDateTime -> DATE_AND_TIME
// Write DATE_AND_TIME value to the PLC
procedure TForm1.Button2Click(Sender: TObject);
var
seconds : Longword;
unix : Int64;
dt : TDateTime;
begin
// Get TDateTime value from TDateTimePicker component
dt := DateTimePicker1.DateTime;
// Truncate milliseconds (prevents from rounding up)
dt := RecodeMillisecond( dt, 0 );
// Convert TDateTime -> Unix time, 64 bit, seconds since 1.1.1970-00:00:00
unix := DateTimeToUnix( dt );
// Convert 64 bit -> 32 bit, seconds since 1.1.1970-00:00:00
seconds := unix;
// Wite DATE_AND_TIME value to the PLCAdsWriteUI32( hDateAndTime, seconds );
end;
Reading DATE_AND_TIME from the PLC
//////////////////////////////////////////////////////////////////
// Read DATE_AND_TIME value from the PLC
// Convert DATE_AND_TIME -> TDateTime
procedure TForm1.Button3Click(Sender: TObject);
var
seconds : Longword;
unix : Int64;
dt : TDateTime;
begin
// Read DATE_AND_TIME value from PLC, 32 bit, seconds since 1.1.1970-00:00:00
if AdsReadUI32( hDateAndTime, seconds ) then
begin
// Create Unix time, 64 bit, seconds since 1.1.1970-00:00:00
unix := seconds;
// Convert Unix time -> TDateTime
dt := UnixToDateTime( unix );
// Assign TDateTime value to the TDateTimePicker component
DateTimePicker1.DateTime := dt;
end;
end;
Writing DATE to the PLC
//////////////////////////////////////////////////////////////////
// Convert TDate -> DATE
// Write DATE value to the PLC
procedure TForm1.Button4Click(Sender: TObject);
var
seconds : Longword;
unix : Int64;
d : TDate;
begin
// Get TDate value from TDateTimePicker component
d := DateOf( DateTimePicker1.DateTime );
// Convert TDate -> Unix time, 64 bit, seconds since 1.1.1970-00:00:00
unix := DateTimeToUnix( d );
// Convert 64 bit -> 32 bit, seconds since 1.1.1970-00:00:00
seconds := unix;
// Wite DATE value to the PLCAdsWriteUI32( hDate, seconds );
end;
Reading DATE from the PLC
//////////////////////////////////////////////////////////////////
// Read DATE value from the PLC
// Convert DATE -> TDate
procedure TForm1.Button5Click(Sender: TObject);
var
seconds : Longword;
unix : Int64;
d : TDate;
begin
// Read DATE value from PLC, 32 bit, seconds since 1.1.1970-00:00:00
if AdsReadUI32( hDate, seconds ) then
begin
// Create Unix time, 64 bit, seconds since 1.1.1970-00:00:00
unix := seconds;
// Convert Unix time -> TDate
d := DateOf( UnixToDateTime( unix ) );
// Assign TDate value to the TDateTimePicker component
DateTimePicker1.Date := d;
end;
end;
Writing TIME_OF_DAY to the PLC
//////////////////////////////////////////////////////////////////
// Convert TTime -> TIME_OF_DAY
// Write TIME_OF_DAY value to the PLC
procedure TForm1.Button6Click(Sender: TObject);
var
milliseconds : Longword;
t : TTime;
begin
// Get TTime value from TDateTimePicker component
t := TimeOf(DateTimePicker1.DateTime);
// Truncate milliseconds (prevents from rounding up)
t := RecodeMillisecond( t, 0 );
// Convert TTime -> Milliseconds of the day, 32 bit
milliseconds := MillisecondOfTheDay( t );
// Write TIME_OF_DAY value to the PLCAdsWriteUI32( hTimeOfDay, milliseconds );
end;
Reading TIME_OF_DAY from the PLC
//////////////////////////////////////////////////////////////////
// Read TIME_OF_DAY value from the PLC
// Convert TIME_OF_DAY -> TTime
procedure TForm1.Button7Click(Sender: TObject);
var
milliseconds : Longword;
unix : Int64;
t : TTime;
begin
// Read TIME_OF_DAY value from PLC, 32 bit, milliseconds of the day
if AdsReadUI32( hTimeOfDay, milliseconds ) then
begin
// Create Unix time, 64 bit, seconds since 1.1.1970-00:00:00
unix := milliseconds Div 1000;
// Convert Unix time -> TTime
t := TimeOf( UnixToDateTime( unix ) );
// Assign TTime value to the TDateTimePicker component
DateTimePicker1.Time := t;
end;
end;
Writing TIME (time duration) to the PLC
//////////////////////////////////////////////////////////////////
// Write TIME value to the PLC
procedure TForm1.Button8Click(Sender: TObject);
var milliseconds : Longword;
begin
milliseconds := UpDown1.Position;
AdsWriteUI32( hTime, milliseconds );
end;
Reading TIME (time duration) from the PLC
//////////////////////////////////////////////////////////////////
// Read TIME value from the PLC
procedure TForm1.Button9Click(Sender: TObject);
var milliseconds : Longword;
begin
if AdsReadUI32( hTime, milliseconds ) then
UpDown1.Position := milliseconds;
end;
end.
Language / IDE | Unpack sample program |
---|---|
Delphi XE2 | |
Delphi 7 or higher (classic) |