Skocz do zawartości

Zarchiwizowany

Ten temat jest archiwizowany i nie można dodawać nowych odpowiedzi.

gus

[C/C++] Asynchroniczny odczyt portu szeregowego

Polecane posty

Witam,

Mam problem z komunikacją z pewnym urządzeniem przez port szeregowy, korzystam z WinAPI. Wysyłam do niego sekwencję bajtów i w odpowiedzi również powinienem otrzymać jakiś ciąg bajtów. Z portu czytam w sposób asynchroniczy z ustawionym timeoutem. Po wysłaniu danych na port i próbie odczytu następuje timeout, nie wiem dlaczego? Kilka faktów pomocnych przy rozwiązywaniu problemu:

- Urządzenie otrzymuje wysłane dane prawidłowo i również prawidłowo odpowiada (miga dioda TX)

- Ustawienia portu są prawidłowe. Z tak samo skonfigurowanego portu korzystając z Doclight'a wysyłam i otrzymuję poprawną odpowiedź

- Przy synchronicznym odczycie portu dostaję odpowiedź

- Timeout nie jest za krótki, ustawiam go na 1s, przy czym w Doclight'cie odpowiedź przychodzi po około 80 ms

- Być może ma to jakieś znaczenie - nie korzystam z fizycznego portu w kompie tylko adaptera Moxa NPort Express

Poniżej kod programu:

#include <windows.h>
#include <stdio.h>

char buf[] = { 0xFF, 0xFF, 0xE1, 0x01, 0x01, 0x53, 0xAA, 0xAA, 0xE2 };
char input[1000];

#define SHOW_LAST_ERROR(_action_) \
do { \
char* errorMessage; \
DWORD error = GetLastError(); \
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, \
NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &errorMessage, 0, NULL ); \
fprintf(stderr, "Error %ld occured while %s: %s", error, _action_, errorMessage); \
} while (false);

#define CLOSE_APP do { CloseHandle(readOverlapped.hEvent); CloseHandle(writeOverlapped.hEvent); CloseHandle(portHandle); return 0; } while (false);

int main(int argc, char** argv)
{
HANDLE portHandle;
DCB config;

config.DCBlength = sizeof(config);
config.BaudRate = CBR_19200;
config.fBinary = TRUE;
config.fParity = FALSE;
config.fOutxCtsFlow = FALSE;
config.fOutxDsrFlow = FALSE;
config.fDtrControl = DTR_CONTROL_DISABLE;
config.fDsrSensitivity = FALSE;
config.fTXContinueOnXoff = FALSE;
config.fOutX = FALSE;
config.fInX = FALSE;
config.fErrorChar = FALSE;
config.fNull = TRUE;
config.fRtsControl = RTS_CONTROL_DISABLE;
config.fAbortOnError = FALSE;
config.wReserved = 0;
config.ByteSize = 8;
config.Parity = NOPARITY;
config.StopBits = ONESTOPBIT;

portHandle = CreateFile("COM2", GENERIC_WRITE | GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);

if (portHandle == INVALID_HANDLE_VALUE)
SHOW_LAST_ERROR("opening port");

SetCommState(portHandle, &config);

OVERLAPPED writeOverlapped;
OVERLAPPED readOverlapped;

readOverlapped.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

if (readOverlapped.hEvent == INVALID_HANDLE_VALUE)
SHOW_LAST_ERROR("creating read event");

writeOverlapped.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if (writeOverlapped.hEvent == INVALID_HANDLE_VALUE)
SHOW_LAST_ERROR("creating write event");

DWORD bytesWritten = 0;
if (!WriteFile(portHandle, (void*)buf, sizeof(buf), &bytesWritten, &writeOverlapped))
{
if (GetLastError() != ERROR_IO_PENDING)
{
SHOW_LAST_ERROR("writing file");
CLOSE_APP;
}
}

DWORD bytesReaded = 0;
if (!GetOverlappedResult(portHandle, &writeOverlapped, &bytesWritten, TRUE))
{
SHOW_LAST_ERROR("getting write overlapped result");
CLOSE_APP;
}
if (bytesWritten != sizeof(buf))
{
fprintf(stderr, "Bytes written mismatch size of output buffer\n");
CLOSE_APP
}

if (!ReadFile(portHandle, (void*)input, sizeof(input), &bytesReaded, &readOverlapped))
{
if (GetLastError() != ERROR_IO_PENDING)
{
SHOW_LAST_ERROR("reading file");
CLOSE_APP;
}
}

switch (WaitForSingleObject(readOverlapped.hEvent, 1000))
{
case WAIT_OBJECT_0:
if (!GetOverlappedResult(portHandle, &readOverlapped, &bytesReaded, FALSE))
{
SHOW_LAST_ERROR("getting read overlapped result");
CLOSE_APP;
}
for (unsigned i = 0; i < bytesReaded; printf("%02X ", i++));
CLOSE_APP;
break;
case WAIT_TIMEOUT:
fprintf(stderr, "Timeout while reading file\n");
CancelIo(portHandle);
CLOSE_APP
break;
default:
SHOW_LAST_ERROR("waiting for read event");
CLOSE_APP;
break;
}

return 0;
}

Na wyjściu programu otrzymuję: Timeout while reading file

Udało mi się odczytać dane z portu wstawiając zamiast sizeof(input), stałą 10. Oznacza to, że operacja odczytu trwa dopóki nie zapełni się bufor do odczytu. Ale z tego co przeczytałem:

BOOL WINAPI ReadFile(

__in HANDLE hFile,

__out LPVOID lpBuffer,

__in DWORD nNumberOfBytesToRead,

__out_opt LPDWORD lpNumberOfBytesRead,

__inout_opt LPOVERLAPPED lpOverlapped

);

oraz:

nNumberOfBytesToRead [in]

The maximum number of bytes to be read.

Rozumiem, że parametr ten określa maksymalną ilość, którą chcemy odczytać, a nie ile funkcje musi odczytać. W odpowiedzi urządzenie wysyła różną ilość bajtów, więc nie mogę z góry założyć ile ich będzie. Natomiast odczytywanie bajt po bajcie też jest kłopotliwe, bo ciężko będzie obliczyć timeout dla całej operacji odczytu.

Link do komentarza
Udostępnij na innych stronach

Korzystasz z asynchronicznego zapisu ? Ma to jakiś sens ? Zlecasz zadanie i od razu czekasz aż się skończy. Masz w tym jakiś cel ?

Korzystasz z asynchronicznego odczytu tak jakby to był odczyt synchroniczny. Zlecasz zadanie i od razu czekasz aż się skończy. Masz w tym jakiś cel ?

nNumberOfBytesToReadOdczyta właśnie tyle bajtów. Z moich doświadczeń wynika, że ile prosisz, tyle dostajesz. Tylko jakieś dziwne wydarzenia w stylu zamknięcia portu mogą przerwać odczyt. Jeżeli druga strona nie wyśle dostatecznie dużo danych, to rzecz jasna dostaniesz timeout po zadanym czasie.

Próbowałeś sprawdzić dane jakie udało się odebrać zanim był timeout ? Nie wiem jak to dokładnie jest, ale pewnie dałoby się przerwać zadanie, a następnie pobrać wynik.

Jeżeli mogę coś doradzić, to daruj sobie WinAPI. Użyj (mocy ;) ) boost::asio. Nadaje się do pracy synchronicznej i asynchronicznej. Gdybyś chciał zrobić jakiś protokół komunikacji to przeczytaj ten post. Jest tam sprawdzony algorytm. Trzeba go tylko sobie ładnie opakować w klasę i odpalić w osobnym wątku. Bufor cykliczny jest w boost. Obsługa wątków też jest w boost.

Link do komentarza
Udostępnij na innych stronach

No więc na początek, rzeczywiście do portu zapisuję w sposób asynchroniczny, a później czekam na zakończenie operacji. Ale chyba nie da się otworzyć portu do synchronicznego zapisu i asynchronicznego odczytu.

Port odczytuję w sposób asynchroniczny po to, żeby móc stwierdzić, że urządzenie w określonym czasie nie odpowiada, co oznacza jego odłączenie (tak naprawdę do portu szeregowego podłączona jest cała magistrala, do której w każdej chwili może się podłączyć/odłączyć jakieś urządzenie, a ja muszę o tym wiedzieć).

Sprawdzanie odebranych danych po timeoucie nie jest dobrym rozwiązaniem, bo by znacznie spowalniało komunikacją (normalnie proces nadanie komendy -> odebranie odpowiedzi trwa 50-100 ms, ale dopuszcza się maksymalny timeout 300 ms). Ale dobra sugestia żeby to sprawdzić. Boost::asio znam, użycie tej biblioteki warte rozważenia. A co do protokołu komunikacji, to jest narzucony przez producenta urządzenia ;)

Popróbuję jutro i napiszę jakie efekty i jak się uda to rozwiązanie problemu.

Zrobiłem to w ten sposób, że odczytuję z portu po 1 bajcie, na razie takie rozwiązanie mi wystarczy. Temat do zamknięcia.

Link do komentarza
Udostępnij na innych stronach



  • Kto przegląda   0 użytkowników

    • Brak zalogowanych użytkowników przeglądających tę stronę.
×
×
  • Utwórz nowe...