29 декабря 2016
Кравченко Виктор

Arduino: Подключение нескольких кнопок к одному аналоговому входу

Радиотехника и электроника Цифровые устройства Arduino
01

Речь в статье пойдет о двух разных схемах подключения большого количества кнопок к одному аналоговому входу Arduino. Принцип работы схем основан на чтении и интерпретации аналого-цифровым преобразователем микроконтроллера индивидуального напряжения, формируемого разными комбинациями отдельных участков схемы.

02
03

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

04

Схема №1 — резистивно-последовательная

Принципиально схема может исполняться в 2 вариантах и выглядит следующим образом:

05
Несмотря на кажущуюся схожесть схемы работают немного по разному
Несмотря на кажущуюся схожесть схемы работают немного по разному
06

Различия схем заключаются в том, что при нажатии кнопки S4 на левой схеме, аналоговый вход считает максимальное напряжение — 5 В, а кнопка S1 даст минимальное напряжение сниженное, всеми резисторами в цепи. В правой схеме, наоборот, максимальное напряжение даст кнопка S1.

07

Приведенные схемы — пример последовательного соединения резисторов. Как известно, для последовательного соединения резисторов:

08
09
$$R_{общ}=R_1+R_2+...+R_n$$
10

При нажатии любой кнопки в схеме мы получаем классический резистивный делитель напряжения:

11
12

В котором значения напряжений и сопротивлений находятся в зависимости, выражающейся следующей формулой:

13
$$U_{вых}={U_{вх} \times {R_2 \over {R_1+R_2}} }$$
14

Из формулы видно, что зависимость выходного напряжения $U_{вых}$ от сопротивлений $R_1$ и $R_2$ нелинейная, а экпоненциальная. График это подтверждает. Единственный нюанс заключается в том, что на оси абсцисс номиналы резистора R1, представлены в номиналах стягивающего резистора R2 $x=R_2$:

15
Например, если $R_2=10 КОм$, то при $R_1=3 \times R_2=30 КОм, U_{вых}=1,25 В$
Например, если $R_2=10 КОм$, то при $R_1=3 \times R_2=30 КОм, U_{вых}=1,25 В$
16

Исходя из графика можно наглядно представить каким образом рассчитать номиналы резисторов в зависимости от количества кнопок. И здесь, как обычно, возможны 2 варианта — либо подбирать номиналы так, чтобы разбить интервал на равные промежутки напряжений, либо использовать в схеме резисторы только одного номинала:

17
Пример использования 4 резисторов для 5 кнопок. <br/>Слева резисторы подобраны под равномерную дискретность напряжений, справа — резисторы одного номинала.
Пример использования 4 резисторов для 5 кнопок.
Слева резисторы подобраны под равномерную дискретность напряжений, справа — резисторы одного номинала.
18

В первом случае (левый график), номиналы резисторов подобраны таким образом, чтобы обеспечить равные интервалы при определении значений напряжения АЦП. Такой подход очень удобен для количества кнопок $2^n$, (при $n \geqslant 2$), т. е. 4, 8, 16,… Удобство заключается в том, что результат работы АЦП при помощи битового сдвига можно округлить, при этом максимально нивелировать погрешности АЦП и номиналов резисторов. Пример:

19
Предположим 4 кнопки генерируют напряжения — 5 В (первая кнопка без резистора), 3.75 В, 2.5 В, 1.25 В, что соответствует значениям АЦП — 1023, 767, 511, 255. Теперь, если к полученным значениям применить операцию битового сдвига, получим значения 4, 3, 2, 1 для кнопок, и 0 для состояния покоя:
1
2
3
4
5
6
7
8
9
10
11
void setup() { Serial.begin(9600); Serial.println( (1023 >> 8) + 1); // 4 Serial.println( (767 >> 8) + 1); // 3 Serial.println( (511 >> 8) + 1); // 2 Serial.println( (255 >> 8) + 1); // 1 } void loop() { }
Сдвигаем на 8 бит потому из 10-битного значения нужно оставить всего 2 бита, в которых закодированы значения от 0 до 3. К ним прибавляется единица, чтобы отделить полученные состояния от состояния спокойствия — 0. В случае использования большего количества кнопок сдвиг нужно уменьшать — для 8 кнопок до 7, для 16 до 6 и т. д.

Но если пороговое значение 1023 никогда не будет превышено, то значения 767, 511 и 255 могут колебаться и в большую и в меньшую сторону. Так вот, чтобы значение 512 не давало результат 3, а значение 256 не давало результат 2, от преобразуемого значения нужно вычесть половину диапазона между значениями — 128.
1
2
3
4
5
6
for (int i = 1023; i >= 0; i--) { int result = i; Serial.print(i); Serial.print(": "); Serial.println( ((result - 128) >> 8) + 1); }
В этом случае погрешность АЦП будет устранена — ошибок уже не будет.
Результат:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1023: 4 ... 896: 4 895: 3 ... 640: 3 639: 2 ... 384: 2 383: 1 ... 128: 1 127: 0 // ... // состояние покоя 0: 0 //
Весь диапазон жестко поделен на сектора значений.
Соответственно для большего количества кнопок диапазон будет уменьшаться.
20

Но использовать этот вариант можно и с количеством кнопок не кратным $2^n$. В этом случае погрешность АЦП устраняется программно другими методами — внедрением значения ошибки.

21

Недостаток такого способа заключается в ограничении количества кнопок – при увеличении их числа, номиналы резисторов кнопок, отвечающих за низкие напряжения (менее 1 В), растут в геометрической прогрессии.

22

Формула для расчета номиналов резисторов для установки равных промежутков напряжений для количества кнопок $k$, и привязанных к номиналу стягивающего регистра $R_{cт}$ (если принять $R_{cт}$ за единицу, то полученные коэффициенты можно использовать при расчете сопротивлений при другом заданном стягивающем сопротивлении):

23

$u={U_{вх} \over k}$ — шаг дискретности выходного напряжения

24
$$R_{n}={{U_{вх} \times R_{ст}} \over {u \times (k-n)}}- R_{ст} - \sum^{n-1}_{i=1} {R_i}, \text { при } n \in \mathbb {Z} \mid 1\leqslant n \leqslant k-1$$
25

Поскольку первая кнопка замыкается без резистора, то резистор нулевого сопротивления опускаем, отсюда $n \leqslant k-1$ — эта кнопка даст результат равный $U_{вх}$ — $5В$. Рассмотрим расчет номиналов на примере приведенных 5 кнопок и 4 резисторов, стягивающий резистор $R_{ст}=10КОм$ (полученный результат будет в КОм):

26
$$u={U_{вх} \over k}={5В \over 5}=1В $$
27
$$R_1={{5В \times 10КОм} \over {1В \times (5-1)}}- 10КОм - 0КОм=2,5КОм$$
28
$$R_2={{5В \times 10КОм} \over {1В \times (5-2)}}- 10КОм - (2,5КОм)=4,167КОм$$
29
$$R_3={{5В \times 10КОм} \over {1В \times (5-3)}}- 10КОм - (2,5КОм+4,167КОм)=8,333КОм$$
30
$$R_4={{5В \times 10КОм} \over {1В \times (5-4)}}- 10КОм - (2,5КОм+4,167КОм+8,333КОм)=25КОм$$
31

Результат: нам понадобятся резисторы следующих номиналов — 2.5 КОм, 4.167 КОм, 8.333 КОм, 25 КОм.

32
Расположение резисторов с указанными номиналами и получаемые напряжения на каждой кнопке, в зависимости от выбранной схемы
Расположение резисторов с указанными номиналами и получаемые напряжения на каждой кнопке, в зависимости от выбранной схемы
33 На заметку:
Данный способ, автор считает наиболее предпочтительным. При чем, имеет смысл рассчитывать схему с количеством кнопок равным $2^n$, а использовать столько, сколько нужно. Например, если нужно 3 кнопки, то рассчитывать схему на 4 кнопки, а если нужно 5 кнопок, то схему сформировать исходя из 8 кнопок.

Этот способ также на программном уровне упрощает трактовку результатов, исключая дополнительные затраты ресурсов на проверку принадлежности к диапазонам.
34

Для второго случая, все намного проще – установлены резисторы одного номинала. При чем лучше, если этот номинал будет меньше номинала стягивающего резистора $x$. В этом случае первые кнопки попадут в диапазон, где различия между напряжениями максимальны:

35
На рисунке 10 резисторов, номиналом 0.1$x$ — например, при номинале стягивающего резистора 10 КОм, номинал резисторов кнопок — 1 КОм
На рисунке 10 резисторов, номиналом 0.1$x$ — например, при номинале стягивающего резистора 10 КОм, номинал резисторов кнопок — 1 КОм
36

Диапазон напряжений находится по формуле, при номинале стягивающего резистора $R_{ст}$ и номинале прочих резисторов $R$:

37
$$U_{вых_{n}}=U_{вх} \times {{R_{ст}} \over {R \times n + {R_{ст}}}}$$
38

Но как видно из графика дискретность напряжений неминуемо снижается. В данном случаем можно пойти на хитрость – там, где разница между напряжениями становится критически минимальной, начать устанавливать резисторы большего номинала, увеличив дискретность:

39
40

Основной отличительной особенностью резистивно-последовательной схемы является игнорирование нажатия нескольких кнопок — результат всегда будет указывать на нажатие единственной кнопки с меньшим по отношению к другим нажатым кнопкам суммарным сопротивлением:

41
При одновременном нажатии 2 и 3 кнопки, ток потечет по контуру через 3 кнопку
При одновременном нажатии 2 и 3 кнопки, ток потечет по контуру через 3 кнопку
42 На заметку:
Достоинства схемы:
  • предсказуемое поведение,
  • сравнительная легкость расчетов,
  • нажатие нескольких кнопок одновременно приводит к предсказуемому результату,
  • варианты реализации — одинаковый шаг изменения выходного напряжения на выходе или одинаковые номиналы используемых резисторов.

Недостатки:
  • накапливаемая погрешность подбираемых номиналов резисторов,
  • для первого случая (равномерная дискретность значений напряжений) сложность расчетов,
  • ограничение количества кнопок в диапазоне напряжений до 1 В,
  • принципиален порядок расположения резисторов на схеме.
43
Есть ещё один комбинированный способ подключения нескольких кнопок к одному аналоговому входу МК, при котором при использовании одинаковых номиналов напряжений, на выходе получаются напряжения равномерной дискретности.

При таком способе подключения точность выходного напряжения возрастает с увеличением номинала стягивающего резистора, относительно номиналов других резисторов.
44

Схема №2 — резистивно-параллельная

Принципиально схема может исполняться в 2 вариантах и выглядит следующим образом:

45
Обе схемы абсолютно идентичны
Обе схемы абсолютно идентичны
46

Особенность данной схемы заключается в том, что резистор каждой кнопки задает необходимое напряжение на выходе — его номинал рассчитывается также как и в первой схеме. Схема исключает использование резисторов одного номинала. При нажатии двух или нескольких кнопок, генерируется индивидуальное напряжение, которое определяется исходя из параллельного подключения сопротивлений:

47
48
$${\frac {1}{R_{общ}}}={\frac {1}{R_{1}}}+{\frac {1}{R_{2}}}+\ldots+{\frac {1}{R_{n}}}$$
49

И как следствие:

50
$${R_{общ}}={\frac {1}{\frac {1}{{R_{1}}}+{\frac {1}{R_{2}}}+\ldots+{\frac {1}{R_{n}}}}}$$
51

Эта особенность (распознавание одновременно нажатых кнопок) является преимуществом при малом их количестве, и в то же время делает поведение схемы сложно предсказуемой при большом количестве кнопок. К этой ситуации мы вернемся чуть позже.

52 На заметку:
Важно иметь ввиду, что при подключении одной кнопки без сопротивления, для генерации 5 В (это не противоречит общей концепции) и, при её нажатии, вся схема не будет реагировать на нажатия других кнопок. Эта ситуация является исключением в общем правиле однозначной идентификации каждой комбинации из нажатых кнопок.
53

Расчет номиналов сопротивлений схож с расчетом из резистивно-последовательной схемы, за тем лишь исключением, что каждое последующее сопротивление не нужно корректировать на полученные ранее:

54
$$R_{n}={{U_{вх} \times R_{ст}} \over {U_{вых_{n}}}}- R_{ст}$$
55

Например, у нас есть схема из 3 кнопок, которые должны генерировать соответственно 1 В, 2.5 В и 4 В. Номинал стягивающего резистора 10 КОм, входное напряжение 5 В. Расчет номиналов резисторов будет следующим:

56
$$R_1={{5В \times 10КОм} \over {1В}}- 10КОм=40КОм$$
57
$$R_2={{5В \times 10КОм} \over {2,5В}}- 10КОм=10КОм$$
58
$$R_3={{5В \times 10КОм} \over {4В}}- 10КОм=2,5КОм$$
59

Но самое интересное, в этой схеме всплывает тогда, когда одновременно нажимается несколько кнопок — а с тремя кнопками этих комбинаций 4: 1+2, 1+3, 2+3, 1+2+3.

60

Немного математики...

Количество комбинаций нажатий кнопок определяется по формуле числа сочетаний из общего числа $n$-объектов по $k$-штук в комбинации:

61
$$C_n^k=\frac{n!}{(n-k)! \times k!}$$
62

Но поскольку у нас исключены нажатия по одной кнопке (поведение по умолчанию), и возможны нажатия не только 2, но и всех кнопок сразу, то общая формула для нахождения числа сочетаний будет находиться путем сложения числа сочетаний 2 кнопок, числа сочетаний 3 кнопок и т. д. и будет иметь вид:

63
$$C_{n_{общ}}=С_n^2+C_n^3+\ldots+C_n^n$$
64
$$C_{n_{общ}}=\sum^{n}_{k=2} \frac {n!}{(n-k)! \times k!}$$
65

Например, для 5 кнопок, помимо 5 «официальных» обособленных нажатий по одной кнопке, общее количество комбинаций рассчитывается следующим образом. Количество комбинаций нажатий по две кнопки (1+2, 1+3, 1+4, 1+5, 2+3, ...) будет:

66
$$C_5^2=\frac{5!}{(5-2)! \times 2!}=10$$
67

Количество комбинаций нажатий 3 кнопок (1+2+3, 1+2+4, 1+2+5, 2+3+4, ...):

68
$$C_5^3=\frac{5!}{(5-3)! \times 3!}=10$$
69

Количество комбинаций нажатий 4 кнопок (1+2+3+4, 1+2+3+5, 1+2+4+5, 1+3+4+5, 2+3+4+5):

70
$$C_5^4=\frac{5!}{(5-4)! \times 4!}=5$$
71

И количество одновременных нажатий 5 кнопок — $1$. Итого:

72
$$C_{n_{общ}}=10+10+5+1=26$$
73

26 комбинаций! Помимо 5 стандартных. А для 6 кнопок возможных комбинаций уже 57 (не считая 6 нажатий по одной кнопке)!!! Но все бы ничего если бы не...

74

Назад к кнопкам

Вернемся к нашим 3 кнопкам и рассчитаем напряжение, которое будет получено, путем одновременного нажатия 1 и 3 кнопки (1 В и 4 В). Как известно, для двух параллельных сопротивлений, общее сопротивление находится по формуле:

75
$$R={\frac {R_{1}R_{2}}{R_{1}+R_{2}}}$$
76
$$R={\frac {40КОм \times2{,}5КОм}{40КОм+2{,}5КОм}}=2{,}35КОм$$
77
$$U_{вых}={U_{вх} \times {R_{ст} \over {R+R_{ст}}} }=5В \times {10КОм \over {2{,}35КОм+10КОм}}=4{,}05В $$
78

Обратите внимание, что полученное значение очень близко к номиналу третьей кнопки — при условии погрешности номиналов резисторов и АЦП данная комбинация, с большой долей вероятности может быть интерпретирована как нажатие 3 кнопки. В случае увеличения количества кнопок, очень трудно спрогнозировать, каким образом будут интерпретироваться различные комбинации нажатий кнопок — очень вероятны ситуации, когда комбинации нажатых кнопок будут давать результат, не имеющий к ним никакого отношения.

79

В случае одновременного нажатия всех трех кнопок:

80
$$R={\frac {1} {\frac {1}{40КОм} +{\frac {1}{10КОм}}+{\frac {1}{2,5КОм}} }}=1,9КОм$$
${R_{общ}}={\frac {1}{\frac {1}{{R_{1}}}+{\frac {1}{R_{2}}}+\ldots+{\frac {1}{R_{n}}}}}$
81
$$U_{вых}={U_{вх} \times {R_{ст} \over {R+R_{ст}}} }=5В \times {10КОм \over {1{,}9КОм+10КОм}}=4{,}2В $$
82 На заметку:
Достоинства схемы:
  • простота подбора номиналов,
  • не важен порядок установки резисторов — каждый номинал включается в схему с соответствующей кнопкой,
  • возможно наращивание дополнительного функционала за счет интерпретаций комбинаций нажатий.

Недостатки:
  • подходит только для малого количества кнопок (до 5) — при включении в схему более 5 кнопок, поведение схемы при одновременном нажатии двух и более кнопок становится непредсказуемым.
83

Хватит математики, в бой!

Подкрепим теорию примерами на практике, работать будем с 4 кнопками.

84 На заметку:
Для ускорения расчетов номиналов элементов схем данной статьи, автором написан Калькулятор.xlsx (25,0 KB), который можно совершенно бесплатно скачать и производить расчеты.
85

Пример №1. Резистивно-последовательная схема, номиналы резисторов будем подбирать под равные промежутки напряжения. Воспользовавшись калькулятором, получаем следующие номиналы резисторов — 3.334 КОм, 6.666 КОм, 20 КОм — как раз в наличии 3.3 КОм, 6.8 КОм и 20 КОм. Собираем схему:

Принципиальная схема
Принципиальная схема
86
87

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

88 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
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 oldKeyValue; // Переменная для хранения предыдущего значения состояния кнопок static long lastChange; // Переменная для хранения времени последнего изменения состояния int actualKeyValue = analogRead(pinIn); // Получаем актуальное состояние if ((actualKeyValue != oldKeyValue) && (millis() - lastChange > 200)) { // Пришло новое значение, и с последнего изменения прошло достаточно времени oldKeyValue = actualKeyValue; // Запоминаем новое значение lastChange = millis(); // Обнуляем таймер } return oldKeyValue; // Отправляем старое, либо уже модифицированное новое значение }
89

Но если этот код запустить в том виде, в котором он представлен то увидим, как погрешность преобразования АЦП вносит свои коррективы в его работу:

90
91

Для исправления будем использовать битовый сдвиг, описанный в 18 абзаце. Если обратить внимание на погрешность получаемых значений, с учетом не идеально подобранных номиналов резисторов и их погрешности, она не превышает ±16 единиц. А это составляет $16+16=32=2^5$. Значит и сдвигать можно только на 5 бит — результат будет стабильным. Но для упрощения интерпретации будем сдвигать на 8 бит, чтобы результатом получать номер нажатой кнопки 1...4. Откорректируем несколько строк:

92 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
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 oldKeyValue; // Переменная для хранения предыдущего значения состояния кнопок static long lastChange; // Переменная для хранения времени последнего изменения состояния int actualKeyValue = analogRead(pinIn); // Получаем актуальное состояние
//actualKeyValue = ((actualKeyValue - 16) >> 5) + 1; // Для 32 кнопок
//actualKeyValue = ((actualKeyValue - 32) >> 6) + 1; // Для 16 кнопок
//actualKeyValue = ((actualKeyValue - 64) >> 7) + 1; // Для 8 кнопок
actualKeyValue = ((actualKeyValue - 128) >> 8) + 1; // Для 4 кнопок
if ((actualKeyValue != oldKeyValue) && (millis() - lastChange > 200)) { // Пришло новое значение, и с последнего изменения прошло достаточно времени oldKeyValue = actualKeyValue; // Запоминаем новое значение lastChange = millis(); // Обнуляем таймер } return oldKeyValue; // Отправляем старое, либо уже модифицированное новое значение }
93

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

94
В примере из предыдущего скетча раскомментирована 30 строка — для 8 кнопок, а 31 закомментирована
В примере из предыдущего скетча раскомментирована 30 строка — для 8 кнопок, а 31 закомментирована
95

...модифицируем код таким образом, чтобы нажатие генерировалось только после получения 10 подряд идущих одинаковых значений (модифицированная функция GetKeyValue()):

96 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
int GetKeyValue() { // Функция устраняющая дребезг static int count; static int oldKeyValue; // Переменная для хранения предыдущего значения состояния кнопок static int innerKeyValue; int actualKeyValue = analogRead(pinIn); // Получаем актуальное состояние //actualKeyValue = ((actualKeyValue - 16) >> 5) + 1; // Для 32 кнопок //actualKeyValue = ((actualKeyValue - 32) >> 6) + 1; // Для 16 кнопок //actualKeyValue = ((actualKeyValue - 64) >> 7) + 1; // Для 8 кнопок actualKeyValue = ((actualKeyValue - 128) >> 8) + 1; // Для 4 кнопок if (innerKeyValue != actualKeyValue) { // Пришло значение отличное от предыдущего count = 0; // Все обнуляем и начинаем считать заново innerKeyValue = actualKeyValue; // Запоминаем новое значение } else { count += 1; // Увеличиваем счетчик } if ((count >= 10) && (actualKeyValue != oldKeyValue)) { // Счетчик преодолел барьер, можно иницировать смену состояний oldKeyValue = actualKeyValue; // Присваиваем новое значение } return oldKeyValue; }
97

Пример №2. Резистивно-последовательная схема, номиналы резисторов будут одинаковые — 3.3 КОм. Количество кнопок также 4. Схема остается прежней — меняются только номиналы резисторов.

98
99

Воспользовавшись калькулятором (25,0 KB), получаем следующие напряжения на выходе (с соответствующими значениями АЦП) — 5 В (1023), 3.76 В (769), 3.01 В (615) и 2.51 В (513). Для проверки на модифицированной схеме запускаем скетч:

100 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
54
55
56
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 if (keyValue < 0) { // Если -1 - неизвестное состояние, незапрограммированное нажатие Serial.println("unknown pressed"); } 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; // Значение не принадлежит заданному диапазону }
101

В коде написана дополнительная функция GetButtonNumberByValue(), проверяющая причастность полученного АЦП значения к диапазонам, закрепленным за каждой кнопкой, с учетом заданного значения ошибки error. Результат:

102
103

Пример №3. Резистивно-параллельная схема, количество кнопок также 4. Для упрощения будем использовать номиналы резисторов из примера №1 — 3.3 КОм, 6.8 КОм, 10 КОм и 20 КОм. Стягивающий резистор также 10 КОм. Расчетные напряжения получаемые на выходе (с соответствующими значениями АЦП) будут следующими: 3.76 В (769), 2.98 В (609), 2.5 В (511) и 1.67 В (341).

104

Как раз для примера, опять же при помощи калькулятора (25,0 KB), рассчитаем значение общего сопротивления и выходного напряжения, получаемое при одновременном нажатии 2, 3 и 4 (6.8 КОм, 10 КОм и 20 КОм) кнопок. Получаем общее сопротивление 3.37 КОм, и выходное напряжение — 3.74 В (765), что как видно очень близко к значениям первой кнопки. Посмотрим что будет происходить в реальности.

Принципиальная схема
Принципиальная схема
105

Тестовая схема выглядит следующим образом:

106
107

Скетч оставляем из примера №2, за тем лишь исключением, что модифицируем идентификационные значения. Для большей показательности не будем в коде отрабатывать сценарии одновременного нажатия большого количества кнопок (делается это очень просто, оставим для домашнего задания). Заменим 49 строку предыдущего скетча на:

108 Arduino (C++)
1
int values[5] = {0, 769, 609, 511, 341};// Массив значений АЦП
109

Запустив код, можно убедиться в недостатках этой схемы — одновременное нажатие 3 и 4 кнопки интерпретируется как нажатие 2 кнопки, а одновременное нажатие кнопок 2, 3 и 4 — как нажатие 1 кнопки.

110

Демонстрационное видео:

111
112 На заметку:
Исходники для скачивания:
Пример №1 Пример №2 Пример №3
Принципиальная схема: sample_circuit_01.png (28,4 KB)
sample_circuit_eagle_01.sch (289 KB)
sample_circuit_02.png (27,8 KB)
sample_circuit_eagle_02.sch (289 KB)
sample_circuit_03.png (28,0 KB)
sample_circuit_eagle_03.sch (290 KB)
Макет: sample_sketch_01.png (72,5 KB)
sample_circuit_fritzing_01.fzz (8,22 KB)
sample_sketch_02.png (72,3 KB)
sample_circuit_fritzing_02.fzz (8,22 KB)
sample_sketch_03.png (72,2 KB)
sample_circuit_fritzing_03.fzz (8,50 KB)
Код: sample_code_01.ino (2,78 KB) sample_code_02.ino (3,54 KB) sample_code_03.ino (3,57 KB)

Все одним файлом — Materials.rar (365 KB)
comments powered by HyperComments

Яндекс.Метрика