Beispiel: Verkettete-Liste (FB_LinkedListCtrl)

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

Das Beispielprojekt besitzt zwei Programmteile:

Die maximale Anzahl der Knotenelemente kann zur Laufzeit nicht verändert werden und wird in dem Beispielprojekt durch die MAX_DATA_ELEMENTS begrenzt. Wenn Sie mehr Knoten benötigen, dann müssen Sie das nodes-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_LIST_OF_UDINT

Im ersten SPS-Zyklus wird das Handle der verketteten Liste initialisiert. Dieses Handle wird dann bei den Zugriffen auf die Liste als VAR_IN_OUT-Variable an den FB_LinkedListCtrl-Funktionsbaustein übergeben. Die verkettete Liste wird über die Aktionsaufrufe des FBs manipuliert. Auf diese Weise können Knotenelemente hinzugefügt/entfernt/durchsucht werden. Bei einer steigenden Flanke an der entsprechenden boolschen Variable wird die gewünschte Aktion ausgeführt. Wenn Sie das Programm starten werden alle Operationen einmalig ausgeführt.

PROGRAM P_LIST_OF_UDINT
VAR
    sInfo               : T_MaxString := '';
    bAddTailValue       : BOOL := TRUE;
    bAddHeadValue       : BOOL := TRUE;
    bGetTail            : BOOL := TRUE;
    bGetHead            : BOOL := TRUE;
    bFind               : BOOL := TRUE;
    bRemoveHeadValue    : BOOL := TRUE;
    bRemoveTailValue    : BOOL := TRUE;
    bCount              : BOOL := TRUE;

    search              : UDINT := 12345;

    fbList              : FB_LinkedListCtrl;(* basic linked list control function block *)
    hList               : T_HLINKEDLIST;(* linked list handle *)
    nodes               : ARRAY[0..MAX_DATA_ELEMENTS] OF T_LinkedListEntry;(* Max. number of linked list nodes. The value of list node = 32 bit integer *)
    putValue            : PVOID;(* Pointer or integer value (x86=>32bit, x64=>64bit)*)
    getValue            : PVOID;(* Pointer or integer value (x86=>32bit, x64=>64bit)*)
    getPosPtr           : POINTER TO T_LinkedListEntry := 0;
    bInit               : BOOL := TRUE;
END_VAR
IF bInit THEN
    bInit     := FALSE;
    F_CreateLinkedListHnd( ADR( nodes ), SIZEOF( nodes ), hList );
END_IF

IF bAddTailValue THEN(* add some nodes to the list *)
    bAddTailValue := FALSE;

    putValue := 22222;
    fbList.A_AddTailValue( hList := hList, putValue := putValue, getPosPtr=>getPosPtr, getValue=>getValue );
    putValue := 11111;
    fbList.A_AddTailValue( hList := hList, putValue := putValue, getPosPtr=>getPosPtr, getValue=>getValue );
    putValue := 12345;
    fbList.A_AddTailValue( hList := hList, putValue := putValue, getPosPtr=>getPosPtr, getValue=>getValue );
    putValue := 67890;
    fbList.A_AddTailValue( hList := hList, putValue := putValue, getPosPtr=>getPosPtr, getValue=>getValue );
END_IF

IF bAddHeadValue THEN
    bAddHeadValue := FALSE;

    putValue := 33333;
    fbList.A_AddHeadValue( hList := hList, putValue := putValue, getPosPtr=>getPosPtr, getValue=>getValue );
    putValue := 44444;
    fbList.A_AddHeadValue( hList := hList, putValue := putValue, getPosPtr=>getPosPtr, getValue=>getValue );
END_IF


IF bGetTail THEN(* enumerate all nodes in list (start at tail node) *)
    bGetTail := FALSE;
    sInfo := '';

    fbList.A_GetTail( hList := hList, getValue=>getValue, getPosPtr=>getPosPtr );
    IF fbList.bOk THEN
        sInfo := PVOID_TO_STRING( getValue );

        REPEAT
            fbList.A_GetPrev( hList := hList, putPosPtr := getPosPtr, getValue=>getValue, getPosPtr=>getPosPtr );
            IF fbList.bOk THEN
                sInfo := PVOID_TO_STRING( getValue );
            ELSE
                EXIT;
            END_IF
        UNTIL NOT fbList.bOk
        END_REPEAT
    END_IF
END_IF

IF bGetHead THEN(* enumerate all nodes in list (start at head node) *)
    bGetHead := FALSE;
    sInfo := '';

    fbList.A_GetHead( hList := hList, getValue=>getValue, getPosPtr=>getPosPtr );
    IF fbList.bOk THEN
        sInfo := PVOID_TO_STRING( getValue );

        REPEAT
            fbList.A_GetNext( hList := hList, putPosPtr := getPosPtr, getValue=>getValue, getPosPtr=>getPosPtr );
            IF fbList.bOk THEN
                sInfo := PVOID_TO_STRING( getValue );
            ELSE
                EXIT;
            END_IF
        UNTIL NOT fbList.bOk
        END_REPEAT
    END_IF
END_IF


IF bFind  THEN(* search for node in the list by node value*)
    bFind     := FALSE;
    getPosPtr := 0;(* start from first node element *)
    sInfo := '';

    REPEAT
        fbList.A_FindNext( hList := hList, putPosPtr := getPosPtr, putValue := search, getValue=>getValue, getPosPtr=>getPosPtr );
        IF fbList.bOk THEN
            sInfo := PVOID_TO_STRING( getValue );
        ELSE
            EXIT;
        END_IF
    UNTIL NOT fbList.bOk
    END_REPEAT

END_IF


IF bRemoveTailValue THEN(* remove tail node from node list *)
    bRemoveTailValue := FALSE;
    sInfo := '';
    fbList.A_RemoveTailValue( hList := hList, getValue=>getValue, getPosPtr=>getPosPtr );
    IF fbList.bOk THEN
        sInfo := PVOID_TO_STRING( getValue );
    END_IF
END_IF


IF bRemoveHeadValue THEN(* remove head node from node list *)
    bRemoveHeadValue := FALSE;
    sInfo := '';
    fbList.A_RemoveHeadValue( hList := hList, getValue=>getValue, getPosPtr=>getPosPtr );
    IF fbList.bOk THEN
        sInfo := PVOID_TO_STRING( getValue );
    END_IF
END_IF

IF bCount THEN(* count nodes in list *)
    bCount := FALSE;
    sInfo := UDINT_TO_STRING( hList.nCount );
END_IF

PROGRAM P_LIST_OF_STRUCTDATA

Dieses Programmteil zeigt wie strukturierte Datensätze statt einfacher 32-Zahlen in der Liste bearbeitet werden können. Dabei wird der 32 bit Knotenwert nur als Referenzpointer auf den tatsächlichen Knotenwert 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_SpecialLinkedListCtrl kann als eine spezialisierte Version des FB_LinkedListCtrl-Funktionsbausteins bezeichnet werden. Der FB_LinkedListCtrl-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_SpecialLinkedListCtrl-Funktionsbaustein Veränderungen durchführen müssen.

Die Typdeklaration von ST_DataElement:

(* Structured application data entry *)
TYPE ST_DataElement :
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

Es wurde eine einfache Suchfunktion implementiert. Sie können nach Knoten mit einem bestimmten name, number oder price suchen.

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

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

Jedes T_LinkedListEntry-Array-Element kann nur einmal in die Liste "reingehängt" werden. Dabei sucht der FB_LinkedListCtrl-Funktionsbaustein nach einem freien/unbenutzten T_LinkedListEntry-Array-Element und fügt es bei Erfolg der Liste zu. Mit Hilfe der Aktion A_GetIndexAtPosPtr kann der verwendete Index des T_LinkedListEntry-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 Aktionsaufruf: A_SetValueAtPosPtr.

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

Die Zuweisung wird z.B. in der FB_SpecialLinkedListCtrl->A_AddHeadValue-Aktion durchgeführt:

(* Adds head to the node list *)
MEMSET( ADR( getValue ), 0, SIZEOF( getValue ) );
getPosPtr := 0;

fbList.A_AddHeadValue( hList := hList, putValue := 16#00000000(* we will set this value later *), getPosPtr=>getPosPtr, bOk=>bOk );(* Add new element to the list, getPosPtr points to the new list node *)
IF fbList.bOk THEN(* Success *)
    fbList.A_GetIndexAtPosPtr( hList := hList, putPosPtr := getPosPtr, getValue =>indexOfElem, bOk=>bOk );(* Get array index of getPosPtr *)
    IF fbList.bOk THEN(* Success *)
        pRefPtr     := ADR( dataPool[indexOfElem] );(* Get pointer to the data element *)
        pRefPtr^     := putValue;(* set element value *)

        fbList.A_SetValueAtPosPtr( hList := hList, putPosPtr := getPosPtr, putValue := pRefPtr, bOk=>bOk );(* Assign the node value = pointer to the data element *)
        IF fbList.bOk THEN(* Success *)
            getValue := putValue;
        END_IF
    END_IF
END_IF
PROGRAM P_LIST_OF_STRUCTDATA
VAR
    sInfo             : T_MaxString := '';
    bAddTailValue     : BOOL := TRUE;
    bAddHeadValue     : BOOL := TRUE;
    bGetTail          : BOOL := TRUE;
    bGetHead          : BOOL := TRUE;
    bFind             : BOOL := TRUE;
    bRemoveHeadValue  : BOOL := TRUE;
    bRemoveTailValue  : BOOL := TRUE;
    bCount            : BOOL := TRUE;

    search            : ST_DataElement := ( name := 'Couch', price := 99.98, number := 12345 );(* search value ( by name, by price or by number ) *)
    eSearch           : E_SEARCH_CRITERIA := eSEARCH_BY_NAME;(* / eSEARCH_BY_PRICE / eSEARCH_BY_NUMBER *)

    fbList            : FB_SpecialLinkedListCtrl;(* Specialized linked list control function block *)
    putValue          : ST_DataElement;
    getValue          : ST_DataElement;
    getPosPtr         : POINTER TO T_LinkedListEntry := 0;
    bInit             : BOOL := TRUE;
END_VAR
IF bInit THEN
    bInit     := FALSE;
    fbList.A_Reset();(* reset / initialize list *)
END_IF


IF bAddTailValue THEN(* add some nodes to the list *)
    bAddTailValue := FALSE;

    putValue.number := 22222;
    putValue.name := 'TV set';
    putValue.price :=  99.98;
    fbList.A_AddTailValue( putValue := putValue, getPosPtr=>getPosPtr, getValue=>getValue );
    IF NOT fbList.bOk THEN
        ;(* List overflow *)
    END_IF


    putValue.number := 11111;
    putValue.name := 'Couch';
    putValue.price :=  99.98;
    fbList.A_AddTailValue( putValue := putValue, getPosPtr=>getPosPtr, getValue=>getValue );
    IF NOT fbList.bOk THEN
        ;(* List overflow *)
    END_IF

    putValue.number := 12345;
    putValue.name := 'Chair';
    putValue.price := 44.98;
    fbList.A_AddTailValue( putValue := putValue, getPosPtr=>getPosPtr, getValue=>getValue );
    IF NOT fbList.bOk THEN
        ;(* List overflow *)
    END_IF

    putValue.number := 67890;
    putValue.name := 'Table';
    putValue.price := 99.98;
    fbList.A_AddTailValue( putValue := putValue, getPosPtr=>getPosPtr, getValue=>getValue );
    IF NOT fbList.bOk THEN
        ;(* List overflow *)
    END_IF
END_IF

IF bAddHeadValue THEN
    bAddHeadValue := FALSE;

    putValue.number := 33333;
    putValue.name := 'Couch';
    putValue.price := 199.98;
    fbList.A_AddHeadValue( putValue := putValue, getPosPtr=>getPosPtr, getValue=>getValue );
    IF NOT fbList.bOk THEN
        ;(* List overflow *)
    END_IF

    putValue.number := 44444;
    putValue.name := 'Couch';
    putValue.price := 299.98;
    fbList.A_AddHeadValue( putValue := putValue, getPosPtr=>getPosPtr, getValue=>getValue );
    IF NOT fbList.bOk THEN
        ;(* List overflow *)
    END_IF
END_IF



IF bGetTail THEN(* enumerate all nodes in list (start at tail node) *)
    bGetTail := FALSE;
    sInfo := '';

    fbList.A_GetTail( getValue=>getValue, getPosPtr=>getPosPtr );
    IF fbList.bOk THEN
        sInfo := DATAELEMENT_TO_STRING( getValue );

        REPEAT
            fbList.A_GetPrev( putPosPtr := getPosPtr, getValue=>getValue, getPosPtr=>getPosPtr );
            IF fbList.bOk THEN
                sInfo := DATAELEMENT_TO_STRING( getValue );
            ELSE
                EXIT;
            END_IF
        UNTIL NOT fbList.bOk
        END_REPEAT
    END_IF
END_IF

IF bGetHead THEN(* enumerate all nodes in list (start at head node) *)
    bGetHead := FALSE;
    sInfo := '';

    fbList.A_GetHead( getValue=>getValue, getPosPtr=>getPosPtr );
    IF fbList.bOk THEN
        sInfo := DATAELEMENT_TO_STRING( getValue );

        REPEAT
            fbList.A_GetNext( putPosPtr := getPosPtr, getValue=>getValue, getPosPtr=>getPosPtr );
            IF fbList.bOk THEN
                sInfo := DATAELEMENT_TO_STRING( getValue );
            ELSE
                EXIT;
            END_IF
        UNTIL NOT fbList.bOk
        END_REPEAT
    END_IF
END_IF


IF bFind  THEN(* search for node in the list by node value (name, price, number... )*)
    bFind     := FALSE;
    getPosPtr := 0;(* start from first node element *)
    sInfo := '';

    REPEAT
        fbList.A_Find( eSearch := eSearch, putPosPtr := getPosPtr, putValue := search, getValue=>getValue, getPosPtr=>getPosPtr );
        IF fbList.bOk THEN
            sInfo := DATAELEMENT_TO_STRING( getValue );
        ELSE
            EXIT;
        END_IF
    UNTIL NOT fbList.bOk
    END_REPEAT

END_IF


IF bRemoveTailValue THEN(* remove tail node from node list *)
    bRemoveTailValue := FALSE;
    sInfo := '';
    fbList.A_RemoveTailValue( getValue=>getValue, getPosPtr=>getPosPtr );
    IF fbList.bOk THEN
        sInfo := DATAELEMENT_TO_STRING( getValue );
    END_IF
END_IF


IF bRemoveHeadValue THEN(* remove head node from node list *)
    bRemoveHeadValue := FALSE;
    sInfo := '';
    fbList.A_RemoveHeadValue( getValue=>getValue, getPosPtr=>getPosPtr );
    IF fbList.bOk THEN
        sInfo := DATAELEMENT_TO_STRING( getValue );
    END_IF
END_IF

IF bCount THEN(* count nodes in list *)
    bCount := FALSE;
    sInfo := '';
    fbList.A_Count( );
    IF fbList.bOk THEN
        sInfo := UDINT_TO_STRING( fbList.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)