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 :
LONG CSerialComm:: Init(CString szPortName, DWORD dwBaudRate,
BYTE byParity,BYTE byStopBits, BYTE byByteSize, DWORD timeout)
{
m_hCommPort = ::CreateFile(szPortName,
GENERIC_READ|GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_FLAG_NO_BUFFERING, 0 );
and another on use FILE_FLAG_OVERLAPPED
as the dwFlagsAndAttributes
parameter to catch the CTS or DSR signal changes on serial port:
LONG CSerialComm:: Init2Signal(CString szPortName, DWORD dwBaudRate,
BYTE byParity,BYTE byStopBits,BYTE byByteSize, DWORD timeout)
{
m_hCommPort = ::CreateFile(szPortName,
GENERIC_READ|GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0 );
In the sample code, first I have initialized the serial port on common data transfer mode to communicate with
the hardware:
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:
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:
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:
m_bListenOnSerialPort = TRUE;
ResetEvent(m_hWaitForSerialPortListenTerminate);
m_thListenOnSerialPort = CreateThread( NULL, 0, ListenOnSerialPort, this, 0, &dwThreadId);
and when the stop button is pressed the listening thread will be terminated and the port re-initialized to the common mode:
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:
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)
{
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:
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.