C++ Serial Port - Asynchronicznie

C++ Serial Port - Asynchronicznie
Marcin Kosela
  • Rejestracja:ponad 4 lata
  • Ostatnio:8 miesięcy
  • Postów:36
0

Witam serdecznie,

Ostatnim razem gdy miałem problem otrzymałem tu super pomoc. Mam nadzieję, że i tym razem się uda.

Potrzebuję pomocy w napisaniu funkcji wysyłającej i odbierającej dane po porcie COM w trybie asynchronicznym. Posiadam urządzenie które nadaje i odbiera małe paczki danych (kilka bajtów). Chciałbym jednak, żeby ta transmisja była obsługiwana możliwie szybko.
Przeszukałem już chyba cały internet i nie mogę zrozumieć obsługi portu. U mnie, w momencie kiedy czekam na przychodzącą wiadomość, nie mogę nic wysłać pomimo, że nadawanie wykonywane jest w innym wątku.
Próbowałem również ustawić timeout ale przestaje on działać po wysłaniu pierwszego pakietu.

Jak mogę to rozwiązać?

Kopiuj
static DCB dcb;
static char lpBuffor_read[33], lpBuffor_write[33];
static DWORD RS_ile;
static DWORD dwEventMask;
static COMMTIMEOUTS cto;

bool TXFlag = 0;
char TXBuf[16] = "/SPEED50/\n";

HANDLE hComm;


static std::atomic<bool> KEEP_GOING; //zmienna do utrzymania wątku

void serial_init(HANDLE hComm)
{
    dcb.DCBlength = sizeof(dcb);
    dcb.BaudRate = CBR_115200;
    dcb.fParity = TRUE;
    dcb.Parity = EVENPARITY;
    dcb.StopBits = ONESTOPBIT;
    dcb.ByteSize = 8;

    dcb.fDtrControl = DTR_CONTROL_ENABLE;
    dcb.fRtsControl = RTS_CONTROL_ENABLE;
    dcb.fOutxCtsFlow = FALSE;
    dcb.fOutxDsrFlow = FALSE;
    dcb.fDsrSensitivity = FALSE;
    dcb.fAbortOnError = FALSE;
    dcb.fOutX = TRUE;// FALSE;
    dcb.fInX = TRUE;// FALSE;
    dcb.fErrorChar = FALSE;
    dcb.fNull = FALSE;
    dcb.EofChar = '\n';
    dcb.EvtChar = '\n';

    if (!SetCommMask(hComm, EV_RXFLAG)) {
        printf("SetCommMask Error");
    }
    SetCommState(hComm, &dcb);

    GetCommTimeouts(hComm, &cto);
    cto.ReadIntervalTimeout = 100;
    cto.ReadTotalTimeoutMultiplier = 1;
    cto.ReadTotalTimeoutConstant = 2000;
    cto.WriteTotalTimeoutMultiplier = 2;
    cto.WriteTotalTimeoutConstant = 2000;
    SetCommTimeouts(hComm, &cto);

    if (hComm == INVALID_HANDLE_VALUE)
        printf("Error in opening serial port");
    else
        printf("opening serial port successful");
}

void wyslijCOM(HANDLE hcom, const char* dane) {
    strcpy_s(lpBuffor_write, dane);
    WriteFile(hcom, lpBuffor_write, strlen(lpBuffor_write), &RS_ile, 0);
}

char *odbierzDane(HANDLE hcom){
    DWORD dwCommEvent;

    SetCommMask(hcom, EV_RXFLAG || EV_RXCHAR); //EV_RXCHAR

    if (WaitCommEvent(hcom, &dwCommEvent, NULL)) {
        std::fill(lpBuffor_read, lpBuffor_read+sizeof(lpBuffor_read), 0);
        ReadFile(hcom, lpBuffor_read, 15, &RS_ile, 0);
        std::cout << "-> " << lpBuffor_read;
   }
   return lpBuffor_read;
}


void RX_Thread()
{
    char buf[50];

    while (true) {
        if (!KEEP_GOING.load())
            break;
        sprintf_s(buf, "%s \n", odbierzDane(hComm));
        std::cout << "!\n";
        if (TXFlag == 1) {
            wyslijCOM(hComm, TXBuf);
        }
    }
}

void TX_Thread()
{
    DWORD dwCommEvent;

    while (true) {
        if (!KEEP_GOING.load())
            break;
        std::cout << "...\n";
        sleep_until(system_clock::now() + seconds(2));
        wyslijCOM(hComm, TXBuf);
    }
}


int main()
{
    hComm = CreateFileA("\\\\.\\COM4",                //port name
        GENERIC_READ | GENERIC_WRITE, //Read/Write
        0,                            // No Sharing
        NULL,                         // No Security
        OPEN_EXISTING,// Open existing port only
        1,           // Non Overlapped I/O
        NULL);        // Null for Comm Devices

    serial_init(hComm);

    wyslijCOM(hComm, "?");

    KEEP_GOING.store(true);
    std::thread t1(&RX_Thread);
    std::thread t2(&TX_Thread);
    t1.join();
    t2.join();

    while(1) {
        sleep_until(system_clock::now() + seconds(1));
    }

    CloseHandle(hComm);//Closing the Serial Port

    return 0;
}
edytowany 1x, ostatnio: Marcin Kosela
06
Po co ta pętla w main, skoro masz 2x join? Dostęp do cout powinieneś synchronizować. wyslijCOM używasz w dwóch wątkach, a w niej globalnej tablicy lpBuffor_write (po co?). Ta funkcja powinna być synchronizowana. IMO odczyt/wysyłka może być realizowana w jednym wątku.
Sławomir Wielądek
Co w tym poście jest wyjątkowe. Przeczytajcie wszyscy borykający się z obsługą COM'ów o wyższych numerach (powyżej 10) nadawanych prxez emulatory USB portów COM. Większość przykładów w sieci bazuje na otwarciu portu funkcją w/w CreateFile(...). Nie wiem kto kogo natchnął aby nazwę portu w tym poście zapisać w formie "\\.\COMx" ale niech będzie błogosławiony. Wpisanie "COMx" także działa ale tylko dla x={1..10}. Przy >x funkcja się wykłada mimo, że port jest zainstalowany i podłączony. Tak że "COM38" nie działa a "\\.\COM38" już tak. Używam wersji CreateFileA(...) pzd
MasterBLB
  • Rejestracja:około 19 lat
  • Ostatnio:2 dni
  • Lokalizacja:Warszawa
  • Postów:1454
2

Bracie, czy musisz ograniczać się do samiutkiego C++? Jeśli masz możliwość to obczaj obsługę portu szeregowego przy pomocy QSerialPort z frameworka Qt, sam go używałem, i mogę polecić.


"Sugeruję wyobrazić sobie Słońce widziane z orbity Merkurego, a następnie dupę tej wielkości. W takiej właśnie dupie specjalista ma teksty o wspaniałej atmosferze, pracy pełnej wyzwań i tworzeniu innowacyjnych rozwiązań. Pracuje się po to, żeby zarabiać, a z resztą specjalista sobie poradzi we własnym zakresie, nawet jeśli firma mieści się w okopie na granicy obu Korei."
-somekind,
konkretny człowiek-konkretny przekaz :]
edytowany 1x, ostatnio: MasterBLB
AL
  • Rejestracja:prawie 11 lat
  • Ostatnio:około 3 lata
  • Postów:1493
1

Gotowiec jest tu https://stackoverflow.com/a/59874155/4885321
Ale jak Kolega wyżej polecam skorzystać z QT, ew. https://www.boost.org/doc/libs/1_65_0/doc/html/boost_asio/overview/serial_ports.html (aczkolwiek ASIO potrafi człowieka wychłostać, także nalegałbym na QT;))

Marcin Kosela
  • Rejestracja:ponad 4 lata
  • Ostatnio:8 miesięcy
  • Postów:36
0

Dzięki za zainteresowanie!

Przepraszam, że tak długo ale mam mnóstwo roboty, a i chwile zajęło mi sprawdzenie Waszych podpowiedzi.

Muszę użyć C++ (a właściwie C) ze wzgledu na bibiliotekę którą muszę wykorzystać w programie. Nie umiałbym jej wykorzystać w inny sposób.
Przejrzałem Qt, wygląda fajnie ale nie chcę przerzucać się na kolejne narzędzia których poznanie zajmie mi dużo czasu.

Ogranąłem trochę gotowiec zaproponowany przez alagner. Odbieranie działa, wysyłanie też działa ale gdy używam obydwu tak jak w przykładzie to wywala błąd:

Run-Time Check Failure #2 - Stack around the variable 'commOverlapped' was corrupted.>

Aktualny kod:

Kopiuj
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <assert.h>
#include <thread>
#include <chrono>

using namespace std;
using namespace std::this_thread; // sleep_for, sleep_until
using namespace std::chrono; // nanoseconds, system_clock, seconds

int ConfigureSerialPort(HANDLE hPort) {
    int Status;
    DCB dcb = { 0 };
    COMMTIMEOUTS timeouts;

    dcb.DCBlength = sizeof(dcb);

    Status = GetCommTimeouts(hPort, &timeouts);
    GetCommTimeouts(hPort, &timeouts);
    timeouts.ReadIntervalTimeout = 50;
    timeouts.ReadTotalTimeoutConstant = 50;
    timeouts.ReadTotalTimeoutMultiplier = 50;
    timeouts.WriteTotalTimeoutConstant = 50;
    timeouts.WriteTotalTimeoutMultiplier = 10;
    if (!SetCommTimeouts(hPort, &timeouts)) {
        printf("Error setting timeouts on port!\n");
    }

    Status = GetCommState(hPort, &dcb);
    dcb.BaudRate = 19200;
    dcb.Parity = NOPARITY;
    dcb.fBinary = TRUE; // Binary mode; no EOF check 
    dcb.fParity = FALSE; // Enable parity checking 
    dcb.fOutxCtsFlow = FALSE; // No CTS output flow control 
    dcb.fOutxDsrFlow = FALSE; // No DSR output flow control 
    dcb.fDtrControl = DTR_CONTROL_DISABLE; // DTR flow control type 
    dcb.fDsrSensitivity = FALSE; // DSR sensitivity 
    dcb.fTXContinueOnXoff = FALSE; // XOFF continues Tx 
    dcb.fOutX = FALSE; // No XON/XOFF out flow control 
    dcb.fInX = FALSE; // No XON/XOFF in flow control
    dcb.fErrorChar = FALSE; // Disable error replacement 
    dcb.fNull = FALSE; // Disable null stripping 
    dcb.fRtsControl = RTS_CONTROL_DISABLE; // RTS flow control 
    dcb.fAbortOnError = FALSE; // Do not abort reads/writes on err
    dcb.ByteSize = 8; // Number of bits/byte, 4-8 
    dcb.StopBits = ONESTOPBIT; // 0,1,2 = 1, 1.5, 2
    dcb.EvtChar = 0x84; // 'T' 

    if (!SetCommState(hPort, &dcb)) {
        printf("Unable to configure serial port!\n");
    }
    return 0;
};
//------------------------------------------------------------------------------------
int sendpckt(HANDLE hComm, unsigned length, unsigned char* pckt)
{
    BOOL result;
    DWORD dwCommEvent;
    OVERLAPPED oWrite = { 0 };
    DWORD errCode;

    result = SetCommMask(hComm, EV_TXEMPTY);
    if (!result) printf("Err: %d\n", GetLastError());

    result = WriteFile(hComm, pckt, length, NULL, &oWrite);
    if (result == FALSE)
    {
        errCode = GetLastError();

        if (errCode != ERROR_IO_PENDING)
            printf("Error! Setting CommMask\n");
    }

    OVERLAPPED commOverlapped = { 0 };

    commOverlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (commOverlapped.hEvent == NULL)
        return FALSE; // Error creating overlapped event handle.

    assert(commOverlapped.hEvent);

    result = WaitCommEvent(hComm, &dwCommEvent, &commOverlapped);
    if (!dwCommEvent)
        printf("Error Setting WaitCommEvent()%d\n", GetLastError());
    else
    {
        // If WaitCommEvent() == TRUE then Read the received data 
        if (dwCommEvent & EV_TXEMPTY)
        {
            printf("Send complete.\n");
        }

    }

    CloseHandle(oWrite.hEvent);
    CloseHandle(commOverlapped.hEvent);

    return 0;
};
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int recvpckt(HANDLE hComm, unsigned char* pckt)
{
    BOOL result;
    int len = 0;
    OVERLAPPED oRead = { 0 };
    DWORD errCode;
    DWORD dwCommEvent;

    result = SetCommMask(hComm, EV_RXCHAR);
    if (!result) printf("Err: %d\n", GetLastError());

    result = ReadFile(hComm, pckt, 2048, NULL, &oRead);
    if (result == FALSE)
    {
        errCode = GetLastError();

        if (errCode != ERROR_IO_PENDING)
            printf("nError! Setting CommMask\n");
    }

    OVERLAPPED commOverlapped = { 0 };

    commOverlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (commOverlapped.hEvent == NULL)
        return FALSE; // Error creating overlapped event handle.

    assert(commOverlapped.hEvent);

    result = WaitCommEvent(hComm, &dwCommEvent, &commOverlapped);
    if (!dwCommEvent)
        printf("Error Setting WaitCommEvent()%d\n", GetLastError());
    else
    {
        if (dwCommEvent & EV_TXEMPTY)
        {
            printf("Chars Recveived\n");
            len = oRead.InternalHigh;
        }

    }

    CloseHandle(oRead.hEvent);
    CloseHandle(commOverlapped.hEvent);

    return len;
};  //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! <<----------- TU ZWRACA BŁAD !!!!!!!!!!!!!!!!!!!!!

void main(int argc, char* argv[]) {

    HANDLE hPort;
    int len;
    unsigned idx = 0;
    unsigned char TXBUF[] = "?", RXBUF[128];
    LPCWSTR port = L"COM4";  //   <------ TO ZMIENIŁEM

    SetConsoleTitle(port); // To specify serial port number on open 
    hPort = CreateFile((LPCWSTR)port, GENERIC_READ | GENERIC_WRITE, 0, NULL,
        OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);

    ConfigureSerialPort(hPort);
    if (hPort == INVALID_HANDLE_VALUE) {
        printf("Error in openning serial port\n");
        exit(0); // No point in going on if serial port didn't open
    }
    else
        printf("Serial Port Open\n");

    while (1) {

        sendpckt(hPort, 1, TXBUF);
        printf("Sending 0x%.2X\n", TXBUF[0]);
        len = recvpckt(hPort, RXBUF);
        len = 0;
        printf("REC: %s", RXBUF);
        if (len > 0)
        {
            printf("RCVD(%d): \n", len);
        }

        for (idx = 0; idx < len; idx++)
        {
            printf("%d:0x%.2X \n", idx + 1, RXBUF[idx]);
        }
        sleep_until(system_clock::now() + seconds(1));
    }

    CloseHandle(hPort); // Close the serial port
}
edytowany 1x, ostatnio: Marcin Kosela
RE
"Muszę użyć C++ (a właściwie C) ze wzgledu na bibiliotekę którą muszę wykorzystać w programie" - nie rozumiem, qt to framework dla c++ no i używasz tu np. std::chrono czyli mieszasz czyste C z C++.
overcq
  • Rejestracja:około 7 lat
  • Ostatnio:około 4 godziny
  • Postów:394
1
Marcin Kosela napisał(a):
Kopiuj
int recvpckt(HANDLE hComm, unsigned char* pckt)
{
    // ...
    result = ReadFile(hComm, pckt, 2048, NULL, &oRead);
    // ...
};  //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! <<----------- TU ZWRACA BŁAD !!!!!!!!!!!!!!!!!!!!!

void main(int argc, char* argv[]) {
    // ...
    unsigned char TXBUF[] = "?", RXBUF[128];
    while (1) {
        // ...
        len = recvpckt(hPort, RXBUF);
        // ...
    }
    // ...
}

Rezerwujesz na stosie 128 bajtów, a czytasz 2048, więc uszkadza wszystkie zmienne w funkcji recvpckt.


Nie znam się, ale się wypowiem.
Wizytówka
joh­nny_Be_go­od jest mistrzem ‘eskejpowania’ i osadzania.
Marcin Kosela
  • Rejestracja:ponad 4 lata
  • Ostatnio:8 miesięcy
  • Postów:36
0

overcq rzeczywiście! Przykład i jak widać również z błędami.

Zainstalowałem Qt. To zupełnie inna bajka. Co prawda ten framework jest dość rozbudowany ale warto było.

Wszystko już działa. Komunikacja asynchroniczna, rozpoznawanie błędów komunikacji, rozpoznawanie urządzeń.
Znalazłem wszystko czego szukałem.

Dziękuję serdecznie!

edytowany 1x, ostatnio: Marcin Kosela
MasterBLB
To ustaw ptaszka pod tym z postów, który najbardziej pomógł rozwiązać twój problem.

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.