我现在有一种解决办法,见下文
首先,我很抱歉再次问一个关于该死的串行通信问题的问题。我在这里读到了一些关于此类问题的威胁,并希望我现在了解这些东西是如何工作的。但看来我错了。
我正在为我的自制 RS485 家庭自动化系统编写一个简单的总线监视器。在控制器端一切正常,但 Windows 部分无法正常工作。
我的总线协议通过 RS484 线路发送最大 260 字节、1200Bd 的数据包。数据包结束由一个小中断(三个字符时间)来报告。跟踪数据包应该很简单:
它适用于小于 32 字节的小数据包。但读文件在 32 字节处结束,所以我读到了一个不完整的数据包。在下一个读取文件中,我得到了数据包的其余部分。我可以将两个部分组装在一起,但为什么它在 32 字节后停止?
** 一种解决方案**
Windows 上似乎无法安全地使用超时设置。特别是 ReadIntervalTimeout 无法按预期工作。无论是否有更多字节到来,它都会在 32 字节后返回。我也无法读取 max 的完整数据块。一块 260 字节。即使我将 ReadTotalTimeoutConstant 设置为很长,WaitForSingleObject 函数也会在读取所需字节数之前返回,并且您必须启动一个新的 ReadFile。
现在的解决方案是,忽略所有这些超时内容,首先读取包含长度字段的四字节标头,然后使用此长度信息读取其余数据。这是“一些程序员老兄”建议的解决方案。
下面的代码可以工作,但由于没有通过超时检测数据包间间隙,如果标头损坏,例如,可能会读取错误的数据量。由于总线冲突或在传输过程中启动程序。
我完全重写了代码,所以现在它是一个完全可用的代码。
/*
* Test program for reading homenet packets from RS485 bus via USB-to-RS485 adapter
*
* Restrictions:
* - program do not use timeouts to detect packets, so it may misinterpret data under heavy bus load
* - in case of transmission errors or bus collisions the packet header may be corrupted and therefor a false length
* information is read. This may lead to misread next packets
*/
#include <stdio.h>
#include <stdint.h>
#include <windows.h>
#include <process.h>
#define BAUDRATE 1200 // RS485 bus soeed
struct HN_PACKET
{
uint8_t dest;
uint8_t src;
uint8_t socket;
uint8_t length;
uint8_t d[256];
} rcvbuffer;
HN_PACKET RS485PacketBuffer;
/*
* dump packet to console
*/
void PrintPacket(HN_PACKET *packet)
{
DWORD adr = 0;
DWORD i;
printf("\nSrc: %u, dest: %u, socket:%u, length: %u:", packet->src, packet->dest, packet->socket, packet->length);
if (!packet->dest)
{
packet->d[packet->length] = 0; // for security put a 0-character at the end of the packet
printf(" DEBUGMSG : %s", packet->d);
}
do
{
printf("\n ");
for (i = 0; i < 16; i++)
{
if (adr + i < packet->length) printf(" %02x", packet->d[adr+i]);
else printf(" ");
}
printf(" ");
for (i = 0; i < 16; i++)
{
if (adr + i < packet->length) putchar(isprint(packet->d[adr+i])?packet->d[adr+i]:'.');
}
adr += 16;
} while (adr < packet->length);
printf("\n");
}
// Opens the specified serial port, configures its timeouts, and sets its
// baud rate. Returns a handle on success, or INVALID_HANDLE_VALUE on failure.
HANDLE uart_init(LPCWSTR szPortName, uint32_t baud_rate)
{
HANDLE hComm;
hComm = CreateFile(szPortName,
GENERIC_READ | GENERIC_WRITE,//access ( read and write)
0, //(share) 0:cannot share the COM port
0, //security (None)
OPEN_EXISTING,// creation : open_existing
FILE_FLAG_OVERLAPPED,// we want overlapped operation
0// no templates file for COM port...
);
if (hComm == INVALID_HANDLE_VALUE) return hComm;
DCB dcb = { 0 };
dcb.DCBlength = sizeof(DCB);
if (!GetCommState(hComm, &dcb))
{
printf("CSerialCommHelper : Failed to Get Comm State Reason: %d\n", GetLastError());
CloseHandle(hComm);
return NULL;
}
dcb.BaudRate = baud_rate;
dcb.ByteSize = 8;
dcb.Parity = NOPARITY;
dcb.StopBits = ONESTOPBIT;
if (!SetCommState(hComm, &dcb))
{
printf("CSerialCommHelper : Failed to Set Comm State Reason: %d\n", GetLastError());
CloseHandle(hComm);
return NULL;
}
SetCommMask(hComm, EV_RXCHAR);
COMMTIMEOUTS timeouts;
timeouts.ReadIntervalTimeout = MAXDWORD;
timeouts.ReadTotalTimeoutMultiplier = MAXDWORD;
timeouts.ReadTotalTimeoutConstant = 7000;
timeouts.WriteTotalTimeoutMultiplier = 0;
timeouts.WriteTotalTimeoutConstant = 0;
if (!SetCommTimeouts(hComm, &timeouts))
{
printf("CSerialCommHelper : Error setting time-outs. %d\n", GetLastError());
CloseHandle(hComm);
return NULL;
}
return hComm;
}
/*
* Serial data receive thread
* Thread is started from uartInit function
*/
unsigned int __stdcall RS485Receiver(void* data)
//int SerialThreadFn(HANDLE m_hCommPort)
{
HANDLE hComm;
DWORD dwBytes; // number of bytes read
DWORD dwRead; // number of bytes read per ReadFile
DWORD dwRes; // result for WaitForSingleObject
BOOL fWaitingOnRead = FALSE; // flag for pending data reception
OVERLAPPED osReader = { 0 }; // overlapped structure
char* buffer = (char*)&RS485PacketBuffer; // pointer to packet buffer
puts("Serial Thread started");
if (hComm = uart_init((LPCWSTR)data, 1200))
{
// Create the overlapped event. Must be closed before exiting
// to avoid a handle leak.
osReader.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (osReader.hEvent == NULL)
{
// Error creating overlapped event; abort.
puts("Error creating overlapped event");
CloseHandle(hComm);
return 1;
}
dwBytes = 0;
while (1)
{
if (!fWaitingOnRead)
{
// Issue read operation.
if (!ReadFile(hComm, (char*)&RS485PacketBuffer + dwBytes, (dwBytes<4) ? 4 : RS485PacketBuffer.length + 1, &dwRead, &osReader))
{
if (GetLastError() != ERROR_IO_PENDING) // read not delayed ?
// Error in communications; report it.
printf("Communication error\n");
else
fWaitingOnRead = TRUE;
}
else
{
// read completed immediately
if (dwRead)
{
dwBytes += dwRead;
if (dwBytes > RS485PacketBuffer.length + 4U)
{
PrintPacket(&RS485PacketBuffer);
dwBytes = 0;
}
}
else
{
// packet timeout, restart reading header
dwBytes = 0;
}
}
}
if (fWaitingOnRead)
{
dwRes = WaitForSingleObject(osReader.hEvent, INFINITE);
switch (dwRes)
{
// Read completed.
case WAIT_OBJECT_0:
if (!GetOverlappedResult(hComm, &osReader, &dwRead, FALSE))
// Error in communications; report it.
printf("Communication error\n");
else
{
if (dwRead)
{
// Read completed successfully.
dwBytes += dwRead;
if (dwBytes > RS485PacketBuffer.length + 4U)
{
PrintPacket(&RS485PacketBuffer);
dwBytes = 0;
}
}
else
{
// packet timeout, restart reading header
dwBytes = 0;
}
}
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.
puts("Waittimeout");
break;
default:
// Error in the WaitForSingleObject; abort.
// This indicates a problem with the OVERLAPPED structure's
// event handle.
puts("Error");
break;
}
}
}
CloseHandle(osReader.hEvent);
CloseHandle(hComm);
// return dwRead;
}
puts("Serial Thread stopped");
return 1;
}
int main()
{
// Choose the serial port name.
LPCWSTR device = L"\\\\.\\COM4";
// Choose the baud rate (bits per second). This does not matter if you are
// connecting to the SMC over USB. If you are connecting via the TX and RX
// lines, this should match the baud rate in the SMC G2's serial settings.
_beginthreadex(0, 0, &RS485Receiver, (void *)device, 0, 0);
puts("Press any key to stop");
getchar();
return 0;
}
我现在有了一个解决方案,基于“一些程序员花花公子”在第3条评论中的回答。我无法将其标记为解决方案,没有显示复选标记。
我在文章中添加了描述。