Serial Comm Using Writefile/Readfile

Serial Comm using WriteFile/ReadFile

You might try some code something like this after you've opened the file, but before you try to use it:

COMMTIMEOUTS timeouts;

timeouts.ReadIntervalTimeout = 1;
timeouts.ReadTotalTimeoutMultiplier = 1;
timeouts.ReadTotalTimeoutConstant = 1;
timeouts.WriteTotalTimeoutMultiplier = 1;
timeouts.WriteTotalTimeoutConstant = 1;
if (!SetCommTimeouts(m_hCommPort, &timeouts))
// setting timeouts failed.

Edit: perhaps it's easier to start with some code that works, and make it do what you want rather than trying to get your code to work. Here's a simple terminal program. It's minimalist in the extreme, but does work (at least well enough to let me see output from my GPS, for one example). It's a long ways from what anybody (least of all me) would call sophisticated, but should give at least some idea of how to get started.

#include <stdio.h>
#include <conio.h>
#include <string.h>

#define STRICT
#define WIN32_LEAN_AND_MEAN
#include <windows.h>

void system_error(char *name) {
// Retrieve, format, and print out a message from the last error. The
// `name' that's passed should be in the form of a present tense noun
// (phrase) such as "opening file".
//
char *ptr = NULL;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM,
0,
GetLastError(),
0,
(char *)&ptr,
1024,
NULL);

fprintf(stderr, "\nError %s: %s\n", name, ptr);
LocalFree(ptr);
}

int main(int argc, char **argv) {

int ch;
char buffer[1];
HANDLE file;
COMMTIMEOUTS timeouts;
DWORD read, written;
DCB port;
HANDLE keyboard = GetStdHandle(STD_INPUT_HANDLE);
HANDLE screen = GetStdHandle(STD_OUTPUT_HANDLE);
DWORD mode;
char port_name[128] = "\\\\.\\COM3";
char init[] = ""; // e.g., "ATZ" to completely reset a modem.

if ( argc > 2 )
sprintf(port_name, "\\\\.\\COM%c", argv[1][0]);

// open the comm port.
file = CreateFile(port_name,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
0,
NULL);

if ( INVALID_HANDLE_VALUE == file) {
system_error("opening file");
return 1;
}

// get the current DCB, and adjust a few bits to our liking.
memset(&port, 0, sizeof(port));
port.DCBlength = sizeof(port);
if ( !GetCommState(file, &port))
system_error("getting comm state");
if (!BuildCommDCB("baud=19200 parity=n data=8 stop=1", &port))
system_error("building comm DCB");
if (!SetCommState(file, &port))
system_error("adjusting port settings");

// set short timeouts on the comm port.
timeouts.ReadIntervalTimeout = 1;
timeouts.ReadTotalTimeoutMultiplier = 1;
timeouts.ReadTotalTimeoutConstant = 1;
timeouts.WriteTotalTimeoutMultiplier = 1;
timeouts.WriteTotalTimeoutConstant = 1;
if (!SetCommTimeouts(file, &timeouts))
system_error("setting port time-outs.");

// set keyboard to raw reading.
if (!GetConsoleMode(keyboard, &mode))
system_error("getting keyboard mode");
mode &= ~ ENABLE_PROCESSED_INPUT;
if (!SetConsoleMode(keyboard, mode))
system_error("setting keyboard mode");

if (!EscapeCommFunction(file, CLRDTR))
system_error("clearing DTR");
Sleep(200);
if (!EscapeCommFunction(file, SETDTR))
system_error("setting DTR");

if ( !WriteFile(file, init, sizeof(init), &written, NULL))
system_error("writing data to port");

if (written != sizeof(init))
system_error("not all data written to port");

// basic terminal loop:
do {
// check for data on port and display it on screen.
ReadFile(file, buffer, sizeof(buffer), &read, NULL);
if ( read )
WriteFile(screen, buffer, read, &written, NULL);

// check for keypress, and write any out the port.
if ( kbhit() ) {
ch = getch();
WriteFile(file, &ch, 1, &written, NULL);
}
// until user hits ctrl-backspace.
} while ( ch != 127);

// close up and go home.
CloseHandle(keyboard);
CloseHandle(file);
return 0;
}

Serial WriteFile returns before completion

Since Windows is not a real-time OS and is a multi-process & multi-thread OS, time accuracy should not be guaranteed.

If the system is lightly loaded, most of it will work as intended, but not always.

Conversely, the completion of WriteFile() may be notified earlier than it actually is, depending on the hardware and device driver stack configuration.

For example, it may be considered that the process is completed at the time when the data is completely stored in the buffer of the device driver or when the last data is written to the FIFO buffer of the interface chip.

It is better to think that WriteFile() may be completed even if not all the data actually reaches the other party.

It is considered the same as writing file data to the hard disk. Completion of writing to a file on disk is done in the system buffer, and should be written to the actual media at a different time.


If the next serial_send() function is called before all the WriteFile data of the previous time has reached the other party due to bad conditions, there is a possibility that some of the previous transmission data will be discarded.

Because PurgeComm(*hPort, PURGE_TXCLEAR); is called at the beginning of the serial_send() function.

It's not as critical as specifying PURGE_TXABORT, but there is still the possibility of data being discarded with PURGE_TXCLEAR.

PurgeComm function

PURGE_TXABORT 0x0001 Terminates all outstanding overlapped write operations and returns immediately, even if the write operations have not been completed.

PURGE_TXCLEAR 0x0004 Clears the output buffer (if the device driver has one).

If a thread uses PurgeComm to flush an output buffer, the deleted characters are not transmitted. To empty the output buffer while ensuring that the contents are transmitted, call the FlushFileBuffers function (a synchronous operation).

The workaround is to simply not call PurgeComm().

If it is a serial port API, it may be possible to wait for the completion of transmission by specifying/detecting EV_TXEMPTY with SetCommMask()/WaitCommEvent(), but this will only be complicated.

SetCommMask function / WaitCommEvent function

EV_TXEMPTY 0x0004 The last character in the output buffer was sent.


Then your usage of WriteFile() + WaitForSingleObject() + GetOverlappedResult() will eventually work similar to calling WriteFile() synchronously.

Asynchronous operation is not always necessary, but it is better to consider in detail based on what kind of behavior your system requires.

Serial port, WriteFile affects ReadFile

It was the cable. Not properly shielded and too long.

Serial port ReadFile receives repeated data

Thanks to the help of @quetzalcoatl, I was able to solve the problem. I have to check if the bytesRead are bigger then 0.

Solution:

int newLineCounter = 0;
DWORD dwCommModemStatus;
unsigned char tmp;
while (!endCurrentRead)
{
ReadFile(hSerial, &tmp, 1, &bytesRead, NULL); // Get new letter
if (bytesRead > 0)
{
if (tmp != '\n')
{
newLineCounter = 0;
if (tmp >= 32 && tmp <= 126)
{
output += tmp; // Add current letter to the output
}
}
else
{
output += tmp;
Print(output);
output = receiverName;
endCurrentRead = true;
}
}
}

WriteFile() and ReadFile() asynchronously not writing or reading

MSDN contains detailed information about how to use overlapped I/O with serial port communications:

Serial Communications

Microsoft Windows API Serial ReadFile Producing Unexpected Output

At least IMO, using overlapped I/O for this job is pretty severe overkill. You could make it work, but it would take a lot of extra effort on your part, and probably accomplish very little.

The big thing with using comm ports under Windows is to set the timeouts to at least halfway meaningful values. When I first did this, I started by setting all of the values to 1, with the expectation that this would sort of work, but probably consume excessive CPU time, so I'd want to experiment with higher values to retain fast enough response, while reducing CPU usage.

So, I wrote some code that just set all the values in the COMMTIMEOUTS structure to 1, and setup the comm port to send/read data.

I've never gotten around to experimenting with longer timeouts to try to reduce CPU usage, because even on the machine I was using when I first wrote this (probably a Pentium II, or thereabouts), it was functional, and consumed too little CPU time to care about--I couldn't really see the difference between the machine completely idle, and this transferring data. There might be circumstances that would justify more work, but at least for any need I've had, it seems to be adequate as it is.



Related Topics



Leave a reply



Submit