Reputation: 21
I am writing a little serial bus (RS485) monitor in c++. It should read small data packages up to 32 bytes from the serialport an occasionally writes such a databus to the serial port when the user requests it.
Reading works perfectly. I set up a thread for reading using the SetCommMask(hComm, EV_RXCHAR); during initialization and later ReadFile(hComm, RS485PacketBuffer, sizeof(T_PACKET), &nBytes, NULL) to receive the package using timeouts for getting the inter package gaps.
In the main program I use a simple WriteFile(hComm, packet, packet->length + 5, &nBytes, NULL) to write the package.
This WriteFile seems to hang until there are some bytes received from the bus. Only then the package is sent and the bus devices recognize it and answer properly. Why does WriteFile wait for character receiving?
This is my init and thread code
HANDLE uart_init(char *portname, int baudrate)
{
HANDLE hComm;
BOOL Write_Status, Read_Status;
DCB dcbSerialParams;
COMMTIMEOUTS timeouts = { 0 };
hComm = CreateFile(L"com8", //port name
GENERIC_READ | GENERIC_WRITE, //Read/Write
0, // No Sharing
NULL, // No Security
OPEN_EXISTING, // Open existing port only
0, // Non Overlapped I/O
NULL); // Null for Comm Devices
if (hComm == INVALID_HANDLE_VALUE)
printf("Error in opening serial port\n");
else
printf("opening serial port successful\n");
Write_Status = GetCommState(hComm, &dcbSerialParams); //retreives the current settings
if (Write_Status == FALSE)
{
printf(" Error in GetCommState()\n");
CloseHandle(hComm);
return NULL;
}
dcbSerialParams.BaudRate = baudrate; // Setting BaudRate = 1200
dcbSerialParams.ByteSize = 8; // Setting ByteSize = 8
dcbSerialParams.StopBits = ONESTOPBIT; // Setting StopBits = 1
dcbSerialParams.Parity = NOPARITY; // Setting Parity = None
Write_Status = SetCommState(hComm, &dcbSerialParams); //Configuring the port according to settings in DCB
if (Write_Status == FALSE)
{
printf(" Error! in Setting DCB Structure\n");
CloseHandle(hComm);
return NULL;
}
else
{
printf(" Setting DCB Structure Successful\n");
printf(" Baudrate = %d\n", dcbSerialParams.BaudRate);
printf(" ByteSize = %d\n", dcbSerialParams.ByteSize);
printf(" StopBits = %d\n", dcbSerialParams.StopBits);
printf(" Parity = %d\n\n", dcbSerialParams.Parity);
}
// Set COM port timeout settings
timeouts.ReadIntervalTimeout = 50;
timeouts.ReadTotalTimeoutConstant = 0;
timeouts.ReadTotalTimeoutMultiplier = 0;
timeouts.WriteTotalTimeoutConstant = 0;
timeouts.WriteTotalTimeoutMultiplier = 0;
if (SetCommTimeouts(hComm, &timeouts) == 0)
{
printf("Error setting timeouts\n");
CloseHandle(hComm);
return NULL;
}
Read_Status = SetCommMask(hComm, EV_RXCHAR); //Configure Windows to Monitor the serial device for Character Reception
if (Read_Status == FALSE)
printf(" Error! in Setting CommMask\n\n");
else
printf(" Setting CommMask successfull\n\n");
return hComm;
}
unsigned int __stdcall RS485Receiver(void* data)
{
BOOL status = 0;
DWORD dwEventMask = 0;
DWORD nBytes; // Bytes read by ReadFile()
puts("Serial Thread started");
if (hComm = uart_init("COM8", 1200))
{
printf("Waiting for Data Reception...\n");
status = WaitCommEvent(hComm, &dwEventMask, NULL); //Wait for the character to be received
if (status == FALSE)
{
printf("Error! in Setting WaitCommEvent()\n");
}
else //If WaitCommEvent()==True Read the RXed data using ReadFile();
{
_putch('.'); fflush(stdout);
do
{
//Read_Status = ReadFile(hComm, &TempChar, sizeof(TempChar), &NoBytesRead, NULL);
if ((!ReadFile(hComm, RS485PacketBuffer, sizeof(T_PACKET), &nBytes, NULL)))
{
printf("wrong character");
}
else if (nBytes)
{
RS485PrintPacket(RS485PacketBuffer, RS485PacketBuffer->length + 4, stdout);
}
} while (1);
}
CloseHandle(hComm);//Closing the Serial Port
}
puts("Serial Thread stopped");
return 0;
}
#endif
And this is the write function, which hangs until character receiving:
uint8_t sendPacket(T_PACKET* packet)
{
DWORD nBytes;
//calculate checksum
packet->d[packet->length] = crc8((uint8_t*)packet, packet->length + 4);
// PORTB &= ~(1 << TRANSENABLE); // ToDo: How do I enable transmitter on PC
Sleep(10); // short pause to stabilize bus transceiver
printf("Sende Paket, length = %u\n", packet->length+5);
if (!WriteFile(hComm, packet, packet->length + 5, &nBytes, NULL))
{
printf("Error writing text to RS485 port\n");
return 6;
}
printf("gesendet\n"); fflush(stdout);
if (nBytes != packet->length + 5) return 7;
printf("kein Fehler\n");
// PORTB |= (1 << TRANSENABLE); // ToDo: How do I disable transmitter on PC
return 0;`
}
I already tried several timout settings. These seems to have no effect to the problem. Maybe it is an issue with the USB/RS485 converter. I think as the RS485 is a bus, the serial port will probably see it's own send bytes. Maybe that causes the problem.
Or maybe the blocking receiver thread which is waiting for characters blocks the whole serial port.
As @Codo mentioned, I tried now to use overlapped IO. I have a complete solution now. See the answer below.
Upvotes: 1
Views: 977
Reputation: 21
thanks for all the hints. @Codo's first remark was the solution. I have to use overlapping because Windows blocks also the sender when the receiver is waiting. Stupid, but true. Since overlapping IO is a bit complicated I post my solution her for reference. Now I have to try the same in C#. :-)
HANDLE uart_init(char *portname, int baudrate)
{
HANDLE hComm;
BOOL Result;
DCB dcbSerialParams;
COMMTIMEOUTS timeouts = { 0 };
wchar_t wstr[50];
MultiByteToWideChar(CP_UTF8, 0, portname, -1, wstr, 50);
hComm = CreateFile(wstr, //port name
GENERIC_READ | GENERIC_WRITE, //Read/Write
0, // No Sharing
NULL, // No Security
OPEN_EXISTING, // Open existing port only
FILE_FLAG_OVERLAPPED, // Non Overlapped I/O
NULL); // Null for Comm Devices
if (hComm == INVALID_HANDLE_VALUE)
printf("Error in opening serial port\n");
else
printf("opening serial port successful\n");
Result = GetCommState(hComm, &dcbSerialParams); //retreives the current settings
if (Result == FALSE)
{
printf(" Error in GetCommState()\n");
CloseHandle(hComm);
return NULL;
}
dcbSerialParams.BaudRate = baudrate; // Setting BaudRate = 1200
dcbSerialParams.ByteSize = 8; // Setting ByteSize = 8
dcbSerialParams.StopBits = ONESTOPBIT; // Setting StopBits = 1
dcbSerialParams.Parity = NOPARITY; // Setting Parity = None
dcbSerialParams.fBinary = TRUE; // has to be TRUE in Windows
dcbSerialParams.fParity = FALSE; // No parity
dcbSerialParams.fOutxCtsFlow = FALSE; // No CTS flow control
dcbSerialParams.fOutxDsrFlow = FALSE; // No DSR flow control
dcbSerialParams.fDtrControl = FALSE; // No DTR low control
dcbSerialParams.fDsrSensitivity = FALSE; // Ignore DSR
dcbSerialParams.fOutX = FALSE; // No XON/XOFF flow control
dcbSerialParams.fInX = FALSE; // No XON/XOFF flow control
dcbSerialParams.fErrorChar = FALSE; // do not replace errors
dcbSerialParams.fNull = FALSE; // allow NULL bytes
dcbSerialParams.fRtsControl = RTS_CONTROL_ENABLE; // Enable RTS pin
dcbSerialParams.fAbortOnError = FALSE; // do not stop on error
Result = SetCommState(hComm, &dcbSerialParams); //Configuring the port according to settings in DCB
if (Result == FALSE)
{
printf(" Error! in Setting DCB Structure\n");
CloseHandle(hComm);
return NULL;
}
else
{
printf(" Setting DCB Structure Successful\n");
printf(" Baudrate = %d\n", dcbSerialParams.BaudRate);
printf(" ByteSize = %d\n", dcbSerialParams.ByteSize);
printf(" StopBits = %d\n", dcbSerialParams.StopBits);
printf(" Parity = %d\n\n", dcbSerialParams.Parity);
}
// Set COM port timeout settings
timeouts.ReadIntervalTimeout = 50;
timeouts.ReadTotalTimeoutConstant = 0;
timeouts.ReadTotalTimeoutMultiplier = 0;
timeouts.WriteTotalTimeoutConstant = 0;
timeouts.WriteTotalTimeoutMultiplier = 0;
if (SetCommTimeouts(hComm, &timeouts) == 0)
{
printf("Error setting timeouts\n");
CloseHandle(hComm);
return NULL;
}
Result = SetCommMask(hComm, EV_RXCHAR); //Configure Windows to Monitor the serial device for Character Reception
if (Result == FALSE)
printf(" Error in Setting CommMask\n\n");
else
printf(" Setting CommMask successfull\n\n");
return hComm;
}
unsigned int __stdcall RS485Receiver(void* data)
{
BOOL status = 0;
DWORD dwEventMask = 0;
DWORD nBytes; // Bytes read by ReadFile()
DWORD dwRes;
DWORD dwRead;
BOOL fWaitingOnRead = FALSE;
OVERLAPPED osRead = { 0 };
puts("Serial Thread started");
if (hComm = uart_init((char *)data, 1200))
{
printf("Waiting for Data Reception...\n");
// Create the overlapped event. Must be closed before exiting
// to avoid a handle leak.
osRead.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (osRead.hEvent == NULL)
{
CloseHandle(hComm);//Closing the Serial Port
puts("Serial Thread stopped");
return 0;
}
while (1)
{
if (!fWaitingOnRead)
{
// Issue read operation.
if (!ReadFile(hComm, RS485PacketBuffer, sizeof(T_PACKET), &dwRead, &osRead))
{
if (GetLastError() != ERROR_IO_PENDING) // read not delayed?
printf("wrong character");
else
fWaitingOnRead = TRUE;
}
else {
// read completed immediately
RS485PrintPacket(RS485PacketBuffer, RS485PacketBuffer->length + 4, stdout);
}
}
if (fWaitingOnRead)
{
dwRes = WaitForSingleObject(osRead.hEvent, INFINITE);
switch (dwRes)
{
// Read completed.
case WAIT_OBJECT_0:
if (!GetOverlappedResult(hComm, &osRead, &dwRead, FALSE))
printf("wrong character");
else
// Read completed successfully.
RS485PrintPacket(RS485PacketBuffer, RS485PacketBuffer->length + 4, stdout);
// Reset flag so that another opertion can be issued.
fWaitingOnRead = FALSE;
break;
case WAIT_TIMEOUT:
// 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.
//
// This is a 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(hComm); //Closing the Serial Port
}
puts("Serial Thread stopped");
return 0;
}
/*
* Send a data packet
* use this only for Windows
*/
uint8_t sendPacket(T_PACKET* packet)
{
DWORD nBytes;
uint8_t fRes = 0;
OVERLAPPED osWrite = { 0 };
//calculate checksum
packet->d[packet->length] = crc8((uint8_t*)packet, packet->length + 4);
// PORTB &= ~(1 << TRANSENABLE); // ToDo: How do I enable transmitter on PC
Sleep(10); // short pause to stabilize bus transceiver
osWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (osWrite.hEvent == NULL)
// error creating overlapped event handle
return ERR_COMM_WR;
if (!WriteFile(hComm, packet, packet->length + 5, &nBytes, &osWrite))
{
if (GetLastError() != ERROR_IO_PENDING) // read not delayed?
{
fRes = ERR_COMM_WR;
}
else
{
if (!GetOverlappedResult(hComm, &osWrite, &nBytes, TRUE))
{
fRes = ERR_COMM_WR;
}
else
{
// Read completed successfully.
RS485PrintPacket(packet, packet->length + 4, stdout);
}
}
}
CloseHandle(osWrite.hEvent);
// PORTB |= (1 << TRANSENABLE); // ToDo: How do I disable transmitter on PC
return fRes;
}
Upvotes: 1