Ошибка ввода-вывода при попытке изменить последовательный бод в C-программе после изменения в unix STTY - PullRequest
0 голосов
/ 19 мая 2019

Я заметил что-то странное, но воспроизводимое.

Сначала я проверяю настройки моего последовательного порта:

    bash-3.1# stty -F /dev/ttyS0
    speed 0 baud; line = 0;
    intr = <undef>; quit = <undef>; erase = <undef>; kill = <undef>; eof = <undef>; start = <undef>;
    stop = <undef>; susp = <undef>; rprnt = <undef>; werase = <undef>; lnext = <undef>; flush = <undef>;
    min = 1; time = 0;
    -cread
    -brkint -icrnl -imaxbel
    -opost -onlcr
    -isig -icanon -iexten -echo -echoe -echok -echoctl -echoke

Затем меняю скорость на 1200 бит / с:

bash-3.1# stty -F /dev/ttyS0 1200

Я тогдавыполнить этот фрагмент моей программы в функции для изменения скорости передачи:

fd=open(dev,O_NOCTTY | O_NONBLOCK | O_RDWR);
struct termios ser[1];
tcflush(fd,TCIFLUSH);
tcflush(fd,TCOFLUSH);
cfmakeraw(ser);
 // I call tcsetattr after each terminal setting to make sure its applied.
if (tcsetattr(fd,TCSANOW,ser) < 0){
    return -1;
}
cfsetspeed(ser,B9600);
if (tcsetattr(fd,TCSANOW,ser) < 0){
  return -2; //returns this after manually setting port via STTY
}

Проблема в том, что скорость передачи НЕ изменяется должным образом.Фактически, я получаю -2, возвращенное из функции, а strerror (errno) возвращает «ошибку ввода / вывода».

После выполнения программы я проверяю настройки системного порта:

bash-3.1# stty -F /dev/ttyS0
speed 0 baud; line = 0;
intr = <undef>; quit = <undef>; erase = <undef>; kill = <undef>; eof = <undef>; start = <undef>;
stop = <undef>; susp = <undef>; rprnt = <undef>; werase = <undef>; lnext = <undef>; flush = <undef>;
min = 1; time = 0;
-cread
-brkint -icrnl -imaxbel
-opost -onlcr
-isig -icanon -iexten -echo -echoe -echok -echoctl -echoke

И он сбрасывается до нуля бит / с, даже если я специально просил 9600 бит / с.

Почему он это делает?и как мне заставить скорость перейти на 9600bps программно?

1 Ответ

1 голос
/ 19 мая 2019

В вашем коде много ошибок.

  • Вы открываете tty-устройство с помощью O_NONBLOCK, поэтому при выполнении вызовов ioctl (tc*attr(3) вызовы приводят к ioctl(2) системным вызовамв зависимости от используемой вами версии Unix) вы не знаете, было ли устройство уже открыто для совершения вызовов tc*attr(3).То же касается и флага O_NOCTTY.Вы установили эти флаги, не зная, какова их функция в системном вызове open.O_NOCTTY бесполезен в программе, которая запускается изнутри сеанса, и O_NONBLOCK сделает ваши tc*attr(3) вызовы для возврата с ошибкой (EAGAIN), если устройство еще не открыто, при попытке сделатькорректировки параметров.
  • Вы не проверяете результат вызова open(2).Это может привести к ошибке, если вы попытаетесь использовать -1 в качестве дескриптора файла (ENODEV, ENOTTY или EBADF, EINVAL, ENXIO и т. Д.)
  • Вы не делаетеинициализируйте данные структуры struct termios, поэтому, вероятно, в этом и заключается причина ошибки.Как вы показываете (ваш пример кода не завершен, одна из причин, по которой вам нужно прочитать Как создать пример Minimal, Complete и Verifiable ), используемый вами struct termios объявлен в автоматической переменной (потому что его объявление встроено в код) так что оно наверняка неинициализировано и с мусорными данными.Обычно вам нужно сделать tcgetattr() на нем, чтобы инициализировать правильные значения и иметь возможность восстановить настройки после завершения вашей программы.
  • bash(1) заставляет ioctl(2) s устанавливать и получать параметры termiosна стандартном дескрипторе ввода, когда он подключен к tty устройству.Если вы работаете с stdin, вы должны учитывать помехи bash(1).Это отличает значения, которые вы получаете от тех, которые вы установили с помощью stty.
  • В целом, операционные системы Unix (боюсь, не в linux), когда последняя близкая к устройству обычно сбрасывает параметрыtty к стандартным фиксированным значениям, поэтому флаги, которые вы устанавливаете, когда вы меняете не стандартное устройство (не стандартный ввод, который не закрывается в последний раз, когда stty завершает работу) с помощью stty, эти параметры сбрасываются на значения по умолчанию, как только stty завершается (напоследнее закрытие tty).Выполните команду sleep 999999999 </dev/ttyBlaBla & перед выполнением команды stty(1), чтобы порт оставался открытым (путем перенаправления команды sleep) после настройки на странице stty(1).
  • READ termios(3), чтобы вы моглиустановить параметры из самой вашей программы.Только если ваша программа обычно не занимается настройкой параметров, вам не придется делать это программно.Но тогда нет смысла изменять параметры терминала, поэтому лучше всего научиться программировать параметры устройства.

Надлежащий способ сделать это должен быть (скопирован из вашего фрагмента и отредактирован), напримерэто:

#include <string.h> /* for strerror */
#include <errno.h> /* for errno definition */

/* ... */

fd=open(dev,O_NOCTTY | O_NONBLOCK | O_RDWR);
if (fd < 0) {
    fprintf(stderr, "OPEN: %s (errno = %d)\n",
        strerror(errno), errno);
    return -1;
}
struct termios ser; /* why an array? */
tcflush(fd,TCIFLUSH); /* unneeded, you have not used the tty yet */
tcflush(fd,TCOFLUSH); /* idem. */
/******* THIS IS THE MOST IMPORTANT THING YOU FORGOT ***********/
int res = tcgetattr(fd, &ser); /* *****this initializes the struct termios ser***** */
if (res < 0) {
    fprintf(stderr, "TCGETATTR: %s (errno = %d)\n",
        strerror(errno), errno);
    return -2; /* cannot tcgetattr */
}
/***************************************************************/
cfmakeraw(&ser); /* now it is valid to set it */
cfsetspeed(&ser,B9600);  /* better do all in only one system call */
// I call tcsetattr after each terminal setting to make sure its applied.
/* nope, tcsetattr and tcgetattr are the only calls that make something 
 * on the tty, the rest only manipulate bits on the struct termios
 * structure, but don't do anything to the terminal, you begun with a
 * trashed struct termios, so it's normal you end with an error. */
if ((res = tcsetattr(fd, TCSANOW, &ser)) < 0){
    fprintf(stderr, "ERROR: %s (errno = %d)\n",
        strerror(errno), errno); /* better to know what happened. */
    return -3; /* couldn't tcsetattr */
}

наконец, этот код (как ваш первый) не был проверен, главным образом потому, что вы не опубликовали полный, минимальный и проверяемый пример.Так что вам, вероятно, придется немного переделать его, прежде чем включать в код.И, пожалуйста, RTFM (последнее значение, чтобы прочитать termios(3) полностью, и самое важное: Как создать пример Minimal, Complete и Verifiable ) :).Кроме того, не проверяйте настройки tty в stdin, если вы используете bash(1), так как обычно он восстанавливает настройки tty после выхода из команды, прежде чем выдавать приглашение.

...