Telecom Framework

Telecom позволяет интегрировать вашу телефонию с андройдом. Для этого предусматривается несколько типов компонент:

  • Default In Call UI. Это пользовательский интерфейс, который позволяет выполнять и принимать вызовы, ну или манипулировать текущим звонком. Телефонические решения могут воспользоваться готовым деволтовым UI и не запариваться над своим. В свою очередь можно заменить нативный Default In Call UI своим и тогда он будет использоваться для отображения вызовов для GSM.
  • Managed Call Service. Телефоническое решение которое использует дефолтовый In Call UI.
  • Self Managed Call Service. Телефоническое решение которое реализует не только сервис соединений, но и пользовательский интерфейс. В этом случае события этого сервиса будут роутится не на дефолтовый UI, а на этот персональный UI.
  • Non-UI Self Managed Call Service. Телефоническое решение которое реализует только сервис соединений. В этом случае события этого сервиса никуда не будут роутится. Приложение должно позаботься об UI без Telecom Framework. Telecom Framework нужен в этом случае для согласования различных телефонических сервисов и перевода системы в режим звонка при поступлении вызова.

Это не законченная статья. Если вам интересно ее продолжение, то оставляйте коменты.

Прерыване PCINT для arduino

Речь пойдет про использования Pin Change Interrupt (PCINT) для платы ардуино на основе Atmel ATmega328. Но статья так же справедлива и для всего семейства AVR контроллеров.

Термины

  • SREG (AVR Status Register) — Основной регистр статусов процессора AVR
  • SREG I-bit: Global Interrupt Enable — Бит 7 регистра SREG: включение прерываний процессора AVR. Если этот бит сброшен, то никакие прерывания не вызываются, независимо от других локальных настроек прерываний.
  • PCICR (Pin Change Interrupt Control Register) — Регистр управления PCINT прерываниями. биты 0..2 отвечают за активацию PCI0..2 групп прерываний.
  • PCIFR (Pin Change Interrupt Flag Register) — Регистр флагов сработавших прерываний PCINT. 0..2 биты выставляются, если было изменения на любом PINT из портов групп PCINT0..2
  • PCMSK (Pin Change Mask Register) 0..2 — Регистр маскировки прерываний каждого из портов для групп PCI0..2. На каждый порт группы отводится бит соответствующего регистра. Младший бит маскирует порт с меньшим номером.
  • EICRA (External Interrupt Control Register A) — Регистр управления внешними прерываниями, содержит биты управления чувствительностью прерываний INT: Любой переход между уровнями, переход на верхний уровень, на нижний, низкий уровень.
  • EIMSK (Extrnal Interrupt Mask Register) — Регистр маскировки внешних прерываний INT. Включает/выключает по отдельности прерывания INT0, INT1
  • EIFR (Extrnal Interrupt Flag Register) — Содержит флаги сработавших прерываний INT0, INT1. Соответствующие флаги выставляется перед вызовом процедуры обработки INT0 или INT1 и автоматически обнуляется после ее завершения.
  • PINx — Регистр значений на выводах микроконтроллера. х — значение порта: B, C, D. Каждый бит регистра означает уровень сигнала на соответствующем биту пине.

Введение

ATmega328 имеет два вывода поддерживающих INT-прерывания и 24 поддерживающих PCINT. Т.е. PCINT поддерживают почти все цифровые и аналоговые выводы ардуины в отличии от INT. Оба типа прерываний срабатывают даже если они сконфигурированы на выходы вывода.

Разница между этими типами прерывания в том что обработчик INT можно навесить индивидуально на любой INT вывод, тогда как PCINT выводы объединены в группы и изменение по любому пину группы вызовет общее для группы прерывание PCI.

ATmega328 поддерживает три такие группы:

  • PCI0: PCINT0..7, что соответствует пинам D08 — D13
  • PCI1: PCINT8..14, A0 — A5
  • PCI2: PCINT16..23, RX,TX, D02 — D07

Пины A6, A7 — сугубо аналоговые и не имеют дополнительных функций, включая PCINT
PCINT14 — не имеет ножки на микросхеме ATmega328, но может иметь на других моделях.
D02, D03 — могут вызывать как INT, так и PCINT прерывания.

Конфигурация

Рассмотрим как сконфигурировать PCINT на примере ноги D04.

Для того чтобы заработал обработчик прерываний PCINT необходимо выполнить следующие шаги:

  1. Определить группу прерываний для заданного пина. Для D04 это группа 2 (PCINT2)
  2. Добавить обработчик прерываний для группы.
// Для D04 используется прерывание группы PCINT2
ISR(PCINT2_vect) {
// функция будет вызвана для любого изменения уровня пина D04,
// впрочем как и для любого другого пина группе,
// если это указано в PCIMSK2 
}
  1. Активировать прерывание для группы в регистре управления PCICR
    PCICR |= 1 << 2
  2. Активировать прерывание для конкретного пина в группе. Для D04 это бит 4 в группе 2. Ставим его в единичку.
    PCIMSK2 |= 1 << 4
  3. Разрешить прерывания микроконтроллера.
    SREG |= 1 << SREG_I; // бит 7

После этого любое изменение уровня на пине D04 должно приводить в вызову функции ISR(PCINT2_vect).

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

Для примера рассмотри следующий код:

// Библиотека TaskManagerIO поможет организовать
// очередь для обработки прерываний.
// https://www.thecoderscorner.com/products/arduino-libraries/taskmanager-io/
#include <TaskManager.h>

// Кнопка должна быть подключена к выводу D04.
#define BUTTON_PIN 4

/**
 * Event class to marshal interupt to the loop thread.
 */
class PciEvent: public Executable {
    const uint8_t _Pcifr;
public:
    PciEvent(uint8_t aPcifr): _Pcifr(aPcifr) {}
    void exec() {
        Serial.print(F("PIND: 0b"));
        Serial.println(_Pcifr, 2);
    }
};

/**
 * Обработчик прерываний группы PCINT2 в которую входит D04.
 * Pins 0 - 7
 */
ISR(PCINT2_vect) {
    uint8_t pins = PIND; // Port D pins 0 - 7.
    // Формируем событие и отправляем его на обработку в функции loop().
    taskManager.scheduleOnce(0, new PciEvent(pins), TIME_MICROS, true);
}

void setup() {
    // Настроить серийный порт для передачи сообщений на PC на скорости 9600 бот.
    Serial.begin(9600);

    // Перeключить пин кнопки на вход с резистором подтяжки.
    pinMode(BUTTON_PIN, INPUT_PULLUP);
    // Включить PCINT для группы порта BUTTON_PIN (4) D0-D7 - группа 2
    PCICR |= bit(digitalPinToPCICRbit(BUTTON_PIN));  // PCICR |= 1 << 2
    // Включить PCINT для пина внутри группы (пин 4)
    *digitalPinToPCMSK(BUTTON_PIN) |= bit(digitalPinToPCMSKbit(BUTTON_PIN)); // PCIMSK2 |= 1 << 4
    // Включить прерывания глобально.
    sei(); // SREG |= 1 << 7  или SREG |= bit(SREG_I)
}

void loop() {
    taskManager.runLoop();
}

При каждом нажатии на кнопку программа выводит значение регистра PIND в бинарном виде. бит 4 (5й справа) этого регистра соответствует высокому или низкому уровню на пине D04. Остальные биты могут произвольно меняться согласно значениям на советующих пинах. В частности биты 0 и 1 соответствуют ногам RX, TX поэтому в примере вывода программы они принимают различные значения:

# Кнопка отжата
# резистор подтяжки дает на ногу высокий уровень - бит 4 = 1
PIND: 0b10011
# Кнопка нажата и выдает на ногу низкий уровень - бит 4 = 0
PIND: 0b11
PIND: 0b10011
...
PIND: 0b11
PIND: 0b10011

Но рассмотрим код программы внимательней.

ISR(PCINT2_vect) {
    uint8_t pins = PIND; // Port D pins 0 - 7.
    // Формируем событие и отправляем его на обработку в функции loop().
    taskManager.scheduleOnce(0, new PciEvent(pins), TIME_MICROS, true);
}

Функция ISR вызывается для обработки прерывания и она должна быть максимально быстрой. Пока она не завершится, другие прерывания не будут обрабатываться, в том числе те, что изменяют счетчик функции millis(). Поэтому, в примере значение регистра PIND, просто передается в очередь для последующей обработки уже в основном цикле программы.

    // Включить PCINT для группы порта BUTTON_PIN (4) D0-D7 - группа 2
    PCICR |= bit(digitalPinToPCICRbit(BUTTON_PIN));  // PCICR |= 1 << 2

Здесь используется макрос digitalPinToPCICRbit, объявленный в Arduino.h, который возвращает номер группы прерываний для указанного номера порта. Далее это значение используется для активации группы в регистре управления PCICR.

 // Включить PCINT для пина внутри группы (пин 4)
    *digitalPinToPCMSK(BUTTON_PIN) |= bit(digitalPinToPCMSKbit(BUTTON_PIN)); // PCIMSK2 |= 1 << 4

Здесь используются сразу два макроса:

digitalPinToPCMSK — возвращает ссылку на нужный регистр маскировки прерываний PCMSK0, PCMSK1 или PCMSK2, для включения прерываний для указанного пина.

digitalPinToPCMSKbit — позволяет получить номер соответствующего бита.

Контекстные функции (Scope Functions)

Небольшая шпаргалка по функциям: let, apply, run, also и with для kotlin.

Основная цель этих функции: выполнение ряда действий в контексте какого-либо объекта, Например

Person("Alice", 20, "Amsterdam").let {
    println(it)
    it.moveTo("London")
    it.incrementAge()
    println(it)
}

Что аналогично

val alice = Person("Alice", 20, "Amsterdam")
println(alice)
alice.moveTo("London")
alice.incrementAge()
println(alice)

Т.е. let создал контекстную область, где подменил основной объект контекста короткой ссылкой it.

В следующем примере c apply вместо it используется this:

val adam = Person("Adam").apply { 
    age = 20                       // same as this.age = 20 or adam.age = 20
    city = "London"
}
println(adam)

В примере с also, в добавок можно использовать сам объект как результат лямбды

val numberList = mutableListOf<Double>()
numberList.also { println("Populating the list") }
    .apply {
        add(2.71)
        add(3.14)
        add(1.0)
    }
    .also { println("Sorting the list") }
    .sort()

Другими словами функции отличаются по двум критериям:

  • Как обращаться к объекту контекста: по it или через this
  • Что возвращает функция

Что с чем видно по сводной таблице.

ФункцияПриемник (receiver)Аргумент лямбдыВозвращает
letitРезультат
runthisРезультат
withthisРезультат
applythisсам
alsoitсам

with

Эта функция как и run работает в контексте this и возвращает результат лямбды, но в отличии от run, this передается как аргумент, а не приемник лямбды (receiver). У авторов котлина для with есть специальные варианты использования, для повышения читаемости.

Подчеркивание общности операций

val numbers = mutableListOf("one", "two", "three")
with(numbers) {
    println("'with' is called with argument $this")
    println("It contains $size elements")
}

Вычисление результата

val numbers = mutableListOf("one", "two", "three")
val firstAndLast = with(numbers) {
    "The first element is ${first()}," +
    " the last element is ${last()}"
}
println(firstAndLast)

Запоминалки

Для упрощения запоминания что c чем, здесь буду собирать различные запоминалки

LARA

Результат лямбдыПриемник this
Аргумент лямбды itlet()also()
Приемник thisrun()apply()

Подключение Arduino Pro Mini к ПК

О плате.

Ardгino Pro Mini построена на микросхеме ATmega328p компании Atmel Corporation. Она содержит 14 цифровых выхода ввода/вывода, 6 из которых могут быть использованы как выводы в режиме ШИМ (PWM). Также есть 6 аналоговых входа, кнопка ресет.
Среди прочих функций, ATmega328p реализует двухпроводной USART интерфейс (Universal Synchronous Asynchronous Receiver Transmitter). Это последовательный интерфейс передачи данных, более известный по связанным терминам UART, RS-232 или Serial port. Для удобства выводы USART также включены в группу контактов на торцевой части платы, для подключения FTDI кабеля или другого USB адаптера, например, на базе CP2102, FT232RL или CH340G. Именно дешевый китайский адаптер на базе CP2102 и будет использоваться в качестве примера в этой статье.
Обычный Arduino уже имеет на борту USB адаптер с USB разъемом, что значительно упрощает программирование этой платы. Целью Ardгino Pro Mini была компактность, поэтому эта плата не имеет USB разъема ровно как и контактных штырей, поэтому чтобы подключить устройство к компьютеру нам понадобиться внешний адаптер серийного интерфейса для USB и паяльник 🙂
Еще надо отметить, что Ardiono Pro Mini выпускается в двух вариантах питания 5В и 3.3 В и это надо учитывать это при приобретении и подключении адаптера.

Если говорить про неофициальные версии, то есть варианты плат разных форм и цветов, но производители всегда стараются соблюсти расположение основных выводов платы, но иногда добавляют и свои.

Про UART

Строго говоря UART (Universal Asynchronous Receiver-Transmitter) не является USART, который реализует ATmega328p, но в нашем случае отличия не существенны поэтому далее будем говорить про UART. UART — это протокол последовательной передачи данных использующий TTL уровни (5В или 3.3В). RS-232 использует сигналы от -15 до 15 вольт относительно общей земли, поэтому напрямую подключать ардуину к RS-232 нельзя. Я говорю про RS-232 поскольку этот интерфейс был широко распространен на ПК для подключения модемов и другой периферии. Он был более известный как COM-порт и многие программы и протоколы были разработаны для работы с ним. Драйвера UART адаптеров также реализуют виртуальные COМ порты чтобы можно было использовать готовый инструментарий для обмена данными с бордой.
COM порт, кроме линий передачи данных RX/TX, имеет дополнительные сигнальные линии для коммуникации с модемом, например DTR (Data Terminal Ready). Это один из сигналов контроля потока данных, которые ATmega328p не поддерживает, зато плата Arduino Pro Mini использует сигнал DTR для перезагрузки чипа, когда IDE готова записать новую версию прошивки. Загрузка ардуины начинается с работы специальной программы — bootloader именно она отвечает за получение новой новой версии прошивки и размещении ее в памяти. Поэтому для легкой заливки новой прошивки IDE перезагружает чип выставляя сигнал DTR на COM порте. Физически DTR контакт платы связан с RESET входом микросхемы через конденсатор. Конденсатор нужен для для того чтобы ресет происходил только в момент выставления DTR сигнала.

Линия DTR-RESET платы Arduino Pro Mini

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

Об адаптере

Официально предлагается использовать два варианта адаптеров: от фирмы FTDI и от самого SparkFun Electronics. С ними никаких проблем нет, но их стоимость варьируется в пределах 15 баксов. Поэтому люди предпочитают брать китайские аналоги за 1-2 бакса.

Их достаточно много, поэтому тут появляются нюансы которые надо учитывать.

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

  1. Драйвер должен поддерживать виртуальный COM порт. Для адаптеров на базе CP2102, FT232RL и CH340G это справедливо.
  2. Возможность предоставлять питание как 5 В, так 3.3 В. Это может быть либо отдельные штыри для каждого номинала, либо переключатель.
  3. Иметь индикатор готовности. Желательно индикатор приема и передачи, иногда это очень помогает в отладке.
  4. Желательно иметь DTR контакт. Как вы уже знаете, DTR используется для перезагрузки устройства, чтобы дат шанс бутлоадеру загрузить новую версию прошивки.
  5. Желательно чтобы последовательность контактных штырей адаптера была: GND, VCC, RX, TX, DTR. Это упрощает подключение, уменьшает шанс ошибки и позволяет не распаивать контакты на плате, а просто вставить штырьки в адаптера в контактные отверстия платы на время прошивки.

Подключение

Выполнить подключение в соответствии с таблицей

ArduinoАдаптер
GNDGND
VCC+5 В или +3.3 В
RXD (RX)TXD (TX)
TXD (TX)RXD (RX)
DTRDTR
Таблица подключения USD адаптера

Подключить USB адаптер в USB разъем компьютера, убедиться что на адаптере загорелась лампочка питания. После этого можно прошивать устройство.

Совместное использование пина

Бывает такая ситуация что свободных пинов на контролере не осталось или их просто мало как в случае с ATtiny 85, а нужно прицепить еще пару кнопок или даже больше чем пару. И что в этом случае делать? В этом случае может помочь аналоговый вход с 10-битным аналого-цифровым преобразователем (АЦП). Напряжение, проданное на этот вход будет преобразовано в значение от 0 до 1023, где 0 соответствует 0 В, а 1023 — опорному напряжению, заданному с помощью analogReference(). Как правило оно равно напряжению питания — 5В. Напряжение питания и разрядность могут изменяться в зависимости от платы и настроек, но суть совместного использования пина остается той же: мы можем заставить разные кнопки подавать различное напряжение на аналоговый вход и по прочитанному значению определять какая из кнопок была нажата. Или даже комбинацию кнопок. Но давайте все по порядку.

Простой делитель напряжения.

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

Кнопки на простом делителе напряжения.

Используя последовательно соединенные сопротивления одинакового номинала можно получить напряжения с равномерным шагом. На данном примере это 5, 4, 3, 2, 1, 0 вольт. Равный шаг по напряжению дает равных шаг по значению АЦП и чтобы получить номер нажатой кнопки, достаточно значение функции analogReference() поделить на шаг.

int button = analogReference() / 204;

И, к примеру, нажав на третью кнопку получим на выходе 3В, что советует значению АЦП 613 и поделив на шаг получаем 3.

Из-за помех и погрешностей в реальном значении сопротивлении резисторов и работе АЦП, значения могут отличаться, но не настолько чтобы спутать номера кнопок.

Многие читатели уже отметили, что если ни одна кнопка не нажата, то аналоговый вход находится как бы подвешенном состоянии. В этом состоянии с него можно вычитать все что угодно, навеянное различными помехами. Чтобы это устранить необходим резистор подтяжки.

Простая схема с делителем с резистором подтяжки.

На следующей схеме добавлен резистор подтяжки, который подтягивает аналоговый вход платы к 0 если ни одой кнопки не нажато. Но при нажатой кнопке он вносит изменения в равномерное распределение напряжения по кнопкам. А именно, если раньше напряжение было равно U_{bt} = U {n * R\over N*R} = U {n \over N}, Где N — Общее количество резисторов, а n — количество резисторов под кнопкой. То после добавления резистора подтяжки, он встает параллельно сопротивлению нижнего плеча делителя. Это уменьшает сопротивление всего плеча и подтягивает напряжение к нулю. При высоких значения резистора подтяжки, его влияние для 5ти кнопок все еще невелико и можно использовать код с делением значения АЦП на шаг.

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

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

Еще одним недостатком является ток, текущий по резисторам даже если ни одной кнопки не нажато. В автономных схемах это приведет к преждевременному истощению батареи.

Последовательная схема

Модифицируем предыдущую схему убрав нижний резистор, через который шла утечка тока при отжатых кнопках.

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

Если же кнопка нажата, то схема снова превращается в классический делитель, в котором верхнее плечо составляют резисторы до кнопки, а нижнее — резистор подтяжки. Напряжение на АЦП высчитывается по формуле: U_{bt} = U {R \over {R + (R_1 + .. + R_n)}}.

По этой формуле видно что переменная величина, кнопка, находится в знаменателе, это значит, что зависимость нелинейная и при равных номиналах кнопочных резисторов, напряжение на АЦП выглядит как-то так:

График напряжений на АЦП в зависимости от кнопки

Заметно, что чем больше кнопок — тем мельче шаг по напряжению. Как видно из формулы: U'_{n} = U {R * R_{b} \over {(R + R_{b} * n)^2}}, чем дальше, тем шаг очень быстро становится мелким и для того чтобы нажатие кнопки оставалось четким их надо размещать на «верхней» части графика.

Давайте подберем сопротивления используя общую формулу: R{n} = R * ( {U \over U_b} - 1) = R * ({ {U - U_b} \over U_b})

При R = 10k (резистор стяжки) и шаге в 0.5В сопротивления будут 1.1К 1.4К 1.8К 2.4К 3.3К 5К

Напряжения на кнопках с шагом 0.5В

При такой схеме чтобы узнать какая кнопка нажата нам нужно значение аналогового входа поделить на 0.5 вольт. Но ведь аналоговый вход выдает значение не в вольтах, а в отсчетах от 0 до 1023, так как же их поделить на вольты? Ответ прост: 0.5 вольт соответствует {0.5 В \over 5 В} * 1024 = 102.4 отсчетам. Но не очень удобно работать с дробными значениями поэтому давайте подберем шаг равный или близкий степени двойки. В этом случае мы сможем вычислять номер кнопки через битовый сдвиг: {0.625 В \over 5 В} * 1024 = 128 = 2^7. И наши резисторы при Rстяжки = 10k равны 1.4К, 1.9К, 2.6К, 4К, 6.6К, 13.3К.

Теперь чтобы получить номер кнопки нужно выполнить команду:

button_id = analogRead(A1) &gt;&gt; 7;
Сдвиг отбрасывает младшие 7 бит, поэтому если значение АЦП будет чуть ниже ожидаемого, то мы получим номер кнопки на единицу меньше от верного. Чтобы этого избежать применим округление:
button_id = ( analogRead(A1) + 64 ) &gt;&gt; 7; 

Графически это выглядит так:

Голубая линия отображает номер кнопки в зависимости от разбросов в значениях резисторов и напряжений. Сам номер кнопки обозначен на правой оси.

Использования ресет пина (Reset pin) для чтения кнопки.

Даташит для ATTiny85 говорит что ресет наступает если на PB5/ADC0 подается напряжение ниже V_{RST}. В качестве фактического значения указан диапазон 0.2 - 0.9 V_{cc} или 1 — 4.5 вольт. Даже шаг 0.625 В дает напряжение на первой кнопке в 4.375 В, что ниже максимальной границы, но по отзывам все же можно использовать диапазон свыше 2.2 вольт, а это интервал в 2.8 В, в котором можно разместить аж 4 кнопки с шагом 0.625 В. Также в даташите графики Figure 22-27, Figure 22-26, Figure 22-31 дают надежду на использование верхнего диапазона в 2.5 вольта для наших нужд.

Из графиков выше берем Rподтяжки = 55К и порог по напряжению сброса в 2.3В и получается следующая схема для кнопок на пине ресета.

Нижняя кнопка дает напряжение в 2.5 вольта, что очень близко к порогу срабатывания ресета, поэтому лучше обойтись только тремя, а то и двумя кнопками. Иначе можно получать неожиданные ресеты из-за различных наводок. Цепочку из двух или трех нижних резисторов можно заменить одним, равным их сумме.

Чарлиплексинг (Charlieplexing)

Что если остались только цифровые выходы? Например, esp8266 содержит только один ADC пин. Сколько нужно цифровых пинов чтобы обеспечить работу 6 кнопок? 6? — нет достаточно только трех. Секрет кроется в том, что GPIO пины поддерживают так называемое высокоомное состояние или состояние выключен (другие названия: высокоимпедансное состояние,  Z-состояние, high impedance , hi-Z, tri-stated или floating state).

На на схемах выше три цифровых вывода микросхемы попарно соединены парой кнопок, таким образом что через каждую ток может идти только в одном направлении. Для этого последовательно кнопке добавлен диод. Всего подключено 6 кнопок. Выводы сконфигурированы следующим образом А — чтение, В — высокий уровень (H), C — отключен (высокоомное состояние). в этом случае только при нажатой кнопке BA (схема С3) на выводе А появится высокий уровень. Для того чтобы в остальное время выводе был низкий уровень нужно использовать резистор подтяжки.

На следующей схеме показан случай когда нажато сразу две кнопки BC-CA

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

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

Ссылки

Использованный симулятор цепи: http://www.falstad.com/circuit/circuitjs.html