Интернет-магазин

Просмотр корзины
В корзине:

товаров - 0 шт.



§ 56. Обработка событий COM-порта

Иванов Дмитрий, Декабрь 2006
Статья обновлена 27 Мая 2014

Умы всех начанающих программистов устройств сопряжения всегда будоражит вопрос обработки прерывания от портов. Например, в свое время, у меня желание обрабатывать прерывание LPT порта дошло до маниакального навождения. В конце концов я достиг желаемого, хотя и не был полностью удовлетворен: в Windows XP время обработки этого прерывания от момента его физического возникновения в железе до его получения в пользовательском приложении проходит "целая вечность" (посравнению с DOS, конечно). Все это вызвано огромным колличесвом "программной ваты", сквозь которую информация о прерывании должна к нам пробраться. При этом надо написать такое чудовище в виде драйвера, что у по началу волосы дыбом встают.


К счастью, для COM порта все гораздо проще и события (по сути прерывания) можно легко обрабатывать в пользовательском приложении. Например, вспомним предыдущую статью данного раздела. Для того чтобы мы могли узнать о том, что произошло изменение сигнала на линии CTS или DSR нам приходилось принудительно запускать функцию-обработчик. Наверное, было бы неплохо, чтобы программа сама отслеживала эти события. На практике это можно сделать двумя способами:

  • с помощью таймера (переодический опрос состояния линий). Ход имеет право на жизнь, и например, для LPT порта он частенько и используется, но обладает недостатком - минимальный интервал опроса составляет всего 10-20 мс, а хотелось бы побыстрее
  • использование специальзированных функций-обработчиков для COM порта, которыми мы сейчас и займемся

Эта функция, а именно SetCommMask() указывает стандартному драйверу порта отслеживать определенные события. В нашем приложении мы сможем узнать о каждом случае возникновения события порта.

Давайте модернизируем наше предыдущее приложение, чтобы оно могло автоматически отслеживать манипуляции с ключами. Для этого в прежний проект добавьте копку и назовите ее, например, StartWatch.

Далее создайте обработчик для этой кнопки и назовите его, например, OnStartWatch(). Добавьте в него следующий код:

void CTestCOMDlg::OnStartWatch() 
{
	// TODO: Add your control notification handler code here
	SetCommMask(hFile, EV_CTS|EV_DSR);
	AfxBeginThread(proc1,this);	
}

Теперь каждый раз при нажатии на эту кнопку будет выполняться следующее: сначала с помощью функции SetCommMask() мы указываем драйверу, что бы он отслеживал изменения на линиях CTS и DSR. Далее, по идее, надо бы было поставить вызов функции, которая ожидала бы пока произойдет событие, но так делать не рекомендуется: приложение просто зависнет, т.к. все отведенное процессорное время для нашего приложения будет уходить полностью на ожидание события. Поэтому далее, идет вызов создания программного потока. Первым параметром идет адрес специализированной функции, в контексте которой будет выполняться программный код потока. Второй параметр - дополенительный параметр; в данном случае, я передаю в эту функцию адрес текущего экземпляра диалога, чтобы функция могла обращаться к данным и функциям нашего класса диалога. Это делается потому что proc1() не является членом класса CTestCOMDlg. Давайте теперь объявим эту "загадочную " функцию. Для этого в самом верху файла TestCOMDlg.cpp запишите следующее:

UINT proc1( LPVOID pParam );

Тем самым, мы дали описание для этой функции. Теперь, в этом же файле давайте ее реализуем. Она должна выглядеть так:

UINT proc1(LPVOID pParam)
{
	CTestCOMDlg* CTest = (CTestCOMDlg*)pParam;	
	ULONG lpEvtMask=0;		
	WaitCommEvent(CTest->hFile, &lpEvtMask, NULL);
	CTest->OnRead();
	MessageBox(NULL,"Произошло событие","Info",MB_OK);
	
	return 0;
}

Что здесь происходит? Сначала восстанавливается адрес на класс диалога. Затем вызывается функция WaitCommEvent(), которая останавливает текущий поток, до тех пор, пока не произойдет заказанное драйверу событие. Как только оно происходит, надо бы узнать текущие состояние на линии. Для этого у нас есть готовая функция OnRead(). Но она обьявлена как private. Можно конечно, написать полный аналог OnRead() с атрибутом public, но для наших тестовых целей поступим иначе: просто изменим атрибут на public в описании класса (в файле TestCOMDlg.h).

.....................
// Implementation
protected:
	HICON m_hIcon;
public: //измененный атрибут
	// Generated message map functions
	//{{AFX_MSG(CTestCOMDlg)
	virtual BOOL OnInitDialog();
	afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
	afx_msg void OnPaint();
	afx_msg HCURSOR OnQueryDragIcon();
	afx_msg void OnRead();
	afx_msg void OnStartWatch();
	//}}AFX_MSG
	DECLARE_MESSAGE_MAP()
};

Все. Теперь, запускаем приложение. Убеждаемся в удачности открытия порта. Запускаем детектор событий порта с помощью кнопки StartWatch. Самое интересное: если теперь изменить состояние ключа, то выскочит сообщение образуемое функцией MessageBox() и в Edit-окошках появится значиния линий, получаемые с помощью функции OnRead(). Что бы снова запустить отслеживание событий, опять запускаем функцию OnStartWatch() нажатием соответствующей кнопки.

Следует обратить внимание, что после первой обработки события при последующем запуске OnStartWatch() может возникнуть неприятная ситуация, заключающаяся в том, что ключ Вы не трогали, а событие будет обнаружено. Это связано с явлением "дребезга контактов". При замыкании механического ключа, контакт устанавливается или разрывается не сразу, а после некоторого колличества промежуточных касаний проводников. Драйвер порта при этом зафиксирует несколько событий и при повтором запуске WaitCommEvent() событие уже якобы произошло.



© Иванов Дмитрий
Декабрь 2006
http://www.kernelchip.ru



© KERNELCHIP 2006 - 2023