Click here to Skip to main content
15,892,059 members
Please Sign up or sign in to vote.
3.00/5 (1 vote)
See more:
I'm making a small program(dialog based mfc) which has to be capable of send data continuously through the serial port. One part of the program acts like a sever(sending the data) and the other part is the client. The client must to be capable of read the data from the serial port and also has to draw it in the screen(like an oscilloscope).

My question is, how do i do the management of the threads?, I don't know multithreading in mfc and I'm not sure how to make for the program monitors the serial port and the same time show the data in the screen.

Thanks in advance.
Posted

1 solution

How to implement this in detail depends on the amount and rate of data to be send and received and the used protocol (e.g. acknowledgements, triggers).

But using one or more threads for the communication would be the best solution.

A possible implementation:

  • Create a new class to implement the communication (no base class or CObject)
  • Add event handle member vars for stop and I/O detection
  • Add an Open function that opens and configures the serial port in overlapped mode, creates the events and starts the the thread(s)
  • Add a Close function that signals the thread(s) to stop, and closes the serial port and the events
  • Add the worker thread function(s)
  • Add this class as a member to your CWinApp or CMainFrame derived class

Inside the worker thread create an OVERLAPPED struct using the I/O event handle and call SetCommMask() to specifiy the events. Inside the loop call WaitCommEvent() and use WaitForMultipleObjects() to detect the stop and overlapped I/O events. The time out value can be used to perform periodical actions (e.g. sending data).

To pass received data to your output window, send user defined messages (WM_APP+x) from the thread to the output window (the communication class should have a member var with the window's handle). If the data that should be passed would fit in the LPARAM and WPARAM parameters, pass them using these. Otherwise you must provide a special buffer for your data using locking (e.g. a GetData() function that returns a copy of the data and clears the buffer).

The output window must have a handler for the user defined message that updates the screen.

Example snippets (without error checking):
class CCom : public CObject
{
    HANDLE m_hCom; // serial port handle
private:
    CWinThread *m_pThread;
    HANDLE m_hevThread; // thread event handle for overlapped I/O
    CEvent m_evKill; // stop receive thread event
    UINT WorkerThread(void);
    static UINT CommThread(LPVOID pParam);
};

bool CCom::Open(LPCTSTR lpszPort)
{
    m_hCom = ::CreateFile(lpszPort,
        GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
        FILE_FLAG_OVERLAPPED, NULL);
    // Call SetupComm(), SetCommState(), SetCommTimeOuts() if necessary
    m_hevThread = ::CreateEvent(NULL, TRUE, FALSE, NULL);
    // With serial I/O we should use a higher priority
    //  to avoid delayed processing with high system load.
    m_pThread = AfxBeginThread(CommThread, this, 
        THREAD_PRIORITY_ABOVE_NORMAL, 0, CREATE_SUSPENDED);
    m_evKill.ResetEvent();
    m_pThread->m_bAutoDelete = FALSE;
    m_pThread->ResumeThread();
    return true;
}
void CCom::Close()
{
    if (m_pThread)
    {
        if (m_pThread->m_hThread != INVALID_HANDLE_VALUE)
        {
            DWORD dwExitCode;
            ::GetExitCodeThread(m_pThread->m_hThread, &dwExitCode))
            if (dwExitCode == STILL_ACTIVE)
            {
                m_evKill.SetEvent();
                ::WaitForSingleObject(m_pThread->m_hThread, INFINITE);
            }
        }
        delete m_pThread;
        m_pThread = NULL;
    }
    if (m_hCom != INVALID_HANDLE_VALUE)
    {
        ::CloseHandle(m_hCom);
        m_hCom = INVALID_HANDLE_VALUE;
    }
    if (m_hevThread)
    {
        ::CloseHandle(m_hevThread);
        m_hevThread = NULL;
    }
}
// Static wrapper for thread
UINT CCom::CommThread(LPVOID pParam)
{
    CCom* pThis = reinterpret_cast<ccom*>(pParam);
    return pThis->WorkerThread();
}
UINT CCom::WorkerThread(void)
{
    bool bKill = false;
    bool bPending = false;
    DWORD dwEvent = 0;
    HANDLE ahWait[2] = { m_evKill.m_hObject, m_hevThread };
    OVERLAPPED ov = {0};
    ov.hEvent = m_hevThread;
    // Set events to be monitored
    ::SetCommMask(m_hCom, EV_RXCHAR);
    while (!bKill) // until kill signaled or fatal error occurred
    {
        DWORD nEvent = 0;
        DWORD nDone = 0;
        if (!bPending)
        {
            dwEvent = 0; // must clear this before calling WaitCommEvent
            if (::WaitCommEvent(m_hCom, &dwEvent, &ov))
                nEvent = dwEvent; // use copy of event var
            else
            {
                if (::GetLastError() == ERROR_IO_PENDING)
                    bPending = true;
                else
                    break;
            }
        }
        switch (::WaitForMultipleObjects(
            bPending ? 2 : 1,
            ahWait, FALSE,
            bPending ? m_nWaitEventTO : 0))
        {
        case WAIT_OBJECT_0 :
            bKill = true;
            break;
        case WAIT_OBJECT_0 + 1 :
            bPending = false;
            if (::GetOverlappedResult(m_hCom, &ov, &nDone, FALSE))
                nEvent = dwEvent;
            break;
        case WAIT_TIMEOUT :
            // optional periodical action if pending
        break;
        }
        if (nEvent & EV_RXCHAR)
        {
            // Read data from serial port here
        }
        // Post message to main thread/output window here
        // when new data available. 
        //::PostMessage(m_hwndOutput, WM_APP+1, 0, 0);
    }
    ::SetCommMask(m_hCom, 0);
    return 0;
}
 
Share this answer
 

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900