СоХабр закрыт.

С 13.05.2019 изменения постов больше не отслеживаются, и новые посты не сохраняются.

| сохранено

H Работа с COM портом в windows и linux в черновиках

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.
Предполагается, что любой, кому нужно «загуглить» код, может теперь просто скачать что-то уже работающее и дальше использовать в своем небольшом проекте, менять под себя, как вздумается.
–30
~2400

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

+12
+13 –1
Wilk ,  
Здравствуйте!

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

  1. Где, собственно, статья?
  2. Чем Ваше решение лучше чем, например, github.com/wjwwood/serial
  3. Как и чем собирать проект?
–6
ELEKTRO_YAR ,   * (был изменён)
1.
2. Не знаю, не видел это решение
3. Подключаете файлы xserial.cpp и xserial.hpp в свой проект, собираете чем хотите.
+4
Wilk ,  
Я, конечно, могу ошибаться, но мне кажется, что не чем хочу: у Вас в коде платформа Windows детектируется исключительно при сборке с использованием MinGW. Что делать тем, кто использует MSVC?
–5
+2 –7
ELEKTRO_YAR ,   * (был изменён)
Надо научиться читать:
(используют компиляторы GCC и MinGW)
+4
AntonSazonov ,  

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

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

Я, конечно, не большой поклонник продуктов Microsoft, но MSVC — официальный компилятор под Windows платформу, и является одним из простейших вариантов для начинающего программиста. Не слишком дружелюбно по отношению к новичкам вводить ограничения на используемый компилятор — новички могут не разбираться в сортах.
–4
+2 –6
ELEKTRO_YAR ,  
Изначально код создавался под MinGW и GCC, в принципе код для MSVC от MinGW ничем отличаться не будет. Можно добавить проверку MSVC. Просто если человек пользуется под Линуксом gcc, то под виндой он вполне может задействовать mingw
–4
AntonSazonov ,  
Что делать тем, кто использует MSVC?

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

0
+1 –1
Wilk ,  
Здравствуйте!

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

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


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

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

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

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

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


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

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

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

0
+3 –3
ELEKTRO_YAR ,  
Я добавил проверку _WIN32, теперь по идее должно нормально собираться под MSVC
0
staticmain ,  
Мне кажется, что каждый кто работал с COM-портом так или иначе писал свою обертку: github.com/codemeow/cosmicturtle
+1
Wilk ,  
Здравствуйте!

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

Эм… А раньше это было недоступно?
Это реклама своего профиля на гитхабе для работодателя?
–4
+2 –6
ELEKTRO_YAR ,  
увы, но ни на кого не работаю.
+2
ZaEzzz ,  
Дак я как раз об этом: надо обязательно написать что-то на хабре, чтобы можно было показать будущему работодателю лабораторную работу.
–9
+2 –11
ELEKTRO_YAR ,  
Мне работодатель не нужен, к счастью. Я сам с удовольствием в ближайшем будущем буду перепоручать работу какому нибудь программисту.
+2
Koyanisqatsi ,  
Прочитав заголовок решил, что у меня еще одна интересная статья появится в закладках, но нет.
+1
mapron ,   * (был изменён)
Я, прочитав заголовок, подумал, что в худшем случае расскажут о QSerialPort от Qt. К сожалению, я был оптимистичен.
+1
kinkard ,  
Подскажите пожалуйста чем работа с 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 порт в блокирующем режиме, что не всегда удобно (например я не хочу блокировать поток выполния при работе с портом).