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

Опубликовано lamazavr - вт, 06/04/2013 - 18:31
Body

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

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

В этой статье опишу как работать с 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".

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

Комментарии

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

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

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

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

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

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

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

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

15 Bytes sended. :o)

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

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

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

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

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

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

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

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

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

Скачал ваш код, запустил в 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
" что делать?,заранее благодарю за ответ

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

А как конкретно отсылать и получать информацию с порта на порт. Код скопипастил, а куда название порта-приемника и порта-получателя вписать? В функции для чтения не могу найти имя порта-получателя, а функцию отправки вовсе не найду. Пожалуйста, помогите

Спасибо, очень помогло

А зачем нам argc и argv?

Поклон в ноги автору сей проги! Заработало сразу (разумеется я изменил № ком порта). Скажите пожалуйста, как узнать кол-во доступных байтов для считывания с порта? Также можете ответить мне (ProHacker2006) на киберфоруме - http://www.cyberforum.ru/cpp/thread2147367.html

Спасибо огромное! Программа помогла разобраться в вопросе

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

Этот вопрос задается для того, чтобы выяснить, являетесь ли Вы человеком или представляете из себя автоматическую спам-рассылку.