Работа с COM портом на C++ в Windows

Последовательные порты полюбились разработчикам за их простоту в обслуживании и использовании.

И конечно же писать в консоль терминальной программы это всё хорошо, но хочеться своё приложение, которое по нажатии клавиши на экране выполняет нужные вам действия ;)

В этой статье опишу как работать с com портом на языке Си++.

Решение простое, но почемуто рабочий пример найден был не сразу. За сим сохраняю его тут.

Конечно вы можете использовать кроссплатформенные решения вроде QSerial - библиотеки в составе Qt, я наверное так и сделаю, но в будующем. Сейчас же речь о "чистом" виндовском C++. Писать будем в Visual Studio. У меня 2010, хотя роли это никакой не играет...

Создаём новый консольный Win32 проект.

Инклудим header файлы:

#include <windows.h>
#include <iostream>
using namespace std;

Объявляем обработчик com порта:

HANDLE hSerial;

Я делаю это глобально, чтобы не заморачиваться с указателями при передаче его в функции.

Дальше начинаем формировать функцию main:

int _tmain(int argc, _TCHAR* argv[])
{

Терпеть не могу виндовский стиль программирования. Обозвали всё посвоему и сидят радуются...

Теперь магия объявления строки с именем порта. Дело в том, что char оно преобразовывать само не умеет.

LPCTSTR sPortName = L"COM1";  

Работа с последоавательными портами в Windows проходит как с файлом. Открываем первый ком порт для записи/чтения:

hSerial = ::CreateFile(sPortName,GENERIC_READ | GENERIC_WRITE,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);

Проверяем работоспособность:

if(hSerial==INVALID_HANDLE_VALUE)
{
		if(GetLastError()==ERROR_FILE_NOT_FOUND)
	{
		cout << "serial port does not exist.\n";
	}
	cout << "some other error occurred.\n";
}

Теперь нужно настроить параметры соединения:

DCB dcbSerialParams = {0};
dcbSerialParams.DCBlength=sizeof(dcbSerialParams);
if (!GetCommState(hSerial, &dcbSerialParams))
{
	cout << "getting state error\n";
}
dcbSerialParams.BaudRate=CBR_9600;
dcbSerialParams.ByteSize=8;
dcbSerialParams.StopBits=ONESTOPBIT;
dcbSerialParams.Parity=NOPARITY;
if(!SetCommState(hSerial, &dcbSerialParams))
{
	cout << "error setting serial port state\n";
}

На msdn советуют сначала получить параметры, а затем менять их. Мы ещё только учимся, поэтому делаем как просят.

Теперь объявим строку, которую будем передавать и переменные необходимые для этого:

char data[] = "Hello from C++";  // строка для передачи
DWORD dwSize = sizeof(data);   // размер этой строки
DWORD dwBytesWritten;    // тут будет количество собственно переданных байт

Посылаем строку. Напомню, что пример простейший, поэтому никаких особо проверок я не делаю:

BOOL iRet = WriteFile (hSerial,data,dwSize,&dwBytesWritten,NULL);

Также я решил вывести для контроля размер строки и количество отосланных байт:

cout << dwSize << " Bytes in string. " << dwBytesWritten << " Bytes sended. " << endl;

В конце программы делаем бесконечный цикл чтения данных:

	while(1)
	{
		ReadCOM();
	}
	return 0;
}

Теперь функция чтения:

void ReadCOM()
{
      DWORD iSize;
      char sReceivedChar;
      while (true)
      {
            ReadFile(hSerial, &sReceivedChar, 1, &iSize, 0);  // получаем 1 байт
			if (iSize > 0)   // если что-то принято, выводим
				cout << sReceivedChar;
      }
}

Вот собственно и весь пример.

Я создал виртуальный com порт. И слал из COM1 в COM2:

Демонстрация работы c COM портом на С++

Из нашей программы было отправлено "Hello from C++", а из терминала "hello how2.org.ua".

Скачать пример.

Просмотров:   54404

Комментарии

вт, 05/06/2014 - 18:20
Виталий

Добрый день. Прочитал в этой статье что нужно перед закрытием программы закрыть порт: http://ru.wikibooks.org/wiki/COM-%D0%BF%D0%BE%D1%80%D1%82_%D0%B2_Windows... . Зачем это нужно и чем это чревато?

ср, 05/07/2014 - 21:54

Таким образом вы гарантированно оканчиваете работу с портом. Иначе может возникнуть ситуация, когда при повторном обращении после вашей программы доступ к порту получить нельзя (система считает, что он используется).
Хотя у меня таких проблем не возникало. Видимо со времен dos систему улучшили в этом плане.

вс, 05/25/2014 - 18:20
Виталий

Как реализовать прием и передачу чисел? Я смог это реализовать только с 32 битным int. Но UART передает по 8 байт, и как правило данные 8-битные.

вс, 05/25/2014 - 18:35
Виталий

Добрый день. Подскажите пожалуйста, как правильно передавать 8-ми битные числа через com порт. Я пробовал передавать обычный int, но задавать его размер равным 1, в итоге приходит мусор. Пробовал также uint8_t, но в C++ он реализован очень криво и вместо чисел передаются символы char.
P.S. Написал второй комментарий потому что не уверен отправился ли первый.
Заранее спасибо за ответ.

вт, 12/30/2014 - 13:52
Дима

Извиняюсь,я чайник.Что такое строка DCB dcbSerialParams = {0};?

вс, 01/04/2015 - 10:45

так создается структура для хранения настроек

пн, 10/26/2015 - 23:08
Дмитрий

Здравствуйте мне нужно вместо строки отправить команду "G826" как это сделать?
В с++ я новичёк

вс, 11/01/2015 - 12:54

Я не специалист в g826 но думаю Вам должно быть все равно, заправляете буфер с коммандой и шлете.

сб, 11/14/2015 - 17:01
Дмитрий

спасибо, попробую

сб, 03/26/2016 - 13:39
Кто - то по име...

Здравствуйте, у меня есть некоторые проблемы: COM порт никак не хочет работать. С самого начала, я всё смахивал на то, что я чайник. Но не тут - то было! Я много раз пытался писать код. В итоге, я просто скачал пример.(У меня раньше не было возможностей) И что же? Запускаю программу из примера (не изменяя код) и всё равно не работает! Выводятся только ошибки. Я, извините за выражение, тупой? Или у меня что - то не так? Заранее огромное спасибо!

ср, 08/24/2016 - 14:35
Иван

Спасибо ! Пожалуй единственный код который завелся с пол пинка.

пн, 09/19/2016 - 10:44
Andy

15 Bytes sended. :o)

сб, 09/24/2016 - 23:33
Виктор

А можно ли обойтись без бесконечного цикла, а обрабатывать событие возникновения данных на СОМ порте? Как я полагаю этот цикл затрачивает много ресурсов и что то более серьёзное сделать с этим примером не получится.

чт, 09/29/2016 - 19:04

нужно что-то вроде системного вызова select в linux. честно говоря не в курсе как это в винде сделать

вс, 10/09/2016 - 16:51
Роман

Заколебался - при отправке висло все напрочь. и дело не в циклах оказалось.

Просто если не настроить таймауты, то ответа будет ждать очень долго (при этом графический интерфейс , если у вас он есть, повисает напрочь).

Допустим если как у меня - readFile по таймеру происходит, то надо в конфигурацию порта добавить следующий код:

//настраиваем таймауты на отсутствие ожидания ответа(иначе ReadFile повесит программу)
COMMTIMEOUTS CommTimeOuts={0xFFFFFFFF,0,0,0,1500};

if(!SetCommTimeouts(hSerial, &CommTimeOuts))
{
CloseHandle(hSerial);
hSerial = INVALID_HANDLE_VALUE;
ShowMessage("Ошибка при записи таймаутов порта");
}

Тогда по readFile будут сразу отдаваться байты какие есть без ожидания ответа.

вт, 04/18/2017 - 06:06
Валерий

Ищу пример программирования на форме обмен через компонент SerialPort, желательно в асинхронном режиме.

пт, 04/21/2017 - 22:12
Nero

Спасибо! наконец то нашел)) жалко правда что не ++, но всё же!))

пн, 05/01/2017 - 09:49
Bologot

Скачал ваш код, запустил в Visual studio 2015 вышла ошибка при компляции "
Ошибка LNK1207 несовместимый формат PDB в "C:\Users\Николай\Desktop\com_port_test\Debug\com_port_test.pdb"; удалите и перестройте com_port_test C:\Users\Николай\Desktop\com_port_test\com_port_test\LINK 1
" что делать?,заранее благодарю за ответ

пн, 05/22/2017 - 13:08
Александр

Как настроить совместную работу данноой программы с терминалом порта (как в примере) пишет, что невозможно использовать терминал одновременно с другим ПО

вт, 05/23/2017 - 10:39

в примере два программных порта соединенных между собой
https://blablacode.ru/windows/413

Добавить комментарий

Plain text

  • HTML-теги не обрабатываются и показываются как обычный текст
  • Адреса страниц и электронной почты автоматически преобразуются в ссылки.
  • Строки и параграфы переносятся автоматически.
CAPTCHA
Введи эти символы. Ато роботы одолели!