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