クロスプラットフォームアプリケーション(Linux組み込みおよび実際の組み込みターゲットを使用)のシリアルポートに問題があります。これは、開発を容易にするためにWindowsでも機能します。これはWindowsの実装についてです。
したがって、シリアルプロトコルの実装は、OSシステムと非OSシステムの混合を対象としており、実装自体には触れません。既存の実装と互換性を持たせたいです。それが妥当な時間内に失敗した場合は、シリアル読み取り用に別のスレッドを作成します。
OK、基本的に実装はシリアルポートを開き、ファイル記述子をIOシステム(epoll
LinuxとWaitForMultipleObjects
Windowsで使用)に登録し、基本的にすべてのハンドルを待って必要なことを実行します。したがって、ハンドルに読み取りの信号が送信されたときに、シリアルポートから読み取りを行います。残念ながら、Windowsでは、読み取りと書き込みのどちらを待機しているかを指定できないため、次の解決策を使用すると思いました。
CreateFile
とFILE_FLAG_OVERLAPPED
SetCommMask
とEV_RXCHAR
OVERLAPPED
手動リセットイベントを使用して構造を作成するWaitCommEvent
上記の構造で呼び出しOVERLAPPED
ます。これは通常、ERROR_IO_PENDING
これが基本的な設定です。待機するファイルハンドルの代わりにイベントハンドルを登録します。ハンドルが通知されたら、次のようにします。
ReadFile
- 成功した場合は、もう一度
ResetEvent
お電話くださいWaitCommEvent
ただし、を指定する場合はFILE_FLAG_OVERLAPPED
、読み取りと書き込みにもオーバーラップIOを使用する必要があるようです。だから、いつでもReadFile
、またはWriteFile
戻ってきたら、とERROR_IO_PENDING
でIOを待つだけだと思いました。でも、私はそれには入らないようです。基本的には機能しているように見えますが、オーバーラップがまだアクティブであるかのように、呼び出しの1つでクラッシュすることがあります(ただし、クラッシュすることはないはずです)。WaitForSingleObject
GetOverlappedResult
ResetEvent
だから、実際の質問。これは私が望むように行うことができますか?一般的なアプローチに問題がありますか、それとも機能するはずですか?それとも、さらに別のスレッドを使用することが唯一の良い解決策ですか?通信はすでに別のスレッドにあるため、少なくとも3つのスレッドになります。
シリアル読み取りに直接関係のない多くのものを含む実際のコードからは削減されていますが、必要なだけコードを投稿しようと思います。
SerialPort::SerialPort(const std::string &filename)
{
fd = INVALID_HANDLE_VALUE;
m_ov = new OVERLAPPED(); // Pointer because header shouldn't include Windows.h.
memset(m_ov, 0, sizeof(OVERLAPPED));
m_waitHandle = m_ov->hEvent = CreateEvent(0, true, 0, 0);
}
SerialPort::~SerialPort(void)
{
Close();
CloseHandle(m_ov->hEvent);
delete m_ov;
}
コンストラクターは別のスレッドで呼び出され、後でOpenを呼び出します。
bool SerialPort::Open(void)
{
if (fd != INVALID_HANDLE_VALUE)
return true;
fd = CreateFile(filename.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
if (fd != INVALID_HANDLE_VALUE) {
DCB dcb;
ZeroMemory(&dcb, sizeof(DCB));
COMMTIMEOUTS timeouts = {0};
timeouts.ReadIntervalTimeout = TimeOut();
timeouts.ReadTotalTimeoutConstant = TimeOut();
timeouts.ReadTotalTimeoutMultiplier = TimeOut() / 5;
if (timeouts.ReadTotalTimeoutMultiplier == 0) {
timeouts.ReadTotalTimeoutMultiplier = 1;
}
if (!SetCommTimeouts(fd, &timeouts)) {
DebugBreak();
}
SetCommMask(fd, EV_RXCHAR);
InitWait();
return true;
}
return false;
}
void SerialPort::InitWait()
{
if (WaitForSingleObject(m_ov->hEvent, 0) == WAIT_OBJECT_0) {
return; // Still signaled
}
DWORD dwEventMask;
if (!WaitCommEvent(fd, &dwEventMask, m_ov)) {
// For testing, I have some prints here for the different cases.
}
}
次に、スレッドはかなり長いチェーンを介して、構造体のメンバーと同じWaitForMultipleObjects
m_を呼び出します。これはループで実行され、リストには他にもいくつかのハンドルがあります。そのため、シリアルポートから排他的に読み取るスレッドがある一般的なソリューションとは異なります。基本的に、ループを制御することはできません。そのため、(内で)適切なタイミングで実行しようとしています。waitHandle
hEvent
OVERLAPPED
WaitCommEvent
InitWait
ハンドルが通知されると、ReadDataメソッドがスレッドによって呼び出されます。
int SerialPort::ReadData(void *buffer, int size)
{
if (fd != INVALID_HANDLE_VALUE) {
// Timeouts are reset here to MAXDWORD/0/0, not sure if necessary.
DWORD dwBytesRead;
OVERLAPPED ovRead = {0};
ovRead.hEvent = CreateEvent(0, true, 0, 0);
if (ReadFile(fd, buffer, size, &dwBytesRead, &ovRead)) {
if (WaitForSingleObject(m_ov->hEvent, 0) == WAIT_OBJECT_0) {
// Only reset if signaled, because we might get here because of a timer.
ResetEvent(m_waitHandle);
InitWait();
}
CloseHandle(ovRead.hEvent);
return dwBytesRead;
} else {
if (GetLastError() == ERROR_IO_PENDING) {
WaitForSingleObject(ovRead.hEvent, INFINITE);
GetOverlappedResult(fd, &ovRead, &dwBytesRead, true);
InitWait();
CloseHandle(ovRead.hEvent);
return dwBytesRead;
}
}
InitWait();
CloseHandle(ovRead.hEvent);
return -1;
} else {
return 0;
}
}
書き込みは、同期せずに次のように実行されます。
int SerialPort::WriteData(const void *buffer, int size)
{
if (fd != INVALID_HANDLE_VALUE) {
DWORD dwBytesWritten;
OVERLAPPED ovWrite = {0};
ovWrite.hEvent = CreateEvent(0, true, 0, 0);
if (!WriteFile(fd, buffer, size, &dwBytesWritten, &ovWrite)) {
if (GetLastError() == ERROR_IO_PENDING) {
WaitForSingleObject(ovWrite.hEvent, INFINITE);
GetOverlappedResult(fd, &ovWrite, &dwBytesWritten, true);
CloseHandle(ovWrite.hEvent);
return dwBytesWritten;
} else {
CloseHandle(ovWrite.hEvent);
return -1;
}
}
CloseHandle(ovWrite.hEvent);
}
return 0;
}
今は動作しているようです。クラッシュはもうありません、少なくとも私はそれらを再現することはできません。ですから、今はうまく機能しているので、私がしていることは正気であるのか、それとも別のことをすべきなのかを尋ねているだけです。