I tried following Allen Denver's article to implement asynchronous serial port I/O. What isn't mentioned in the article is how or where to call the functions in a main program loop in a useful manner. After putting them in a loop that comes back every second, it was clear there should be no need to call the ReadFile() periodically. I tried putting a WaitCommEvent() in the loop so the read would only occur if characters arrived at the port. That caused a cascade of errors. In trying to simplify the code into something I could post here, even my attempts to open the file in OVERLAPPED mode started failing, so I can't seem to post a useful example. However, I will post something similar with the basic notion that I don't really care if the reads and writes have to be synchronous if I can simply trigger them with an asynchronous event like characters showing up on the serial port. The asynchronous functions from Allen Denver's article are quite involved, and it's not clear to me from his examples how to trigger a read on characters arriving at the port.
In the following code there are five lines commented out in the body of the while(1) loop where I was trying to wait for the characters to arrive. The WaitCommEvent() errors appeared to cite pending I/O even though the port was opened in overlap mode (in the original code), and both the send and receive functions had their own OVERLAPPED structures. What is the proper way to resolve this?
/* WINSIO.c 2020-01-22 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int ConfigureSerialPort(HANDLE hPort)
{
int Status;
DCB dcb = {0};
COMMTIMEOUTS timeouts;
dcb.DCBlength = sizeof(dcb);
Status = GetCommTimeouts(hPort, &timeouts);
GetCommTimeouts(hPort, &timeouts);
timeouts.ReadIntervalTimeout = 50;
timeouts.ReadTotalTimeoutConstant = 50;
timeouts.ReadTotalTimeoutMultiplier = 50;
timeouts.WriteTotalTimeoutConstant = 50;
timeouts.WriteTotalTimeoutMultiplier = 10;
if (!SetCommTimeouts(hPort, &timeouts)) {
printf("Error setting timeouts on port!\n");
}
Status = GetCommState(hPort, &dcb);
dcb.BaudRate = 19200;
dcb.Parity = NOPARITY;
dcb.fBinary = TRUE; // Binary mode; no EOF check
dcb.fParity = FALSE; // Enable parity checking
dcb.fOutxCtsFlow = FALSE; // No CTS output flow control
dcb.fOutxDsrFlow = FALSE; // No DSR output flow control
dcb.fDtrControl = DTR_CONTROL_DISABLE; // DTR flow control type
dcb.fDsrSensitivity = FALSE; // DSR sensitivity
dcb.fTXContinueOnXoff = FALSE; // XOFF continues Tx
dcb.fOutX = FALSE; // No XON/XOFF out flow control
dcb.fInX = FALSE; // No XON/XOFF in flow control
dcb.fErrorChar = FALSE; // Disable error replacement
dcb.fNull = FALSE; // Disable null stripping
dcb.fRtsControl = RTS_CONTROL_DISABLE; // RTS flow control
dcb.fAbortOnError = FALSE; // Do not abort reads/writes on err
dcb.ByteSize = 8; // Number of bits/byte, 4-8
dcb.StopBits = ONESTOPBIT; // 0,1,2 = 1, 1.5, 2
dcb.EvtChar = 0x84; // 'T'
if (!SetCommState (hPort, &dcb)) {
printf("Unable to configure serial port!\n");
}
return 0;
};
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int sendpckt(HANDLE hComm, unsigned length, unsigned char * pckt)
{
unsigned long NbytesWritten = 0;
int result;
DWORD dwCommEvent;
OVERLAPPED oWrite;
oWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (oWrite.hEvent == NULL)
return FALSE; // Error creating overlapped event handle.
result = WriteFile(hComm, pckt, length, &NbytesWritten, NULL);
//result = WriteFile(hComm, pckt, length, &NbytesWritten, &oWrite);
if (!result) printf("Err: %d\n", GetLastError());
WaitCommEvent(hComm, &dwCommEvent, &oWrite);
// printf("Wrote? %d:%d\n", result, NbytesWritten);
CloseHandle(oWrite.hEvent);
return 0;
};
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int recvpckt(HANDLE hComm, unsigned char * pckt)
{
int Status, idx = 0, len = 0;
unsigned long Nbytes;
unsigned char chRead;
OVERLAPPED oRead;
do {
//Status = ReadFile(hComm, &chRead, 1, &Nbytes, &oRead);
Status = ReadFile(hComm, &chRead, 1, &Nbytes, NULL);
if (Status) {
pckt[idx++] = chRead;
if(Nbytes > 0) len = idx;
}
}
while(Nbytes > 0);
return len;
};
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int args(int argc, char * argv[], char * port)
{
static int i;
i = atoi(argv[1]);
sprintf(port, "\\\\.\\COM%d", i);
return i;
};
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void main(int argc, char *argv[]) {
HANDLE hPort;
int result, len, Status;
DWORD dwCommEvent;
OVERLAPPED o;
unsigned idx = 0;
unsigned char TXBUF[] = "T", RXBUF[2048], port[64];
if (argc > 1) result = args(argc, argv, port);
SetConsoleTitle(port); // To specify serial port number on open
hPort = CreateFile (port, GENERIC_READ | GENERIC_WRITE, 0, NULL,
//OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
OPEN_EXISTING, 0, NULL);
ConfigureSerialPort(hPort);
if (hPort == INVALID_HANDLE_VALUE) {
printf("Error in openning serial port\n");
exit(0); // No point in going on if serial port didn't open
}
else
printf("Serial Port Open\n");
Status = SetCommMask(hPort, EV_RXCHAR);
if (Status == FALSE) printf("Error! Setting CommMask\n");
o.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (o.hEvent == NULL) printf("Error creating overlapped event; abort.\n");
while (1) {
sendpckt(hPort, 1, TXBUF);
printf("Sending 0x%.2X\n", TXBUF[0]);
// Status = WaitCommEvent(hPort, &dwCommEvent, &o); // Wait for chars
// if (Status == FALSE)
// printf("Error Setting WaitCommEvent()%d\n", GetLastError());
// else { // If WaitCommEvent() == TRUE then Read the received data
// printf("Chars Recveived\n");
len = recvpckt(hPort, RXBUF);
if (len > 0)
{
printf("RCVD(%d): ", len);
}
for (idx=0; idx<len; idx++)
{
printf("%d:0x%.2X ", idx+1, RXBUF[idx]);
}
// }
SleepEx(1000, TRUE); // How often to look for data in RX buffer
}
CloseHandle(o.hEvent); // Close the Event Handle
CloseHandle(hPort); // Close the serial port
}
Posting additional code after much appreciated feedback from Rita Han. What was clear from the other posts was my Synchronous example didn't get the point across. So I'm attaching the asynchronous version that mostly works, but leaves much to be desired. If it could be simplified to the length of Rita Han's example, that would be fantastic. This example does "function" meaning that it both sends and receives. The problem is, as originally stated, it requires polling the ports periodically for characters present, and that is a super huge drag. The following code is a nearly identical recreation of Allen Denver's code, and it does not provide the event based response I'm looking for, but it does send and receive chars. I am currently running it across a null modem cable with two serial ports, so the behavior is quite easy to observe. The reliance on polling the serial port is clear by changing the timeout value on the SleepEX() statement. As useful as Rita Han's feedback was, the code did not exhibit the behavior I'm looking for. If someone can take the following example, and make it respond to characters arriving at the serial port rather than waiting to get polled, that is what I'm looking for. Do I need to use ReadFileEX()? Is there some other way to register an event that needs to get mixed in here? Joseph M. Newcomer discounts all use of serial events in this article saying they only exist for 16 bit windows compatibility, and then goes on to provide a substantially more convoluted example of how to program a serial port than Allen Denver did. Does it really need to be this difficult?
/* WINAIO.c 2020-01-22 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int ConfigureSerialPort(HANDLE hPort) {
int Status;
DCB dcb = {0};
COMMTIMEOUTS timeouts;
dcb.DCBlength = sizeof(dcb);
Status = GetCommTimeouts(hPort, & timeouts);
GetCommTimeouts(hPort, & timeouts);
timeouts.ReadIntervalTimeout = 50;
timeouts.ReadTotalTimeoutConstant = 50;
timeouts.ReadTotalTimeoutMultiplier = 50;
timeouts.WriteTotalTimeoutConstant = 50;
timeouts.WriteTotalTimeoutMultiplier = 10;
if (!SetCommTimeouts(hPort, & timeouts)) {
printf("Error setting timeouts on port!\n");
}
Status = GetCommState(hPort, & dcb);
dcb.BaudRate = 19200;
dcb.Parity = NOPARITY;
dcb.fBinary = TRUE; // Binary mode; no EOF check
dcb.fParity = FALSE; // Enable parity checking
dcb.fOutxCtsFlow = FALSE; // No CTS output flow control
dcb.fOutxDsrFlow = FALSE; // No DSR output flow control
dcb.fDtrControl = DTR_CONTROL_DISABLE; // DTR flow control type
dcb.fDsrSensitivity = FALSE; // DSR sensitivity
dcb.fTXContinueOnXoff = FALSE; // XOFF continues Tx
dcb.fOutX = FALSE; // No XON/XOFF out flow control
dcb.fInX = FALSE; // No XON/XOFF in flow control
dcb.fErrorChar = FALSE; // Disable error replacement
dcb.fNull = FALSE; // Disable null stripping
dcb.fRtsControl = RTS_CONTROL_DISABLE; // RTS flow control
dcb.fAbortOnError = FALSE; // Do not abort reads/writes on err
dcb.ByteSize = 8; // Number of bits/byte, 4-8
dcb.StopBits = ONESTOPBIT; // 0,1,2 = 1, 1.5, 2
dcb.EvtChar = 0x84; // 'T'
if (!SetCommState(hPort, & dcb)) {
printf("Unable to configure serial port!\n");
}
return 0;
};
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int sendpckt(HANDLE hComm, unsigned length, unsigned char *pckt) {
unsigned long NbytesWritten = 0;
OVERLAPPED ovl = {0};
BOOL fRes;
ovl.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (ovl.hEvent == NULL)
return FALSE; // Error creating overlapped event handle.
//Issue Write.
if (!WriteFile(hComm, pckt, length, & NbytesWritten, & ovl)) {
if (GetLastError() != ERROR_IO_PENDING) {
fRes = FALSE; // WriteFile failed, but isn't delayed. Report error...
} else {
//Write is pending.
if (!GetOverlappedResult(hComm, & ovl, & NbytesWritten, TRUE))
fRes = FALSE;
else
fRes = TRUE;
}
} else // Write operation completed successfully.
fRes = TRUE;
// printf(" 0X%.2X ", chWrite);
CloseHandle(ovl.hEvent);
return fRes;
};
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int recvpckt(HANDLE hComm, unsigned char *pckt) {
# define READ_TIMEOUT 500 // milliseconds
int len = 0;
unsigned long Nbytes;
unsigned char chRead, BUF[2048];
BOOL fWaitingOnRead = FALSE;
OVERLAPPED ovl = {0};
DWORD dwRes;
/*
Status = SetCommMask(hComm, EV_RXFLAG);
if (Status == FALSE) printf("Error! Setting CommMask\n");
// else printf("Setting CommMask Succesful\n");
// */
ovl.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (ovl.hEvent == NULL) printf("Error creating overlapped event; abort.\n");
if (!fWaitingOnRead) {
// Issue read operation
if (!ReadFile(hComm, & BUF, 2048, & Nbytes, & ovl))
//if(!ReadFile(hComm, &chRead, 1, &Nbytes, &ovl))
{
if (GetLastError() != ERROR_IO_PENDING) // read not delayed?
// Error in communications; report it
printf("Error in Communications...\n");
else {
fWaitingOnRead = TRUE;
// printf("l:%d ", len); // shows loop alive
}
} else {
if (Nbytes > 0) {
// read completed immediately
memcpy(pckt, BUF, Nbytes);
len = Nbytes;
printf("Immediate Read Completion\n");
//HandleASuccessfulRead(lpbuf, dwRead);
}
}
}
if (fWaitingOnRead) {
dwRes = WaitForSingleObject(ovl.hEvent, READ_TIMEOUT);
switch (dwRes) {
// Read completed.
case WAIT_OBJECT_0:
if (!GetOverlappedResult(hComm, & ovl, & Nbytes, FALSE))
printf("Error in Communications Lower Portion\n");
// Error in communications; report it.
else {
if (Nbytes > 0) {
// Read completed successfully
// HandleASuccessfulRead(lpbuf, dwRead);
// Will run away and execute here every time
fWaitingOnRead = FALSE;
memcpy(pckt, BUF, Nbytes);
len = Nbytes;
printf("Read Completion After Wait\n");
}
}
case WAIT_TIMEOUT:
//printf("l:%d %d\n", len,rxstate);
// Operation isn't complete yet. fWaitingOnRead flag isn't changed
// since I'll loop back around, and I don't want to issue another read
// until the first one finishes.
// Good time to do some background work.
break;
default:
// Error in the WaitForSingleObject; abort.
// This indicates a problem with the OVERLAPPED structure's event handle
break;
}
}
CloseHandle(ovl.hEvent);
return Nbytes;
};
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int args(int argc, char *argv[], char *port) {
static int i;
i = atoi(argv[1]);
sprintf(port, "\\\\.\\COM%d", i);
return i;
};
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
void main(int argc, char *argv[]) {
HANDLE hPort;
int result, len, Status;
DWORD dwCommEvent;
OVERLAPPED o;
unsigned idx = 0;
unsigned char TXBUF[] = "T", RXBUF[2048], port[64];
if (argc > 1) result = args(argc, argv, port);
SetConsoleTitle(port); // To specify serial port number on open
hPort = CreateFile(port, GENERIC_READ | GENERIC_WRITE, 0, NULL,
OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
//OPEN_EXISTING, 0, NULL);
ConfigureSerialPort(hPort);
if (hPort == INVALID_HANDLE_VALUE) {
printf("Error in openning serial port\n");
exit(0); // No point in going on if serial port didn't open
} else
printf("Serial Port Open\n");
Status = SetCommMask(hPort, EV_RXCHAR);
if (Status == FALSE) printf("Error! Setting CommMask\n");
o.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (o.hEvent == NULL) printf("Error creating overlapped event; abort.\n");
while (1) { // This is the loop for getting work done.
sendpckt(hPort, 1, TXBUF);
printf("Sending 0x%.2X\n", TXBUF[0]);
// Status = WaitCommEvent(hPort, &dwCommEvent, &o); // Wait for chars
// if (Status == FALSE)
// printf("Error Setting WaitCommEvent()%d\n", GetLastError());
// else { // If WaitCommEvent() == TRUE then Read the received data
// printf("Chars Recveived\n");
len = recvpckt(hPort, RXBUF);
if (len > 0) {
printf("RCVD(%d): ", len);
}
for (idx = 0; idx < len; idx++) {
printf("%d:0x%.2X ", idx + 1, RXBUF[idx]);
}
// }
SleepEx(1000, TRUE); // How often to look for data in RX buffer
// And how often to send data to the other port
}
CloseHandle(o.hEvent); // Close the Event Handle
CloseHandle(hPort); // Close the serial port
}
FILE_FLAG_OVERLAPPED
in CreateFile
if you want to write and read asynchronously.lpNumberOfBytesRead
parameter in ReadFile
: Use NULL
for this parameter if this is an asynchronous operation to avoid potentially erroneous results. You can check read length via InternalHigh
of OVERLAPPED.ReadFile
and WriteFile
may result in an error code ERROR_IO_PENDING
, it is not a failure; it designates the write operation is pending completion asynchronously.The following is an example you can refer to:
int sendpckt(HANDLE hComm, unsigned length, unsigned char * pckt)
{
BOOL result;
DWORD dwCommEvent;
OVERLAPPED oWrite = { 0 };
DWORD errCode;
result = SetCommMask(hComm, EV_TXEMPTY);
if (!result) printf("Err: %d\n", GetLastError());
result = WriteFile(hComm, pckt, length, NULL, &oWrite);
if (result == FALSE)
{
errCode = GetLastError();
if (errCode != ERROR_IO_PENDING)
printf("Error! Setting CommMask\n");
}
OVERLAPPED commOverlapped = { 0 };
commOverlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (commOverlapped.hEvent == NULL)
return FALSE; // Error creating overlapped event handle.
assert(commOverlapped.hEvent);
result = WaitCommEvent(hComm, &dwCommEvent, &commOverlapped);
if (!dwCommEvent)
printf("Error Setting WaitCommEvent()%d\n", GetLastError());
else
{
// If WaitCommEvent() == TRUE then Read the received data
if (dwCommEvent & EV_TXEMPTY)
{
printf("Send complete.\n");
}
}
CloseHandle(oWrite.hEvent);
CloseHandle(commOverlapped.hEvent);
return 0;
};
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int recvpckt(HANDLE hComm, unsigned char * pckt)
{
BOOL result;
int len = 0;
OVERLAPPED oRead = { 0 };
DWORD errCode;
DWORD dwCommEvent;
result = SetCommMask(hComm, EV_RXCHAR);
if (!result) printf("Err: %d\n", GetLastError());
result = ReadFile(hComm, pckt, 2048, NULL, &oRead);
if (result == FALSE)
{
errCode = GetLastError();
if (errCode != ERROR_IO_PENDING)
printf("nError! Setting CommMask\n");
}
OVERLAPPED commOverlapped = { 0 };
commOverlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (commOverlapped.hEvent == NULL)
return FALSE; // Error creating overlapped event handle.
assert(commOverlapped.hEvent);
result = WaitCommEvent(hComm, &dwCommEvent, &commOverlapped);
if (!dwCommEvent)
printf("Error Setting WaitCommEvent()%d\n", GetLastError());
else
{
if (dwCommEvent & EV_RXCHAR)
{
printf("Chars Recveived\n");
len = oRead.InternalHigh;
}
}
CloseHandle(oRead.hEvent);
CloseHandle(commOverlapped.hEvent);
return len;
};
void main(int argc, char *argv[]) {
HANDLE hPort;
int len;
unsigned idx = 0;
unsigned char TXBUF[] = "T", RXBUF[2048], port[64] = "COM8";
SetConsoleTitle(port); // To specify serial port number on open
hPort = CreateFile(port, GENERIC_READ | GENERIC_WRITE, 0, NULL,
OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
ConfigureSerialPort(hPort);
if (hPort == INVALID_HANDLE_VALUE) {
printf("Error in openning serial port\n");
exit(0); // No point in going on if serial port didn't open
}
else
printf("Serial Port Open\n");
while (1) {
sendpckt(hPort, 1, TXBUF);
printf("Sending 0x%.2X\n", TXBUF[0]);
len = recvpckt(hPort, RXBUF);
if (len > 0)
{
printf("RCVD(%d): \n", len);
}
for (idx = 0; idx < len; idx++)
{
printf("%d:0x%.2X \n", idx + 1, RXBUF[idx]);
}
SleepEx(1000, TRUE); // How often to look for data in RX buffer
}
CloseHandle(hPort); // Close the serial port
}
Update:
Another way is using WaitForSingleObject
without SetCommMask
and WaitCommEvent
. Take read operation as an example:
int recvpckt(HANDLE hComm, unsigned char * pckt)
{
BOOL result;
int len = 0;
OVERLAPPED oRead = { 0 };
DWORD errCode;
DWORD dwCommEvent;
oRead.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (oRead.hEvent == NULL)
return FALSE; // Error creating overlapped event handle.
assert(oRead.hEvent);
result = ReadFile(hComm, pckt, 2048, NULL, &oRead);
if (result == FALSE)
{
errCode = GetLastError();
if (errCode != ERROR_IO_PENDING)
printf("nError! Setting CommMask\n");
}
DWORD dwWaitResult = WaitForSingleObject(oRead.hEvent, INFINITE);
switch (dwWaitResult)
{
case WAIT_OBJECT_0:
printf("Received.\n");
break;
case WAIT_TIMEOUT:
printf("Timeout.\n");
break;
case WAIT_FAILED:
printf("Failed.\n");
break;
case WAIT_ABANDONED:
printf("Abandoned.\n");
return FALSE;
}
CloseHandle(oRead.hEvent);
return len;
};