Example: Hash table (FB_HashTableCtrl)
Here you can unpack the complete sources: HashTableExample.zip
The example project has two parts to the program:
- P_TABLE_OF_UDINT is a simple example program that simply edits 32-bit values in the hash table.
- P_TABLE_OF_STRUCTDATA illustrates how other data types (e.g. structured data types) can be managed in the context of the hash table.
The maximum number of table elements cannot be changed at runtime, and is limited in the example project by MAX_DATA_ELEMENTS. If you need more nodes, then you must increase the size of the table array accordingly (i.e. increase the value of the constant).
VAR_GLOBAL CONSTANT
MAX_DATA_ELEMENTS : UDINT := 100;(* Max. number of elements in the list *)
MAX_NAME_LENGTH : UDINT := 30;(* Max. length of article name *)
END_VAR
PROGRAM P_TABLE_OF_UDINT
In the first PLC cycle the article number and article name are stored in the table. The article number serves as key and the array index of the article name as value. Via a rising edge at bLookup the article name can be found via the article number.
PROGRAM P_TABLE_OF_UDINT
VAR
sInfo : T_MaxString := '';
bAdd : BOOL := TRUE;
bLookup : BOOL := TRUE;
bRemove : BOOL := TRUE;
bEnum : BOOL := TRUE;
bCount : BOOL := TRUE;
search : UDINT := 11111;(* article number *)
fbTable : FB_HashTableCtrl;(* basic hash table control function block *)
hTable : T_HHASHTABLE;(* hash table handle *)
table : ARRAY[0..MAX_DATA_ELEMENTS] OF T_HashTableEntry;(* Max. number of hash table entries. The value of hash table entry = 32 bit integer *)
names : ARRAY[0..MAX_DATA_ELEMENTS] OF STRING(MAX_NAME_LENGTH);
bInit : BOOL := TRUE;
END_VAR
IF bInit THEN
bInit := FALSE;
F_CreateHashTableHnd( ADR( table ), SIZEOF( table ), hTable );(* Intialize table handle *)
fbTable.A_Reset( hTable := hTable );
END_IF
IF bAdd THEN
bAdd := FALSE;
(* Fill table. Article number is the key. Array index number is the value (article name) *)
names[0] := 'Chair';
fbTable.A_Add( key := 12345, putValue := 0(* array index*), hTable := hTable );
IF NOT fbTable.bOk THEN
;(* Table overflow *)
END_IF
names[1] := 'Table';
fbTable.A_Add( key := 67890, putValue := 1, hTable := hTable );
IF NOT fbTable.bOk THEN
;(* Table overflow *)
END_IF
names[2] := 'Couch';
fbTable.A_Add( key := 11111, putValue := 2, hTable := hTable );
IF NOT fbTable.bOk THEN
;(* Table overflow *)
END_IF
names[3] := 'TV set';
fbTable.A_Add( key := 22222, putValue := 3, hTable := hTable );
IF NOT fbTable.bOk THEN
;(* Table overflow *)
END_IF
END_IF
IF bLookup THEN(* search for the article name by article number *)
bLookup := FALSE;
sInfo := '';
fbTable.A_Lookup( key := search, hTable := hTable );
IF fbTable.bOk THEN
sInfo := names[fbTable.getValue];
ELSE
;(* Entry not found *)
END_IF
END_IF
IF bRemove THEN
bRemove := FALSE;
sInfo := '';
fbTable.A_Remove( key := articleNo, hTable := hTable );
IF fbTable.bOk THEN
sInfo := names[fbTable.getValue];
ELSE
;(* Entry not found *)
END_IF
END_IF
IF bEnum THEN(* enumerate table entries *)
bEnum := FALSE;
sInfo := '';
fbTable.A_GetFirst( putPosPtr := 0, hTable := hTable );
IF fbTable.bOk THEN
sInfo := names[fbTable.getValue];
REPEAT
fbTable.A_GetNext( putPosPtr := fbTable.getPosPtr , hTable := hTable );
IF fbTable.bOk THEN
sInfo := names[fbTable.getValue];
END_IF
UNTIL NOT fbTable.bOk
END_REPEAT
END_IF
END_IF
IF bCount THEN(* count entries in the table *)
bCount := FALSE;
sInfo := UDINT_TO_STRING( hTable.nCount );
END_IF
PROGRAM P_TABLE_OF_STRUCTDATA
This section of the program illustrates how structured data sets can be manipulated in the table in place of simply 32-bit numbers. In this case, the 32-bit element value is only used as a reference pointer to the actual value of the element. The reference pointer is able to point to instances of structured variables or other data types. The functionality is encapsulated in a function block. This function block, FB_SpecialHashTableCtrl, can be thought of as a specialised version of the function block FB_HashTableCtrl. The FB_HashTableCtrl block is also used internally by the specialised FB.
The DATAELEMENT_TO_STRING function is only used to permit visual output of the value of the element.
A structured variable of type ST_DataElement is used as an example. The highlight: You can add further member variables to the data type declaration of ST_DataElement without having to make any changes to the program or to the FB_SpecialHashTableCtrl function block.
The type declaration for ST_DataElement:
TYPE ST_DataElement :
STRUCT
number : UDINT := 0;
name : STRING(MAX_NAME_LENGTH) := '';
price : REAL := 0.0;
(* add additional member variables here *)
END_STRUCT
END_TYPE
How do the 32-bit element values become reference pointers to the instances of the ST_DataElement array?
The maximum size of the table is limited by the constant MAX_DATA_ELEMENTS. It follows that no more than MAX_DATA_ELEMENTS reference pointers can be stored in the table. The FB_SpecialHashTableCtrl function block has an internal ST_DataElement array variable with the same size as the T_HashTableEntry array variable. For the sake of simplicity, the array indices in the two arrays are identical!
Each T_HashTableEntry array element can only be inserted into the table once. The FB_HashTableCtrl function block therefore searches for a free/unused T_HashTableEntry array element, and inserts it into the table if successful. The index of the T_HashTableEntry being used can be determined through the action A_GetIndexAtPosPtr. In the next step, the 32-bit node value that has just been added is assigned the address of the same array element in the ST_DataElement array. In the example project, this is done by calling the action A_Add again.
nodes[index].value := ADR( dataPool[index] )
The assignment is, for instance, carried out in the FB_SpecialHashTableCtrl->A_Add action:
fbTable.A_Add( hTable := hTable, key := key, putValue := 16#00000000, getPosPtr=>getPosPtr, bOk=>bOk );
IF fbTable.bOk THEN
fbTable.A_GetIndexAtPosPtr( hTable := hTable, putPosPtr := getPosPtr, getValue =>indexOfElem, bOk=>bOk );
IF fbTable.bOk THEN
pRefPtr := ADR( dataPool[indexOfElem] );
pRefPtr^ := putValue;
fbTable.A_Add( hTable := hTable, key := key, putValue := pRefPtr, bOk=>bOk );
IF fbTable.bOk THEN
getValue := putValue;
END_IF
END_IF
END_IF
PROGRAM P_TABLE_OF_STRUCTDATA
VAR
sInfo : T_MaxString := '';
bAdd : BOOL := TRUE;
bLookup : BOOL := TRUE;
bRemove : BOOL := TRUE;
bEnum : BOOL := TRUE;
bCount : BOOL := TRUE;
search : UDINT := 11111;(* article number *)
fbTable : FB_SpecialHashTableCtrl;(* Specialized hash table control function block *)
putValue : ST_DataElement;
getValue : ST_DataElement;
getPosPtr : POINTER TO T_HashTableEntry := 0;
bInit : BOOL := TRUE;
END_VAR
IF bInit THEN
bInit := FALSE;
fbTable.A_Reset();(* reset / initialize table *)
END_IF
IF bAdd THEN
bAdd := FALSE;
(* Fill table. Article number is the key and data structure is the value *)
putValue.number := 12345;
putValue.name := 'Chair';
putValue.price := 44.98;
fbTable.A_Add( key := 12345, putValue := putValue, getPosPtr=>getPosPtr, getValue=>getValue );
IF NOT fbTable.bOk THEN
;(* Table overflow *)
END_IF
putValue.number := 67890;
putValue.name := 'Table';
putValue.price := 99.98;
fbTable.A_Add( key := 67890, putValue := putValue, getPosPtr=>getPosPtr, getValue=>getValue );
IF NOT fbTable.bOk THEN
;(* Table overflow *)
END_IF
putValue.number := 11111;
putValue.name := 'Couch';
putValue.price := 99.98;
fbTable.A_Add( key := 11111, putValue := putValue, getPosPtr=>getPosPtr, getValue=>getValue );
IF NOT fbTable.bOk THEN
;(* Table overflow *)
END_IF
putValue.number := 22222;
putValue.name := 'TV set';
putValue.price := 99.98;
fbTable.A_Add( key := 22222, putValue := putValue, getPosPtr=>getPosPtr, getValue=>getValue );
IF NOT fbTable.bOk THEN
;(* Table overflow *)
END_IF
END_IF
IF bLookup THEN(* search for the article name by article number *)
bLookup := FALSE;
sInfo := '';
fbTable.A_Lookup( key := search, getPosPtr=>getPosPtr, getValue=>getValue );
IF fbTable.bOk THEN
sInfo := DATAELEMENT_TO_STRING( getValue );
ELSE
;(* Entry not found *)
END_IF
END_IF
IF bRemove THEN(* remove one entry from the table *)
bRemove := FALSE;
sInfo := '';
fbTable.A_Remove( key := search, getPosPtr=>getPosPtr, getValue=>getValue );
IF fbTable.bOk THEN
sInfo := DATAELEMENT_TO_STRING( getValue );
ELSE
;(* Entry not found *)
END_IF
END_IF
IF bEnum THEN(* enumerate table entries *)
bEnum := FALSE;
sInfo := '';
fbTable.A_GetFirst( putPosPtr := 0, getPosPtr=>getPosPtr, getValue=>getValue );
IF fbTable.bOk THEN
sInfo := DATAELEMENT_TO_STRING( getValue );
REPEAT
fbTable.A_GetNext( putPosPtr := fbTable.getPosPtr , getPosPtr=>getPosPtr, getValue=>getValue );
IF fbTable.bOk THEN
sInfo := DATAELEMENT_TO_STRING( getValue );
END_IF
UNTIL NOT fbTable.bOk
END_REPEAT
END_IF
END_IF
IF bCount THEN(* count entries in the table *)
bCount := FALSE;
fbTable.A_Count();
IF fbTable.bOk THEN
sInfo := UDINT_TO_STRING( fbTable.nCount );
END_IF
END_IF