Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

High Speed Serial Port Bit Transfer Between Micro-Controller and PC

4.25/5 (5 votes)
22 Aug 2012CPOL3 min read 27.3K   1.6K  
A good way to high speed bit transfer through Serial Port in communication with micro-controllers

Introduction 

RS232 is reliable in industrial applications without any doubt. So everyone who develops an industrial application tends to use serial port to transfer data. But in some applications the 115200 baud rate seems so slow, especially when you have a micro-controller based hardware which transfers the result of the process to a PC in its interrupt service routines. Because it means that approximately 100 micro-seconds is needed for each byte transfer. On the other hand, in some applications you don't need to transfer a byte necessarily from a hardware to PC and just the hardware would like to send a flag (bit) to trigger or change a status in the software. Now you may think that "Is there a fast way to transfer a bit from a hardware to PC through serial port in 10 micro-seconds or less?"

Fortunately, it is possible to do a bit transfer between a micro-controller and PC by this time constraint. If you can deal with some control signals such as CTS (Clear to Send) or DSR (Data Set Ready) both of which are inputs to PC. You will find a way for listening to serial port signals to detect the change of these signal status in a separate thread. 

Background  

I can't trust on C# and its serial port component to use in industrial applications, yet. I always develop such applications on C++. But when you are developing industrial application, you will be working in conjunction of hardware engineers which they are always have some resource limitations such as memory, clock frequency and so on. One of the major limitations in hardware, especially in micro-controllers, is that usually the input signals such as encoder pulses are connected to an interruptible input of micro-controller and when the code enter to an interrupt service routine it should exit as soon as to do not miss the next pulse. So, if the hardware should communicate with PC and announce it about the signal changes, it have not enough time to transfer a perfect byte to PC.  

Using the code  

First, you need to add the CSerialPort class to your C++ project by adding the SerialPort.h and SerialPort.cpp files. In this class, you have two Init() methods. One of them use FILE_FLAG_NO_BUFFERING as the dwFlagsAndAttributes parameter for common data transfer :  

C++
LONG CSerialComm:: Init(CString szPortName, DWORD dwBaudRate, 
       BYTE byParity,BYTE byStopBits, BYTE byByteSize, DWORD timeout)
{
    //open the COM Port
    m_hCommPort = ::CreateFile(szPortName,
                           GENERIC_READ|GENERIC_WRITE, //access ( read and write)
                   0, //(share) 0:cannot share the COM port
                   0, //security  (None)                
                   OPEN_EXISTING, // creation : open_existing
                   FILE_FLAG_NO_BUFFERING, // common send/receive operation
                   0// no templates file for COM port...
                   );

and another on use FILE_FLAG_OVERLAPPED as the dwFlagsAndAttributes parameter to catch the CTS or DSR signal changes on serial port:

C++
LONG CSerialComm:: Init2Signal(CString szPortName, DWORD dwBaudRate, 
       BYTE byParity,BYTE byStopBits,BYTE byByteSize, DWORD timeout)
{

    //open the COM Port
    m_hCommPort = ::CreateFile(szPortName,
    GENERIC_READ|GENERIC_WRITE,//access ( read and write)
       0,    //(share) 0:cannot share the COM port
       0,    //security  (None)                
       OPEN_EXISTING,// creation : open_existing
       FILE_FLAG_OVERLAPPED,// we want overlapped operation
       0// no templates file for COM port...
       );

In the sample code, first I have initialized the serial port on common data transfer mode to communicate with the hardware:

C++
LONG lError = m_hSerialPort.Init(m_strSerialPortName, 115200, 0, 1, 8, 100); 
if(lError == NO_ERROR)
    return TRUE;
else
    return FALSE;

This communication may consist of getting a hardware acknowledge and setting its parameters which are dependent on your application: 

C++
BYTE aData[1];
aData[0] = COMMAND_HARDWARE_ACKNOWLEDGE;
m_hSerialPort.Write(aData, 1);
BYTE btResponse = 0;
m_hSerialPort.Read(btResponse);
if(btResponse != COMMAND_HARDWARE_ACKNOWLEDGE)
    m_strStatus = _T("Not Connected");
else
    m_strStatus = _T("Connected");
m_hSerialPort.UnInit();
UpdateData(FALSE);

But when you would like to start the signal capture procedure, the serial port will be initiated on catching the serial port signal changes mode:

C++
pDlg->m_hSerialPort.UnInit();
LONG lError = pDlg->m_hSerialPort.Init2Signal(pDlg->m_strSerialPortName, 115200, 0, 1, 8, 100);
if(lError != NO_ERROR)
{
  pDlg->SetDlgItemTextW(IDC_EDIT_STATUS, 
     _T("Can not Init Serial Port in signaling mode!"));
  return 1;
}

Truly, a thread will be started for listening on serial port signal changes:

C++
m_bListenOnSerialPort = TRUE;
ResetEvent(m_hWaitForSerialPortListenTerminate);
m_thListenOnSerialPort = CreateThread( NULL,    // default security attributes
                    0,      // use default stack size  
                    ListenOnSerialPort,     // thread function 
                    this,    // argument to thread function 
                    0,    // use default creation flags 
                    &dwThreadId);    // returns the thread identifier

and when the stop button is pressed the listening thread will be terminated and the port re-initialized to the common mode:

C++
m_bListenOnSerialPort = FALSE;
DWORD dwResult = WaitForSingleObject(m_hWaitForSerialPortListenTerminate, 5000);
if (dwResult != WAIT_OBJECT_0)
{
    m_hSerialPort.UnInit();
    LONG lError = m_hSerialPort.Init(m_strSerialPortName, 115200, 0, 1, 8, 100);
    if(lError != NO_ERROR)
    SetDlgItemTextW(IDC_EDIT_STATUS, _T("Can not Init Serial Port in usual mode!"));
}

But, in listening mode, there are two key points in the code: first, the waiting object which keeps the thread till the signal status changes:

C++
DWORD dwRet = GetLastError();
if( ERROR_IO_PENDING == dwRet)
{
    WaitForSingleObject(overlapped.hEvent, INFINITE);
    if (dwEvtMask & EV_DSR) 
    {
         DWORD lpModemStat = 0;
         ::GetCommModemStatus(m_hCommPort, &lpModemStat);

        if(lpModemStat == MS_DSR_ON)
            btResult = btResult | DSR_ON;
        else
            btResult = btResult | DSR_OFF;
    }

    if (dwEvtMask & EV_CTS) 
    {
        // To do. 
        DWORD lpModemStat = 0;
        ::GetCommModemStatus(m_hCommPort, &lpModemStat);
        if(lpModemStat == MS_CTS_ON)
            btResult = btResult | CTS_ON;
        else
            btResult = btResult | CTS_OFF;
    }

}

and second the job after any signal changes have occurred:

C++
BYTE bStatus = pDlg->m_hSerialPort.GetPortStatus();
CString msg = _T(" ");
if((bStatus & CTS_OFF) > 0)
{
    if(bCtsStatus == TRUE)
    {
        msg += _T("CTS switched off");
        bCtsStatus = FALSE;
    }
}
if ((bStatus & CTS_ON) > 0)
{
    if(bCtsStatus == FALSE)
    {
        msg += _T("CTS switched on");
        bCtsStatus = TRUE;
    }
}
if ((bStatus & DSR_OFF) > 0)
{
    if(bDsrStatus == TRUE)
    {
        msg += _T("DSR switched off");
        bDsrStatus = FALSE;
    }
}
if ((bStatus & DSR_ON) > 0)
{
    if(bDsrStatus == FALSE)
    {
        msg += _T("DSR switched on");
        bDsrStatus = TRUE;
    }
}

You can change the code of this job to do anything your application needs.

Points of Interest

You can deal with your hardware men if you can understand their limitations and make the true decision to find the hardware and software details. Thanks to Reza Mesbah, my colleague in Kavosh company as the hardware man.

License

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