Управление на сериен порт с POSIX

From Ilianko

Достъп до сериен порт

Всеки сериен порт в UNIX система има един или повече съответни файла, намиращи се в директория /dev. В Linux най-често съответните файлове за серийните портове са:

  • Port 1 - /dev/ttyS0,
  • Port 2 - /dev/ttyS1 и т.н..

Отваряне на серийния порт

Както вече знаем, серийният порт се представя като файл и лесно може да се досетим, че за достъп до този файл може да използваме функцията open(2). Единствено трябва да съобразим и настроим правата за достъп, които по подразбиране са дадени единствено на root потребителя.

Примерен код на C за отваряне на сериен порт:

#include <stdio.h> /* Входно изходна библиотека */
#include <string.h>/* Стринг библиотека */
#include <unistd.h>/* UNIX стандартна библиотека */
#include <fcntl.h> /* Библиотека за управление на файловете */
#include <errno.h> /* Библиотека за обработка на грешки */
#include <termios.h> /* POSIX библиотека за управление на терминала  */
 // 'open_port()' - Отваря сериен порт 1.
 // Връща файлов дескриптор при успешно отваряне и -1 при грешка.     
int open_port(void)  {
  int fd; // Файлов дескриптор на порта
  fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NDELAY);
  if (fd == -1) //портът не може да бъде отворен
    perror("open_port: Unable to open /dev/ttyS0 - ");
  else
    fcntl(fd, F_SETFL, 0); // ресетване на флаговете за настройкa

Опции при отваряне

Освен стандартната опция при отваряне на файл за четене и запис ( O_RDWR - флаг) има още две опции, които се задават.

  • O_NOCTTY – програмата няма да поема управлението на терминала
  • O_NDELAY – не се чака сигнал DCD (Data Carier Detect – пин 1), за да се активира четенето от порта.

Изпращане на данни.

Изпращането на данни е лесно, просто пишем във файла на порта с функцията write(2) и проверяваме дали има грешки. Примерно:

n = write(fd, "ATZ\r", 4);
if (n < 0)
fputs("write() of 4 bytes failed!\n", stderr);

Функцията write(2) връща броя на изпратените битове или -1 при грешка.

Четене на данни от серийния порт

Четенето от серийния порт е малко особено. Когато портът е в режим „raw data“, всяко повикване на функцията read(2) ще върне символите, които реално се намират в буфера. Примерно:

while ( stop == 0) {
  usleep(100); fflush (stdout);
  res = read(fd,buf,10); 
  for( c=0;c<res; c=c+1) printf("%c", (char) buf[c]); 
}

Ако няма символи, функцията ще чака, докато пристигне някакъв символ, докато не изтече времето на таймера или не настъпи някаква грешка. За да се избегне чакането може да се използва следното:

fcntl(fd, F_SETFL, FNDELAY);

FNDELAY – дава настройка read(2) да връща 0, когато няма данни.

Затварянето на серийния порт става с close(fd). Затварянето на серийния порт ще постави DTR в ниско ниво.

Настройка на серийния порт

POSIX терминален интерфейс. Повечето системи поддържат POSIX терминален интерфейс за промяна на параметри като скорост на предаване, дължина на символа, контрол по четност и т.н. Първото нещо, което трябва да се направи, е <termios.h> да бъде включен към програмата. В този файл се дефинират както структурата, която ще управлява терминала, така и POSIX управляващи функции.

Двете най-важни функции са tcgetattr(3) и tcsetattr(3). Тези две функции съответно връщат текущите настройки и задават нови настройки на терминала. Един от аргументите на функциите е показалец към структурата termios, съдържаща следните полета: Таблица 1.

Променлива   Описание
c_cflag    Общи настройки
c_lflag    Настройки на линията
c_iflag    Входни настройки
c_oflag    Изходни настройки
c_cc       Управление на символите
c_ispeed   Скорост на приемане
c_ospeed   Скорост на прадаване

Общи настройки

Битовете в полето на c_cflag управляват скоростта, броя на битовете в изпращаната дума, контрол по четност, стоп битовете и хардуерното управление на данните. В таблицата са описани константите за някои от подържаните опции:

Константи Описание
CBAUD     Bit mask for baud rate
B0        0 baud (drop DTR)
B2400     2400 baud
B19200    19200 baud
B115200   115,200 baud
EXTA      External rate clock
EXTB      External rate clock
CSIZE     Bit mask for data bits
CS7       7 data bits
CS8       8 data bits
CSTOPB    2 stop bits (1 otherwise)
CREAD     Enable receiver
PARENB    Enable parity bit
PARODD    Use odd parity instead of even
HUPCL     Hangup (drop DTR) on last close
CLOCAL    Local line - do not change "owner" of port
LOBLK     Block job control output
CNEW_RTSCTS Enable hardware flow control (not supported on all platforms) 
CRTSCTS

Две от опциите на c_cflag трябва да бъдат винаги активирани CLOCAL и CREAD. Първата няма да позволи на вашата програмата да поеме контрола върху серийния порт, което би причинило случайни затваряния на линията и сигнали за управление. Втората ще осигури драйвера на серийния интерфейс да чете пристигащите данни. Константите за скоростта (CBAUD, B9600 и т.н) се използват при по-стари интерфейси, където липсват полетата c_ispeed и c_ospeed.

Важно: Никога да не се инициализират флаговете на полетата директно, винаги трябва да се използват побитовите оператори AND, OR и NOT за задаване стойността на битовете в променливата. Различните операционни системи (дори пачовете) биха могли да използват по различен начин битовете, така че използването на побитовите оператори ще предотврати нежелано объркване.

Настройка на скоростта

Функциите cfsetospeed(3) и cfsetispeed(3) се използват за настройка на termios структурата, независимо от операционната система. Функцията tcgetattr(3) записва текущите настройки на серийния контролер в termios структурата. След като зададем нова стойност за скоростта и активираме CLOCAL и CREAD, функцията tcsetattr() записва новите настройките в контролера. Опцията TCSANOW задава настройките да бъдат активирани веднага, без да се чака край на приемане или изпращане на текуща сесия от данни. Друга опция, която позволява задаване изчакване до приключване на сесията за приемане или предаване е TCSADRAIN, също и за директно изтриване на данните в изходните и входните буфери се ползва TCSAFLUSH.

Примерен код за задаване на скоростта:

// Извличане на текущите настройки на порта
tcgetattr(fd, &options);
// Задаване на скорост 19200 бода в секунда
cfsetispeed(&options, B19200);
cfsetospeed(&options, B19200);
// Активиране на применика 
options.c_cflag |= (CLOCAL | CREAD);
// Запис на новите настройки на порта
tcsetattr(fd, TCSANOW, &options);


Повечето системи не поддържат различни скорости на приемане и предаване, затова за по-голяма съвместимост е добре двете скорости да са еднакви.

Други важни настройки

  • задаване на дължина на символа;
  • проверка по четност (Parity check);
  • хардуерно управление на връзката ( Hardware Flow Control)
options.c_cflag &= ~CSIZE;  /* Задаване на размер на думата */
options.c_cflag |= CS8; /* Избира 8 бита дължина на думата */
options.c_cflag |= PARENB /*Активиране на контролен бит */
options.c_cflag &= ~PARODD /*Задаване на контрол по четност*/
options.c_cflag &= ~CSTOPB /* един стопов бит */
options.c_cflag |= CNEW_RTSCTS; /* Активиране на хардуерно управление */

Локални настройки

Локалните настройки задавани с полето c_lflag управляват как входящите символи се обработват от серийния драйвер. По същество два са режимите на приемане, които могат да се настроят – каноничен и директен.

Режим каноничен вход.

Режим каноничен вход е линийно ориентиран. Входните символи се записват в буфера, те могат да бъдат редактирани интерактивно от потребителя, докато не се получи символ CR ( carriage return) или LF (line feed). За задаване на този режим обикновено се избират следните опции: ICANON, ECHO и ECHOE.

options.c_lflag |= (ICANON | ECHO | ECHOE);

Режим на директен достъп.

При директен достъп входните данни не се обработват. Пристигналите символи се предават директно без промени в момента на пристигането им. За работа в директен режим следните опции се деактивират: ICANON, ECHO, ECHOE и ISIG.

options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);

Важно: Никога не активирайте echo (ECHO, ECHOE), когато изпращате команди към модем или друг компютър, който връща ехо, в противен случай ще се генерира неуправляема обратна връзка.

Входящи настройки

Настройките, задавани с полето c_iflag управляват обработката на всеки пристигнал символ.

Задаване на проверка по четност

Ако в c_cflag  е активирана проверка по четност, то трябва да се зададат и съответните настройки в c_iflag. За тази цел се използват следните константи:  INPCK, IGNPAR, PARMRK и ISTRIP. За проверка и изрязване на бита за проверка се използват INPCK и ISTRIP.
options.c_iflag |= (INPCK | ISTRIP);

INGPAR трябва да се ползва внимателно, защото казва на драйвера на серийния порт да игнорира грешките и да предаде входните данни, независимо дали има грешки или не.

PARMRK задава маркиране на входните грешки във входящия поток, като се използват специални символи. Ако INGPAR е активирано, NUL символ (х000) се добавя пред всеки символ с грешка. Ако INGPAR не е активирано, DEL (x177) и NUL символи се изпращат заедно с всеки сгрешен символ.

Настройка на софтуерно управление на връзката

Софтуерното управление на връзката се активира с константите IXON, IXOFF и IXANY.

options.c_iflag |= (IXON | IXOFF | IXANY);

За деактивиране на софтуерното управление просто се нулират битовете options.c_iflag &= ~(IXON | IXOFF | IXANY);

Масивите съдържащи описание на XON (символи за начало) и XOFF ( символи за край) са дефинирани в c_cc масива.

Изходни настройки

Полето c_oflag съдържа опции за филтриране на изходните данни. Както при режимите за приемане може да се избере обработен или директен изход.

 options.c_oflag |= OPOST; // Избор на обработен изход
 options.c_oflag &= ~OPOST; //Директен изход

Допълнителна литература

termios библиотека POSIX serial port