Я уже писал о том, как работать с последовательным портом в 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
Все сработало, ответ получен.
Скачать исходник примера.








