Text über die Serielle Schnittstelle (COM3) einlesen



  • Hallo,
    ich möchte einen Text über die Serielle-Schnittstelle (COM3) einlesen.
    Dazu habe ich hier im Forum folgendes Beispiel gefunden:

    // ConsoleApplication3.cpp : Diese Datei enthält die Funktion "main". Hier beginnt und endet die Ausführung des Programms.
    // Serielle Schnittstelle ansprechen und Daten senden
    // https://www.c-plusplus.net/forum/topic/300637/serielle-schnittstelle-ansprechen-und-daten-senden
    
    
    #include <iostream>
    #include <stdio.h>
    #include "ComPort.h"
    
    ComPort::ComPort(void)
    {
        hCom = INVALID_HANDLE_VALUE;
    }
    
    ComPort::~ComPort(void)
    {
        CloseHandle(hCom);
    }
    
    void ComPort::InitComPort(void)
    {
        DCB Dcb{};
        COMMTIMEOUTS Cto{};
    
        //if ((hCom = CreateFile(TEXT("COM3:"), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL)) != INVALID_HANDLE_VALUE)
        if ((hCom = CreateFile(TEXT("\\\\.\\COM3"), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL)) != INVALID_HANDLE_VALUE)
        {
            Dcb.DCBlength = sizeof(Dcb);
            GetCommState(hCom, &Dcb);
            Dcb.BaudRate = 4800;
            Dcb.fParity = false;
            Dcb.fNull = false;
            Dcb.StopBits = ONESTOPBIT;
            Dcb.Parity = NOPARITY;
            Dcb.ByteSize = 8;
            Dcb.EvtChar = 'X';
            SetCommState(hCom, &Dcb);
    
            Cto.ReadIntervalTimeout = 0;
            Cto.ReadTotalTimeoutMultiplier = 0;
            Cto.ReadTotalTimeoutConstant = 0;
            Cto.WriteTotalTimeoutMultiplier = 0;
            Cto.WriteTotalTimeoutConstant = 0;
            SetCommTimeouts(hCom, &Cto);
    
            cout << "Port opened" << endl;
        }
        else
        {
            cout << "Couldn't open Port" << endl << "ERROR: " << GetLastError() << endl;
        }
    }
    
    int ComPort::Read() const
    {
        DWORD mask = 0;
        DWORD bytesRead = 0;
        char buf[1024] = "";
        char buf_E[1024] = "";
        int i = 0;
    
        SetCommMask(hCom, EV_RXCHAR | EV_RXFLAG | EV_RXFLAG);
        while (true)
        {
            if (WaitCommEvent(hCom, &mask, 0))
            {
                cout << "mask " << mask << endl;
                switch (mask)
                {
                case EV_RXCHAR:
                    ReadFile(hCom, &buf[i++], 1, &bytesRead, NULL);
                    cout << "Empfangen: " << buf[i - 1] << endl;
                    if (buf[i - 1] == '@') exit;         // Einlesen Ende wenn Zeichen '@' gesendet wird
                    break;
                case EV_ERR:
                    cout << "Fehler beim lesen" << endl;
                    break;
                case EV_RXFLAG:
                    cout << "Eventchar empfangen" << endl;
                    break;
                }
            }
        }
        return bytesRead;
    }
    
    int ComPort::Write() const
    {
        DWORD BytesWritten = 0;
    
        //char buf[] = "Hallo";
        char buf[] = "X";
    
        if (!WriteFile(hCom, buf, 1, &BytesWritten, NULL))
        {
            cout << "Error writing to Port" << endl;
            return -1;
        }
    
        return BytesWritten;
    }
    
    int main() {
        DWORD BytesRead = 0;
        ComPort var1;
        var1.InitComPort();
        var1.Write();           // Zeichen 'X' senden
        BytesRead = var1.Read();
        cout << "BytesRead " << BytesRead << endl;
        system("pause");
        return 0;
    }
    
    // ComPort.h
    #pragma once
    
    #ifndef _COMPORT_H_
    #define _COMPORT_H_
    
    #include "windows.h"
    #include <iostream>
    
    using namespace std;
    
    class ComPort
    {
    public:
        ComPort(void);
        ~ComPort(void);
        void InitComPort(void);
        int Read() const;
        int Read_org() const;
        int Write() const;
    
    private:
        HANDLE hCom;
    };
    
    #endif
    

    Das Einlesen des Textes funktioniert soweit.
    Wie aber kann ich das Einlesen mit "Read()" benden.
    Mein Versuch war das Einlesen benden wenn z.B. das Zeichen '@' gesendet wird: " if (buf[i - 1] == '@') exit;".
    Funktioniert leider nicht, das Einlesen wird nicht beendet.
    Juergen



  • exit; macht nicht das was du vermutlich willst. Es macht nämlich gar nix. exit ist nämlich in C++ kein Keyword. exit ist eine Standard-Library Funktion, die dein Programm beendet. Wenn du diese aufrufen wolltest, müsstest du exit(123); schreiben, bzw. statt 123 was auch immer der Exit-Code sein soll.

    Nur exit, ohne () ist kein Funktionsaufruf. Daher läuft dein Programm dann weiter. Nur was ist exit, ohne (), und warum bekommst du keinen Fehler? Der Grund ist dass du in C++ als "Anweisung" sog. "Expressions" (Ausdrücke) hinschreiben kannst -- auch wenn diese nichts tun. Und exit ist so ein Ausdruck. Er hat einen Wert (eine Referenz auf die exit Funktion), aber keinen Effekt. Macht in diesem Fall keinen Sinn, ist aber erlaubt. Wobei dein Compiler dir hier eine Warning geben sollte. Wie z.B.:

    <source>:5:5: warning: expression result unused [-Wunused-value]
        exit;
    

    Wobei vermutlich willst du gar nicht exit aufrufen, sondern dass der Programm nach der while (true) Schleife weitermacht. Wie geht das? z.B. mit goto:

    int ComPort::Read() const
    {
    ...
        while (true)
        {
    ...
                    if (buf[i - 1] == '@') goto done;         // Einlesen Ende wenn Zeichen '@' gesendet wird
    ...
        }
    done:
        return bytesRead;
    }
    

    Wobei es einen viel eleganteren Weg gibt: du kannst dort wo du die Funktion verlassen willst einfach direkt return bytesRead; hinschreiben. Also einfach

    int ComPort::Read() const
    {
    ...
        while (true)
        {
    ...
                    if (buf[i - 1] == '@') return bytesRead;    // Einlesen Ende wenn Zeichen '@' gesendet wird
    ...
        }
        // Hier brauchst du kein return, da diese Stelle sowieso nie erreicht werden kann.
    }
    


  • ps: du solltest den Returnwert von ReadFile prüfen und Fehler behandeln.



  • Mir sind noch zwei weitere Dinge aufgefallen (auch wenn es nicht dein Quelltext ist):

    1. using namespace std sollte im Header nicht verwendet werden, überhaupt sollte using namespace ... in Headerdateien nicht verwendet werden, weil sie Mehrdeutigkeiten erzeugen können und das Namespace-Konzept aushebeln
    2. seit C++11 gibt es das Schlüsselwort nullptr, das statt des Wertes NULL benutzt werden sollte.


  • @jbaben

    ReadFile(hCom, &buf[i++], 1, &bytesRead, NULL);

    Du solltest hier besser auf Rückgabewerte achten. In bytesRead kann durchaus auch 0 stehen und trotzdem springst du eine Stelle in buf weiter. Davon mal abgesehen kann ReadFile() auch FALSE zurückliefern.

    Ferner gefällt mir deine Variable char buf[1024] nicht. Denn:

    • Aus deinem Code wird nicht ersichtlich wieviele Zeichen du in der Summe empfangen hast. Du achtest nicht auf Nullterminierung und hast auch keine Variable, welche dir die Anzahl der gespeicherten Bytes angibt.
    • Du musst besser auf deine Grenzen achten. Aktuell darf die Gegenseite nur 1024 Zeichen senden, sonst gibt es einen Buffer-Overflow. Bloß das weiß die Gegenseite halt nicht.

    Mein Tipp: Nimm stattdessen einen std::vector<char>. Zusätzlich musst du aber eine max. Größe festlegen und implementieren, falls die Gegenseite nur Blödsinn (z.B. 5 GByte an a) sendet.


    Mach dir auch die Natur der seriellen Daten klar.

    In manchen Fällen kommen diese in einer Befehlsform (z.B. "at\r\n") vor. Da kann es durchaus Sinn machen, die Daten periodisch abzufragen. Wird dann ein Startzeichen gefunden, so liest die Funktion alle weiteren Zeichen ein, bis zu das Endezeichen gefunden hast oder du in einem Timeout läufst.

    In anderen Fällen können diese sporadisch kommen. Dann würde ein Thread Sinn machen. Der Thread liest dann alle Zeichen ein bis er in einen Timeout rennt und leitet dann die eingelesenen Daten per Callback an dich weiter.



  • @hustbaer sagte in Text über die Serielle Schnittstelle (COM3) einlesen:

    exit; macht nicht das was du vermutlich willst. Es macht nämlich gar nix. exit ist nämlich in C++ kein Keyword. exit ist eine Standard-Library Funktion, die dein Programm beendet. Wenn du diese aufrufen wolltest, müsstest du exit(123); schreiben, bzw. statt 123 was auch immer der Exit-Code sein soll.

    Nur exit, ohne () ist kein Funktionsaufruf. Daher läuft dein Programm dann weiter. Nur was ist exit, ohne (), und warum bekommst du keinen Fehler? Der Grund ist dass du in C++ als "Anweisung" sog. "Expressions" (Ausdrücke) hinschreiben kannst -- auch wenn diese nichts tun. Und exit ist so ein Ausdruck. Er hat einen Wert (eine Referenz auf die exit Funktion), aber keinen Effekt. Macht in diesem Fall keinen Sinn, ist aber erlaubt. Wobei dein Compiler dir hier eine Warning geben sollte. Wie z.B.:

    <source>:5:5: warning: expression result unused [-Wunused-value]
        exit;
    

    Wobei vermutlich willst du gar nicht exit aufrufen, sondern dass der Programm nach der while (true) Schleife weitermacht. Wie geht das? z.B. mit goto:

    int ComPort::Read() const
    {
    ...
        while (true)
        {
    ...
                    if (buf[i - 1] == '@') goto done;         // Einlesen Ende wenn Zeichen '@' gesendet wird
    ...
        }
    done:
        return bytesRead;
    }
    

    Wobei es einen viel eleganteren Weg gibt: du kannst dort wo du die Funktion verlassen willst einfach direkt return bytesRead; hinschreiben. Also einfach

    int ComPort::Read() const
    {
    ...
        while (true)
        {
    ...
                    if (buf[i - 1] == '@') return bytesRead;    // Einlesen Ende wenn Zeichen '@' gesendet wird
    ...
        }
        // Hier brauchst du kein return, da diese Stelle sowieso nie erreicht werden kann.
    }
    

    Hallo,
    vielen Dank für Deine hilfreichen Hinweise: mit "return bytesRead" funktioniert es (natürlich auch mit "goto").
    Zum Hinweis: "Returnwert von ReadFile prüfen" kann ich dazu noch genauere Hinweise erhalten ?

    Juergen



  • @DocShoe sagte in Text über die Serielle Schnittstelle (COM3) einlesen:

    Mir sind noch zwei weitere Dinge aufgefallen (auch wenn es nicht dein Quelltext ist):

    1. using namespace std sollte im Header nicht verwendet werden, überhaupt sollte using namespace ... in Headerdateien nicht verwendet werden, weil sie Mehrdeutigkeiten erzeugen können und das Namespace-Konzept aushebeln
    2. seit C++11 gibt es das Schlüsselwort nullptr, das statt des Wertes NULL benutzt werden sollte.

    Hallo,
    vielen Dank für den Hinweis: werde ich berücksichtigen.

    Juergen



  • @Quiche-Lorraine sagte in Text über die Serielle Schnittstelle (COM3) einlesen:

    @jbaben

    ReadFile(hCom, &buf[i++], 1, &bytesRead, NULL);

    Du solltest hier besser auf Rückgabewerte achten. In bytesRead kann durchaus auch 0 stehen und trotzdem springst du eine Stelle in buf weiter. Davon mal abgesehen kann ReadFile() auch FALSE zurückliefern.

    Ferner gefällt mir deine Variable char buf[1024] nicht. Denn:

    • Aus deinem Code wird nicht ersichtlich wieviele Zeichen du in der Summe empfangen hast. Du achtest nicht auf Nullterminierung und hast auch keine Variable, welche dir die Anzahl der gespeicherten Bytes angibt.
    • Du musst besser auf deine Grenzen achten. Aktuell darf die Gegenseite nur 1024 Zeichen senden, sonst gibt es einen Buffer-Overflow. Bloß das weiß die Gegenseite halt nicht.

    Mein Tipp: Nimm stattdessen einen std::vector<char>. Zusätzlich musst du aber eine max. Größe festlegen und implementieren, falls die Gegenseite nur Blödsinn (z.B. 5 GByte an a) sendet.


    Mach dir auch die Natur der seriellen Daten klar.

    In manchen Fällen kommen diese in einer Befehlsform (z.B. "at\r\n") vor. Da kann es durchaus Sinn machen, die Daten periodisch abzufragen. Wird dann ein Startzeichen gefunden, so liest die Funktion alle weiteren Zeichen ein, bis zu das Endezeichen gefunden hast oder du in einem Timeout läufst.

    In anderen Fällen können diese sporadisch kommen. Dann würde ein Thread Sinn machen. Der Thread liest dann alle Zeichen ein bis er in einen Timeout rennt und leitet dann die eingelesenen Daten per Callback an dich weiter.

    Hallo,
    auch Dir vielen Dank für Deine Hinweise.
    Es ist so das ich die ersten Schritte (mit C++) mit der Seriellen-Schnittstelle mache.
    Ersteinmal bin ich froh ein Beispiel-Programm zu haben das funktioniert auch wenn es noch nicht fertig ist.
    Deshal bin ich froh wenn ich dazu entsprechende Hinweise erhalte.

    Juergen



  • Hi Juergen! It looks like you're on the right track with reading from the serial port, but the way you're trying to exit the loop isn't quite right. Instead of using exit, which will terminate the entire program, you might want to use a break statement to exit just the reading loop. So, you could modify your code to something like if (buf[i - 1] == '@') break;. That way, when the '@' character is detected, it will break out of the loop and stop reading. Give that a try and let me know how it goes!


Anmelden zum Antworten