image

Когда-то давно я делал приложение, которое должно было работать из под Windows и Linux и подключаться к плате с STM32 через UART. Данная статья может быть полезна новичкам, которые программируют на С++ (используют компиляторы GCC и MinGW) и которым нужна поддержка COM порта под двумя ОС сразу, и которым лень гуглить и нужен готовый код.

Как реализована поддержка COM порта в разных ОС


В Windows для работы с портом используются средства WinAPI. В Linux системах для работы с устройствами используются специальные файлы. Для того, чтобы определить, в какой ОС мы работаем и какой компилятор используем, в коде используются директивы определения компилятора и ОС (__MINGW32__ и __linux).

Я написал класс ComPort, который позволяет работать с COM портом в синхронном режиме. Класс не поддерживает асинхронную работу с портом. Репозиторий с классом ComPort можно найти здесь. Для подключения в своей проект вам необходимо добавить всего-лишь два файла: xserial.cpp и xserial.hpp.

Пример использования
#include <iostream>
#include "xserial.hpp"
using namespace std;

int main() {
    /* инициализируем доступный COM порт, без проверки бита четности, 
       с 8-мью битами данных и одним стоп битом. */
    const int baudRate = 115200; // скорость порта
    const int dataBits = 8; // длина данных
    xserial::ComPort serial(
        baudRate, 
        xserial::ComPort::COM_PORT_NOPARITY, 
        dataBits, 
        xserial::ComPort::COM_PORT_ONESTOPBIT);

    if (!serial.getStateComPort()) { // Если порт не открылся
        cout << "Error: com port is not open!" << endl;
        return 0;
    }

    // выводим список доступных портов
    serial.printListSerialPorts();

    // получаем текст до символа \n
    cout << "Test getLine()..." << endl;
    serial << "Test 1\n";
    cout << serial.getLine() << endl;

    // проверяем функцию проверки количества принятых байт
    cout << "Test bytesToRead()..." << endl;
    serial.print("Test 2\n");
    int k = serial.bytesToRead();
    cout << "bytes to read = " << k << endl;
    while(k < 6) {
        k = serial.bytesToRead();
    }
    cout << "bytes to read = " << k << endl;

    // проверяем функцию чтения
    char data[512];
    cout << "Test read()..." << endl;
    serial.read(data, 7);
    cout << data << endl;

    // проверяем функцию чтения слова
    serial.print("Bla Bla Bla\n");
    cout << "Test getWord(), print Bla Bla Bla" << endl;
    cout << "word 1: " << serial.getWord() << endl;
    cout << "word 2: " << serial.getWord() << endl;
    cout << "word 3: " << serial.getWord() << endl;

    return 0;
}


В класс так же добавил функцию getListSerialPorts для получения списка доступных COM портов.

Нюансы использования COM порта
Под Windows могут возникнуть проблемы при записи в COM порт, если в качестве переходника USB-UART используется плата Nucleo STM32. Часто проблема возникает после переинициализации порта, порой помогает только перезагрузка ноутбука.

P.S.

Предполагается, что любой, кому нужно «загуглить» код, может теперь просто скачать что-то уже работающее и дальше использовать в своем небольшом проекте, менять под себя, как вздумается.

Комментарии (24)


  1. Wilk
    07.09.2018 21:18
    +11

    Здравствуйте!

    У меня возникло несколько вопросов.

    1. Где, собственно, статья?
    2. Чем Ваше решение лучше чем, например, github.com/wjwwood/serial
    3. Как и чем собирать проект?


    1. ELEKTRO_YAR Автор
      07.09.2018 21:35
      -6

      1.
      2. Не знаю, не видел это решение
      3. Подключаете файлы xserial.cpp и xserial.hpp в свой проект, собираете чем хотите.


      1. Wilk
        07.09.2018 23:04
        +3

        Я, конечно, могу ошибаться, но мне кажется, что не чем хочу: у Вас в коде платформа Windows детектируется исключительно при сборке с использованием MinGW. Что делать тем, кто использует MSVC?


        1. ELEKTRO_YAR Автор
          07.09.2018 23:14
          -4

          Надо научиться читать:
          (используют компиляторы GCC и MinGW)


          1. AntonSazonov
            07.09.2018 23:18
            +4

            MinGW — это частный случай GCC. Т.е. это один и тот же компилятор.


          1. Wilk
            07.09.2018 23:24
            +3

            Прошу простить мне мою невнимательность.

            Я, конечно, не большой поклонник продуктов Microsoft, но MSVC — официальный компилятор под Windows платформу, и является одним из простейших вариантов для начинающего программиста. Не слишком дружелюбно по отношению к новичкам вводить ограничения на используемый компилятор — новички могут не разбираться в сортах.


            1. ELEKTRO_YAR Автор
              07.09.2018 23:35
              -4

              Изначально код создавался под MinGW и GCC, в принципе код для MSVC от MinGW ничем отличаться не будет. Можно добавить проверку MSVC. Просто если человек пользуется под Линуксом gcc, то под виндой он вполне может задействовать mingw


        1. AntonSazonov
          07.09.2018 23:16
          -3

          Что делать тем, кто использует MSVC?

          Подключить файлы к проекту. Что за глупый вопрос?


          1. Wilk
            07.09.2018 23:32

            Здравствуйте!

            Мои знания C++ весьма ограничены, но ветвления в коде идут по двум условиям: MinGW и Linux. Не исключаю, что могут быть какие-то неизвестные мне возможности в компиляторе MSVC (извиняюсь за тавтологию), позволяющие без особых проблем маскировать его под MinGW. Безусловно, есть вариант с использованием -D__MINGW32__, но это не очень хорошо, на мой взгляд: насколько мне известно, идентификаторы, начинающиеся с двойного подчёркивания, зарезервированы для внутренних нужно компиляторов. Формально, объявляя макрос через опцию компилятора, мы нарушаем данную договорённость. Скорее всего, это не приведёт к проблемам. Однако, если проект использует более одной библиотеки, и какая-либо из этих библиотек специфичным образом обрабатывает компилятор MinGW, и эта обработка находится в той части исходных кодов, на которую влияет использование -D__MINGW32__, могут возникнуть определённые трудности.


            1. AntonSazonov
              07.09.2018 23:45
              -4

              насколько мне известно, идентификаторы, начинающиеся с двойного подчёркивания, зарезервированы для внутренних нужно компиляторов

              Ну так MINGW32 это и есть внутренний макрос.


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


              1. Wilk
                07.09.2018 23:52
                +2

                Во-первых, я насчитал одну «библиотеку».

                Во-вторых, на момент публикации в репозитории был вот такой код. В README.md не было указано ничего относительно подключения. А если Вы заглянете в xserial.cpp, то увидите, что проверка платформы производится только по двум символам: __linux и __MINGW__. Соответственно, использовать данную «библиотеку» с компилятором MSVC без правки кода библиотеки невозможно.

                Приведённый выше рассказ был посвящён тому, что если очень захотеть, то попробовать скомпилировать под MSVC можно, но для этого придётся нарушить договорённости относительно имён.


                1. AntonSazonov
                  08.09.2018 00:01
                  -3

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


                  В исходники не заглядывал. По поводу кросплатформенности — упущение автора, согласен.


                  1. Wilk
                    08.09.2018 00:06
                    +2

                    Я говорил о проблемах, которые возникнут, если использовать хак с ручным определением __MINGW__ для подключения данной «библиотеки» при использовании MSVC, и это повлияет на другие используемые библиотеки. По большому счёту — мысленный эксперимент, посвящённый придумыванию маловероятных проблем.


                    1. AntonSazonov
                      08.09.2018 00:23
                      -2

                      Так там и не нужен никакой хак. Либа просто не поддерживает компилятор от MS. Ну или не поддерживала час назад...


            1. ELEKTRO_YAR Автор
              07.09.2018 23:47
              +1

              Я добавил проверку _WIN32, теперь по идее должно нормально собираться под MSVC


    1. staticmain
      09.09.2018 18:46

      Мне кажется, что каждый кто работал с COM-портом так или иначе писал свою обертку: github.com/codemeow/cosmicturtle


      1. Wilk
        09.09.2018 19:09
        +1

        Здравствуйте!

        У меня лапки, поэтому я не писал свою обёртку, а просто использовал сначала обозначенную выше библиотеку serial, а потом решил, что не хочу обрабатывать исключения, и перешёл на QSerialPort, т.к. остальная часть ПО всё равно использует Qt. Часть этого кода, опять же, перешла на использование libmodbus, которая сама выполняет все необходимые действия для работы с последовательным портом. По причине лапок, сам я всё никак не напишу свою принципиально лучшую реализацию Modbus.


  1. ZaEzzz
    07.09.2018 23:02
    +2

    Предполагается, что любой, кому нужно «загуглить» код, может теперь просто скачать что-то уже работающее

    Эм… А раньше это было недоступно?
    Это реклама своего профиля на гитхабе для работодателя?


    1. ELEKTRO_YAR Автор
      07.09.2018 23:13
      -4

      увы, но ни на кого не работаю.


      1. ZaEzzz
        08.09.2018 00:15
        +2

        Дак я как раз об этом: надо обязательно написать что-то на хабре, чтобы можно было показать будущему работодателю лабораторную работу.


        1. ELEKTRO_YAR Автор
          08.09.2018 00:57
          -8

          Мне работодатель не нужен, к счастью. Я сам с удовольствием в ближайшем будущем буду перепоручать работу какому нибудь программисту.


  1. Koyanisqatsi
    08.09.2018 08:26
    +2

    Прочитав заголовок решил, что у меня еще одна интересная статья появится в закладках, но нет.


    1. mapron
      08.09.2018 15:09
      +1

      Я, прочитав заголовок, подумал, что в худшем случае расскажут о QSerialPort от Qt. К сожалению, я был оптимистичен.


  1. kinkard
    09.09.2018 18:43
    +1

    Подскажите пожалуйста чем работа с COM портом от работы с файлом кроме выставления флагов (tcsetattr() на linux и SetCommState(), SetCommTimeouts() на Windows)?

    Ещё замечания касательно обработки ошибок в вашей библиотеке:

    if(!ReadFile(hComPort, data, maxNumBytesRead, &dwBytesRead, NULL)) {
      printf("read error\r\n");
      return 0;
    }

    hComPort = ::open(const_cast<char*>(_comPortName.c_str()), O_RDWR | O_NOCTTY );
    if (hComPort <0) {
      printf("Error opening port\n");
      isOpenPort = false;
      return 
    }

    1. Принтовать из библиотеки не очень хорошая идея, стоило бы бросить исключение или через код возврата функции что-нибудь высунуть.
    2. Раз уж пошла речь о printf как обработке ошибок, стоит использовать GetLastError() + FormatMessage() для Windows и errno + strerror() для linux что бы получить код ошибки и его текстовое представление.

    Так же вы открываете COM порт в блокирующем режиме, что не всегда удобно (например я не хочу блокировать поток выполния при работе с портом).