Beispiel: Hash-Tabelle (FB_HashTableCtrl)

Hier können Sie die kompletten Sourcen entpacken: HashTableExample.zip

Das Beispielprojekt besitzt zwei Programmteile:

Die maximale Anzahl der Tabellenelemente kann zur Laufzeit nicht verändert werden und wird in dem Beispielprojekt durch die MAX_DATA_ELEMENTS begrenzt. Wenn Sie mehr Elemente benötigen, dann müssen Sie das table-Array entsprechend vergrößern (d.h. den Wert der Konstante erhöhen).

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

Im ersten SPS-Zyklus werden in der Tabelle als Beispiel Informationen über Artikelnummer und Artikelname abgelegt. Die Artikelnummer dient als Schlüssel und der Array-Index des Artikelnamens als Wert.
Über eine steigende Flanke am bLookup kann der Artikelname anhand der Artikelnummer gefunden werden.

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 *)
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(* remove one entry from the table *)
    bRemove := FALSE;
    sInfo := '';
    fbTable.A_Remove( key := search, 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

Dieses Programmteil zeigt wie strukturierte Datensätze statt einfacher 32-Zahlen in der Tabelle bearbeitet werden können. Dabei wird der 32 bit Elementwert nur als Referenzpointer auf den tatsächlichen Elementwert verwendet. Der Referenzpointer kann dann auch auf Instanzen von strukturierten Variablen oder anderen Datentypen zeigen. Die Funktionalität wurde in einem Funktionsblock gekapselt. Dieser Funktionsblock FB_SpecialHashTableCtrl kann als eine spezialisierte Version des FB_HashTableCtrl-Funktionsbausteins bezeichnet werden. Der FB_HashTableCtrl-Baustein wird auch intern von dem spezialisierten FB verwendet.

Die Funktion DATAELEMENT_TO_STRING wird nur verwendet um eine visuelle Ausgabe der Knotenwerte zu ermöglichen.

Als Beispiel wird die Strukturierte Variable vom Typ: ST_DataElement verwendet. Der Clou: Sie können die Datentypdeklaration von ST_DataElement um weitere Membervariablen erweitern, ohne dass Sie an dem Programm oder dem FB_SpecialHashTableCtrl-Funktionsbaustein Veränderungen durchführen müssen.

Die Typdeklaration von ST_DataElement:

TYPE ST_DataElement :(* Structured application data entry *)
STRUCT
    (* Adapt this structure to match your application needs *)
    number        : UDINT := 0;
    name        : STRING(MAX_NAME_LENGTH) := '';
    price        : REAL := 0.0;
END_STRUCT
END_TYPE

Wie werden die 32 bit Elementwerte zu Referenzpointern auf die ST_DataElement-Array-Instanzen?

Die max. Größe der Tabelle ist durch MAX_DATA_ELEMENTS-Konstante begrenzt. Folglich können in der Tabelle nur MAX_DATA_ELEMENTS-Referenzpointer gespeichert werden. Der FB_SpecialHashTableCtrl-Baustein besitzt intern eine ST_DataElement-Array-Variable mit derselben Array-Größe wie die T_HashTableEntry-Array-Varriable. Zur Vereinfachung sind die Array-Indizies in beiden Arrays gleich!

Jedes T_HashTableEntry-Array-Element kann nur einmal in die Tabelle "reingehängt" werden. Dabei sucht der FB_HashTableCtrl-Funktionsbaustein nach einem freien/unbenutzten T_HashTableEntry-Array-Element und fügt es bei Erfolg der Tabelle zu. Mit Hilfe der Aktion A_GetIndexAtPosPtr kann der verwendete Index des T_HashTableEntry-Arrays ermittelt werden. Im nächsten Schritt wird dem zuletzt hinzugefügten 32 bit Knotenwert die Adresse desselben Arrayelements vom ST_DataElement-Array zugewiesen. Im Beispielprojekt durch den zweiten Aktionsaufruf: A_Add.

nodes[index].value := ADR( dataPool[index] )

Die Zuweisung wird z.B. in der FB_SpecialHashTableCtrl->A_Add-Aktion durchgeführt:

(* Adds entry to the table *)
MEMSET( ADR( getValue ), 0, SIZEOF( getValue ) );
getPosPtr := 0;

fbTable.A_Add( hTable := hTable, key := key, putValue := 16#00000000(* we will set this value later *), getPosPtr=>getPosPtr, bOk=>bOk );(* Add new element to the table, getPosPtr points to the new entry *)
IF fbTable.bOk THEN(* Success *)
    fbTable.A_GetIndexAtPosPtr( hTable := hTable, putPosPtr := getPosPtr, getValue =>indexOfElem, bOk=>bOk );(* Get array index of getPosPtr entry *)
    IF fbTable.bOk THEN(* Success *)
        pRefPtr     := ADR( dataPool[indexOfElem] );(* Get pointer to the data element *)

        pRefPtr^ := putValue;(* copy application value *)

        fbTable.A_Add( hTable := hTable, key := key, putValue := pRefPtr, bOk=>bOk );(* Assign the entry value = pointer to the data element *)
        IF fbTable.bOk THEN(* Success *)
            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

Voraussetzungen

Entwicklungsumgebung

Zielplattform

Einzubindende SPS-Bibliotheken (Kategoriegruppe)

TwinCAT v3.1.0

PC oder CX (x86, x64, ARM)

Tc2_Utilities (System)