В предыдущей статье я писал об организации индикации на светодиодах.
О работе с кнопками на Ардуино написано очень много. В этой статье я напишу свое видение, как удобно разрабатывать управление микроконтроллера с использованием тактовых кнопок, подключенных к цифровым входам.
Не буду вдаваться в подробности схем подключения кнопок к цифровым (и не только) входам микроконтроллера. В данной статье рассматривается кнопка, подтянутая резистором к плюсу и замыкающая вход на землю. Поэтому состояния нажатой кнопки LOW, а отпущенной HIGH.
Тип кнопки не имеет значения, например может быть таким
Самый простой скетч работы с кнопкой выглядит так
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#define PIN_BUTTON 4 void setup() { Serial.begin(115200); Serial.println("Test button ..."); pinMode(PIN_BUTTON, INPUT_PULLUP); } void loop(){ if( digitalRead(PIN_BUTTON) == LOW ){ Serial.println("Press key"); delay(1000); } } |
Оператор delay(1000) задает задержку между нажатиями кнопки. Если время нажатия превысит 1000 мс, то происходит автоматическое повторное нажатие кнопки. Такой метод может разве что сгодиться для отладки, но в реальной жизни мало применим, так как delay не дает выполнятся остальным операторам скетча (если только не используются прерывания) и ограничивает минимальное время между нажатиями кнопки.
В следующем скетче попробую запоминать состояния кнопки
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#define PIN_BUTTON 4 bool button_state = false; void setup() { Serial.begin(115200); Serial.println("Test button ..."); pinMode(PIN_BUTTON, INPUT_PULLUP); } void loop(){ // Фиксируем нажатие кнопки if( digitalRead(PIN_BUTTON) == LOW && !button_state ){ button_state = true; Serial.println("Press key"); } // Фиксируем отпускание кнопки if( digitalRead(PIN_BUTTON) == HIGH && button_state ){ button_state = false; } } |
Все вроде бы работает, но иногда проскакивает два или более срабатывания. В чем же дело? Дело в дребезге контактов механической кнопки.
Обычное срабатывание кнопки выглядит так. Казалось бы все правильно.
Но иногда срабатывание может выглядеть и так
Или даже так
Особенно это заметно у изношенных или просто некачественных кнопок. Есть разные способы борьбы с этим эффектом, от установки конденсатора на цифровой вывод до целых электрических схем. Но у нас ведь есть целый микроконтроллер! Проанализировав работу кнопок, можно сделать вывод, что нажатие кнопки обычно длится более 150 мс, а дребезг контактов порождает импульсы длительностью 50 мс, ну очень редко чуть больше. Поэтому установив минимальное время между нажатиями кнопки в 50-100мс можно побороть эффект дребезга контактов «программным путем».
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
#define PIN_BUTTON 4 bool button_state = false; uint32_t ms_button = 0; void setup() { Serial.begin(115200); Serial.println("Test button ..."); pinMode(PIN_BUTTON, INPUT_PULLUP); } void loop(){ uint32_t ms = millis(); // Фиксируем нажатие кнопки if( digitalRead(PIN_BUTTON) == LOW && !button_state && ( ms - ms_button ) > 50 ){ button_state = true; ms_button = ms; Serial.println("Press key"); } // Фиксируем отпускание кнопки if( digitalRead(PIN_BUTTON) == HIGH && button_state && ( ms - ms_button ) > 50 ){ button_state = false; ms_button = ms; } } |
Иногда для уменьшения числа кнопок в устройстве применяют короткое и длинное нажатие на кнопку. В этом случае короткое нажатие фиксируется при отпускания кнопки А длинное нажатие при достижении заданного интервала. код получается такой.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
#define PIN_BUTTON 4 bool button_state = false; bool button_long_state = false; uint32_t ms_button = 0; void setup() { Serial.begin(115200); Serial.println("Test button ..."); pinMode(PIN_BUTTON, INPUT_PULLUP); } void loop(){ uint32_t ms = millis(); bool pin_state = digitalRead(PIN_BUTTON); // Фиксируем нажатие кнопки if( pin_state == LOW && !button_state && ( ms - ms_button ) > 50 ){ button_state = true; button_long_state = false; ms_button = ms; } // Фиксируем длинное нажатие кнопки if( pin_state == LOW && !button_long_state && ( ms - ms_button ) > 2000 ){ button_long_state = true; Serial.println("Long press key"); } // Фиксируем отпускание кнопки if( pin_state == HIGH && button_state && ( ms - ms_button ) > 50 ){ button_state = false; ms_button = ms; if( !button_long_state )Serial.println("Press key"); } } |
Иногда, например в часах для установки времени, применяется режим, когда длинное нажатие кнопки вызывает срабатывание с заданным интервалом. Для реализации этой функции будет такой код.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
#define PIN_BUTTON 4 bool button_state = false; bool button_auto_state = false; uint32_t ms_button = 0; uint32_t ms_auto_click = 0; void setup() { Serial.begin(115200); Serial.println("Test button ..."); pinMode(PIN_BUTTON, INPUT_PULLUP); } void loop(){ uint32_t ms = millis(); bool pin_state = digitalRead(PIN_BUTTON); // Фиксируем нажатие кнопки if( pin_state == LOW && !button_state && ( ms - ms_button ) > 50 ){ button_state = true; button_auto_state = false; ms_button = ms; } // После 2000 мс нажатия кнопки каждые 500 мс фиксируем событие нажатия if( pin_state == LOW && ( ms - ms_button ) > 2000 && ( ms - ms_auto_click )>500 ){ button_auto_state = true; ms_auto_click = ms; Serial.println("Auto press key"); } // Фиксируем отпускание кнопки if( pin_state == HIGH && button_state && ( ms - ms_button ) > 50 ){ button_state = false; ms_button = ms; if( !button_auto_state )Serial.println("Press key"); } } |
Все это хорошо и функционально, но очень уж громоздко. Поэтому я убрал работу с кнопками в класс.
В конструкторе класса указывается цифровой вход кнопки. А также четыре необязательных параметра:
- Таймаут для игнорирования дребезга контактов (По умолчанию 50 мс )
- Время длинного нажатия кнопки, мс. Если 0, то длинное нажатие не фиксируется. (По умолчанию отключено)
- Время удержания кнопки, после которого происходит автонажатие кнопки. Если 0, то не происходит. (По умолчанию отключено)
- Интервал срабатывания кнопки при автонажатии (По умолчанию 500 мс)
Функция begin() производит инициализацию цифрового входа.
Функция loop() вызывается в основном цикле или по таймеру, но достаточно часто и возвращает следующие значения:
- SB_NONE — ничего не произошло
- SB_CLICK — событие срабатывания кнопки
- SB_AUTO_CLICK — событие автонажатия кнопки при длинном удержании
- SB_LONG_CLICK — событие длинного нажатия кнопки
Пример работы с данным классом выглядит так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
/** * Работа с кнопками * Copyright (C) 2016 Alexey Shikharbeev * http://samopal.pro */ #include "sav_button.h" SButton button1(4,50,2000,4000,500); SButton button2(5,50,2000,4000,500); void setup() { Serial.begin(115200); Serial.println("Test smart button ..."); // Инициация кнопок button1.begin(); button2.begin(); } void loop(){ switch( button1.Loop() ){ case SB_CLICK: Serial.println("Press button 1"); break; case SB_LONG_CLICK: Serial.println("Long press button 1"); break; case SB_AUTO_CLICK: Serial.println("Auto press button 1"); break; } switch( button2.Loop() ){ case SB_CLICK: Serial.println("Press button 2"); break; case SB_LONG_CLICK: Serial.println("Long press button 2"); break; case SB_AUTO_CLICK: Serial.println("Auto press button 2"); break; } } |
Можно использовать для фиксации событий кнопки таймер при помощи библиотеки TimerOne, о которой я писал в предыдущей статье
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
/** * Работа с кнопками * Copyright (C) 2016 Alexey Shikharbeev * http://samopal.pro */ #include "TimerOne.h" #include "sav_button.h" SButton button1(4,50,2000,4000,500); SButton button2(5,50,2000,4000,500); SBUTTON_CLICK button_state1 = SB_NONE; SBUTTON_CLICK button_state2 = SB_NONE; // Callback функция по таймеру void timerIsr(){ button_state1 = button1.Loop(); button_state2 = button2.Loop(); } void setup() { Serial.begin(115200); Serial.println("Test smart button ..."); // Инициация кнопок button1.begin(); button2.begin(); // Инициализация таймера Timer1.initialize(10000); Timer1.attachInterrupt( timerIsr ); } void loop(){ if( button_state1 != SB_NONE ){ Serial.print("Press button 1 mode "); Serial.println(button_state1); button_state1 = SB_NONE; } if( button_state2 != SB_NONE ){ Serial.print("Press button 2 mode "); Serial.println(button_state2); button_state2 = SB_NONE; } } |
Данный класс можно адаптировать для работы с «аналоговыми кнопками» (несколько кнопок подключенных к аналоговому входу через делитель с разными номиналами сопротивлений).
Спасибо всем кто осилил )))
Алексей, спасибо за статью по кнопкам. Я не совсем понял — что за параметры передаются в класс. 4 — пин дуины. 50 и 500 тайминги короткого и длинного нажатий. А остальное (2000 и 4000) что такое?
Спасибо.
/**
* Конструктор класса кнопки
* Кнопка это цифровой пин подтянутый к питанию и замыкаемый на землю
* Событие срабатывания происходит по нажатию кнопки (возвращается 1)
* и отпусканию кнопки (возвращается время нажатия кнопки, мсек)
* tm1 — таймаут дребезга контактов. По умолчанию 50мс
* tm2 — время длинного нажатия клавиши. По умолчанию 2000мс
* tm3 — врямы перевода кнопки в генерацию серии нажатий. По умолсанию отключено
* tm4 — время между кликами в серии. По умолчанию 500 мс. Если tm3 = 0 то не работает
*/
SButton::SButton(uint8_t pin,uint16_t tm1, uint16_t tm2,uint16_t tm3, uint16_t tm4){
Уважаемый Алексей ! а как замутить код короткого и длинного нажатия двух кнопок ик пульта для управления лед диммером ? тоесть первую коротко нажали включение 255 вторую коротко нажали 0 первая длинное удерживание яркость + аналогично вторая яркость — . За ранее спасибо !
Alex,добрый день.
Сможете помочь ( не бесплатно)? Нужен скетч на ардуино:
подключение к резистивной клавиатуре,
при нажатии на определенные кнопки должен появляться минус на выводах ардуино (4 вывода)
при длительном нажатии — длительный минус на выходе
при коротком нажатии — короткий минус.
в скетче в идеале возможность менять значение напряжения для подстройки по месту.
Извиняюсь. Правильночитание у меня хромает. Оно хорошее, но почему-то хромает. (С)
Большое спасибо за полезную и качественную + подробную статью!)))) Очень полезно!
Алексей, спасибо за статьи, эту и другие. Как задать параметры tm2, tm3, tm4 если мне не нужно у кнопки использовать длинное нажатие и серии нажатий?
И можно ли использовать этот класс для реализации кнопки включения/выключения (длинное нажатие — прибор выключается, короткое — включается).
Если не нужно длинное нажатие и автоповторение, то tm2-tm4 = 0
Для включения выключения класс использовать можно, но при этом придется вручную задавать режим сна при длинном нажатии и в этом режиме кнопку посадить на прерывание, чтобы из режима сна выходить. Иначе выключение не имеет смысла — потребление контроллера будет большим
Спасибо огромное автору за статью.Респект.
Алексей, огромное спасибо за статью, за «Класс для работы с кнопками». Мне, как новичку это очень помогло, взял на вооружение и буду пользовать с благодарностью. Кнопочки работают просто шик, но нехватка свободных пинов…, вынуждает обратиться с просьбой о помощи, адаптировать данный класс для работы с «аналоговыми кнопками». Перекопал кучу материала, но из-за нехватки опыта, знаний а может быть уже и возраста одолеть эту задачку самостоятельно не удается.
Хорошо. Научились нажимать кнопку и отправлять её эхо на комп. Как теперь прикрутить светодиод на эту кнопку? К примеру кнопка на D2 светик на D3
Я только начал разбираться в этой теме.
http://samopal.pro/arduino-led/
Там примеры от простого к сложному
Большое спасибо за статью. Пересмотрел кучу примеров, но Ваши примеры оказались наиболее легки для понимания. Строю управление светом в доме:
плавное вкл.\выкл.
case SB_AUTO_CLICK:
if (brightness == 0 || brightness == 255) {
fadeAmount = -fadeAmount ;
}
Двойной клик в Ваш класс добавить возможно? Для записи в eeprom максимальной яркости светодиода.
Спасибо! очень хороший пример! Но не могу сообразить как? «Данный класс можно адаптировать для работы с «аналоговыми кнопками» (несколько кнопок подключенных к аналоговому входу через делитель с разными номиналами сопротивлений).»
Если не трудно прдскажите куда копать?
Здравствуйте, Алексей. Я, к сожалению, никогда не сталкивался с ардуино. Но мне очень нужно подключить три кнопки к ардуино про мини, для управления компьютером ( например: Предыдущий трек, следующий трек и пауза и играть). Я могу воспользоваться Вашим классом и схемой подключения кнопок? Спасибо
Можете
Для этого код и публикуется
Спасибо, просто беру и заливаю его как есть?
Здравствуйте, Алексей! Очень удобная библиотека, спасибо. Попробовал ее применить, но столкнулся с неприятной вещью:
«Long press button 1» посылается в Монитор последовательного порта еще до отпускания кнопки. Поэтому при удержании кнопки более 4 секунд, сначала передается сообщение «Long press button 1», а уже потом «Auto press button 1». Т.е. передается паразитное сообщение. Может это как-то можно исправить?
Разобрался. Видимо, для использования Auto press button нужно отключать Long press button.
Всем большой ПРИВЕТ! Помогите вставить две кнопки,нажатие на первую запускает движение в одну сторону и потом мотор просто останавливается,а нажатие на вторую кнопку запускает процесс в обратную сторону.Если нажимать одну и туже кнопку то мотор каждый раз начинает движение в одну сторону.
#include
#define HALFSTEP 8
// Определение пинов для управления двигателем
#define motorPin1 3 // IN1 на 1-м драйвере ULN2003
#define motorPin2 4 // IN2 на 1-м драйвере ULN2003
#define motorPin3 5 // IN3 на 1-м драйвере ULN2003
#define motorPin4 6 // IN4 на 1-м драйвере ULN2003
// Инициализируемся с последовательностью выводов IN1-IN3-IN2-IN4
// для использования AccelStepper с 28BYJ-48
AccelStepper stepper1 (HALFSTEP, motorPin1, motorPin3, motorPin2, motorPin4);
void setup (){ stepper1.setMaxSpeed (800.0);
stepper1.setAcceleration (100.0);
stepper1.setSpeed (200);
stepper1.moveTo (20000);
} void loop (){
// Изменяем направление, если шаговик достигает заданного положения
if (stepper1.distanceToGo ()==0)
stepper1.moveTo (-stepper1.currentPosition ());
stepper1.run (); }
в этом скетче мотор не останавливаясь крутится то в одну то в другую сторону медленно разгоняясь и медленно останавливаясь.
Цитата: «Данный класс можно адаптировать для работы с «аналоговыми кнопками»»
Можно пример сдвумя кнопками, плиз?
Нужно чтобы при нажатии одной кнопки каждый раз выводились разные сообщения на дисплее с помощью lcd.print(). У кого есть соображения?
А в чем проблема?
Выводите информацию по нажатию кнопки. Секунды через 3-5-10 lcd.clear()
Проблема в том что функция void loop() не даст этого сделать так как имеет замкнутый цикл, и для того чтобы выводить разные сообщения при нажатии одной кнопки нужно воспользоваться операцией сравнения true и false. Тогда возможно что через каждые 3 секунды будет высвечиваться новое сообщение, а через 5 при отпускании кнопки остановится на оном. Вероятно что придется воспользоваться внутренней памятью ядра EEPROM так как собственной памяти для хранения данных в цикле void loop() нет.
Как сделать чтобы после Auto press key срабатывала кнопка «отпускание»
Помогите пожалуйста, как сделать что бы время нажатия кноки просто выводилось в монитор портов?
Есть такая функция millis(). При помощи ее запросто можно сосчитать сколько кнопка была нажата.Для этого нужно запомнить значение millis() в момент нажатия, и значение millis() во время отпускания кнопки. Разность этих значений и будет время нажатия кнопки в миллисекундах.
МУЖЧИНЫ кто согласится написать скетч за вознаграждение,к примеру на телефон.
пишите по поводу написания скетча palkann@mail.ru
Просто супер. Молодец Алексей. Спасибо тебе от всей души. Я целый день этот велосипед изобретал, но как-то не совсем заработало. )))
Алексей, а можно добавить опцию двойного клика ?
У меня в одном скетче есть реализация подсчета числа кликов за единицу времени. Можно прикрутить хоть двойной, хоть тройной. Постараюсь код оформить для нормального использования
Большое спасибо. Отличная работа. Для себя добавил начальное состояние кнопки. у меня кнопка по умолчанию к земле прижата.
Все работает в лучшем виде!
Спасибо! Часто использую! Подскажите, как пин аналогового входа обзывать в библе? Ide Ардуино. Скажем, SButton button1(А4, 50, 0, 0, 0) — выдает ошибку
Для Arduino UNO/ NANO/ PRO MINI
A0 — 14
A1 — 15
A2 — 16
A3 — 17
A4 — 18
A5 — 19
Добрый день Алексей все понравилось хоть много не понял. Я в свое время заказывал скетч по управлению мышки и использовал его на arduino ATmega32U4 скетч написан был прям для чайников заполняйте алгоритм движения в массивах заливаешь и мышки выполняет алгоритм и не чего не надо паять долгое время пользовался но вот возникла проблема как раз по вашей теме, нажатие левой клавиши я могу вызывать а вот удержание по времени нет. Может вы бы могли прочитать посмотреть скетч до работать к сожалению того кто его писал уже не найти координаты утеряны сам этими навыками не владею.буду рад услышать ваши условия заранее спс
Есть часы на DS3231 и 7-ми сегментниках.
Хочется сделать удобную ручную 2-х кнопочную настройку следующей схемы:
кн1 — режим установки ЧАСА кн2 — изменение значения
кн1 — режим установки МИНУТ кн2 — изменение значения
кн1 — режим отображения значения СЕКУНД кн2 — сброс значения секунд на 00 (возможность многократного нажатия)
кн1 — выход из режима установки.
Вот бы такой кусочек кода…
Доброго времени суток.
Новичок, осваиваюсь в программировании.
Подскажите пожалуйста как убрать подтяжку, а то у меня готовые кнопки из наборы.
Спасибо
pinMode(PIN,INPUT);
Спасибо! Вопрос: пин 2. Если разрешить прерывание, то оно также по кнопке отработается?
Да. Только там контроль дребезга нужно отдельно реаоизовывать
В принципе, можно SButton.Loop() повесить на прерывание
Большое спасибо за статью! Очень пригодилось для моего проекта.
Автор, огромное спасибо за подсказку!!!
Очень помогла херота с длительностью нажатия кнопок!
Ненавижу С и подобное говно и вообще программировать, а ты просто зарешал!!!
Признателен браток!
Всем привет и хорошего настроения. Помогите пожалуйста мне написать простой скетч, защита от двойного включения кнопок.
На примере:
Если первая нажата кн 1 то она блокирует нажатие кн2
Или первая нажата кн 2 то она блокирует нажатие кн1
Или другой вариант :
Нажимаем кн 1 горит светодиод 1
(отпускаем не горит)
Нажата кн2 горит светодиод 2
(отпускаем не горит)
При случайном нажатии двух кнопок оба светодиода потухают.
(пример защиты кнопок от двойного включения на кранбалках или каких-то реверсах)
int BUTTON_A=2;
int LED_A=4;
int BUTTON_B=3;
int LED_B=5;
void setup () {
pinMode(LED_A,OUTPUT);
pinMode(BUTTON_A,INPUT_PULLUP);
pinMode(LED_B,OUTPUT);
pinMode(BUTTON_B,INPUT_PULLUP);
}
void loop () {
if(digitalRead(BUTTON_A)==HIGH){
digitalWrite(LED_A,LOW);
}else{
digitalWrite(LED_A,HIGH);
}
if(digitalRead(BUTTON_B)==HIGH){
digitalWrite(LED_B,LOW);
}else{
digitalWrite(LED_B,HIGH);
}
}
Как можно сделать в этом скетче,чтобы кнопки BUTTON_A и BUTTON_B не могли работать одновременно, а блокировали друг друга.
Подскажите пожалуйста кто сможет, весь интернет перерыл подходяшего примера даже близко нент.
Везде только примеры
Включить, выключить, клики, выдержки,счётчики и тд.
if( digitalRead(BUTTON_A) && !digitalRead(BUTTON_B) ){
digitalWrite(LED_A,HIGH);
digitalWrite(LED_B,LOW);
}
else if( !digitalRead(BUTTON_A) && digitalRead(BUTTON_B) ){
digitalWrite(LED_A,LOW);
digitalWrite(LED_B,HIGH);
}
else {
digitalWrite(LED_A,LOW);
digitalWrite(LED_B,LOW);
}
Спасибо большое за помощь. Удачи в работе и во всём.
int ledPin1=4;
int ledPin2=5;
int buttonPin1=2;
int buttonPin2=3;
void setup () {
pinMode(buttonPin1,INPUT_PULLUP);
pinMode(buttonPin2,INPUT_PULLUP);
pinMode(ledPin1,OUTPUT);
pinMode(ledPin2,OUTPUT);
}
void loop () {
int pin1State=digitalRead(buttonPin1);
int pin2State=digitalRead(buttonPin2);
if(pin1State==LOW && pin2State==HIGH) {
digitalWrite(ledPin1,HIGH);
}else{
digitalWrite(ledPin1,LOW);
if(pin2State==LOW && pin1State==HIGH) {
digitalWrite(ledPin2,HIGH);
}else{
digitalWrite(ledPin2,LOW);
Три дня учился мучился но получилось. Вот этот скетч тоже заработал.
(при нажатии двух кнопок оба светодиода потухают)
int ledPin1=4;
int ledPin2=5;
int buttonPin1=2;
int buttonPin2=3;
void setup () {
pinMode(buttonPin1,INPUT_PULLUP);
pinMode(buttonPin2,INPUT_PULLUP);
pinMode(ledPin1,OUTPUT);
pinMode(ledPin2,OUTPUT);
}
void loop () {
int pin1State=digitalRead(buttonPin1);
int pin2State=digitalRead(buttonPin2);
if(pin1State==LOW && pin2State==HIGH) {
digitalWrite(ledPin1,HIGH);
}else{
digitalWrite(ledPin1,LOW);
}
if(pin2State==LOW && pin1State==HIGH) {
digitalWrite(ledPin2,HIGH);
}else{
digitalWrite(ledPin2,LOW);
}
}
(при нажатии двух кнопок оба светодиода потухают)
Добрый день. Могу ли я попросить у Вас помочь мне?. Нужно управлять 4-мя реле От кнопок без фиксации.Для каждого канала свое реле и кнопка.При нажатии на кнопку №1 включается реле№1 и остается включенным. При нажатии на одну из других кнопок отключается реле№1 и включается соответствующий реле.
Спасибо Вам большое!
Всем доброго дня! Помогите пожалуйста написать скетч :
Светодиод зажигается если держать нажатой кнопку в течение 1 секунды.
Чтобы погасить светодиод нужно нажать кнопку кратковременно (без
использования функции delay()).
Самая классная библиотека управления кнопками !!
Перепробовал многие, но хорошее (четкое, однозначное) срабатывание только с этой библиотекой.
Как отблагодарить? 😉
Alexey, вы пишите, что ваш скетч возможно адаптировать под аналогово-резестивную клавиатуру. подскажите как это возможно, моих познаний не хватает