Работа с 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».

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

 

Похожий код:

Фото аватара
Алексей Петров

Программист, разработчик с 5 летним опытом работы. Учусь на разработчика игр на Unity и разработчика VR&AR реальности (виртуальной реальности). Основные языки программирования: C#, C++.

Оцените автора
Бла, бла код
Добавить комментарий

  1. Виталий

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

    Ответить
  2. lamazavr

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

    Хотя у меня таких проблем не возникало. Видимо со времен dos систему улучшили в этом плане.

    Ответить
  3. Виталий

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

    Ответить
  4. Виталий

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

    P.S. Написал второй комментарий потому что не уверен отправился ли первый.

    Заранее спасибо за ответ.

    Ответить
  5. Дима

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

    Ответить
  6. lamazavr

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

    Ответить
  7. Дмитрий

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

    В с++ я новичёк

    Ответить
    1. lamazavr

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

      Ответить
      1. lamazavr

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

        Ответить
  8. Кто - то по им…

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

    Ответить
  9. Иван

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

    Ответить
  10. Andy

    15 Bytes sended. :o)

    Ответить
  11. Виктор

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

    Ответить
    1. lamazavr

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

      Ответить
  12. Роман

    Заколебался — при отправке висло все напрочь. и дело не в циклах оказалось.
    Просто если не настроить таймауты, то ответа будет ждать очень долго (при этом графический интерфейс , если у вас он есть, повисает напрочь).
    Допустим если как у меня — readFile по таймеру происходит, то надо в конфигурацию порта добавить следующий код:
    //настраиваем таймауты на отсутствие ожидания ответа(иначе ReadFile повесит программу)

    COMMTIMEOUTS CommTimeOuts={0xFFFFFFFF,0,0,0,1500};
    if(!SetCommTimeouts(hSerial, &CommTimeOuts))

    {

    CloseHandle(hSerial);

    hSerial = INVALID_HANDLE_VALUE;

    ShowMessage("Ошибка при записи таймаутов порта");

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

    Ответить
  13. Валерий

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

    Ответить
  14. Nero

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

    Ответить
  15. 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

    " что делать?,заранее благодарю за ответ

    Ответить
  16. Александр

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

    Ответить
    1. lamazavr

      в примере два программных порта соединенных между собой

      https://blablacode.ru/windows/413

      Ответить
  17. Алеша

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

    Ответить
  18. Максим

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

    Ответить
  19. Михаил

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

    Ответить
  20. Михаил

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

    Ответить
    1. lamazavr

      я бы лучше использовал Qt с QSerial, на win api нужно все писать, а в qt готово

      Ответить