01 |
Для того, чтобы понять, что такое дребезг, соберем очень простую схему с Arduino, кнопкой и светодиодом. При нажатии на кнопку светодиод должен загораться, если он не горит. При следующем нажатии на кнопку светодиод должен гаснуть. Схема подключения: |
|
02 |
|
Не забываем о стягивающем резисторе 10 КОм от пина 8 и ноги кнопки к земле. О том для чего нужны стягивающие и подтягивающие резисторы можно почитать в статье Подтягивающий (стягивающий) резистор - что это, и зачем он нужен.
|
03 |
Скетч: |
|
04 | Arduino (C++) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 int pinButton = 8; // Пин кнопки
int pinLED = 7; // Пин светодиода
void setup() {
pinMode(pinButton, INPUT);
pinMode(pinLED, OUTPUT);
}
bool flag = LOW;
bool lastButton = LOW;
void loop() {
int currentButton = digitalRead(pinButton); // Считываем значение пина кнопки
if (lastButton == LOW && currentButton == HIGH) { // Если состояние кнопки "нажата" - HIGH, а предыдущее LOW - фиксируем событие нажатия кнопки
flag = !flag; // Меняем флаг статуса светодиода
digitalWrite(pinLED, flag); // В зависимости от флага, зажигаем или гасим светодиод
}
lastButton = currentButton; // Запоминаем последнее состояние кнопки
} |
|
05 |
После сборки схемы и заливки скетча, можно убедиться в том, что схема работает не так, как задумывалось — светодиод загорается и гаснет не всегда предсказуемым образом. Почему так происходит? Ответ на этот вопрос даст осциллограф, подключенный к 8 пину Arduino и земле GND. |
|
06 | На заметку: |
Вот и пришло время взрослеть начинающим электронщикам — тем, у кого нет осциллографа. Далее практический пример того, для чего нужен осциллограф и как его использовать.
Автором в демонстрации использован самый бюджетный вариант, доступный любому начинающему ардуинщику — осциллограф DSO138 (набор для самостоятельной сборки). Самое время обзаводиться осциллографом! |
|
07 |
На самом деле смыкание-размыкание контактов кнопки в реальном мире немного отличается от того, как этого бы хотелось: |
|
08 |
Дребезг — это явление между стабильными состояниями контактов
|
|
09 |
Это подтверждают данные осциллографа DSO 138: |
|
10 |
Даже с низкой частотой захвата виден дребезг на размыкании контакта
|
С обзором и инструкцией по эксплуатации осциллографа DSO138 (набора для самостоятельной сборки) можно ознакомиться в статье Осциллограф DSO 138 - инструкция по эксплуатации (основы, ликбез, что умеет и как работать?).
|
11 |
Логический анализатор Saleae Logic 16 нагляднее отображает дребезг из-за большей частоты сэмплирования: |
|
13 |
Итак, дребезг контактов (англ. bounce — дребезг, debounce — устранение дребезга) — явление, происходящее в электромеханических коммутационных устройствах, длящееся некоторое время после замыкания электрических контактов и состоящее из многократных неконтролируемых замыканий и размыканий контактов, обусловленных упругостью материалов и деталей контактной системы — некоторое время контакты «подпрыгивают» при соударениях, размыкая и замыкая электрическую цепь. |
|
14 |
Дребезг при нажатии кнопки, зафиксированный осциллографом Hantek DSO4202C
|
|
15 |
Но с этим вредным явлением можно справиться. При чем, это можно сделать 2 путями — программно и аппаратно (на уровне железа). |
|
16 |
Программное устранение дребезга. Для программного устранения дребезга, достаточно после первого изменения состояния кнопки, установить паузу в несколько милисекунд, для того чтобы контакт стабилизировался и дребезг был проигнорирован. Для этого дополним код функцией debounce(): |
|
17 | Arduino (C++) |
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 int pinButton = 8; // Пин кнопки
int pinLED = 7; // Пин светодиода
void setup() {
pinMode(pinButton, INPUT);
pinMode(pinLED, OUTPUT);
}
bool flag = LOW;
bool lastButton = LOW;
bool debounce() {
bool current = digitalRead(pinButton);
if (current != lastButton) { // Старое значение отличается от полученного
delay(10); // Ждем пока состояние стабилизируется - игнорируем дребезг
current = digitalRead(pinButton); // Считываем стабилизированное значение
}
return current;
}
void loop() {
int currentButton = debounce(); // Получаем стабилизированное значение
if (lastButton == LOW && currentButton == HIGH) { // Если состояние кнопки "нажата" - HIGH, а предыдущее LOW - фиксируем событие нажатия кнопки
flag = !flag; // Меняем флаг статуса светодиода
digitalWrite(pinLED, flag); // В зависимости от флага, зажигаем или гасим светодиод
}
lastButton = currentButton; // Запоминаем последнее состояние кнопки
} |
|
18 |
После добавления функции, стабилизирующей состояние кнопки, дребезг игнорируется. |
|
19 |
Аппаратное устранение дребезга. Аппаратное устранение дребезга в подавляющем большинстве случаев более предпочтительно программному, по ряду причин:
|
|
20 | Важно: |
Одна из главных причин применения аппаратного устранения дребезга заключается в частом использовании подобных сигналов в прерываниях. Программно решить проблему дребезга для «скармливания» сигналов прерываниям невозможно!
|
|
21 |
Начнем с рассмотрения RC-цепочки — последовательного включения в цепь резистора (10 КОм) и конденсатора (10 мкФ): |
|
23 |
Время, численно равное произведению RC, называется постоянной времени цепи RC и обозначается τ (не путать с временем заряда конденсатора $t$). |
|
24 |
$$τ = RC$$
|
|
25 |
При замкнутой цепи и заряде конденсатора, напряжение $U_с$ на его выводах будет увеличиваться от нуля до значения $U=5 В$ по экспоненте: |
|
26 |
$$\begin{equation}U_c = U(1 - e^{-t\over{RC}})\end{equation}$$
|
|
28 |
Из формулы $(1)$ можно выявить следующие закономерности (выделены на графике):
|
|
29 |
В нашем примере время зарядки конденсатора составит $10КОм \times 10мкФ = 0.1 сек$. |
|
30 |
Исходя из этих данных необходимо подбирать номиналы резистора и конденсатора. |
|
31 |
Для аппаратного устранения дребезга придется изменить принципиальную электрическую схему. В первую очередь необходимо резистор сделать не стягивающим, а подтягивающим, а во-вторых, параллельно с выводами кнопки, установить конденсатор. Он уберет дребезг: |
|
32 |
Слева — стандартное подключение кнопки со стягивающим резистором, справа — модифицированная схема для устранения дребезга
|
|
33 |
Теперь в состоянии покоя на 8 пине будет 5 В, в случае нажатия на кнопку, произойдет разряд конденсатора: |
|
35 |
После того, как кнопка будет отпущена, конденсатор начнет заряжаться за время 5τ.: |
|
37 |
Теперь необходимо дополнить схему дополнительным резистором R2 (он сделает работу схемы стабильнее — разрядка конденсатора будет происходить медленнее, скорость разряда зависит от номинала резистора) и инвертирующим триггером Шмитта — 74HC14N. |
38 | На заметку: |
Даташит на инвертирующий триггер Шмитта — sn74hc14.pdf.
|
|
40 |
Основными особенностями инвертирующего триггера Шмитта являются:
|
|
42 |
Результат: |
|
44 |
Устранение дребезга на аналоговом входе Данная ситуация отличается от вышеописанных и встречается в схемах подключения нескольких кнопок к одному аналоговому входу. Разница заключается в том, что интерпретация нажатия кнопки происходит путем вызова функции analogRead(), которая запускает механизм преобразования АЦП. И его погрешность не даст воспользоваться обычным ожиданием того момента, когда дребезг пройдет. Для устранения дребезга АЦП можно фиксировать приходящие значения и когда придет, например, 5-10 одинаковых значений можно говорить о нажатии кнопки. Но поскольку АЦП тоже имеет погрешность этот путь не очень эффективен. Эффективность метода можно повысить путем преобразования полученных данных в идентификатор кнопки, путем сравнения полученного значения с эталонным, скорректированным на допуск. И уже фиксировать полученные значения идентификатора кнопки. Таким образом погрешность АЦП будет нивелирована. |
|
45 |
Этот метод использован в коде примера №2 статьи Arduino: Подключение нескольких кнопок к одному аналоговому входу. Схема подключения оттуда же: |
Принципиальная схема |
47 |
Код: |
|
48 | Arduino (C++) |
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 46 47 48 49 50 51 52 53 int pinIn = A0;
int keyValue = 0; // Состояние покоя
void setup() {
pinMode(pinIn, INPUT);
Serial.begin(9600);
}
void loop() {
int newKeyValue = GetKeyValue(); // Получаем актуальное состояние кнопок с коррекцией дребезга
if (keyValue != newKeyValue) { // Если новое значение не совпадает со старым - реагируем на него
keyValue = newKeyValue; // Актуализируем переменную хранения состояния
if (keyValue > 0) { // Если значение больше 0, значит кнопка нажата
Serial.println("Key pressed: " + String(keyValue));
}
else { // Если 0, то состояние покоя
Serial.println("all keys are not pressed");
}
}
}
int GetKeyValue() { // Функция устраняющая дребезг
static int count;
static int oldKeyValue; // Переменная для хранения предыдущего значения состояния кнопок
static int innerKeyValue;
// Здесь уже не можем использовать значение АЦП, так как оно постоянно меняется в силу погрешности
int actualKeyValue = GetButtonNumberByValue(analogRead(pinIn)); // Преобразовываем его в номер кнопки, тем самым убирая погрешность
if (innerKeyValue != actualKeyValue) { // Пришло значение отличное от предыдущего
count = 0; // Все обнуляем и начинаем считать заново
innerKeyValue = actualKeyValue; // Запоминаем новое значение
}
else {
count += 1; // Увеличиваем счетчик
}
if ((count >= 10) && (actualKeyValue != oldKeyValue)) {
oldKeyValue = actualKeyValue; // Запоминаем новое значение
}
return oldKeyValue;
}
int GetButtonNumberByValue(int value) { // Новая функция по преобразованию кода нажатой кнопки в её номер
int values[5] = {0, 513, 615, 769, 1023};
int error = 15; // Величина отклонения от значений - погрешность
for (int i = 0; i <= 4; i++) {
// Если значение в заданном диапазоне values[i]+/-error - считаем, что кнопка определена
if (value <= values[i] + error && value >= values[i] - error) return i;
}
return -1; // Значение не принадлежит заданному диапазону
} |
|
49 |
Что почитать: |
|
50 |
Похожие запросы:
|
|