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

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

 
04  

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

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

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

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

 
07  

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

 
08  
 
09  
Rобщ=R1+R2+...+RnRобщ=R1+R2+...+Rn
 
10  

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

 
11  
 
12  

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

 
13  
Uвых=Uвх×R2R1+R2Uвых=Uвх×R2R1+R2
 
14  

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

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

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

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

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

 
19  
Предположим 4 кнопки генерируют напряжения — 5 В(первая кнопка без резистора), 3.75 В2.5 В1.25 В, что соответствует значениям АЦП — 1023767511255. Теперь, если к полученным значениям применить операцию битового сдвига, получим значения 4321 для кнопок, и 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  

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

 
21  

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

 
22  

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

 
23  

u=Uвхku=Uвхk — шаг дискретности выходного напряжения

 
24  
Rn=Uвх×Rстu×(kn)Rстn1i=1Ri,приnZ1nk1Rn=Uвх×Rстu×(k−n)−Rст−∑i=1n−1Ri, при n∈Z∣1⩽n⩽k−1
 
25  

Поскольку первая кнопка замыкается без резистора, то резистор нулевого сопротивления опускаем, отсюда nk1n⩽k−1 — эта кнопка даст результат равный UвхUвх — 5В. Рассмотрим расчет номиналов на примере приведенных 5 кнопок и 4 резисторов, стягивающий резистор Rст=10КОмRст=10КОм(полученный результат будет в КОм):

 
26  
u=Uвхk=5В5=1Вu=Uвхk=5В5=1В
 
27  
R1=5В×10КОм1В×(51)10КОм0КОм=2,5КОмR1=5В×10КОм1В×(5−1)−10КОм−0КОм=2,5КОм
 
28  
R2=5В×10КОм1В×(52)10КОм(2,5КОм)=4,167КОмR2=5В×10КОм1В×(5−2)−10КОм−(2,5КОм)=4,167КОм
 
29  
R3=5В×10КОм1В×(53)10КОм(2,5КОм+4,167КОм)=8,333КОмR3=5В×10КОм1В×(5−3)−10КОм−(2,5КОм+4,167КОм)=8,333КОм
 
30  
R4=5В×10КОм1В×(54)10КОм(2,5КОм+4,167КОм+8,333КОм)=25КОмR4=5В×10КОм1В×(5−4)−10КОм−(2,5КОм+4,167КОм+8,333КОм)=25КОм
 
31  

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

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

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

 
34  

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

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

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

 
37  
Uвыхn=Uвх×RстR×n+RстUвыхn=Uвх×RстR×n+Rст
 
38  

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

 
39  
 
40  

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

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

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

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

 
44  

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

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

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

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

 
47  
 
48  
1Rобщ=1R1+1R2++1Rn1Rобщ=1R1+1R2+…+1Rn
 
49  

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

 
50  
Rобщ=11R1+1R2++1RnRобщ=11R1+1R2+…+1Rn
 
51  

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

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

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

 
54  
Rn=Uвх×RстUвыхnRстRn=Uвх×RстUвыхn−Rст
 
55  

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

 
56  
R1=5В×10КОм1В10КОм=40КОмR1=5В×10КОм1В−10КОм=40КОм
 
57  
R2=5В×10КОм2,5В10КОм=10КОмR2=5В×10КОм2,5В−10КОм=10КОм
 
58  
R3=5В×10КОм4В10КОм=2,5КОмR3=5В×10КОм4В−10КОм=2,5КОм
 
59  

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

 
60  

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

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

 
61  
Ckn=n!(nk)!×k!Cnk=n!(n−k)!×k!
 
62  

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

 
63  
Cnобщ=С2n+C3n++CnnCnобщ=Сn2+Cn3+…+Cnn
 
64  
Cnобщ=nk=2n!(nk)!×k!Cnобщ=∑k=2nn!(n−k)!×k!
 
65  

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

 
66  
C25=5!(52)!×2!=10C52=5!(5−2)!×2!=10
 
67  

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

 
68  
C35=5!(53)!×3!=10C53=5!(5−3)!×3!=10
 
69  

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

 
70  
C45=5!(54)!×4!=5C54=5!(5−4)!×4!=5
 
71  

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

 
72  
Cnобщ=10+10+5+1=26Cnобщ=10+10+5+1=26
 
73  

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

 
74  

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

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

 
75  
R=R1R2R1+R2R=R1R2R1+R2
 
76  
R=40КОм×2,5КОм40КОм+2,5КОм=2,35КОмR=40КОм×2,5КОм40КОм+2,5КОм=2,35КОм
 
77  
Uвых=Uвх×RстR+Rст=5В×10КОм2,35КОм+10КОм=4,05ВUвых=Uвх×RстR+Rст=5В×10КОм2,35КОм+10КОм=4,05В
 
78  

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

 
79  

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

 
80  
R=1140КОм+110КОм+12,5КОм=1,9КОмR=1140КОм+110КОм+12,5КОм=1,9КОм
Rобщ=11R1+1R2++1RnRобщ=11R1+1R2+…+1Rn
81  
Uвых=Uвх×RстR+Rст=5В×10КОм1,9КОм+10КОм=4,2ВUвых=Uвх×RстR+Rст=5В×10КОм1,9КОм+10КОм=4,2В
 
82 На заметку:
Достоинства схемы:
  • простота подбора номиналов,
  • не важен порядок установки резисторов — каждый номинал включается в схему с соответствующей кнопкой,
  • возможно наращивание дополнительного функционала за счет интерпретаций комбинаций нажатий.

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

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

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

 
84 На заметку:
Для ускорения расчетов номиналов элементов схем данной статьи, автором написан Калькулятор.xlsx (24,9 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=2516+16=32=25. Значит и сдвигать можно только на 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  

Воспользовавшись калькулятором (24,9 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  

Как раз для примера, опять же при помощи калькулятора (24,9 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 кнопки.+