#include "stdafx.h"
#include "IRPS.h"

#include <conio.h>
#include "Platform.h"
#include "BKMessageBox.h"

// Проблема: приём с консоли вообще никак не работает, код ещё не написан.
//
// Проблема: приём с ком порта работает только для одиночных символов, пакетная передача не работает
//          потому что происходит потеря байтов.
// Решение:  но с помощью очереди эта проблема решается. только это приводит к полному игнорированию
//          скорости передачи. т.е. потерь данных не будет никогда.
//
// Проблема: прерывания геренируются очень быстро. А должны через достаточно большое время.
// Решение:  С помощью таймера, делается задержка в реальном времени на заданное количество
//          микросекунд (зависит от скорости канала). Но это неправильное решение, т.к.
//          выполнение инструкций делается в пакетном режиме. Поэтому с точки зрения команд -
//          задержка сильно большая. Но прям как в реальном времени


CIRPS::CIRPS()
	: m_bRX_ParityCheck(true)
	, m_bRX_PEV(true)
	, m_hPort(INVALID_HANDLE_VALUE)
	, m_bStopEVTThread(false)
	, m_bStopQRXThread(false)
	, m_bStopCONThread(false)
	, m_bCONTrigger(false)
	, m_hWaitableTimer(nullptr)
{
	m_nMemorySize = 0;              // памяти нет
	InitMemory(BK_PORTSIO_AREA);    // Инициализация модуля памяти
	Configure(static_cast<IRPS_MODE>(g_Config.m_nTTYMode));
	m_hPort = OpenPort(g_Config.m_strMapCOMPort);
	CreateTimer();
	StartThreads();
}

CIRPS::~CIRPS()
{
	StopThreads();
	DeleteTimer();
	ClosePort(m_hPort);
}

BK_DEV_MPI CIRPS::GetBoardModel()
{
	return BK_DEV_MPI::IRPS;
}

// конфигурирование адресов портов и векторов поерывания
// реализовано только внутреннее. задание внешних адресов - игнорируется
void CIRPS::Configure(IRPS_MODE nMode)
{
	uint16_t addr, vec;

	switch (nMode)
	{
		default:
		case IRPS_MODE::MODE_V060:
			addr = 0177560;
			vec = 060;
			break;

		case IRPS_MODE::MODE_V360:
			addr = 0176560;
			vec = 0360;
			break;

		case IRPS_MODE::MODE_V370:
			addr = 0176570;
			vec = 0370;
			break;

		case IRPS_MODE::MODE_SMKC: // спец конфигурация для совместимости с СМК-512
			addr = 0177560;
			vec = 0360;
			break;
	}

	RX_CSR = addr;
	RX_DAT = addr + 2;
	TX_CSR = addr + 4;
	TX_DAT = addr + 6;
	RX_VEC = vec;
	TX_VEC = vec + 4;

	// выставим настройки проверки паритета и чётности/нечётности
	switch (g_Config.m_nTTYParity)
	{
		default:
			g_Config.m_nTTYParity = NOPARITY; // все остальные режимы не обабатываются и

		case NOPARITY: // без паритета
			m_bRX_ParityCheck = false; // паритет не проверять
			m_bRX_PEV = false; // не имеет значения
			break;

		case ODDPARITY: // нечётные биты
			m_bRX_ParityCheck = true; // паритет проверять
			m_bRX_PEV = false; // по нечётным битам
			break;

		case EVENPARITY: // чётные биты
			m_bRX_ParityCheck = true; // паритет проверять
			m_bRX_PEV = true; // по чётным битам
			break;
	}

	CalcDelay();
}

void CIRPS::Reconfigure()
{
	StopThreads();
	ClosePort(m_hPort);
	Reset();
	Configure(static_cast<IRPS_MODE>(g_Config.m_nTTYMode));
	m_hPort = OpenPort(g_Config.m_strMapCOMPort);
	StartThreads();
}

void CIRPS::CalcDelay()
{
	// рассчитаем временные задержки
	double clk_in = 4602000.0;

	if ((g_Config.m_nTTYSpeed == 115200) || (g_Config.m_nTTYSpeed == 38400))
	{
		clk_in *= 2;
	}

	double baud_time;

	switch (g_Config.m_nTTYSpeed)
	{
		case 115200:
		case 57600:
			baud_time = 5.0 / clk_in;
			break;

		case 38400:
		case 19200:
			baud_time = 15.0 / clk_in;
			break;

		default:
		case 9600:
			baud_time = 30.0 / clk_in;
			break;

		case 4800:
			baud_time = 60.0 / clk_in;
			break;

		case 2400:
			baud_time = 120.0 / clk_in;
			break;
	}

	// длина посылки в битах
	int len = g_Config.m_nTTYByteSize + ((g_Config.m_nTTYParity != NOPARITY) ? 1 : 0);
	// добавляем стартовый бит
	len++;

	// добавляем стоповый бит
	if (g_Config.m_nTTYStopBits == ONESTOPBIT)
	{
		len++;
	}
	else if (g_Config.m_nTTYStopBits == TWOSTOPBITS)
	{
		len += 2;
	}

	double datatime = len * baud_time;
	// ещё надо прибавить время на формирование сигналов в портах
	datatime += 32.0 / 4608000.0;
	// задержка для таймера
	m_liDelayMKS.QuadPart = -10LL * int(datatime * 1000000.0 + 0.5);
}

// Эта хрень как-то работает примерно, потому что нет никакого реального времени
void CIRPS::MakeDelay()
{
	SetWaitableTimer(m_hWaitableTimer, &m_liDelayMKS, 0, nullptr, nullptr, FALSE); // однократный
	// ждём срабатывания таймера.
	WaitForSingleObject(m_hWaitableTimer, INFINITE);
}

bool CIRPS::CreateTimer()
{
	m_hWaitableTimer = CreateWaitableTimer(nullptr, FALSE, nullptr); // самосбрасывающийся
	return (m_hWaitableTimer != nullptr);
}

void CIRPS::DeleteTimer()
{
	if (m_hWaitableTimer)
	{
		CancelWaitableTimer(m_hWaitableTimer);
		CloseHandle(m_hWaitableTimer);
	}
}



void CIRPS::OnReset()
{
	m_chTXData = 0;
	m_chRXData = 0;
	m_bTX_Ready = true;
	m_bRX_Ready = false;
	m_bTX_InterruptEnable = m_bTX_InterruptEnable_prev = false;
	m_bRX_InterruptEnable = m_bRX_InterruptEnable_prev = false;
	m_bTX_Loop = false;
	m_bRX_Overflow = false;
	m_bRX_ParityError = false;
	m_bTX_Break = false;
	m_bRX_Break = false;
}

bool CIRPS::GetByteT(uint16_t addr, uint8_t *pValue, int &nTC)
{
	uint16_t w;

	if (GetWordT(addr, &w, nTC))
	{
		if (addr & 1)
		{
			*pValue = HIBYTE(w);
		}
		else
		{
			*pValue = LOBYTE(w);
		}

		return true;
	}

	*pValue = 0;
	return false;
}

bool CIRPS::GetWordT(uint16_t addr, uint16_t *pValue, int &nTC)
{
	addr &= 0177776;
	bool bRet = false;

	if (addr == RX_CSR)
	{
		*pValue = (m_bRX_Ready ? 0200 : 0);

		if (m_bRX_InterruptEnable)
		{
			*pValue |= 0100;
		}

		if (m_bRX_Overflow)
		{
			*pValue |= (1 << 12);
		}

		if (m_bRX_ParityError)
		{
			*pValue |= (1 << 15);
		}

		if (m_bRX_Break)
		{
			*pValue |= 1;
		}

		bRet = true;
	}
	else if (addr == RX_DAT)
	{
		{
			std::lock_guard<std::mutex> lk(m_QRXmutex); // блокируем
			*pValue = m_chRXData;   // получаем текущие данные
			m_bRX_Ready = false;    // говорим, что можно присылать следующие данные
			m_bRX_Overflow = false;
			m_bRX_ParityError = false;
		}
		m_QRXcv.notify_one();   // трогаем поток, который выдаёт следующие данные

		if (m_hPort != INVALID_HANDLE_VALUE)    // эта фигня кажется всё равно не работает
		{
			EscapeCommFunction(m_hPort, SETDTR);
		}
		else
		{
			RXFunc(); // это для приёма данных с консоли
		}

		bRet = true;
	}
	else if (addr == TX_CSR)
	{
		*pValue = (m_bTX_Ready ? 0200 : 0);

		if (m_bTX_InterruptEnable)
		{
			*pValue |= (1 << 6);
		}

		if (m_bTX_Loop)
		{
			*pValue |= (1 << 2);
		}

		if (m_bTX_Break)
		{
			*pValue |= 1;
		}

		bRet = true;
	}
	else if (addr == TX_DAT)
	{
		*pValue = RX_VEC;
		bRet = true;
	}

	if (bRet)
	{
		nTC = 2;
		return true;
	}

	nTC = 0;
	*pValue = 0;
	return false;
}


bool CIRPS::GetByteIndirect(uint16_t addr, uint8_t *pValue)
{
	uint16_t w;

	if (GetWordIndirect(addr, &w))
	{
		if (addr & 1)
		{
			*pValue = HIBYTE(w);
		}
		else
		{
			*pValue = LOBYTE(w);
		}

		return true;
	}

	*pValue = 0;
	return false;
}

// почти копипаста обычной функции, но чтение RX_DAT - просто чтение текущего значения
bool CIRPS::GetWordIndirect(uint16_t addr, uint16_t *pValue)
{
	addr &= 0177776;

	if (addr == RX_CSR)
	{
		*pValue = (m_bRX_Ready ? 0200 : 0);

		if (m_bRX_InterruptEnable)
		{
			*pValue |= 0100;
		}

		if (m_bRX_Overflow)
		{
			*pValue |= (1 << 12);
		}

		if (m_bRX_ParityError)
		{
			*pValue |= (1 << 15);
		}

		if (m_bRX_Break)
		{
			*pValue |= 1;
		}

		return true;
	}
	else if (addr == RX_DAT)
	{
		*pValue = m_chRXData;   // получаем текущие данные
		return true;
	}
	else if (addr == TX_CSR)
	{
		*pValue = (m_bTX_Ready ? 0200 : 0);

		if (m_bTX_InterruptEnable)
		{
			*pValue |= (1 << 6);
		}

		if (m_bTX_Loop)
		{
			*pValue |= (1 << 2);
		}

		if (m_bTX_Break)
		{
			*pValue |= 1;
		}

		return true;
	}
	else if (addr == TX_DAT)
	{
		*pValue = RX_VEC;
		return true;
	}

	*pValue = 0;
	return false;
}


bool CIRPS::SetByteT(uint16_t addr, uint8_t value, int &nTC)
{
	auto w = uint16_t(value);

	if (addr & 1)
	{
		w <<= 8;
	}

	return SetWordT(addr, w, nTC);
}

bool CIRPS::SetWordT(uint16_t addr, uint16_t value, int &nTC)
{
	addr &= 0177776;
	bool bRet = false;

	if (addr == RX_CSR)
	{
		m_bRX_InterruptEnable_prev = m_bRX_InterruptEnable;
		m_bRX_InterruptEnable = !!(value & 0100);

		if (m_bRX_Ready && m_bRX_InterruptEnable && !m_bRX_InterruptEnable_prev)
		{
			g_Platform.GetCPUPtr()->InterruptVIRQ(RX_VEC); // генерируем прерывание
		}

		bRet = true;
	}
	else if (addr == RX_DAT)
	{
		// запись игнорируется
		bRet = true;
	}
	else if (addr == TX_CSR)
	{
		m_bTX_InterruptEnable_prev = m_bTX_InterruptEnable;
		m_bTX_InterruptEnable = !!(value & 0100);
		m_bTX_Loop = !!(value & 4);
		m_bTX_Break = !!(value & 1);

		if (m_bTX_Break && m_hPort != INVALID_HANDLE_VALUE)
		{
			EscapeCommFunction(m_hPort, SETBREAK);
		}

		if (m_bTX_Ready && m_bTX_InterruptEnable && !m_bTX_InterruptEnable_prev)
		{
			g_Platform.GetCPUPtr()->InterruptVIRQ(TX_VEC); // генерируем прерывание
		}

		bRet = true;
	}
	else if (addr == TX_DAT)
	{
		m_bTX_Ready = false;
		m_chTXData = LOBYTE(value);
		TXFunc();
		bRet = true;
	}

	nTC = (bRet) ? 2 : 0;
	return bRet;
}





// получим извне данные для входного регистра ирпс
void CIRPS::ReceiveData(uint8_t data)
{
	if (!m_bTX_Loop) // если установлен бит 2 TX_CSR, то приём извне игнорируется
	{
		_int_ReceiveData(data);
	}
}

void CIRPS::_int_ReceiveData(uint8_t data)
{
	if (m_bRX_Ready) // если пришла посылка, а мы старую не прочитали
	{
		m_bRX_Overflow = true; // установим бит переполнения
	}

	m_chRXData = data;
	{
		std::lock_guard<std::mutex> lk(m_QRXmutex);
		m_bRX_Ready = true;
	}
	// проверка чётности
	uint8_t td = data;
	bool bPar = false;

	for (int i = 0; i < 8; ++i)
	{
		if (td & 1)
		{
			bPar = !bPar;
		}

		td >>= 1;
	}

	m_bRX_ParityError = m_bRX_ParityCheck ? (m_bRX_PEV ^ bPar) : false;

	if (m_bRX_InterruptEnable)
	{
		g_Platform.GetCPUPtr()->InterruptVIRQ(RX_VEC);
	}
}

// поток, для получения текущих данных из очереди
void CIRPS::QRXThreadFunc()
{
	m_mutQRXThreadWork.lock(); // устанавливаем событие работы потока

	for (;;)
	{
		{
			std::unique_lock<std::mutex> lk(m_QRXmutex); // блокируем
			m_QRXcv.wait(lk, [&]()  // ждём события
			{
				return !m_bRX_Ready;
			});

			if (m_bStopQRXThread)   // если надо выходить
			{
				break;  // выходим
			}

			if (!m_RXQueue.empty()) // если очередь не пуста
			{
				m_chRXData = m_RXQueue.front(); // достаём спереди очередной символ
				m_RXQueue.pop(); // удаляем его из очереди
				MakeDelay();
				m_bRX_Ready = true; // ставим флаг, что данные готовы
			}
		}

		// m_bRX_Ready будет true, только если что-то взяли из очереди.
		if (m_bRX_Ready && m_bRX_InterruptEnable) // если данные есть
		{
			g_Platform.GetCPUPtr()->InterruptVIRQ(RX_VEC); // генерируем прерывание
		}
	}

	m_mutQRXThreadWork.unlock();
}


void CIRPS::RXFunc()
{
	if (m_hPort == INVALID_HANDLE_VALUE)
	{
		// TODO: теоретически приём данных из консоли
		m_RXQueue.push(0);
	}
	else
	{
		// приём данных из ком порта
		DWORD feedback;
		DWORD readed;
		uint8_t data;

		for (;;)    // в цикле
		{
			ReadFile(m_hPort, &data, 1, &feedback, &m_ovr); // читаем байт
			WaitForSingleObject(m_ovr.hEvent, INFINITE);    // ждём события
			GetOverlappedResult(m_hPort, &m_ovr, &readed, TRUE);    // проверяем

			if (feedback && feedback == readed) // если что-то прочиталось
			{
				std::lock_guard<std::mutex> lk(m_QRXmutex);
				m_RXQueue.push(data);       // то это в очередь
				// TODO: контроль очереди, на переполнение !!!
			}
			else
			{
				break; // иначе выходим из цикла, нечего читать больше.
			}
		}
	}
}

void CIRPS::TXFunc()
{
	if (m_bTX_Loop)
	{
		_int_ReceiveData(m_chTXData);
	}

	if (m_hPort == INVALID_HANDLE_VALUE)
	{
		{
			std::lock_guard<std::mutex> lk(m_CONmutex);
			m_bCONTrigger = true;
		}
		m_CONcv.notify_one();   // трогаем поток, который выдаёт данные в консоль
	}
	else
	{
		DWORD feedback;
		DWORD written;
		WriteFile(m_hPort, &m_chTXData, 1, &feedback, &m_ovr);
		WaitForSingleObject(m_ovr.hEvent, INFINITE);
		GetOverlappedResult(m_hPort, &m_ovr, &written, TRUE);

		if (feedback && feedback != written)
		{
			TRACE("TX Not Send %c\n", m_chTXData);
		}
	}
}

// поток для вывода данных в консоль
void CIRPS::CONThreadFunc()
{
	m_mutCONThreadWork.lock(); // устанавливаем событие работы потока

	for (;;)
	{
		{
			std::unique_lock<std::mutex> lk(m_CONmutex); // блокируем
			m_CONcv.wait(lk, [&]()  // ждём события
			{
				return m_bCONTrigger;
			});
			m_bCONTrigger = false;
			lk.unlock();

			if (m_bStopCONThread)   // если надо выходить
			{
				break;  // выходим
			}

			// вывод данных в консоль
			COutputWnd *out = g_Platform.GetMainPtr()->GetOutputWndPtr();
			TCHAR ch = (m_chTXData < 32) ? m_chTXData : BKToUNICODE_Char(m_chTXData); // преобразуем кои8 в юникодный символ
			out->SendChar(ch); // выводим в консоль
			MakeDelay();
			m_bTX_Ready = true;
		}

		if (m_bTX_InterruptEnable)
		{
			g_Platform.GetCPUPtr()->InterruptVIRQ(TX_VEC);
		}
	}

	m_mutCONThreadWork.unlock();
}

void CIRPS::EVTThreadFunc()
{
	if (m_hPort == INVALID_HANDLE_VALUE)
	{
		// ничего не надо делать, и поток не нужен
		return;
	}

	memset(&m_ovr, 0, sizeof(OVERLAPPED));
	m_ovr.hEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
	std::lock_guard<std::mutex> lk(m_mutEVTThreadWork); // устанавливаем событие работы потока
	SetCommMask(m_hPort, EV_TXEMPTY | EV_RXCHAR | EV_BREAK | EV_ERR);

	for (;;)
	{
		if (m_bStopEVTThread) // если нужно остановить поток - остановим
		{
			break;
		}

		DWORD dwEventMask = 0;

		if (WaitCommEvent(m_hPort, &dwEventMask, nullptr))
		{
			if (dwEventMask & EV_TXEMPTY)
			{
				MakeDelay();
				// The last character in the output buffer was sent to the serial port device.
				// бит 7 TX_CSR установился в 1, в TX_DAT можно посылать данные
				m_bTX_Ready = true;

				if (m_bTX_InterruptEnable)
				{
					g_Platform.GetCPUPtr()->InterruptVIRQ(TX_VEC);
				}

				TRACE("EV_TXEMPTY\n");
			}

			if (dwEventMask & EV_RXCHAR)
			{
				// A new character was received and placed in the input buffer.
				// бит 7 RX_CSR установился в 1, из RX_DAT можно забирать данные
				TRACE("EV_RXCHAR\n");

				if (m_hPort != INVALID_HANDLE_VALUE)
				{
					EscapeCommFunction(m_hPort, CLRDTR);
				}

				RXFunc();
				// Здесь надо принять всё, что есть в свою очередь.
				// а в другом месте, выдавать из этой очереди всё, пока она не пуста.
				// с той скоростью, с какой может эмулятор. Только это неправильно.
			}

			if (dwEventMask & EV_BREAK)
			{
				// A break was detected on input.
				// бит 0 в RX_CSR установился в 1
				m_bRX_Break = true;
				TRACE("EV_BREAK\n");
			}

			if (dwEventMask & EV_ERR)
			{
				// A line-status error occurred.
				DWORD dwError;
				ClearCommError(m_hPort, &dwError, nullptr);

				if (dwError & CE_BREAK)
				{
					// The hardware detected a break condition.
					// бит 0 в RX_CSR установился в 1
					m_bRX_Break = true;
					TRACE("CE_BREAK\n");
				}

				if (dwError & CE_FRAME)
				{
					// The hardware detected a framing error.
					// ???
					TRACE("CE_FRAME\n");
				}

				if (dwError & CE_OVERRUN)
				{
					// A character-buffer overrun has occurred. The next character is lost.
					// переполнение бит 12 в RX_CSR установился в 1
					m_bRX_Overflow = true;
					TRACE("CE_OVERRUN\n");
				}

				if (dwError & CE_RXOVER)
				{
					// An input buffer overflow has occurred.
					// переполнение бит 12 в RX_CSR установился в 1
					m_bRX_Overflow = true;
					TRACE("CE_RXOVER\n");
				}

				if (dwError & CE_RXPARITY)
				{
					// The hardware detected a parity error.
					// ошибка чётности бит 15 в RX_CSR установился в 1
					m_bRX_ParityError = true;
					TRACE("CE_RXPARITY\n");
				}
			}
		}
		else
		{
			// GetLastErrorOut(_T("COMM"));
			DWORD dwRet = GetLastError();

			if (ERROR_IO_PENDING == dwRet)
			{
				TRACE("I/O is pending...\n");
				// To do.
			}
			else
			{
				TRACE("Wait failed with error %d.\n", dwRet);
			}
		}
	}

	CloseHandle(m_ovr.hEvent);
}

bool CIRPS::StartThreads()
{
	m_QRXthr = std::thread(&CIRPS::QRXThreadFunc, this);
	m_QRXthr.detach();
	m_CONthr = std::thread(&CIRPS::CONThreadFunc, this);
	m_CONthr.detach();
	m_EVTthr = std::thread(&CIRPS::EVTThreadFunc, this);
	m_EVTthr.detach();
	return true;
}

void CIRPS::StopThreads()
{
	if (!m_mutQRXThreadWork.try_lock()) // если поток работает
	{
		// подаём команду на останов потока
		{
			std::lock_guard<std::mutex> lk(m_QRXmutex);
			m_bRX_Ready = false;
			m_bStopQRXThread = true;    // взводим флаг остановки потока
		}
		m_QRXcv.notify_one();
		std::lock_guard<std::mutex> lk(m_mutQRXThreadWork); // ждём завершения потока
	}
	else
	{
		m_mutQRXThreadWork.unlock();
	}

	if (!m_mutCONThreadWork.try_lock()) // если поток работает
	{
		// подаём команду на останов потока
		{
			std::lock_guard<std::mutex> lk(m_CONmutex);
			m_bCONTrigger = true;
			m_bStopCONThread = true;    // взводим флаг остановки потока
		}
		m_CONcv.notify_one();
		std::lock_guard<std::mutex> lk(m_mutCONThreadWork); // ждём завершения потока
	}
	else
	{
		m_mutCONThreadWork.unlock();
	}

	if (!m_mutEVTThreadWork.try_lock()) // если поток работает
	{
		m_bStopEVTThread = true;    // взводим флаг остановки потока
		SetCommMask(m_hPort, 0);    // подаём команду на останов потока, работает только с флагом FILE_FLAG_OVERLAPPED
		std::lock_guard<std::mutex> lk(m_mutEVTThreadWork); // ждём завершения потока
		PurgeComm(m_hPort, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);
		CancelIo(m_hPort);
	}
	else
	{
		m_mutEVTThreadWork.unlock();
	}
}

// открываем нужный нам порт.
// выход - хэндл файла открытого порта
HANDLE CIRPS::OpenPort(CString &strCOM)
{
	// После того, как имя COM порта получено, дальнейшая работа с ним происходит как с файлом.
	// Но для начала нам надо его настроить
	HANDLE Port;   // Дескриптор COM-порта

	if (strCOM == g_ConsoleName) // если мы мапимся в консоль
	{
		Port = INVALID_HANDLE_VALUE; // то хэндл вот такой
		return Port; // и выходим, нечего тут делать
	}

	// Открываем COM-порт
	Port = CreateFile((_T("\\\\.\\") + strCOM).GetString(), // Имя COM-порта
	                  GENERIC_READ | GENERIC_WRITE,
	                  FILE_SHARE_READ,
	                  nullptr,
	                  OPEN_EXISTING,
	                  FILE_FLAG_OVERLAPPED, // FILE_ATTRIBUTE_NORMAL,
	                  nullptr);

	if (Port == INVALID_HANDLE_VALUE)
	{
		g_BKMsgBox.Show(_T("Невозможно открыть последовательный порт."), MB_OK);
		return Port;
	}

	// Получаем состояние управляющей структуры COM-порта,
	// если не удалось выводим сообщение об ошибке и выходим из
	// обработчика
	COMMCONFIG comm;
	memset(&comm, 0, sizeof(COMMCONFIG));
	comm.dwSize = sizeof(COMMCONFIG);
	GetCommState(Port, &comm.dcb);
	// Настраиваем управляющую структуру COM-порта
	comm.dcb.fBinary = 1;
	comm.dcb.fOutxCtsFlow = TRUE;
	comm.dcb.fOutxDsrFlow = TRUE;
	comm.dcb.fDsrSensitivity = TRUE;
	comm.dcb.fDtrControl = g_Config.m_nCOMDtrControl;
	comm.dcb.fRtsControl = g_Config.m_nCOMRtsControl;
	comm.dcb.BaudRate   = g_Config.m_nTTYSpeed;
	comm.dcb.ByteSize   = g_Config.m_nTTYByteSize;  // Размер байта
	comm.dcb.Parity     = g_Config.m_nTTYParity;    // Паритет
	comm.dcb.StopBits   = g_Config.m_nTTYStopBits;  // количество стоповых бита

	// Можно также воспользоваться стандартным диалоговым
	// окном настройки управляющей структуры COM-порта:
	// CommConfigDialog(strCOM.GetString(), nullptr, &comm);

	// Применяем настроенную структуру к COM-порту, если не удалось выводим сообщение об ошибке
	if (!SetCommState(Port, &comm.dcb))
	{
		g_BKMsgBox.Show(_T("Невозможно сконфигурировать последовательный порт."), MB_OK);
		ClosePort(Port);
		return Port;
	}

	// Получаем текущие настройки тайм-аутов COM-порта
	COMMTIMEOUTS commTimeouts;
	GetCommTimeouts(Port, &commTimeouts);
	// Перенастраиваем тайм-ауты:
	// Максимальный интервал чтения в миллисекундах между
	// двумя принимаемыми символами
	commTimeouts.ReadIntervalTimeout = -1;
	// Константа в миллисекундах используемая для вычисления
	// полного тайм-аута операции чтения
	commTimeouts.ReadTotalTimeoutConstant = 0;
	// Множитель используемый для вычисления полного тайм-аута
	// операции чтения в миллисекундах
	commTimeouts.ReadTotalTimeoutMultiplier = 0;
	// Полный максимальный тайм-аут операции чтения
	// вычисляется следующим образом // ReadTotalTimeoutConstant + (ReadIntervalTimeout * количество считываемых байт)

	// Устанавливаем тайм-ауты для COM-порта
	if (!SetCommTimeouts(Port, &commTimeouts))
	{
		g_BKMsgBox.Show(_T("Невозможно настроить тайм-ауты последовательного порта."), MB_OK);
		ClosePort(Port);
		return Port;
	}

	PurgeComm(Port, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);
	SetupComm(Port, 512, 512);
	return Port;
}

void CIRPS::ClosePort(HANDLE &Port)
{
	// И закрываем порт после работы :
	if (Port != INVALID_HANDLE_VALUE)
	{
		// Если порт открыт
		CloseHandle(Port);
		Port = INVALID_HANDLE_VALUE;
	}
}

