LinuxForum.Fans
Тема

Работа с последовательным портом

Автор: sklimkin 36 сообщений 18444 просмотров
#184101

Я, наоборот, при работе с COM-портом пользуюсь только open, read, write
и при работе с периферией лучше использовать именно их, чтобы иметь
полный контроль над устройством.

IDE тоже предпочитаю не использовать, в частности, и когда-то в Windows
и позже в Linux в Qt я программы составлял в текстовом редакторе и
запускал компиляцию из командной строки.

Пётр.

#184115

Какая у Вас версия Убунты? Дело в том, что C::B 10.05 находится в уже готовом виде в репозиториях Убунты, начиная с версии 10.04. Инсталлируется на ура!

У меня Ubuntu Linux 10.04 kernel 2.6.32 и 2.6.33
Эта проблема не только у меня, вот почитайте:

http://forums.codeblocks.org/index.php/topic,14574.0.html
Cannot get 10.05 from Ubuntu repository
« on: April 24, 2011, 06:22:17 am
I am trying to run Code::Blocks on two different machines, A and B. Both are using Ubuntu Linux 10.10 fully updated. Machine B is new, and I put Code::Blocks 10.05 on it from the Ubuntu repository with no problem. Machine A is older, and I put Code::Blocks 8.02 on it over a year ago. Now I would like to update it to 10.05, which I would think I would do by installing 10.05 from the repository and then removing 8.02. But when I look at the repository with the Synaptic Package Manager, it says that the most recent version of CodeBlocks is 8.02! How can it be that two machines running the same operating system find two different versions of CodeBlocks as the latest version? Or, more to the point, how can I get Machine B to find CodeBlocks 10.05 on the Ubuntu repository?

Хотел поставить 10.02 с оф.сайта (codeblocks-10.05-1-debian-i386.tar.bz2) пришлось удалить старый 8.02 а новый встал кривенько и не работает.
Сейчас скачал у http://apt.jenslody.de/ попробую еще раз.

Сейчас не пользуюсь никакими IDE. Все делаю на дескопе -- три традиционных окна: окно псевдо-терминала, окно редактора (gedit) и окно Наутилуса. Управление проектом веду из Makefile-ла с помощью комады make <что-надо-сделать>.

Создаю из трех этих окон себе IDE следующим образом:
...

А вот это мне очень понравилось. Буду повторять (как мартышка smile ).
Но сразу возникает вопрос: а с отладкой как? Мне IDE в 1-ю очередь для отладки нужен. "Рисовать в блокноте" я немного умею.
И потом: не поверю, что все программы у Вас (у других программистов) только консольные, окошечки поди тоже рисуете, куда же без них.
Вот о том, как бОльшую часть программы сделать и отладить в консоли, а затем соединить ее с окошечками, я бы с б-о-о-о-льшим вниманием почитал. Может быть где-то и найду, кто-то поделится опытом.

Я бы Вам рекомендовал работать со второй группой -- с группой функций стандартной библиотеки С, а не с первой. Это и быстрее, и субъективно проще.

Это я и пытался реализовать в своем "первом подходе к снаряду", но получилось пока по 1-му варианту (см. код в предыд. посте).

Я, наоборот, при работе с COM-портом пользуюсь только open, read, write
и при работе с периферией лучше использовать именно их, чтобы иметь
полный контроль над устройством.

IDE тоже предпочитаю не использовать, в частности, и когда-то в Windows
и позже в Linux в Qt я программы составлял в текстовом редакторе и
запускал компиляцию из командной строки.

Пётр.
...

Пётр, а как же отладка? Кроме того Qt многие хвалят именно за "виджеты-гаджеты" - а это все те же окошечки-кнопочки, но с наворотами.
Вот тут кажется уже близко к профессиональным секретам/откровениям, вынужден цитировать самого себя:

Может быть где-то и найду, кто-то поделится опытом.

Я прошу прощения, за то что "плавно ушел" от работы с COM-портом в общие вопросы программирования в Линукс.
Правда это не совсем "фламе". Но может быть стоит продолжить в другой теме, или даже создать новую? Что скажет модератор?

#184152

Пользовательский интерфейс - это одно, а выполнение задачи - совершенно другое.
Для отладки и просто проверки работы проще и быстрее чисто консольные программы,
без графики. Отладчиком я вообще не имею привычки пользоваться, в случае надобности
удовлетворяюсь отладочным выводом. Вообще говорят, что отладчик должен быть в голове.

Вот когда приходилось в Windows делать программы для PIC на ассемблере, то там я делал
это именно в их среде программирования и в случае непонятной ошибки прогонял программу
пошагово.

Пётр.

#184451

Скорее всего Пётр прав:

Я, наоборот, при работе с COM-портом пользуюсь только open, read, write
и при работе с периферией лучше использовать именно их, чтобы иметь
полный контроль над устройством.

Но у меня программа с fopen, fread, fwrite "не понравилась" компилятору, а с open, read, write работает.
Вот ее код:

text
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <stdint.h>  // special for uint8_t type

#define BUFSIZE 4096

// Имя последовательного порта. Вынесем его в отдельную переменную const char *serport = "/dev/ttyS0"; // Имя для выходного файла. const char *fileout = "info-from-garmin.bin"; // Две команды Garmina. Это два константых набора байтов. const uint8_t GarminStop[] = {0x10, 0x1C, 0x02, 0x00, 0x00, 0xE2, 0x10, 0x03}; const uint8_t sizeStop = sizeof(GarminStop) / sizeof(uint8_t); const uint8_t GarminStart[] = {0x10, 0x1C, 0x02, 0xFF, 0xFF, 0xE4, 0x10, 0x03}; const uint8_t sizeStart = sizeof(GarminStart) / sizeof(uint8_t); //---------------------------------------------------------------------------------------------

int open_port(void) { int fd; fd = open(serport, O_RDWR | O_NOCTTY); // |O_NONBLOCK - removed from open() SNK if (fd == -1) perror("Open_port: Unable to open /dev/ttySx"); else { struct termios options; tcgetattr(fd, &options); cfsetispeed(&options, B9600); cfsetospeed(&options, B9600); options.c_cflag &= ~PARENB; options.c_cflag &= ~CSTOPB; options.c_cflag &= ~CSIZE; options.c_cflag |= CS8; // 4 strings added by SNK ////options.c_cc[VMIN] = 1; // minimum bytes options.c_cc[VTIME] = 10; // interval in 1/10 sec options.c_lflag = ICANON; // enable canonical input options.c_oflag = 0; // raw output tcsetattr(fd, TCSANOW, &options); } return fd; // было пропущено!!! } //---------------------------------------------------------------------------------------------

// Найти последовательность sample в buf Функция возвращает: // смещение до искомой последовательности относительно начала буфера, // если последовательность не найдена -- функция возвращает -1 int lookfor(uint8_t *buf, int bufsize, uint8_t *sample, uint8_t samplesize) { int i, k; // Размер буфера должен быть всяко больше размера того, что в нем ищем if (bufsize < samplesize) return -1; // Будем считать, что не нашли

for (i = 0; i < bufsize - samplesize; i++) { if (memcmp(&buf[i], sample, samplesize) == 0) { k=i; ////return i; // Нашли!!! break; } } if(k > 0 && k < bufsize-2) { // чтобы убедиться, какие Garmin-сообщения находит программа на старте printf("bytes csaned: %d, found Garmin message 0x%X 0x%X 0x%X\n", k, buf[k], buf[k+1], buf[k+2]); return k; } return -1; // Не найдено. } //---------------------------------------------------------------------------------------------

void main() { // start Garmin COM-port int BytesRead; // byte-block readed from port int i, n; int fd, fdout; // port FILEDES, fileout FILEDES uint8_t bRead[BUFSIZE]; uint8_t sample1[] = {0x10, 0x36, 0x0C}; uint8_t samplesize1 = sizeof(sample1) / sizeof(uint8_t); uint8_t sample2[] = {0x10, 0x38, 0x28}; uint8_t samplesize2 = sizeof(sample2) / sizeof(uint8_t);

fd = open_port(); // open port as FILEDES(criptor) if (fd < 0) exit (-1); // !!! (EXIT_FAILURE) - не работает, что-то надо добавить в #include <*.h>

fdout = open(fileout, O_WRONLY | O_CREAT | O_TRUNC); // open out-file as FILEDES if (fdout < 0) { perror("out-file open: "); exit (-1); }

// open port - Stop_&_Start commands is feature of all Garmin receivers/navigators! n = write(fd, GarminStop, sizeStop); if (n<8) return; n = write(fd, GarminStart, sizeStart); if (n<8) return;

printf("\nWARNING! Press Ctrl+C to stop program!\n"); sleep (2); // pause in Sec to fill port-buffer // read to BUFSIZE bytes from COM-port & write to bRead-array BytesRead = read(fd, bRead, BUFSIZE); printf("\nBytes readed %d\n", BytesRead); if (BytesRead>samplesize1) { // Найти начало Garmin Message sample1 или sample2 if ((lookfor(bRead, BytesRead, sample1, samplesize1) == -1) && (lookfor(bRead, BytesRead, sample2, samplesize2) == -1)) { printf("Not found Garmin message, EXIT!\n"); exit(-1); // а можно повторить в цикле n-раз } } else { printf("%d bytes readed, EXIT!\n", BytesRead); exit(-1); }

// read in cicle by Timer messages from Garmin while (!SIGINT || !VINTR) // if NOT signal = Ctrl+C Ctrl+Break { sleep (1); // in Sec, or maybe in microSec: usleep(1000000); // write to fdout-file, bytes readed, read to BUFSIZE bytes, read from COM-port BytesRead = read(fd, bRead, BUFSIZE); if (BytesRead<1) {perror("none bytes readed from /dev/ttySx");} else { n = write(fdout, bRead, BytesRead); ////fprintf(fdout, "Message"); printf("%d bytes written Garmin Messages\n", n); } } // Уходя не забываем сказать "до свидания!" и закрыть за собой двери. close(fdout); close(fd); exit(0); } // End main()

А это лог терминала:
[console]root@sklimkin-desktop:/home/sklimkin/Рабочий стол/Programming Linux/linux.ru_Programming# make
gcc -o comport comport.c
comport.c: In function ‘main’:
comport.c:94: warning: incompatible implicit declaration of built-in function ‘exit’
root@sklimkin-desktop:/home/sklimkin/Рабочий стол/Programming Linux/linux.ru_Programming# ./comport

WARNING! Press Ctrl+C to stop program!

Bytes readed 47
bytes csaned: 27, found Garmin message 0x10 0x38 0x28
11 bytes written Garmin Messages
94 bytes written Garmin Messages
131 bytes written Garmin Messages
^C
root@sklimkin-desktop:/home/sklimkin/Рабочий стол/Programming Linux/linux.ru_Programming# ./comport

WARNING! Press Ctrl+C to stop program!

Bytes readed 126
bytes csaned: 29, found Garmin message 0x10 0x36 0xC
34 bytes written Garmin Messages
11 bytes written Garmin Messages
^C
root@sklimkin-desktop:/home/sklimkin/Рабочий стол/Programming Linux/linux.ru_Programming#
[/console]
То есть программа:
1. находит порт
2. открывает его
3. открывает файл для записи из порта
4. ищет и находит специфические для прибора последовательность байтов (0x10 0x36 0xC или 0x10 0x38 0x28)
5а. читает по таймеру в 1 сек из порта и
5б. пишет в файл вывода.
6. по Ctrl+C закрывает файл и порт, завершает работу.

Я постарался учесть и использовать все подсказки А.А.Жевака. Но не все получилось.
Вот никак не пойму, что же нужно для того, чтобы работала связка ф-ций fopen, fread, fwrite.
Спасибо за помощь и терпение, Александр Антонович. Жду следующих подсказок.

Может быть самое интересное то, что под Линуксом я увидел работу устройства в реальном времени: терминал и лог-файл показывают точно "сколько" и "чего". Под Виндовс мне это не удалось и я не мог понять, почему некий прибор с протоколом и наполнением мне известными вместо положенной секунды и положенных 0.5-1.2 кБайт пишет в порт нечто в течение 3-х секунд и размером 3.9 кБайта. И при этом блокирует другие процессы. Я "грешил" на ф. Гармин. А вот теперь ... "чешу репу".

Для правильной работы ф-ции exit() вероятно не хватает какого-то инклюда. Кто знает, какого?
И если на exit (-1); компилятор выдает WARNING, то на exit (EXIT_FAILURE); - ERROR

#184460
sklimkin написал:
Для правильной работы ф-ции exit() вероятно не хватает какого-то инклюда. Кто знает, какого?
И если на exit (-1); компилятор выдает WARNING, то на exit (EXIT_FAILURE); - ERROR
man 3 exit написал:
SYNOPSIS
#include <stdlib.h>

void exit(int status);

макросы объявлены там же...

по поводу write/fwrite:
ИМХО их можно совмещать совершенно без болезненно, однако, чтобы это было красиво нужен опыт.
самое очевидное: с помощью поточных функций работать с файлом, а с помощью низкоуровневых с портом.
с портом тоже можно работать с поточными, но чтобы его настроить(задать скорость итп) придётся использовать костыли...

из замечений:
while (!SIGINT || !VINTR) - это не имеет смысла, ибо это константы: SIGINT == 2 VINTR==0
соответственно цикл получается бесконечный... и строки после него не выполняются. При нажатии CTRL+C процесс просто убиается сигналом в том месте, где он находился (вероятнее всего это произойдёт в sleep()).
правильное решение: перехватить сигнал и аккуратно завершить процесс.

#184491

#include <stdlib.h>

Ай-ай-ай, тут я круто лопухнулся, проглядел. Спасибо!

но чтобы его настроить(задать скорость итп) придётся использовать костыли..

Расскажите хоть немного о том, что это за "костыли", или если есть, ссылку на них, пожалуйста.

соответственно цикл получается бесконечный... и строки после него не выполняются

Да, это бессмысленность, и цикл бесконечный - я исправлю. Но осмелюсь заметить: код (в смысле чтения порта в цикле) выполняется, посмотрите лог с консоли.

правильное решение: перехватить сигнал и аккуратно завершить процесс.

И еще раз, если не трудно, несколько подробней "перехватить сигнал", или ссылку с примером, пожалуйста.
Тогда, может быть, дистанция сократится до Урала :rolleyes:

До темы "первая программа" этому сообщению, как до Китая..
.
#184499
Fat-Zer написал:
по поводу write/fwrite:
ИМХО их можно совмещать совершенно без болезненно, однако, чтобы это было красиво нужен опыт.
самое очевидное: с помощью поточных функций работать с файлом, а с помощью низкоуровневых с портом.
с портом тоже можно работать с поточными, но чтобы его настроить(задать скорость итп) придётся использовать костыли...

вы меня извините, но может здесь будет (для порта) использовать

man 3 open написал:
O_ASYNC
Enable signal-driven I/O: generate a signal (SIGIO by default, but this can be changed via fcntl(2)) when input
or output becomes possible on this file descriptor. This feature is only available for terminals, pseudotermi‐
nals, sockets, and (since Linux 2.6) pipes and FIFOs. See fcntl(2) for further details.

т.е. открывать асинхронный файл, и отлавливать прерывание SIGIO. Как только пришёл сигнал - значит байт готов.
Сигналы можно ловить так:

DESCRIPTION
The sigaction() system call is used to change the action taken by a process on receipt of a specific signal. (See sig‐
nal(7) for an overview of signals.)

signum specifies the signal and can be any valid signal except SIGKILL and SIGSTOP.

If act is non-NULL, the new action for signal signum is installed from act. If oldact is non-NULL, the previous action
is saved in oldact.

Вот пример из мана, как работать с сигналами:

text
       #include <unistd.h>
       #include <signal.h>
       #include <stdio.h>
       #include <malloc.h>
       #include <stdlib.h>
       #include <errno.h>
       #include <sys/mman.h>

#define handle_error(msg) \ do { perror(msg); exit(EXIT_FAILURE); } while (0)

char *buffer;

static void handler(int sig, siginfo_t *si, void *unused) { printf("Got SIGSEGV at address: 0x%lx\n", (long) si->si_addr); exit(EXIT_FAILURE); }

int main(int argc, char *argv[]) { char *p; int pagesize; struct sigaction sa;

sa.sa_flags = SA_SIGINFO; sigemptyset(&sa.sa_mask); sa.sa_sigaction = handler; if (sigaction(SIGSEGV, &sa, NULL) == -1) handle_error("sigaction");

pagesize = sysconf(_SC_PAGE_SIZE); if (pagesize == -1) handle_error("sysconf");

/* Allocate a buffer aligned on a page boundary; initial protection is PROT_READ | PROT_WRITE */

buffer = memalign(pagesize, 4 * pagesize); if (buffer == NULL) handle_error("memalign");

printf("Start of region: 0x%lx\n", (long) buffer);

if (mprotect(buffer + pagesize * 2, pagesize, PROT_NONE) == -1) handle_error("mprotect");

for (p = buffer ; ; ) *(p++) = 'a';

printf("Loop completed\n"); /* Should never happen */ exit(EXIT_SUCCESS); }

а программу можно погрузить в бесконечный цикл, sleep на 1 секунду. Чтоб CPU не жрала. ИМХО это будет получше? Или можно оформить приём/передачу как сопрограмму.

#184500
sklimkin написал:
Расскажите хоть немного о том, что это за "костыли", или если есть, ссылку на них, пожалуйста.

собственно способа два, про один с fdopen я уже писал выше:

  • 1. открыть порт open()
    2. настроить
    3. открыть поток с помощью fdopen()
    4. работать с ним с помощью поточных функций fwrite()/fread() (да хоть fprintf())
    5. закрыть
    с закрытием надо подумать... по идее обычный fclose() должен закрыть и поток и дескриптор... но надо почитать/попробовать.
  • 1. открыть порт fopen()
    2. получить дескриптор через filenum()
    3. настроить
    4. работать с ним с помощью поточных функций fwrite()/fread() (да хоть fprintf())
    5. закрыть fclose()
    тут дескриптор можно потерять после п.3...

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

sklimkin написал:
И еще раз, если не трудно, несколько подробней "перехватить сигнал", или ссылку с примером, пожалуйста.

долго искать не стал, текст не читал, но в конце есть годный пример: http://nofxsss2007.narod.ru/spo/45.htm
в общем по этой теме много-чего написано. будут вопросы-задавайте...
в нце

#184501
sklimkin написал:
Расскажите хоть немного о том, что это за "костыли", или если есть, ссылку на них, пожалуйста.

там в принципе и выставлять особо и нечего ИМХО. хотя от девайса зависит. У меня на дефолте работало. Ваш подход плох тем, что пока принимается/передаётся файл программа висит. А при использовании асинхронной передачи можно пустить процесс на самотёк, а основная программу будет спокойно работать далее. Ну хотя-бы прогресс-бар рисовать. Юзеру-то скучно ;)

#184502
Fat-Zer написал:
с закрытием надо подумать... по идее обычный fclose() должен закрыть и поток и дескриптор... но надо почитать/попробовать.

ИМХО, наверное надо закрывать fclose. И проблема тут не в том, что не выполнится close, проблема в том, что не выполнится fflush, которая должна очистить буфер созданного fdopen потока.
Хотя ИМХО это и не так уж и важно...

Fat-Zer написал:
однако ещё есть сомнения насчёт работы в такой манере с портом, ибо потоки буферзированы и данные в порт могут попасть не сразу после выполнения fwrite()...

ну да. скажем, если буфер в 1К, то первые 1К вы запишите мгновенно. Однако след. 1К выбудете ждать, пока первые 1К уедут в порт. программа конечно могла-бы заняться чем-то полезным, периодически проверяяя степень заполненности буфера, но зачем?

#184504
sklimkin написал:
но чтобы его настроить(задать скорость итп) придётся использовать костыли..
Расскажите хоть немного о том, что это за "костыли", или если есть, ссылку на них, пожалуйста.

читайте эту тему сначала. уже был пример костылей.
ещё почитайте man cfsetispeed там подробно написано, что и куда пихать.

#185049

ну да. скажем, если буфер в 1К, то первые 1К вы запишите мгновенно. Однако след. 1К выбудете ждать, пока первые 1К уедут в порт. программа конечно могла-бы заняться чем-то полезным, периодически проверяяя степень заполненности буфера, но зачем?

Попробую немного пояснить.
1. Программа будет работать с GPS-приемником/навигатором. Посылать в порт требуется совсем немного: в начале работы раз 5 по 10-20 байтов и в конце работы не более 12 байтов. А все остальное время читать бинарный поток из порта, декодировать его и записывать в 4 файла.
2. Почти все премники/навигаторы заточены на выдачу данных 1 раз в секунду (спутниковая навигация - это практически измерение времени прохождения сигнала от спутника до приемника).
3. Поэтому и читать порт надо 1 раз в секунду. Ну и успевать перемалывать полученное за эту секунду.
Я перечитал немало исходников программ по этой теме. И почти все авторы читают по 1 байту, накапливая их в промежуточном буфере. А затем обрабатывют.
Мне это показалось расточительным и я сделал программу (в Виндовс), которая читает из порта то, что в нем есть каждую секунду в буфер размером 4 кБ (данных в буфере порта накапливается до 3-х кБ (зависит от конкретной модели приемника).
В приемниках GPS+Glonass данных уже в 2-4 раза больше, поэтому и скорости порта возросли до 115 кБит/сек.
То есть при побайтовом чтении программа имеет шанс "захлебнуться" таким потоком. В результате будут потерянные "эпохи" - данные за некоторые секунды в этом потоке. А это уже ... Данные должны быть без разрыва во времени.

А вот за ссылку на man cfsetispeed спасибо.

#185091
sklimkin написал:
А вот за ссылку на man cfsetispeed спасибо.

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

#185583

Почитал исходник async.c профессора Мадридского политеха Антонио Табернеро (в 2000-2002 г.г. его интересовало программирование для геодезии и он выложил очень удобные для работы программы на СИ для Линукса и Виндовс).
http://artico.lma.fi.upm.es/numerico/miembros/antonio/async/

Секрет успеха в правильной инициализации COM-порта. Я исправил этот момент и теперь программа читает из порта без пропусков и пишет полученное в бинарный файл и в краткий лог-файл. Второй важный момент - fflush(FILE* fp) и fsync(int fd) - вовремя сбрасывать буферы в файлы.
Вот где положил архив с файлами (пароль comport):

comport_open_read_write.zip
номер: 5497831, размер: 6 Кб, размещён: 2011-08-19 02:47
ссылка на файл: http://webfile.ru/5497831
Описание: ОС Линукс Работа с приемниками и навигаторами Гармин серии eTrex, GPSMap по бинарному протоколу - открыть COM-порт, читать из порта, писать в порт.

Возможно кому-то тоже понадобится работать с GPS-приемником через COM- USB- BT- интерфейс.
Спасибо тем, кто помог разобраться.

#185590

Лучше бы почитать книги по программированию в Linux, их давно много на русском.
Тогда и не потребовалось бы смотреть чужие исходники, т.к. и без них было бы ясно.

Пётр.

Войдите или зарегистрируйтесь, чтобы ответить.