Когда-то давно я делал приложение, которое должно было работать из под 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)
ZaEzzz
07.09.2018 23:02+2Предполагается, что любой, кому нужно «загуглить» код, может теперь просто скачать что-то уже работающее
Эм… А раньше это было недоступно?
Это реклама своего профиля на гитхабе для работодателя?ELEKTRO_YAR Автор
07.09.2018 23:13-4увы, но ни на кого не работаю.
ZaEzzz
08.09.2018 00:15+2Дак я как раз об этом: надо обязательно написать что-то на хабре, чтобы можно было показать будущему работодателю лабораторную работу.
ELEKTRO_YAR Автор
08.09.2018 00:57-8Мне работодатель не нужен, к счастью. Я сам с удовольствием в ближайшем будущем буду перепоручать работу какому нибудь программисту.
Koyanisqatsi
08.09.2018 08:26+2Прочитав заголовок решил, что у меня еще одна интересная статья появится в закладках, но нет.
mapron
08.09.2018 15:09+1Я, прочитав заголовок, подумал, что в худшем случае расскажут о QSerialPort от Qt. К сожалению, я был оптимистичен.
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 порт в блокирующем режиме, что не всегда удобно (например я не хочу блокировать поток выполния при работе с портом).
Wilk
Здравствуйте!
У меня возникло несколько вопросов.
ELEKTRO_YAR Автор
1.
2. Не знаю, не видел это решение
3. Подключаете файлы xserial.cpp и xserial.hpp в свой проект, собираете чем хотите.
Wilk
Я, конечно, могу ошибаться, но мне кажется, что не чем хочу: у Вас в коде платформа Windows детектируется исключительно при сборке с использованием MinGW. Что делать тем, кто использует MSVC?
ELEKTRO_YAR Автор
Надо научиться читать:
(используют компиляторы GCC и MinGW)
AntonSazonov
MinGW — это частный случай GCC. Т.е. это один и тот же компилятор.
Wilk
Прошу простить мне мою невнимательность.
Я, конечно, не большой поклонник продуктов Microsoft, но MSVC — официальный компилятор под Windows платформу, и является одним из простейших вариантов для начинающего программиста. Не слишком дружелюбно по отношению к новичкам вводить ограничения на используемый компилятор — новички могут не разбираться в сортах.
ELEKTRO_YAR Автор
Изначально код создавался под MinGW и GCC, в принципе код для MSVC от MinGW ничем отличаться не будет. Можно добавить проверку MSVC. Просто если человек пользуется под Линуксом gcc, то под виндой он вполне может задействовать mingw
AntonSazonov
Подключить файлы к проекту. Что за глупый вопрос?
Wilk
Здравствуйте!
Мои знания C++ весьма ограничены, но ветвления в коде идут по двум условиям: MinGW и Linux. Не исключаю, что могут быть какие-то неизвестные мне возможности в компиляторе MSVC (извиняюсь за тавтологию), позволяющие без особых проблем маскировать его под MinGW. Безусловно, есть вариант с использованием -D__MINGW32__, но это не очень хорошо, на мой взгляд: насколько мне известно, идентификаторы, начинающиеся с двойного подчёркивания, зарезервированы для внутренних нужно компиляторов. Формально, объявляя макрос через опцию компилятора, мы нарушаем данную договорённость. Скорее всего, это не приведёт к проблемам. Однако, если проект использует более одной библиотеки, и какая-либо из этих библиотек специфичным образом обрабатывает компилятор MinGW, и эта обработка находится в той части исходных кодов, на которую влияет использование -D__MINGW32__, могут возникнуть определённые трудности.
AntonSazonov
Ну так MINGW32 это и есть внутренний макрос.
В целом, я не понял что вы хотите донести, что вам непонятно и о каких проблемах с подключением двух библиотек вы говорите.
Wilk
Во-первых, я насчитал одну «библиотеку».
Во-вторых, на момент публикации в репозитории был вот такой код. В README.md не было указано ничего относительно подключения. А если Вы заглянете в xserial.cpp, то увидите, что проверка платформы производится только по двум символам: __linux и __MINGW__. Соответственно, использовать данную «библиотеку» с компилятором MSVC без правки кода библиотеки невозможно.
Приведённый выше рассказ был посвящён тому, что если очень захотеть, то попробовать скомпилировать под MSVC можно, но для этого придётся нарушить договорённости относительно имён.
AntonSazonov
Вы говорили о каких-то проблемах при подключении более одной библиотеки, поэтому я и затронул эту тему. Кстати, это даже не библиотека, если вдаваться в подробности.
В исходники не заглядывал. По поводу кросплатформенности — упущение автора, согласен.
Wilk
Я говорил о проблемах, которые возникнут, если использовать хак с ручным определением __MINGW__ для подключения данной «библиотеки» при использовании MSVC, и это повлияет на другие используемые библиотеки. По большому счёту — мысленный эксперимент, посвящённый придумыванию маловероятных проблем.
AntonSazonov
Так там и не нужен никакой хак. Либа просто не поддерживает компилятор от MS. Ну или не поддерживала час назад...
ELEKTRO_YAR Автор
Я добавил проверку _WIN32, теперь по идее должно нормально собираться под MSVC
staticmain
Мне кажется, что каждый кто работал с COM-портом так или иначе писал свою обертку: github.com/codemeow/cosmicturtle
Wilk
Здравствуйте!
У меня лапки, поэтому я не писал свою обёртку, а просто использовал сначала обозначенную выше библиотеку serial, а потом решил, что не хочу обрабатывать исключения, и перешёл на QSerialPort, т.к. остальная часть ПО всё равно использует Qt. Часть этого кода, опять же, перешла на использование libmodbus, которая сама выполняет все необходимые действия для работы с последовательным портом. По причине лапок, сам я всё никак не напишу свою принципиально лучшую реализацию Modbus.