Čtení dat RS232 v C++

Marek

Čtení dat RS232 v C++
« kdy: 14. 02. 2014, 17:40:26 »
Ahojte.
Zopár hodín sa trápim s tým, ako čítať DATA zo sériového portu (rs232). Program píšem v C++ (Code::Blocks + MINGW), štandardné WINAPI GUI.

cez case WM_COMMAND: -> case(param) -> Otváram Port ....CreateFile.... co funguje
otvorenie portu, a zápis fungujú v pohode.
Horšie je to s príjmom Dát z RS232. Nedarí sa mi to spraviť tak, aby som mohol prijatý text vykresliť v GUI (label).

Neviem nájsť žiadny EVENT, respektíve prerušenie po prijatí znaku "EV_RXCHAR". Môžete mi poradiť, ako spraviť nepretržité čítanie dát, event, alebo aj pooling, tak aby som to vedel zapracovať do GUI (label)?



Štandardné GUI

Kód: [Vybrat]
#include <windows.h>

/*  Declare Windows procedure  */
LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM);

/*  Make the class name into a global variable  */
char szClassName[ ] = "CodeBlocksWindowsApp";

int WINAPI WinMain (HINSTANCE hThisInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR lpszArgument,
                     int nCmdShow)
{
    HWND hwnd;               /* This is the handle for our window */
    MSG messages;            /* Here messages to the application are saved */
    WNDCLASSEX wincl;        /* Data structure for the windowclass */

    /* The Window structure */
    wincl.hInstance = hThisInstance;
    wincl.lpszClassName = szClassName;
    wincl.lpfnWndProc = WindowProcedure;      /* This function is called by windows */
    wincl.style = CS_DBLCLKS;                 /* Catch double-clicks */
    wincl.cbSize = sizeof (WNDCLASSEX);

    /* Use default icon and mouse-pointer */
    wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
    wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
    wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
    wincl.lpszMenuName = NULL;                 /* No menu */
    wincl.cbClsExtra = 0;                      /* No extra bytes after the window class */
    wincl.cbWndExtra = 0;                      /* structure or the window instance */
    /* Use Windows's default colour as the background of the window */
    wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND;

    /* Register the window class, and if it fails quit the program */
    if (!RegisterClassEx (&wincl))
        return 0;

    /* The class is registered, let's create the program*/
    hwnd = CreateWindowEx (
           0,                   /* Extended possibilites for variation */
           szClassName,         /* Classname */
           "Code::Blocks Template Windows App",       /* Title Text */
           WS_OVERLAPPEDWINDOW, /* default window */
           CW_USEDEFAULT,       /* Windows decides the position */
           CW_USEDEFAULT,       /* where the window ends up on the screen */
           544,                 /* The programs width */
           375,                 /* and height in pixels */
           HWND_DESKTOP,        /* The window is a child-window to desktop */
           NULL,                /* No menu */
           hThisInstance,       /* Program Instance handler */
           NULL                 /* No Window Creation data */
           );

    /* Make the window visible on the screen */
    ShowWindow (hwnd, nCmdShow);

    /* Run the message loop. It will run until GetMessage() returns 0 */
    while (GetMessage (&messages, NULL, 0, 0))
    {
        /* Translate virtual-key messages into character messages */
        TranslateMessage(&messages);
        /* Send message to WindowProcedure */
        DispatchMessage(&messages);
    }

    /* The program return-value is 0 - The value that PostQuitMessage() gave */
    return messages.wParam;
}


/*  This function is called by the Windows function DispatchMessage()  */

LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)                  /* handle the messages */
    {
        case WM_DESTROY:
            PostQuitMessage (0);       /* send a WM_QUIT to the message queue */
            break;
        default:                      /* for messages that we don't deal with */
            return DefWindowProc (hwnd, message, wParam, lParam);
    }

    return 0;
}


MSDN o Seriovej komunikacii
http://msdn.microsoft.com/en-us/library/ms810467.aspx

Dakujem M.
« Poslední změna: 14. 02. 2014, 19:43:01 od Petr Krčmář »


Ogar

Re:C++ WINAPI - Seriovy Port + GUI -> Čítanie Dát
« Odpověď #1 kdy: 14. 02. 2014, 19:27:33 »
1) CreateFile(,OVERLAPPED)
2) ReadFile(,OVERLAPPED)
3) misto GetMessage() pouzit MsgWaitForMultipleObjects() a v Count=1, Handles[0] bude EVENT, ktery byl pouzity pro Overlapped

MsgWaitForMultipleObjects() vrati:
WAIT_OBJECT_0 -> data v bufferu
WAIT_OBJECT_0+1 -> zprava pro okno

Takze misto:
Kód: [Vybrat]
/* Run the message loop. It will run until GetMessage() returns 0 */
    while (GetMessage (&messages, NULL, 0, 0))
    {
        /* Translate virtual-key messages into character messages */
        TranslateMessage(&messages);
        /* Send message to WindowProcedure */
        DispatchMessage(&messages);
    }

bude:
Kód: [Vybrat]
HANDLE File;
HANDLE Event;
OVERLAPPED Overlapped;

File=CreateFile(...,FILE_FLAG_OVERLAPPED,...);

Event=CreateEvent(....);
Overlapped.hEvent=Event;

// start new async read request
ReadFile(File,....,&Overlapped);

while(1){
DWORD Result;

Result=MsgWaitForMultipleObjects(1,&Event,FALSE,INFINITE,
QS_ALLEVENTS | QS_ALLPOSTMESSAGE | QS_SENDMESSAGE);

if (Result==WAIT_OBJECT_0){
// get data ..
GetOverlappedResult(File,&Overlapped,....);

// copy data from Overlapped somewhere

// start new async read request
ReadFile(File,....,&Overlapped);
}

if (Result==WAIT_OBJECT_0+1){
while (PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE)){
if (Msg.message == WM_QUIT){
break;
}
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}

if (Msg.message==WM_QUIT){
break;
}
}
}

// must cancel all pending requests !!!!
CancelIo(File);

Toz tak

Ogar

Re:Čtení dat RS232 v C++
« Odpověď #2 kdy: 14. 02. 2014, 19:57:04 »
Jo, a mimochodem, pokud bys nemel ten port otevrity porad, lze do MsgWaitFor...() dat i prvni parametr (pocet) jako 0 :-)

Re:Čtení dat RS232 v C++
« Odpověď #3 kdy: 15. 02. 2014, 09:51:26 »
Záleží, jaký je to řízení.

V synchronním řízení nejdřív zapíšu a pak přečtu a dokud znova nezapíšu, nepotřebuju číst... třeba jako kdyby to byl http request, tak podobně... pak je to snadné, stačí jen volat WriteFile, ReadFile.

V asynchronním řízení, tedy pokud potřebuju trvale monitorovat port čtením a zároveň občas do něj i zapsat, pak doporučuju jednoznačně
  • Otevřít to s příznakem OVERLAPPED a číst tímto způsobem
  • a zároveň číst to v jiném vlákně
Protože při volání ReadFile se zamyká nějaký zámek na tom handle, nedá se volat WriteFile, když jiné vlákno visí v ReadFile. Proto je nutné mít OVERLAPPED, který způsobí, že vlákno nečeká v systémovém volání, ale na nějakém eventu.

Ono to sice jde číst v UI, ale z hlediska návrhu aplikace to není zrovna čisté řešení. Už jen proto, že musíte modifikovat smyčku pro čtení zpráv, což je zásah mající globální dopad na řízení běhu aplikace

Další možností je použít APC a v obsluze APC si poslat do okna zprávu pomocí PostMessage, která vydráždí UI vlákno k tomu, aby si načetla přijatá data.

Marek

Re:Čtení dat RS232 v C++
« Odpověď #4 kdy: 15. 02. 2014, 11:24:57 »
Ahojte, ďakujem za pomoc, v pláne je spraviť GUI,v GUI budu zobrazene hodnoty prijaté z uartu(virtual USB/COM), občasne sa spravi write(na seriovy port), ale v drtivej vacsine to bude prijimanie.

Prepísal som to, ako mi poradil Ogar, GUI sa vykresli, DRAW funguje. Po prvom write, a následnom receive(read) sa však GUI kusne, v debugu skace stale na (Result==WAIT_OBJECT_0), aj ked ziadne data uz niesu posielane na seriovy port, (Readfile) nema co citat, nic neprichadza. Momentalne mam "premosteny" RX/TX na virtual USB/COM , teda poslem znak, hned ho aj primem.

s WINAPI robim prvykrat, som zvyknuty na procesory, nastavim interrupt flag pre RX, pride event, spracujem, a pokracujem dalej.

:Ogar: Port mam stale otvoreny, writefile(10%), readfile(90%) z celkoveho casu.

:Ondřej Novák: Ďakujem, skusim pozriet ten APC, ale ak by ste mal príklad, resp vhodnu literaturu, budem vdacny.

s pozdravom M.

RS232
Kód: [Vybrat]
#include <windows.h>
#include "RS232.h"

extern HANDLE hSerial;             // Global Handle

bool OpenSerialPort(HWND hwnd,LPCSTR _PortName)
{
    hSerial = CreateFile(_PortName, GENERIC_WRITE | GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);      // OPEN PORT
    if(hSerial == INVALID_HANDLE_VALUE)
    {
        if(GetLastError() == ERROR_FILE_NOT_FOUND)      MessageBox(hwnd, "Port not avalible", "ERROR", MB_OK);
        return false;
    }

    DCB SPort = {0};                    //

    SPort.DCBlength = sizeof(DCB);

    if(GetCommState(hSerial, & SPort))
        {
            SPort.BaudRate=CBR_115200;             // SerilPort Baudrate
            SPort.ByteSize=8;                   // Byte size 8
            SPort.StopBits=ONESTOPBIT;
            SPort.Parity=NOPARITY;
        }
    else
        {
            MessageBox(hwnd, "COM_Error_103", "ERROR", MB_OK);
            return false;
        }

     if (!SetCommMask(hSerial, EV_RXCHAR))  MessageBox(hwnd, "COM_Error_103", "ERROR", MB_OK);
     return true;
}
//----------------------------------
//----------------------------------
bool CloseSerialPort(void)
{
    if(CloseHandle(hSerial))    return 1;           // Closed OK - return 1
    else                        return 0;           // Closed FX - return 0 -> Error
}
//----------------------------------
//----------------------------------
bool WriteABuffer(HWND hwnd, BYTE * lpBuf, DWORD dwToWrite)
{
   OVERLAPPED osWrite = {0};
   DWORD dwWritten;
   DWORD dwRes;
   BOOL fRes;
   osWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

   if (!WriteFile(hSerial,(LPCVOID) lpBuf, dwToWrite, &dwWritten, &osWrite))
    {
      if (GetLastError() != ERROR_IO_PENDING)                                                    fRes = FALSE;
      else
         dwRes = WaitForSingleObject(osWrite.hEvent, INFINITE);
         switch(dwRes)
         {
            case WAIT_OBJECT_0:
                 if (!GetOverlappedResult(hSerial, &osWrite, &dwWritten, FALSE))           fRes = FALSE;
                 else                                                                                            fRes = TRUE;

            default:    fRes = FALSE;
         }
      }
   else
   {
      MessageBox(hwnd, "OK", "OK", MB_OK);
      fRes = true;
   }

   CloseHandle(osWrite.hEvent);
   return fRes;
}

Main.cpp
Kód: [Vybrat]
Event = CreateEvent(NULL, TRUE, FALSE, NULL);
    Overlapped.hEvent = Event;

    char lpBuf;
    DWORD value;
    DWORD dwCommEvent;
    while(1)
    {
Result=MsgWaitForMultipleObjects(1,&Event,FALSE,INFINITE, QS_ALLEVENTS | QS_ALLPOSTMESSAGE | QS_SENDMESSAGE);

if (Result==WAIT_OBJECT_0)
            {
                GetOverlappedResult(hSerial, &Overlapped, &value , FALSE);
                if(WaitCommEvent(hSerial, &dwCommEvent, NULL))
                {
                  do {
                      ReadFile(hSerial, &chRead, 1, &dwRead, &Overlapped);
                         MessageBox(hwnd, "ok", "oko", MB_OK);
                      }while (dwRead);
                }
   }
if (Result==WAIT_OBJECT_0+1){
                    {

                while (PeekMessage(&messages, NULL, 0, 0, PM_REMOVE))
                    {
if (messages.message == WM_QUIT){
break;
}
TranslateMessage(&messages);
DispatchMessage(&messages);
                }

            }

}

}


....

LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
...
            case ID_BUTTON_CONNECT:
                    OpenSerialPort(hwnd,"COM3");       
                break;
            case ID_BUTTON_SEND:
                    WriteABuffer(hwnd,&a,1);
                    if(bSet == true) ReadFile(hSerial, &chRead, 1, &dwRead, &Overlapped);
                    bSet = false;
                break;
...
}



Ogar

Re:Čtení dat RS232 v C++
« Odpověď #5 kdy: 15. 02. 2014, 13:03:05 »
Ach jo, holt (slovy klasika) 'to je tak, kdyz nekdo necte a rozmrazi' :-(

MSDN dokumentace na webu
Kód: [Vybrat]
HANDLE WINAPI CreateEvent(
  _In_opt_  LPSECURITY_ATTRIBUTES lpEventAttributes,
  _In_      BOOL bManualReset,
  _In_      BOOL bInitialState,
  _In_opt_  LPCTSTR lpName
);

Druhy parametr CreateEvent() je bManualReset, coz znamena, ze si ten Event MUSIS VYCISTIT SAM funkci ResetEvent() !!!!

A nebo samozrejmne ho nastavit na FALSE ...

Ogar

Re:Čtení dat RS232 v C++
« Odpověď #6 kdy: 15. 02. 2014, 13:10:58 »
A jak uz psal Ondrej Novak, cistejsi je opravdu to udelat ve dvou vlaknech:
a) prvni cte, zapisuje, a pres nejaky synchro prostredek dava vedet tomu druhemu
b) druhe vlakno sa jenom a pouze stara o GUI.

Ale holt, jaka otazka, takova odpoved :-)