C++ Serial Port - Asynchronicznie

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ć?

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;
}
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ć.

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;))

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:

#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
}
1
Marcin Kosela napisał(a):
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.

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!

1 użytkowników online, w tym zalogowanych: 0, gości: 1