Пользователь
0,0
рейтинг
9 сентября 2014 в 18:35

Разработка → Linux: кнопки, светодиоды и GPIO из песочницы

Введение


Начиная с версии 2.6.26 (кажется) у Linux появляется стандартный интерфейс для работы с GPIO через sysfs. В оригинале прочитать об этом можно в [LGPIO00]. Я попытаюсь пересказать своими словами содержимое этого документа.

Главной точкой работы с GPIO является директория /sys/class/gpio. Если вы загляните в нее, то увидите два файла: export и unexport. Сразу после загрузки системы, все линии GPIO принадлежат ядру и использовать их в своих программах просто так не получится. Для этого, линию GPIO нужно экспортировать, записав её номер в файл export. Например, команда: echo 8 > /sys/class/gpio/export – сообщает ядру, что мы хотим использовать GPIO8. Перевод каретки '\n' и символ окончания строки '\0' – не обязательны: код на языке C: write(fd, “8”, 1); — отработает точно так же.

Номер линии зависит от используемой аппаратной платформы и реализации драйвера. Понятно, что нумерация должна быть уникальной для каждого вывода GPIO. Не секрет, что на некоторых SoC имеется несколько GPIO портов (GPIOA, GPIOB и т.д.), поэтому, как именно распределяются номера линий, необходимо уточнять в каждом случае отдельно. Самым очевидным и простым является такое распределение: имеется два GPIO порта по 32 линии в каждом, при этом, у первого порта нумерация линий идет от 0 до 31, а у второго – от 32 до 63 и т.д.

Следует отметить, что в некоторых современных SoC’ах, всяких периферийных устройств на кристалле больше, чем можно позволить выводов на корпусе микросхемы. Поэтому, некоторые выводы мультиплексируются между различной периферией. Как следствие, некоторые линии GPIO могут быть уже задействованы в текущей конфигурации системы портом LCD дисплея, например, или USB портом. Использовать такие линии вам, скорее всего, не удастся.

Выход: подключаем светодиод




Допустим, мы нашли на печатной плате свободный вывод GPIO порта и хотим повесить на него светодиод. Каким-нибудь шаманством, устанавливаем, что он имеет номер 16. Теперь, к этой линии можно попытаться получить доступ: echo 16 > /sys/class/gpio/export. В случае успеха, в /sys/class/gpio появится новая директория: gpio16.

Заглянув в эту директорию можно увидеть, что для работы с отдельной линией GPIO Linux предоставляет нам интерфейс, состоящий из следующих фалов: direction, value, edge и active_low. Сейчас нам интересны только два из них: direction и value.
  • direction — определяет направление линии: является ли она входом или выходом. Чтобы настроить линию на вход нужно записать в файл слово ‘in’, а если на выход – ‘out’.
  • value – позволяет прочитать значение на линии, если она – вход, либо установить значение, если она – выход. Значение в value может принимать текстовую ‘0’ либо ‘1’. Обратите внимание: ‘0’ и ‘1’ – это именно ASCII символы, а не числа.

Вернемся к нашему светодиоду. Следующий код на shell’е зажгет светодиод и погасит его через секунду.

# exporting and tuning GPIO line
echo 16 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio16/direction
# switch GPIO#16 on
echo 1 >  /sys/class/gpio/gpio16/value
# sleep 1 second
sleep 1
# switch GPIO#16 off
echo 0 >  /sys/class/gpio/gpio16/value

Теперь обратим внимание на файл active_low. Он определяет уровень активного сигнала — то есть, какое напряжение будет соответствовать логическому нулю, а какое – логической единице.

По умолчанию за логическую единицу (высокий уровень) принимается наличие на выводе некоторого напряжения (зависит от типа SoC’а, но обычно это +3.3В), а за ноль (низкий уровень) – отсутствие напряжения (замыкание линии на «землю»). Однако, это не всегда бывает удобно, так как некоторые сигналы могут быть инвертированы. Например, сигнал CS (Chip Select) производители микросхем любят делать так, что микросхема становиться активной когда на соответствующем выводе отсутствует напряжение, и перестает реагировать на внешние сигналы, если напряжение подать. Для управления этой настройкой, в файл active_low нужно записать символы ‘0’ — false или ‘1’ — true, в зависимости от того, хотим ли мы инвертировать активный сигнал или нет. По умолчанию там находится '0'.

Вход: подключаем кнопку




Итак, давайте в качестве ввода придумаем кнопку. Схема может выглядеть так:
Как настроить GPIO на вход уже говорилось выше. Прочитать текущее значение можно из файла value. Просто берем и читаем. Можно даже с помощью cat. Естественно, прочитанное значение зависит от настройки active_low, и по-умолчанию, получается ASCII символ ‘1’, если на выводе присутствует напряжение, иначе — получаем ‘0’.

Отметим, что для CMOS (или что-там сейчас используется) висящий в воздухе вывод скорее будет давать логическую единицу (но не обязательно, так как состояние не определено и зависит от наличии заряда на базе входного транзистора), и если мы хотим получить ноль, то нужно соединить его с землей.

Хорошо, теперь мы может узнать, нажата кнопка или нет, просто прочитав значение из value. Но удобно ли это? Скорее всего — нет. Нам придется постоянно, с некоторой периодичностью считывать текущее значение (данная технология называется polling), чтобы определить момент, когда кнопка будет нажата. Лишняя работа – лишняя трата ресурсов. Большинство производителей SoC’ов снабжают свои GPIO контроллером прерываний, который генерирует прерывание по всяким различным случаям: изменение уровня, установка уровня в высокое или низкое состояние. Можно ли это как-то использовать через sysfs? Документация, сообщает, что можно. Для этого, нам необходимо в файл edge записать одно из следующих значений: none, rising, falling или both. Здесь: none – выключаем отслеживание изменения состояния входящей линии; rising и falling – отслеживаем переход из неактивного состояния в активное и из активного в неактивное соответственно; both – реагируем на любое изменение состояния.

Инструкция гласит, что стоит только установить одно из этих значений (кроме none), так сразу с помощью функции poll() или select() можно определить, изменялось ли состояние линии. В случае, если состояние не менялось, вызов read() для файла value должен быть заблокирован. Однако, тут есть тонкость. Если вы откроете файл value и попытаетесь натравить на него poll(), то получите, что чтение не будет блокироваться независимо от того, менялось состояние линии или нет.

Авторы подсистемы GPIO видимо хотели, чтобы cat value срабатывал всегда, независимо от того, что записано в файле edge, поэтому первое чтение не будет блокироваться никогда. В принципе, это логично: для того, чтобы отслеживать изменения нужно сначала определить изначальное состояние. Однако, мне пришлось потратить почти часа два, и только в каком-то заброшенном форуме я нашел предположение, почему poll() не срабатывает и что для этого можно сделать.

Я открывал файл value на каждое чтение и очень удивлялся, почему не происходит блокировка. Оказалось, что файл нужно открывать один раз за весь сеанс слежения за линией, читать из него начальное значение и только тогда, последующие операции чтения будут блокироваться до появления указанного в edge события. И тут тоже есть одна тонкость: значения из файла value читаются только по смещению 0, в то время как вызов функции read() изменяет позицию чтения. Поэтому, перед вызовом read() нужно сбросить позицию чтения с помощью lseek(). В документации Linux эти моменты почему-то обойдены.

Вот, как будет выглядеть чтение GPIO c использованием событий edge:

// set edge event on specific gpio_line
int gpio_edge_set(int n, const char *edge_str)
{
    char filename[PATH_MAX];
    FILE *file;

    snprintf(filename, sizeof(filename), "/sys/class/gpio/gpio%d/edge", n);
    file = fopen(filename, "w");
    if (file == NULL) return -1;
    fprintf(file, "%s\n", edge_str);
    fclose(file);

    return 0;
}

// set GPIO line polling mode
int gpio_poll(int n)
{
    char filename[PATH_MAX];
    int fd;
    char c;
    int err;

    snprintf(filename, sizeof(filename), "/sys/class/gpio/gpio%d/value", n);
    fd = open(filename, O_RDONLY);
    if (fd < 0) return -1;

    read(fd, &c, sizeof(c));

    return fd;
}

// get GPIO line value
int gpio_get(int fd, int timeout)
{
    struct pollfd pollfd[1];
    char c;
    int err;

    pollfd[0].fd = fd;
    pollfd[0].events = POLLPRI | POLLERR;
    pollfd[0].revents = 0;

    err =  poll(pollfd, 1, timeout);
    if(err != 1) return -1;

    lseek(fd, 0, SEEK_SET);
    err = read(fd, &c, sizeof(c));
    if(err != 1) return -1;

    return c - '0';
}



Заключение


Итак, что мы имеем: мы можем использовать GPIO линию для вывода и ввода, и даже с минимальными ресурсами отслеживать изменения на линии. Единственное, что мы пропустили, так это файл uevent. Если честно, я так толком и не разобрался, что это и для чего нужно. Обычно, uvent – это интерфейс для сервиса hotplug, который, в частности, используется демоном udev. Кажется, в openWRT udev можно настроить таким образом, чтобы при изменении уровня на линии выполнялось некое приложение. Однако, я не уверен: сделано ли это штатным udev или его пришлось соответствующим образом патчить.

Все ли это, на что способен GPIO драйвер? Разумеется, нет. Как уже упоминалось выше: интерфейс в sysfs у GPIO появился относительно недавно, а до этого он использовался исключительно ядром как драйвер некой физической шины. Вы можете подключить к этой шине, например SPI или I2C, можете заставить ее быть частью другой аппаратной шины (например, линия ChipSelect у того же аппаратного SPI), а то и просто, натравить на нее драйвера светодиодов и клавиатуры. Впрочем, описание этих возможностей выходит за рамки данной статьи.

Библиография


LGPIO00:, Documentation/gpio.txt,
@scg
карма
24,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (64)

  • +4
    >Каким-нибудь шаманством, устанавливаем, что он имеет номер 16
    Больше всего интересует нахождение оных :)
    • 0
      Да, задача не из простых :) Можно с помощью shell скрипта выводить импульсы с периодом 1с на интересующей GPIO, а затем мультиметром тыкать в подозрительные места. Говорят еще, что есть такие приборы: логические анализаторы, но у меня из никогда не было. В редких случаях можно найти ногу на микросхеме, а затем глазами проследить, куда идет дорожка. А то и к ноге припаяться напрямую.
      • 0
        У меня есть восьми канальный логический анализатор. Если устройство изначально с GPIO (малинка, роутеры и тд), то найти не проблема. А вот есть ли они на обычных мамках и если есть то как выглядят и как найти.
        • 0
          Почему отметается вариант с использованием светодиодов?
          Запаять на такой случай полигон с выводами.
          Все сразу станет ясно)
          • 0
            Иногда, просто бывает лень много паять)
        • 0
          На обычных Desktop мамках GPIO, как правило, используется для определения BoardID и разводится на специальные перемыки. Но, во-первых: попытка отпаять перемычку может снести голову BIOS'у во время инициализации, а во-вторых: драйвера на такой GPIO все-равно нету. В ноутах GPIO еще используется для всяких там светодиодиков, кнопочек и для управления питанием WiFi и Bluetooth модулей. Но и в этом случае, управление за пределы BIOS'а обычно не передается, а заводится на ACPI. В общем — никак.
          • 0
            Да ладно?
            Вроде как sysfs driver стандартный. Как VESA, работает ограниченно, но зато на всем оборудовании.
  • +1
    Вместо пары lseek() + read() можете попробовать использовать один вызов pread().
    • 0
      Да, точно, можно.
  • 0
    а с какой частотой работают контролер GPIO средне-обычного SoC?
    С частотой процессора или внешнего тактового генератора?
    Или он полностью асинхронен?
    • 0
      Чаще всего контролер GPIO находится на шине APB, которая тактуется у разных SoC по разному, но высоких частот там обычно нет, максимум — 66 или 100 Мгц, конкретно для GPIO больше 50 Мгц я не видел нигде, кроме дорогих FPGA.
      • 0
        Спасибо!
        А при использовании прерываний сколько еще можно потерять циклов процессора на переключениях кернел-юзер спэйс?
        • 0
          Даже крупные корпорации в таком случае предпочитают прямой эксперимент. Это очень сильно зависит от платформы и версии ядра/драйвера.
  • +3
    а как настроить ногу на вывод сразу на логическую '1' без
    echo out > /sys/class/gpio/gpio16/direction
    echo 1 > /sys/class/gpio/gpio16/value
    в этом случае кратковременно появляется 0 на выходе.
    Да, еще у вас первый пример кода не правильный
    надо
    echo out > /sys/class/gpio/gpio16/direction
    • 0
      Думаю, если у вас к GPIO подключено нечто, что чувствительно к этому первоначальному импульсу, то нужно посмотреть в сторону написания модуля ядра. Все-таки интерфейс sysfs предназначен для простых случаев.

      Да, еще у вас первый пример кода не правильный.

      Спасибо, исправил.
      • 0
        а вот, например, если так?

        echo 16 > /sys/class/gpio/export
        echo 1 > /sys/class/gpio/gpio16/actiwe_low
        echo out > /sys/class/gpio/gpio16/direction
        • 0
          Да, конечно, так можно. Но придется мириться с инвертированной логикой.
    • +2
      Как ни странно,
      echo high > /sys/class/gpio/gpio16/direction

      и соответственно
      echo low > /sys/class/gpio/gpio16/direction
      • 0
        действительно работает.
  • +1
    Добавлю, что прерывания (POLLPRI) генерируются не на всех пинах. И если не нашли edge в настройках — смотрите документацию к Вашей плате/SoC'у.
  • 0
    Это можно сделать и при помощи пары драйверов gpio-keys для кнопок и… не помню как называется тот, что для светодиодов, ну это на случай если нужно не побаловаться.
  • +3
    Интересно, а есть ли специализированные платы-контроллеры GPIO, которые можно воткнуть, например, в USB или в PCI-Express-слот компа?
    • 0
      Поддерживаю вопрос про специализированные платы. Подскажите, какой самый дешевый способ получить вход/выход GPIO?
      • +2
        Есть. Можно купить плату.

        Например, такую
        • 0
          Спасибо, как раз сам смотрю на неё. Там есть проблема — драйвера только для Windows, кажется. Я не очень понимаю, насколько это критично. Драйвер usb-to-serial bridge, наверное, и под Linux есть.
          • +1
            Да, определяется как /sys/class/tty/ttyACM0

            • 0
              Спасибо за ответ. Попробую заказать, наверное.
            • 0
              Если можно, ещё один глупый вопрос. Почитал мануал. Правильно ли я понимаю, что состояние переключателя мне придётся определять постоянным опросом устройства? С какой частотой это можно делать? Скажем, если я буду делать опрос 4 раза в секунду (думаю, это нормальное время реакции на переключение тумблера) — скажется ли это на производительности компьютера? Ведь мой процесс будет постоянно забирать CPU, а частота переключения процессов не такая и большая.
              • 0
                Собственно, чем мне понравился пример из статьи — что там подразумеваются прерывания по смене уровня. В этой железке такое, видимо, не поддерживается.
                • 0
                  Конечно, раз устройство висит на какой-то шине, вы неизбежно упираетесь в ее производительность.
                  Генерировать прерывания будет драйвер устройства. То есть, это не будет отличаться от обычного общения с устройством ввода-вывода через tty.
                  • 0
                    В том-то и дело, что это конкретное устройство, как я понимаю, не генерирует прерываний при смене уровня. В мануале в разделе «Using GPIOs with switches» написано, что «This change in voltage and thus the position of the switch can be read using “gpio read”». То есть, только постоянный опрос.
                    • 0
                      Да, похоже что так.

                      Тогда печаль. Похоже что данное устройство только поллингом.

                      Остается реализовать через ардуину. Тогда можно генерировать прерывания.
      • +1
        Я тоже отвечу на этот вопрос. Платы называются: «Платы цифрового ввода-вывода». Имеют промышленное назначение и, соответственно, конские цены. Вот пример такой платы, правда без цены: PCIE-1730: 32-ch TTL and 32-ch Isolated Digital I/O PCI Express Card. В случае USB получится медленный GPIO, т.к. данные передаются фреймами длиною 1мс. Будет что-то около 500Гц. Для кнопки и cетодиода, конечно хватит.
        • 0
          173 евро. Да, немало, если нужно для кнопки и светодиодика.
        • 0
          А для чего-то большего проще с ардуиной скрестить. Благо цена ей около 200 рублей розничная.
    • 0
      Думаю, на Ардуино что-то уже склепали. Если конечно правильно понял вопрос
  • 0
    Я давно уже хочу сделать включение/выключение видеотрансляции на одном объекте внешним тумблером со светодиодом, чтобы не забывали отключать передачу видео после окончания мероприятий. Вы меня вдохновили вернуться к этому вопросу. Может, кстати, какой-то другой метод есть, попроще?

    Кстати, я правильно понимаю, что обработка дрожаний кнопки — на совести программиста? То есть, если я захочу повесить какой-то скрипт на переключение кнопки, то делать это надо с умом, чтобы не запустить включение-выключение слишком часто?
    • –1
      Делать вообще нужно с умом. :) А дребезг в принципе можно гасить и железом (если мне память не изменяет, триггер Шмитта с этой задачей справляется без проблем).
      • +2
        изменяет
      • 0
        Да, проще программно бороться. Либо low-pass фильтр применить, который будет убирать короткие импульсы, либо не реагировать на GPIO какое-то время после получения первого фронта. В старом журнале ЮТ видел аппаратную реализацию именно второго метода, когда преключался тригер по импульсу от кнопки, а в изначальное состояние возвращался благодаря RC чепочке спустя какое-то время.
      • 0
        • 0
          А почему нельзя просто конденсатор повесить?
          • 0
            не наш метод :)
            контакт обгорит, если без резистора.
            а если с ним, то тогда: кнопка — резистор — конденсатор — триггер Шмитта
          • 0
            Конденсатор заряжается не мгновенно, в отличие от сработки триггера. А сбросить триггер можно и с помощью RC-цепочки через некоторое время. Так и делается аппаратное гашение дребезга.
            • 0
              часто бывает нужно фиксировать как момент нажатия, так и момент отпускания кнопки
    • +1
      Делал подобную вещь просто через COM порт + скрипт на python + PyCOM. Ведь кроме входа/выхода данных там есть еще 2 логических выхода и 4 входа. Причем, как помните, тока этих выходов хватало питать ИК-светодиоды + контроллер COM-портовой мыши.

      Про обработку дребезга контактов нужно вспоминать при частотах опроса порядка сотни Гц или если используется прерывание, которое так же быстро может быть обработано. Такой выключатель нет смысла опрашивать чаще 3 Гц, про дребезг можно забыть.
      • 0
        Спасибо за ответ.

        Я правильно понимаю, что при таком подходе нужно будет опрашивать COM-порт 3-4 раза в секунду? Поделитесь опытом, как процесс ведёт себя при таком частом опросе? Сколько % CPU съедает?
        • +1
          Позвольте я отвечу: 3-4 раза в секунду — это ниразу не часто. Польте на здоровье. Никакой нагрузки на процессор не будет. Не забывайте, только паузы чем-то вроде sleep() забивать. Stackoverflow говорит, что в python для этого подойдет time.sleep(1/4), но я в питоне не силен.
          • 0
            Спасибо за ответ :-) Попробую так и сделать.
  • 0
    Светодиоды без усиления по току подключать — грустная затея. Так ненароком можно перегрузить МП. Особенно если больше одного светодиода используете. Лучше уж взять I2C расширитель портов. Благо они сейчас стоят не намного дороже тех же светодиодов.
    • 0
      Как правило, GPIO порты — это не просто голый CMOS, а специализированное устройство, с относительно мощными выходными транзисторами, защитными диодами, входными буферами и, даже, подтягивающими резисторами. Усилители нужно ставить если предполагаемый ток будет больше номинального.
      • 0
        Это вы про МК или всё же про МП? А если про МП то я хочу видеть datasheet на такой МП в котором можно подрубать светодиоды через резистор как у вас на схеме — т.к. у меня вышел ток превышающий норму по току на один вывод для всех известным мне МП.

        Как ни крути ни на одной схеме из хорошо сделанных плат (в том числе и отладочных) никто не пытается напрямую управлять светодиодами.

        И да вот схема простого опыта с BBB — тут сопротивление 470 Ом, что позволяет уложится в разрешённый ток — 8 мА на вывод. Но на порт ограничение 20 мА.
        • 0
          Ok, пускай будет нельзя подключать светодиоды напрямую к выводам GPIO SoC'а. Статья вообще-то не про это, а про Linux и sysfs.

          Кстати, вот тут, например, вообще 270Ом резистор поставили.
          • 0
            Нету закона запрещающего стрелять себе в ногу. Однако учить этому других не следует. По разным причинам.
            Как минимум потому, что по таким неточностям человек может пожечь себе порты у МП. Что опять же, как минимум приведёт к негативной реакции этого человека.

            Но не будет усугублять ситуацию.

            Теперь что касается статьи — в целом рассказ идёт о некотором эксперименте, в ходе которого мы вроде как помигали светодиодом и заюзали кнопку. Всё.
            Вся статья наполнена недоверием и неуверенностью. Что вызывает недоумение и недоверие к автору.

            Далее нету вообще никакой теоретической части, рассказов про sysfs, GPIO, SoC, Linux. Нету ни одной схемы того самого GPIO, или хотя бы ссылок на datasheet или же исходные коды Linux kernel. Кстати ваш метод — находим GPIO методом тыка — трижды неправильно:
            1) выводы могут быть действительно использованы ядром в служебных целях.
            2) выводы могут быть подключены к чему либо
            3) к выводам может быть подключена периферия которая поддерживает другой диапазон напряжений — например контроллер памяти, дисплея или АЦП(первые два требуют в основном 2.5 В, АЦП — 1.8 В).

            Никакой конкретики, даже нету описания того какая плата была использована, описания окружения и прочее.

            А то что вы нашли «методом тыка» экспериментальным путём можно спокойно узнать даже из документации на плату, BSP Linux Kernel, datasheet'a наконец, или же просто воспользовавшись удобным для вас поисковиком.
      • 0
        Вот кстати этот эксперимент — learn.adafruit.com/blinking-an-led-with-beaglebone-black/you-will-need
        Меня немого поправили — ток всего 4 мА на вывод.
  • 0
    Полезная статья. Положил в закладки.

    Есть пара вопросов:
    — что можете посоветовать почитать по поводу управления сервомоторами через Raspberry Pi?

    — в примере считывания пин замыкается кнопкой на плюс.
    Это лучше чем подтяжка пина к плюсу резистором и замыкание кнопкой на землю?
    • +1
      Прошу прощения что встреваю, но можете обратить внимание на эту статью.

      http://habrahabr.ru/post/235207/
  • 0
    — что можете посоветовать почитать по поводу управления сервомоторами через Raspberry Pi?

    Ни чем не могу вам помочь — ни разу не имел дело с сервомоторами.

    — в примере считывания пин замыкается кнопкой на плюс.

    Оба варианта хорошие. В вашем — сигнал просто получается инвертированным. И в том и в том случае ток идет только при нажатой кнопке.
    • +1
      Насчет кнопок это просто на душе спокойней, когда Vcc наружу торчит через десяток килоом, чем когда оно выведено напрямую.
  • –2
    Не троллинга ради, но:
    >>мне пришлось потратить почти часа два
    >>В документации Linux эти моменты почему-то обойдены.
    Вот эти моменты обязательно должны освещаться лектором при знакомстве студентов с миром «опенсорса».
    Просто заострять внимание, что в любом случае вы заплатите: либо за проприетарный софт (с призрачной возможностью потребовать качественных доков) либо — в случае опенсорса — консалтинговой фирме (или своим программистам)
    • +1
      Я совершенно бесплатно связался с разработчиком модуля ядра Claudio Mignanti и проконсультировался.
      За что ему респект.
      Но это после того как покопался в коде и не все понял. В большинстве случаев вполне хватает и просмотра кода. опять же бесплатно.
      Если не помогли бесплатные мануалы и бесплатные форумы.

      Впрочем, подобную сугубо менеджерскую логику я слышу не первый раз. К счастью, она почти ничего общего с реальностью не имеет.
  • 0
    Для каких проблем то, что я только что прочитал, используют?
    • 0
      Индикация событий?
    • 0
      На картинке фотография платы с двумя светодиодами и кнопкой:
      светодиод 1 — устройство подключено к сети через VPN;
      светодиод 2 — к устройству подключен клиент;
      кнопка, короткое нажатие — мягкая перезагрузка;
      кнопка, длинное нажатие — сброс настроек по умолчанию.

      Это так использую я в одном конктетном случае. Как это можно использовать еще — зависит от вашей фантизии.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.