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

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

Как и с любым другим устройством работа из пространства пользователя Linux происходит посредством чтения и записи в файл. Его нужно открыть при помощи функции open, в качестве параметров передаем путь к файлу и флаги.

  • O_RDWR — чтение и запись
  • O_NONBLOCK — не блокировать файл
  • O_NOCTTY — не делать устройство управлящим терминалом
  • O_NDELAY — не использовать DCD линию

Функция возвращает идентификатор открытого файла или -1 в случае ошибки.

fd = open("/dev/ttyS2", O_RDWR | O_NONBLOCK | O_NOCTTY | O_NDELAY);
if (fd == -1) {
    perror("Unable to open port");
}
fcntl(fd, F_SETFL, 0);

Если файл открыт успешно очистим флаги состояния файла вызовом fcntl.

Для управления настройками последовательного порта предусмотрен интерфейс termios. Заводим структуру типа termios.

struct termios {
    tcflag_t c_iflag;      /* input modes */
    tcflag_t c_oflag;      /* output modes */
    tcflag_t c_cflag;      /* control modes */
    tcflag_t c_lflag;      /* local modes */
    cc_t     c_cc[NCCS];   /* special characters */
};

Как видно она содержит флаги управления приемом/передачей.
Сбрасываем значения в 0.

memset(&term, 0, sizeof(struct termios));

Устанавливаем скорость обмена 9600 бод.

if ((cfsetispeed(&term, B9600) < 0) ||
    (cfsetospeed(&term, B9600) < 0)) {
        perror("Unable to set baudrate");
}

Далее необходимо настроить ряд флагов для указания операционной системе, что это не консольный терминал, а канал обмена «сырыми» данными.

/*
CREAD - включить прием
CLOCAL - игнорировать управление линиями с помошью
*/
term.c_cflag |= (CREAD | CLOCAL);
/* CSIZE - маска размера символа */
term.c_cflag &= ~CSIZE;
/* CS8 - 8 битные символы */
term.c_cflag |= CS8;

/* CSTOPB - при 1 - два стоп бита, при 0 - один */
term.c_cflag &= ~CSTOPB;

/*
ICANON - канонический режим
ECHO - эхо принятых символов
ECHOE - удаление предыдущих символов по ERASE, слов по WERASE
ISIG - реагировать на управляющие символы остановки, выхода, прерывания
*/
term.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
/* INPCK - вкл. проверку четности */
term.c_iflag &= ~(INPCK);
/* OPOST - режим вывода по умолчанию */
term.c_oflag &= ~OPOST;

/*
TCSANOW - примернить изменения сейчасже
TCSADRAIN - применить после передачи всех текущих данных
TCSAFLUSH - приемнить после окончания передачи, все принятые но не считанные данные очистить
*/
if (tcsetattr(fd, TCSANOW, &term) < 0)
{
    perror("Unable to set port parameters");     
}

После настройки порта же все просто, используем вызовы write/read для записи/чтения данных.
Для примера передадим Modbus команду чтения:

char tx[] = {0x7F, 0x03, 0x00, 0x00, 0x00, 0x01, 0x8E, 0x14};
write(fd, tx, 8);

где 8 — длина буфера данных для отправки.
Далее необходимо считать ответ. Можно конечно же сразу написать вызов read. Но таким образом мы заблокируем программу до поступления желаемого количества данных, а ответа может и не быть! Чтобы избежать этого заведем структуру типа timeval и организуем вызов select.
Например так:

write(fd, tx, 8);
//ожидание ответа 2с
timeout.tv_sec  = 2;
timeout.tv_usec = 0;
//сбрасываем значения дескрипторов в ноль
FD_ZERO (&fs);
//устанавливаем бит дескриптора fd в 1
FD_SET(fd, &fs);
//ждем пока дескриптор не будет готов для чтения
res = select(fd+1, &fs, NULL, NULL, &timeout);

if (res == 0) {
    perror("Timeout occurs");
} else if (res > 0) {
    if ( FD_ISSET(fd, &fs) ) {
        //сбрасываем буфер
        memset(buf,0x00,sizeof(buf));
        //читаем
        res = read(fd, buf, 512);
        printf("Read %d bytes:\n", res);
        for (i=0; i<res; i++)
            printf("%02x ", buf[i]);
        printf("\n");
    }
}

Компилируем и запускаем:

# ./serial
Read 7 bytes:
7f 03 02 04 80 93 2e

Все сработало, ответ получен.
Скачать исходник примера.

 

Похожий код:

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

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

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