Beispiel Maschine mit Microsoft Visual C++ .NET

Microsoft Visual Studio 2005 ist eine Entwicklungsumgebung um C++-Projekte zu erstellen. Anhand des Maschine Beispiels werden Sie das Einbinden der TwinCAT ADS DLL .LIB Komponente mit der Programmiersprache C++ (ausgeschrieben: CPlusPlus) kennen lernen.

Erforderte Software:

- Microsoft .NET Framework Version 2.0, mehr dazu hier
- Microsoft Visual Studio 2005
- TwinCAT 2.10

Bevor Sie das C++-Programm starten können, muss TwinCAT und das SPS-Programm aktiv sein. Ist auf Ihrem Rechner nicht Microsoft Visual Studio 2005 installiert, so müssen Sie das Microsoft .NET Framework Version 2.0 installieren. Dadurch werden die benötigten DLL's auf Ihrem System eingerichtet.

Die ersten Schritte...

Schritt für Schritt lernen Sie die Entwicklung eines C++-Programms und das Einbinden der TwinCAT ADS DLL .LIB Komponente anhand eines Beispiels kennen.

1. Neues Projekt erstellen:

Starten Sie das Visual Studio 2005. Erstellen Sie ein neues Projekt, indem Sie im Menü 'File -> New -> Project...' anklicken. Im Dialogfeld 'New Project' können Sie ein neues Projekt auf der Basis verschiedener Vorlagen erstellen. Hier wählen Sie unter 'Project Types' die Programmiersprache 'Visual C++', 'MFC' und unter den Vorlagen 'MFC Application'. Geben Sie Ihrem Projekt einen neuen Namen, in diesem Fall bitte den Namen 'Machine' eingeben. Wählen sie dann den Verzeichnispfad unter 'Location' in dem Sie ihr Arbeitsprojekt erstellen wollen.

Beispiel Maschine mit Microsoft Visual C++ .NET 1:

Nach der Begrüßung durch den Wizard, übernehmen Sie bitte folgende Einstellungen.

Beispiel Maschine mit Microsoft Visual C++ .NET 2:

Weitere Features werden nicht benötigt.

Beispiel Maschine mit Microsoft Visual C++ .NET 3:

Letzter Schritt im Wizard. Hier kann alles so bleiben.

Beispiel Maschine mit Microsoft Visual C++ .NET 4:

Um das Grundgerüst zu komplettieren und damit es zum Schluss nicht vergessen wird, verlinken wir gleich die benötigte TcAdsDll.lib. Der Pfad dazu wird unter 'Project -> Machine Properties... -> Configuration Properties -> Linker -> General -> Additional Library Directories' eingetragen.

Beispiel Maschine mit Microsoft Visual C++ .NET 5:

Zudem ist ein weiterer Eintrag unter 'Project -> Machine Properties... -> Configuration Properties -> Linker -> Input -> Additional Dependencies' notwendig.

Beispiel Maschine mit Microsoft Visual C++ .NET 6:

Bitte hierbei darauf achten, dass oben links bei 'Configuration:' auch 'All Configurations' ausgewählt ist!

2. Bedienungsoberfläche erstellen

Hierzu wird zuerst eine Oberfläche im Design Modus der Form 'Machine.rc' mit Hilfe der Toolbox erstellt. Dazu öffnen Sie mit einem Doppelklick 'Machine.rc' im 'Solution Explorer'.

Beispiel Maschine mit Microsoft Visual C++ .NET 7:

Sie befinden sich nun im 'Resource View'. Durch einen weiteren Doppelklick auf 'IDD_MACHINE_DIALOG' wird die Form sichtbar, auf der Sie jetzt alle benötigten Controls (wie z.B. 9 Static Texts, 3 PictureBoxes, 2 Radiobuttons, 1 Progressbar, GroupBoxes...) platzieren können.

Beispiel Maschine mit Microsoft Visual C++ .NET 8:

Das Resultat sollte jetzt in etwa so aussehen.

Beispiel Maschine mit Microsoft Visual C++ .NET 9:

Im oberen linken Bereich sehen Sie die beiden Ausgänge, die auch auf die Busklemmen ausgegeben werden. Unten links ist die Variable abgebildet, welche die Werkstücke zählt. Rechts können Sie mit dem Feld 'Speed' die Taktgeschwindigkeit des Motors verändern. Die Anzeige 'Steps' entspricht der Anzahl der Takte, die auf den Ausgang 1 ausgegeben werden.

3. Quelltext bearbeiten

Nach dem Erstellen der Oberfläche und dem Einbinden der TwinCAT DLL.LIB Komponente kann zum C++ Quelltext gewechselt werden. Im oberen Bereich von 'MachineDlg.h' binden wir die "TcAdsDef.h" und "TcAdsApi.h" ein. Weiter unten im Quelltext nehmen wir unsere Deklarationen vor.

#include"c:\twincat\ads api\tcadsdll\include\tcadsdef.h"#include"c:\twincat\ads api\tcadsdll\include\tcadsapi.h"
[…]

// Variablen des Programms// Variables of the programmprivate:
     // Variablen der Bitmaps// Variables of the bitmaps
     CBitmap CbmUp0, CbmUp1, CbmDown0, CbmDown1;
private:
     // Timerfunktion und Variablen// Timerfunction and variablesvoid OnTimer(UINT nIDEvent);
     UINT_PTR m_nTimer;
     UINT nIDEvent;
private:
     // Membervariable der Statusanzeige// Member variable of the progressbar
     CProgressCtrl     m_ctrlBar;
private:
     // Membervariablen der Controls// Member variables of the controls
     CStatic       m_ctrlCount;
     CStatic       m_ctrlSteps;
     CStatic       m_ctrlEngine;
     CStatic       m_Up;
     CStatic       m_Down;
     CStatic       m_rdoFast;
private:
     // Implementierung der Radio Buttons// Implementation of the radio buttons
     afx_msg void OnBnClickedRadio1();
     afx_msg void OnBnClickedRadio2();
private:
     // Allgemeine Variablendeklarationen// General declarations of variables
     CButton       *pRB;
     short         sCount;
     byte          bySteps;
     CString       sOutput;
     AmsAddr       Addr;
     PAmsAddr      pAddr;
     long          nErr, nSwitch, nNotify, nPort;
     bool          bEngine, bUp, bDown, bSwitch, bNotify;
     ULONG         hEngine, hDeviceUp, hDeviceDown, hSteps, hCount, hSwitch, hNotify;
};

Die Methode 'CMachineDlg::OnInitDialog()' wird beim Start der MFC Application aufgerufen. In ihr werden Instanzen verschiedener Klassen erzeugt, eine Verbindung mit dem Laufzeitsystem 1 der Komponente TcAdsDll.lib über den Port 801 hergestellt, benötigte Bitmaps geladen und die Handles der SPS-Variablen erstellt.

BOOL CMachineDlg::OnInitDialog()
{
     CDialog::OnInitDialog();

     […]

     // Kommunikationsport für lokale SPS (Laufzeitsystem 1) öffnen// Open the communication port for the SPS (Runtime system 1)
     pAddr = &Addr;
     nPort = AdsPortOpen();
     nErr = AdsGetLocalAddress(pAddr);
     pAddr->port = AMSPORT_R0_PLC_RTS1;

     // Timer initialisieren// Timer initialization
     m_nTimer = CDialog::SetTimer( 1, 40, NULL );
     m_ctrlBar.SetRange( 0, 25 );
     
     // Laden der Bitmaps// Loading of the bitmaps
     CbmUp0.LoadBitmapW( IDB_UP0 );
     CbmUp1.LoadBitmapW( IDB_UP1 );
     CbmDown0.LoadBitmapW( IDB_DOWN0 );
     CbmDown1.LoadBitmapW( IDB_DOWN1 );
     
     // Einen Pointer auf ein CButton Objekt holen// Get a pointer to CButton Object
     pRB = (CButton*)GetDlgItem( IDC_rdoFast );

     // Setzt den Radio Button auf "checked"// Set the radio button on checked state
     pRB->SetCheck(1);
     
     // Abfrage vom SPS-Switch-Status// Inquiry of the SPS switch status
     nNotify = AdsSyncReadWriteReq( pAddr, ADSIGRP_SYM_HNDBYNAME, 0x0, sizeof(ULONG), &hNotify, 8, ".switch" );
     nNotify = AdsSyncReadReq ( pAddr, ADSIGRP_SYM_VALBYHND, hNotify, 8, &bNotify );
     if ( bNotify )
     { 
       pRB = ( CButton* )GetDlgItem( IDC_rdoFast ); pRB->SetCheck(1);
       pRB = ( CButton* )GetDlgItem( IDC_rdoSlow ); pRB->SetCheck(0);
     }
     else
     { 
       pRB = ( CButton* )GetDlgItem( IDC_rdoFast ); pRB->SetCheck(0);
       pRB = ( CButton* )GetDlgItem( IDC_rdoSlow ); pRB->SetCheck(1);
     }
     nNotify = AdsSyncWriteReq( pAddr, ADSIGRP_SYM_RELEASEHND, 0x0, sizeof(ULONG), &hNotify );

     // Handles für die SPS-Variablen holen// Get handles for the SPS variables
     nErr = AdsSyncReadWriteReq( pAddr, ADSIGRP_SYM_HNDBYNAME, 0x0, sizeof(ULONG), &hDeviceUp, 10, ".deviceUp" );
     nErr = AdsSyncReadWriteReq( pAddr, ADSIGRP_SYM_HNDBYNAME, 0x0, sizeof(ULONG), &hDeviceDown, 12, ".deviceDown" );
     nErr = AdsSyncReadWriteReq( pAddr, ADSIGRP_SYM_HNDBYNAME, 0x0, sizeof(ULONG), &hSteps, 7, ".steps" );
     nErr = AdsSyncReadWriteReq( pAddr, ADSIGRP_SYM_HNDBYNAME, 0x0, sizeof(ULONG), &hCount, 7, ".count" );
     nNotify = AdsSyncReadWriteReq( pAddr, ADSIGRP_SYM_HNDBYNAME, 0x0, sizeof(ULONG), &hNotify, 8, ".switch" );
     
     return TRUE;  // return TRUE  unless you set the focus to a control
}

SPS Variablen ausgeben:

In 'CMachineDlg::OnTimer()'ist die Ausgabe der Variablen organisiert. Der Parameter 'nElapse' der Methode 'SetTimer()' (der bereits im 'CMachineDlg::OnInitDialog()' angegeben wurde) bestimmt die Zeit (in ms) die vergehen soll, bis der Timer erneut ausgeführt wird.

void CMachineDlg::OnTimer(UINT nIDEvent) 
{
     //--> Ausgabe der SPS-Variablen <--////--> Output of the SPS variables <--/////////////////////////////////////////////////////////////////////////////////////////////// Auslesen der SPS-Variablen// Reading the SPS variables
     nErr = AdsSyncReadReq( pAddr, ADSIGRP_SYM_VALBYHND, hDeviceUp, 10, &bUp );
     // Bitmap für den "Up"-Zustand setzen// Sets the Bitmap of the "Up" positionif( bUp == 0 ){ m_Up.SetBitmap(CbmUp0); }
     else{ m_Up.SetBitmap(CbmUp1); }
     //---------------------------------------------------------------------------------------//
     nErr = AdsSyncReadReq( pAddr, ADSIGRP_SYM_VALBYHND, hDeviceDown, 12, &bDown );
     // Bitmap für den "Down"-Zustand setzen// Sets the Bitmap of the "Down" positionif( bDown == 0 ){ m_Down.SetBitmap(CbmDown0); }
     else{ m_Down.SetBitmap(CbmDown1); }
     //---------------------------------------------------------------------------------------//
     nErr = AdsSyncReadReq( pAddr, ADSIGRP_SYM_VALBYHND, hSteps, 7, &bySteps );
     // Anzahl der Schritte in das Control schreiben// Writes the number of steps in the control
     sOutput.Format( _T("%d"), bySteps );
     m_ctrlSteps.SetWindowText( sOutput );
     m_ctrlBar.SetPos( bySteps );
     //---------------------------------------------------------------------------------------//
     nErr = AdsSyncReadReq( pAddr, ADSIGRP_SYM_VALBYHND, hCount, 7, &sCount );
     // Zählerstand in das Control schreiben// Writes the count in the control
     sOutput.Format( _T("%i"), sCount );
     m_ctrlCount.SetWindowText( sOutput );
     //---------------------------------------------------------------------------------------//// Anpassen der "Switch"-Variable, bei Veränderung in der SPS// Fits the switch variable if it is changing
     nNotify = AdsSyncReadReq( pAddr, ADSIGRP_SYM_VALBYHND, hNotify, 8, &bNotify );
     if ( bNotify )
     { pRB = (CButton*)GetDlgItem( IDC_rdoFast ); pRB->SetCheck(1);
       pRB = (CButton*)GetDlgItem( IDC_rdoSlow ); pRB->SetCheck(0);
     }
     else
     { pRB = (CButton*)GetDlgItem( IDC_rdoFast ); pRB->SetCheck(0);
       pRB = (CButton*)GetDlgItem( IDC_rdoSlow ); pRB->SetCheck(1);
     }
     nNotify = AdsSyncWriteReq( pAddr, ADSIGRP_SYM_RELEASEHND, 0x0, sizeof(ULONG), &hNotify );
     ///////////////////////////////////////////////////////////////////////////////////////////

     CDialog::OnTimer(nIDEvent);
};


Zum Verbinden der Variablen wurde die Methode 'AdsSyncReadWriteReq()' verwendet.

long AdsSyncReadWriteReq( 
PAmsAddr pAddr, ULONG  nIndexGroup, ULONG nIndexOffset, ULONG nReadLength, PVOID pReadData, ULONG nWriteLength, 
 PVOID pWriteData );


Zum Verbinden der Variable 'hSwitch' wurde ebenfalls die Methode 'AdsSyncReadWriteReq()' verwendet. Einziger Unterschied ist hier, dass statt 'AdsSyncWriteReq()' (schreiben der Variablen) 'AdsSyncReadReq()' (lesen der Variablen) benutzt wird.

long AdsSyncReadReq( PAmsAddr  pAddr, ULONG nIndexGroup, ULONG nIndexOffset, ULONG nLength, PVOID pData );

Es fehlen noch zwei Methoden, um die Geschwindigkeit der Maschine zu regeln. Dazu einfach in die Design Ansicht wechseln und je einen Doppelklick auf den Radio Button für 'fast' und für 'slow' ausführen. Nun brauchen wir nur noch den entsprechenden Code einfügen.

Code für Radio Button 'fast'.

void CMachineDlg::OnBnClickedRadio1()
{    
     // Anpassen der "Switch"-Variable, bei klick auf Radio Button "fast"// Fits the switch variable if the radio button "fast" was clicked
     bSwitch = true;
     nSwitch = AdsSyncReadWriteReq( pAddr, ADSIGRP_SYM_HNDBYNAME, 0x0, sizeof(ULONG), &hSwitch, 8, ".switch" );
     nSwitch = AdsSyncWriteReq( pAddr, ADSIGRP_SYM_VALBYHND, hSwitch, 0x1, &bSwitch );
     nSwitch = AdsSyncWriteReq( pAddr, ADSIGRP_SYM_RELEASEHND, 0x0, sizeof(ULONG), &hSwitch );
}

Code für Radio Button 'slow'.

void CMachineDlg::OnBnClickedRadio2()
{    
     // Anpassen der "Switch"-Variable, bei klick auf Radio Button "slow"// Fits the switch variable if the radio button "slow" was clicked
     bSwitch = false;
     nSwitch = AdsSyncReadWriteReq( pAddr, ADSIGRP_SYM_HNDBYNAME, 0x0, sizeof(ULONG), &hSwitch, 8, ".switch" );
     nSwitch = AdsSyncWriteReq( pAddr, ADSIGRP_SYM_VALBYHND, hSwitch, 0x1, &bSwitch );
     nSwitch = AdsSyncWriteReq( pAddr, ADSIGRP_SYM_RELEASEHND, 0x0, sizeof(ULONG), &hSwitch );
}

Handles löschen:

Zum Schluss müssen wir nur noch die erstellten Handles wieder löschen. Hierzu öffnen wir die 'Properties' der Form und stellen bei den kleinen oberen Icons von 'Properties' auf 'Messages'. Danach suchen wir im unteren Fenster die 'WM_CLOSE' Message raus und wählen per DropDown '<Add> OnClose'.

Beispiel Maschine mit Microsoft Visual C++ .NET 10:

Wir landen sofort an der entsprechenden Codestelle an der nur noch nachfolgender Code eingefügt werden muss.

void CMachineDlg::OnClose()
{
     // Handles der SPS-Variablen freigeben und Port schließen// Release the handles of the SPS variables and close the port
     nErr = AdsSyncWriteReq( pAddr, ADSIGRP_SYM_RELEASEHND, 0, sizeof(ULONG), &hDeviceUp );
     nErr = AdsSyncWriteReq( pAddr, ADSIGRP_SYM_RELEASEHND, 0, sizeof(ULONG), &hDeviceDown );
     nErr = AdsSyncWriteReq( pAddr, ADSIGRP_SYM_RELEASEHND, 0, sizeof(ULONG), &hSteps );
     nErr = AdsSyncWriteReq( pAddr, ADSIGRP_SYM_RELEASEHND, 0, sizeof(ULONG), &hCount );
     nErr = AdsPortClose();


Das SPS ProgrammMachine_Final.prosollte auf dem Laufzeitsystem 1 starten und das erstellte C++-ProgrammMachine.exekann getestet werden.

Download C++ Programm Beispiel:

C++ .NET Beispiel