Latency of ReadFile in CE6

I have noticed that the latency time for ReadFile to return characters is up to 4ms after I see characters transmitted at 115,200 baud. I have tried using the following:
SetCommMask(m_hPort, EV_RXCHAR);|
WaitCommEvent(m_hPort, &dwEvent, NULL);|
ReadFile(m_hPort, m_buf, 20, &dwCount, NULL);

ReadFile returns 0 characters unless I insert a Sleep(4) before the read.

Running CE6 v1.8 on a Colibri VF61

Is there a quicker way using DeviceIoControl or reading the UART buffer directly? I am only expecting two characters back.

Hi, I’m the Toradex AI Assistant. The Toradex Support team will follow up with you shortly!

Did you like my answer?

:+1: :-1:


The latency you’re experiencing with the ReadFile function in Windows CE 6.0 on the Colibri VF61 module could be due to the way the serial driver handles the incoming data. Here are a few suggestions to potentially reduce the latency:

  1. Increase the UART Buffer Size: You can try increasing the UART buffer size to ensure that the data is available when you call ReadFile. This can be done using the SetupComm function.

  2. Use Overlapped I/O: Implementing overlapped I/O can help in reducing the latency. This allows the ReadFile operation to be performed asynchronously.

  3. Polling with DeviceIoControl: You can use DeviceIoControl to directly interact with the UART driver. This might give you more control over the timing and handling of the data. However, this approach requires a good understanding of the specific IOCTL codes supported by the UART driver.

  4. Adjusting Timeouts: You can adjust the timeouts for the serial port using the SetCommTimeouts function. Setting the ReadIntervalTimeout, ReadTotalTimeoutMultiplier, and ReadTotalTimeoutConstant to appropriate values might help in reducing the latency.

Here is an example of how you might adjust the timeouts:

COMMTIMEOUTS timeouts;
timeouts.ReadIntervalTimeout = MAXDWORD;
timeouts.ReadTotalTimeoutMultiplier = 0;
timeouts.ReadTotalTimeoutConstant = 0;
SetCommTimeouts(m_hPort, &timeouts);
  1. Direct UART Buffer Access: If the above methods do not yield satisfactory results, you might consider accessing the UART buffer directly. This would involve writing a custom driver or modifying the existing driver to expose the buffer.

Given that you are only expecting two characters back, you might also consider implementing a tight loop with a small sleep interval to poll the UART buffer directly.

If these suggestions do not resolve the issue, you may need to contact Toradex technical support for more specific guidance related to the Colibri VF61 module and its serial driver implementation in Windows CE 6.0.

Please, also check if these links can help you solve your problem:

Reading the Serial Number of an USB Mass Storage Device | Toradex Developer Center.

Is there an example of using DeviceIoControl you can point me to?

"Could you please provide the full source code of your test program? It’s unclear whether you’re using overlapped or non-overlapped I/O, and what the actual value of dwEvent is. Additionally, could you explain why you specified a size of 20 bytes in ReadFile if you’re only expecting two characters? It seems you’re using non-overlapped I/O, and ReadFile is waiting for all 20 bytes to be received. Since only 2 bytes are actually sent, it likely exits after a 4ms timeout.

You might also try using the FILE_FLAG_NO_BUFFERING flag when opening the COM port with the CreateFile function.

Please check this articles for more details - Programming Serial Connections | Microsoft Learn

The Event received is a 0000001 (or RXCHAR) event. I am not using overlapped I/O. I did try using FILE_FLAG_NO_BUFFERING and decreasing the buffer size to 2 character with no improvement seen. I have read timeouts disabled so the ReadFile returns immediately if there are no characters. I will try to create a test program I can send.

It is interesting that it seems to work on CE7, but not CE6.

Here are the important snippets:


#include "stdafx.h"
#include "resource.h"
#include "MCA.h"
#include <math.h>
#include <sysinfo.h>

static DWORD WINAPI MCAProc(void* pmca)
{
  CMCA* p = (CMCA*) pmca;
  p->Service();

  return 0;
}

CMCA::CMCA(TCHAR* szPort)
{
	DWORD			dwError;
	OSVERSIONINFO osvi;

	// What OS version are we running?
	memset(&osvi,0,sizeof(osvi));
	osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
	GetVersionEx(&osvi);
	m_CE_Version = (short) osvi.dwMajorVersion;

	// Define a critical section to prevent spectrum reads while updating.
	InitializeCriticalSection(&m_csSync);

	// Define a critical section to prevent reads while getting Flow.
	InitializeCriticalSection(&m_csRead);

	m_hPort = CreateFile(szPort, 
								GENERIC_READ | GENERIC_WRITE, 
								0, 
								NULL, 
								OPEN_EXISTING, 
								0,
								NULL);

	if (m_hPort == INVALID_HANDLE_VALUE)
	{
		dwError = GetLastError();
		MessageBox(GetForegroundWindow(), TEXT("MCA port failed to initialize."), TEXT("Sabre"), MB_ICONEXCLAMATION | MB_SYSTEMMODAL  | MB_OK);
		return ;
	}

	if ( !EscapeCommFunction(m_hPort, CLRIR) )
		dwError = GetLastError();

	EscapeCommFunction(m_hPort, SETRTS);		// Prevent counts

	DCB dcb;
	dcb.DCBlength = sizeof(DCB);
	GetCommState(m_hPort, &dcb);

	dcb.BaudRate = CBR_115200;
	dcb.fParity = FALSE;
	dcb.fOutxCtsFlow = FALSE;
	dcb.fOutxDsrFlow = FALSE;
	dcb.fDtrControl = DTR_CONTROL_DISABLE;
	dcb.fDsrSensitivity = FALSE;
	dcb.fTXContinueOnXoff = FALSE;
	dcb.fOutX = FALSE;			// Dont use XON/XOFF
	dcb.fInX = FALSE;				// Dont use XON/XOFF
	dcb.fErrorChar = FALSE;
	dcb.fNull = FALSE;
	dcb.fRtsControl = RTS_CONTROL_DISABLE;
	dcb.fAbortOnError = FALSE;
	dcb.fDummy2 = FALSE;
	// dcb.wReserved = 0;
	// dcb.XonLim = 0;
	// dcb.XoffLim = 0;
	dcb.ByteSize = 8;
	dcb.Parity = NOPARITY;
	dcb.StopBits = ONESTOPBIT;
	// dcb.XonChar = 0;
	// dcb.XoffChar = 0;
	// dcb.ErrorChar = 0;
	// dcb.EofChar = 0;
	// dcb.EvtChar = 0;
	// dcb.wReserved1 = 0;

	if (SetCommState(m_hPort, &dcb))
	{
		COMMTIMEOUTS	cto;

		// Set Read timeout values to return immediately on a character.
		cto.ReadIntervalTimeout = MAXDWORD;
		cto.ReadTotalTimeoutMultiplier = 0;
		cto.ReadTotalTimeoutConstant = 0;				// If no character in xxx ms, then return.
		cto.WriteTotalTimeoutMultiplier = 100;
		cto.WriteTotalTimeoutConstant = 500;

		SetCommTimeouts(m_hPort, &cto);
	}
	else
	{
		DWORD dwError = GetLastError();
		MessageBox(GetForegroundWindow(), _T("Error setting I/O Port."), _T("Comm Port"), MB_ICONINFORMATION | MB_SYSTEMMODAL  | MB_OK);
	}

	SetCommMask(m_hPort, EV_CTS|EV_RXCHAR);

	DWORD		dwID;
	m_bShutdown = FALSE;
	m_hThread = CreateThread(NULL, 0, MCAProc, this, 0, &dwID);
}

void CMCA::Service( void )
{
	m_dtLastCountTick = COleDateTime::GetCurrentTime();
	DWORD		dwLastKeepAlive = GetTickCount();
	DWORD		dwCount;
	BYTE		buf[1024], nORcount = 0;
	//static BYTE		nDot = 0;

	SetThreadPriority(this->m_hThread, NORMAL_PRIORITY);
	while ( !m_bShutdown )
	{
		dwCount = 0;
		if ( TryEnterCriticalSection(&m_csRead) )
		{
			ReadFile(m_hPort, buf, 1024, &dwCount, NULL);

			LeaveCriticalSection(&m_csRead);		// Unlock.
		}

		Sleep(10);
	}

	m_hThread = NULL;		// Exiting will shut down the thread.

	return;
}

BOOL CMCA::GetADCCode(short nChan, BOOL bCalibrating, short *pnValue)
{
	BOOL bRes = FALSE;		// Default result is FAILED.
	static short nSkipped = 0;
	static short tries = 0;
	if ( pnValue )
	{
		DWORD		dwCount;
		short		nValue;

		nSkipped = 0;
		if ( !TryEnterCriticalSection(&m_csRead) )
			return bRes;

		SetThreadPriority(this->m_hThread, THREAD_PRIORITY_ABOVE_NORMAL);
		if ( TransmitCommChar(m_hPort, CMD_GET_FLOW_ADC) )
		{
			DWORD dwEvent;
			dwCount = 0;
			SetCommMask(m_hPort, EV_RXCHAR);
			WaitCommEvent(m_hPort, &dwEvent, NULL);
			Sleep(1);
			ReadFile(m_hPort, m_buf, 2, &dwCount, NULL);
			ATLTRACE(_T(" returned %d bytes\n"), dwCount);
		}

		SetThreadPriority(this->m_hThread, NORMAL_PRIORITY);
		LeaveCriticalSection(&m_csRead);
		Sleep(20);

		*pnValue = m_nFlowValue;
	}	
	return bRes;
}


Could you please create and share a Minimal Reproducible Example (preferably as a console app project ) that we can compile and run locally to reproduce and debug your issue?

Here it is,
TestReadFileLatency.zip (22.2 KB)

Thank you for the compilable project, though it doesn’t seem like a Minimal Reproducible Example. If you want to debug the ReadFile issue, it’s not a good approach to involve components like the Watchdog, ADC, etc.

I recommend starting with a very simple project, such as:

  • Loop back COM2 TX to COM1 RX
  • Send 2 characters to COM2
  • Read the data from COM1
  • Measure the delay.

Could you also specify which BSP release you have on your Colibri VF?61?

With Sleep(1) suppressed, above still will make ReadFile() block until second byte arrives. 1ms delay should be enough to receive two bytes @ 115kBps, though Sleep() perhaps (I’m not sure) may sleep some Windows internal process, so that ReadFile on exit from Sleep() still won’t see 2nd byte for some time.
Since EV_RXCHAR fires when 1 or more bytes arrive to RX buffer, after WaitCommEvent() you should check the amount of bytes in the buffer with ClearCommError(). You should call WaitCommEvent() again when only single byte in the buffer. Then your ReadFile(… 2…) should succeed without additional delays.
Since it is blocked IO and you are unconditionally waiting for two bytes, then why not just call ReadFile(… 2…) and check return value? Of course SetCommTimeouts() should define decent timeouts first.

1 Like

Alex,
I am running BSP 1.8.
There is no ADC or Watchdog code in the example (those are simply function names)

As I noted above, this same code works without latency on CE7.

Edward,

Thanks for pointing out the use of ClearCommError(). I will try a loop on that until I see 2 characters and see if it improves the latency. You are correct that WaitCommEvent will return after the first character is received. Even then, ReadFile returns zero characters.

Could you explain the reason for using the WaitCommEvent function? If you’re waiting for a certain number of bytes (two in your case), why not simply use ReadFile(,,2,)? It will wait for 2 bytes to be received and return once they are. You can set up a timeout to handle any loss of communication.

Communications are with a board that continuously transmits characters associated with the output of a multi-channel analyzer. This runs in a thread of it’s own. When I need to send a command to the board, I assert the RTS line (in a different thread) to tell it to suspend sending MCA characters, then send the command. I expect a return of two characters, but if there was already a character in the board’s TX buffer (or the transmission rate is high), additional characters may precede the 2-char response to the command in which case I might receive three or four bytes total (hence the original ReadFile size of 20 bytes). The receive count is then not always 2 and I don’t want to wait in a timeout for characters that may not come. Since receiving the continuous MCA character stream is the highest priority, I want to minimize the time that the RTS signal prevents receiving the MCA characters. Complicating this all is that there are other threads running which take quantum blocks and affect the timing randomly by 10ms or so. In other words, my goal is to minimize the time that RTS is active so I miss as few MCA counts as possible.

So, after WaitCommEvent, perform a ReadFile(,,1,), analyze the received data, and then loop back to WaitCommEvent.

Yes, but ReadFile returns zero bytes after a WaitCommEvent unless I insert a delay. That is the problem I see on CE6 but not on CE7.

Have you checked for the exact error (GetLastError) when ReadFile returns zero bytes? If it’s WAIT_TIMEOUT (0x102) , I would suggest increasing ReadTotalTimeoutMultiplier and/or ReadTotalTimeoutConstant.

As you can see in the code above, read timeouts are disabled. That is why I am using WaitCommEvent. In CE7 ReadFile returns the characters immediately after the WaitCommEvent. In CE6 it does not. What could the difference be?

Have you tried the sample project I sent looping back COM2 to COM1 and using HyperTerminal to send characters? Does ReadFile return any characters immediately after the WaitCommEvent if the Sleep is commented out?

Sorry, but we only debug customer applications if they fall under the Minimal Reproducible Example category. Unfortunately, the project you submitted does not meet that criteria. If you would like me to try your code, please create it following the pattern similar to described in post #9

Ok then, back to my question in the original post, "Is there a quicker way using DeviceIoControl or reading the UART buffer directly? "

Will DeviceIoControl have less latency than ReadFile?