2 порта 4 кнопки

HWman
HWman аватар
Offline
Зарегистрирован: 26.02.2013

Мне из Китая пришла одна приблуда, схему которой нашёл в сети, и когда рассматривал, то обнаружил для себя очень интересную часть, вот она:

 

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

Как это работает?

Для первого порта включаем нагрузочный резистор (pull-up) и проверяем состояние на порту, если нажата кнопка "DOWN", то получим низкий логический уровень. Точно такая же картина и с кнопкой с кнопкой "SET", только pull-up делаем уже для второго порта.

Для чтения кнопки "OK" — переводим второй порт в pull-up, а первый — устанавливаем как выход, и делаем низкий логический уровень. В этом случае, при нажатии кнопки "OK", ток потечёт от второго порта черед диод к первому, тем самым, при считывании состояния второго порта, у нас получится низкий логический уровень. Аналогичная же ситуация и с определением нажатия кнопки "UP" — pull-up на первом порте, второй ставим на выход и устанавливаем низкий логический уровень. При нажатии на кнопку "UP" ток потечёт через диод к второму порту, и соответственно на первом порте будет низкий логический уровень.

HWman
HWman аватар
Offline
Зарегистрирован: 26.02.2013

Написал код для ардуино:
 

#define PIN1 10
#define PIN2 11

#define DOWN 1
#define OK 2
#define UP 3
#define SET 4

void setup()
{
  Serial.begin(9600);
}

char checkKeys()
{ // понеслась

  pinMode(PIN1, OUTPUT);
  digitalWrite(PIN1, LOW);
  pinMode(PIN2, OUTPUT);
  digitalWrite(PIN2, LOW);

  int keysRead = 0;
  for (keysRead = 1; keysRead <= 4; keysRead++) {
    switch (keysRead) {
      case DOWN:
        digitalWrite(PIN1, INPUT_PULLUP); 
        if (digitalRead(PIN1) == false) {

          return DOWN;
        }
      break;

      case OK:
        pinMode(PIN1, OUTPUT);
        digitalWrite(PIN1, LOW);
        digitalWrite(PIN2, INPUT_PULLUP);
        if (digitalRead(PIN2) == false) {

          return OK;
        }
      break;

      case UP:
        pinMode(PIN2, OUTPUT);
        digitalWrite(PIN2, LOW);
        digitalWrite(PIN1, INPUT_PULLUP);
        if (digitalRead(PIN1) == false) {

          return UP;
        }
      break;

      case SET:
        digitalWrite(PIN2, INPUT_PULLUP); 
        if (digitalRead(PIN2) == false) {

          return SET;
        }
      break;
      default:
        return 0;

    }
  }
}


void loop()
{
  int keyScan = checkKeys();
  Serial.print(keyScan);
  Serial.print(" ");
  switch (keyScan) {
    case DOWN:
      Serial.println("DOWN");
      break;
    case OK:
      Serial.println("OK");
      break;
    case UP:
      Serial.println("UP");
      break;
    case SET:
      Serial.println("SET");
      break;
  }

  delay(500);
}

В железе собрать не могу пока что, пробовал в протеусе, там вообще всё лажает:

 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

HWman пишет:

Написал код для ардуино:

В железе собрать не могу пока что, пробовал в протеусе, там вообще всё лажает:

Принято к сведению.

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

dhog1
Offline
Зарегистрирован: 01.03.2016

ЕвгенийП, ну что вы вредничаете? Уже о простых вещах приходится просить. 4 кнопки на 2 порта интереснее, чем обрыдшие семисегментники.

Код смелый, э-ээ... решительный. Насколько понимаю, в _этом_конкретном_ случае сжечь порты не получится.

jeka_tm
jeka_tm аватар
Offline
Зарегистрирован: 19.05.2013

да при желании 1 цифровым пином можно опрашивать 4 кнопки, замороченная схема правда получается. время считать надо. конденсаторы нужны и резистор. или резисторы и конденсатор

axill
Offline
Зарегистрирован: 05.09.2011

Сжечь порты по глупости можно, если перевести оба на выход и установить разные уровни

хитрость прозорливая, единственно надо понимать, что различить значения одновременно нажатых down и up или set и ok не получится

Здесь скорее всего сама логика испльзования кнопока исключает одновременное нажатие up и down или set и ok

 

axill
Offline
Зарегистрирован: 05.09.2011

jeka_tm пишет:

да при желании 1 цифровым пином можно опрашивать 4 кнопки, замороченная схема правда получается. время считать надо. конденсаторы нужны и резистор. или резисторы и конденсатор

Проще тогда 4 кнопки на аналоговый вход повесить

jeka_tm
jeka_tm аватар
Offline
Зарегистрирован: 19.05.2013

проще. но разговор про цифровые шел

axill
Offline
Зарегистрирован: 05.09.2011

Для цифровых проще взять корпус с большим числом ног)

для DIY это самый верный вариант

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

 

Tomasina
Tomasina аватар
Offline
Зарегистрирован: 09.03.2013

а зачем светодиод шунтировать резистором?

dhog1
Offline
Зарегистрирован: 01.03.2016

HWman

Ирония была оттого, что вы сами дали ссылку на "что такое pull up", но потом в коде немного поторопились. Подумал, может это хак какой, ну кто ж знает.

На самом деле DigitalWrite(pin, INPUT_PULLUP) это вроде DigitalWrite(pin, HIGH) (константа INPUT_PULLUP заведомо больше 1), а вы хотели написать pinMode(pin, INPUT_PULLUP), т.е. перевод пина в высокоимпедансное состояние, что несколько другое.

jeka_tm пишет

(да при желании 1 цифровым пином можно опрашивать 4 кнопки, замороченная схема правда получается. время считать надо. конденсаторы нужны и резистор. или резисторы и конденсатор) [ссылки делать не научился]

Точно, при желании 1 PCI (ножка, где разрешены внешние прерывания по смене уровня) нога может быть завязана на разумно большое количество тактовых кнопок - несколько, десяток-другой. Смысл именно в том, как вы и заметили, что прийдется считать время. Иными словами - заставить конкретную кнопку порождать импульс определенной длинны, чтобы их (кнопки, по длительности импульса) как-то отличать в обработчике прерывания. Не знаю как с т.з. экономики, но структурно это несложно, программная обработка тривиальна. Если это интересно, приведу подробности.

 

jeka_tm
jeka_tm аватар
Offline
Зарегистрирован: 19.05.2013

dhog1 пишет:

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

приводи)

dhog1
Offline
Зарегистрирован: 01.03.2016

jeka_tm     Привожу.

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

2. Внешних прерываний у ATMega мало. Число прерываний по состоянию пина считаем по таблице векторов прерываний, для народного 328Р их 5. Ковыряться в одном обработчике прерывания с конкретным пином, вызвавшим это прерывание, э-ээ... не хочется.

3. Изврат выглядит таким образом.

Формируемое цепочкой RxCx возмущение отрисовывается в импульс триггером Шмитта, выходы стандартной логики объединяются схемой OR в один общий. На рисунке нас интересует верхняя часть, где 5 тактовых кнопок, которые оптимально заполняют ножки логических ИС.

Мне не удалось посчитать с приемлемой точностью зависимость длины поступающего на вход МК импульса от времени RC. На практике работаю с импульсами длительностью от 920 мкс до 3.1 мс (конденсаторы 104, 224, 334, 474 и 105, резисторы подстроечные на 10 кОм).

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

interrupt [PC_INT1] void pin_change_isr1(void) {    // Pin change 8-14 interrupt service routine

unsigned char bidx;

    if (C_CHECKBIT(BPIN1_PIN)) {
        TCNT1H = 0;
        TCNT1L = 0;
        TCCR1B |= (1<<CS12);                // 32 us per tick
    } else {
        TCCR1B &= ~(1<<CS12);
        bidx = TCNT1L;
        if ((bidx >> 4)) {
            C_SETBIT(BTN_PRESSED);
            if (bidx < bticks[2] ) {
                if (bidx > bticks[0]) {
                    if (bidx < bticks[1]) C_SETBIT(BTN3);
                    else C_SETBIT(BTN4);
                } else C_SETBIT(BTN2);
            } else {
                if (bidx < bticks[3]) C_SETBIT(BTN1);
                else  C_SETBIT(BTN5);
            }
        }
    }
}

В приведенном коде (для 8 МГц МК) используется Timer1, считающий по 32 мкс за тик (его младший байт, так подобраны длительности импульсов в тиках). Значения таймера сравниваются с характерными значениями (их величины находятся "между" длительностями импульсов). И выставляются какие-то биты, соответствующие определенным кнопкам, для последующей обработки в state machine основного цикла приложения (loop).

Timer1 не монополизируется, не используются обработчики его прерываний, поэтому он доступен и для других дел. (У меня он еще обычно служит для считывания датчиков вроде DHT и/или работы с One Wire, все в рамках одного приложения).

5. На практике обычно "назначаю" одну (из пяти) кнопку чем-то вроде компьютерного CapsLock'а и программно располагаю 8-ю тактовыми кнопками.

6. Ну и да, никаких "двойных кликов" или "долгих нажатий" от тактовой кнопки (этот девайс так называется) "мне религия не позволяет". Ну это личные заморочки.

7. На доступном для моих рук железе это выглядит так.

P.S. в п.6 погорячился. "Долгое нажатие" имеет место быть, но с извратом. В момент нажатия в коде выставляется какой-нибудь бит, который сбрасывается в момент отпускания. Трактуется это так - пока нажата кнопка. Это вот шестая кнопка на схеме или фото (без подстроечника), но она подключается традиционно и заводится на отдельное прерывание. К теме это не относится, просто чтобы на "гибкость религии" указать.

axill
Offline
Зарегистрирован: 05.09.2011

ужас) это называется нагромоздить аналог на цифру

никто конечно так не запретит делать, но ИМХО здесь мы в цифровую логику где есть четкое разделение на ноль и один вводим аналоговые цепочки с достаточно нестабильными харрактеристиками

по сути это вариант АЦП, то есть аналого-цифрового преобразования, только преобразуются не уровни, а временные задержки заданные аналоговыми RC цепочками. Все намного проще делается на аналоговом пине, к тому же кто не знает у atmega328 в TQFP корпусе есть два "бесплатных" аналоговых пина которые могут работать исключительно как аналоговые это A6/A7

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

насчет прерываний, их в atmega328 24 штуки. Другое дело, что они все группируются всего на 5 векторов прерываний, но это не бог весть какая проблемка

 

jeka_tm
jeka_tm аватар
Offline
Зарегистрирован: 19.05.2013

да уж. слишком замороченно. проще ацп пин использовать

dhog1
Offline
Зарегистрирован: 01.03.2016

2 axill

Ну ужас, кто же спорит, но не ужас-ужас-ужас же. Думаете мне приятно паять этот изврат всякий раз, когда нужно много кнопок? Вот jeka_tm захотел подробностей, заодно и другие развлеклись.

У меня только два замечания, одно скорее вопрос, с него и начну.

Вы пишете, что с ADC все проще, но не пойму - как, вы же не приводите последовательности действий. (Это мы наверное будем говорить о цепочке резисторов, подобранных по номиналам, где все подается на ADC, "аналоговых кнопках"). Указал же в самом начале, что кнопки "должны" сидеть на прерываниях, по "своей природе", ну или в силу моего представления о них. Как ADC отрапортует об окончании измерения, понятно. А как ADC поймет, что нужно начать измерение?

Если заставить ADC непрерывно сэмплировать вход, будем дергать прерывание ADC complete каждые 13 - 260 мкс (чтобы убедиться, что ничего не произошло, эти кнопки все ж не клавиши пианино), если использовать внешний триггер вроде INT0, это менять схему, заводить провод на пин, использовать еще один обработчик прерывания. Ну и действительно тут какое-то "идеологическое" смешение аналога и цифры. "Пинать" ADC таймером - терять таймер, он будет монополизирован.

Мне не понять простоты использования "аналогово" пина, в этом и вопрос.

И второе. Я в курсе прерываний ATMega и представляю, что PCINT означает возможность задействовать пин в прерывании по изменению уровня, которых, прерываний такого типа, всего 3 (для 328Р), по одному для каждого порта. Просто внутри прерывания (в обработчике) мне несколько неуютно, и все, что хочется сделать, так это побыстрее оттуда смыться. А не ощупывать каждый пин порта. Это, конечно, "не бог весть какая проблемка", просто... как-то неаккуратненько.

axill
Offline
Зарегистрирован: 05.09.2011

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

с аналоговым пином я бы настроил adc в режиме freerun и постоянно замерял с выставлением флагов нажатия кнопок внутри прерывания. Частоту можно поставить самую минимальную

все равно как правило в прерывании поткнопке только ставятся фоаги, а сама обработка идет в рабочем цикле

проще здесь потому, что нужно лишь n+1 резисторов где n число кнопок

при небольшом числе кнопок можно использовать 8ми битный режим, что заметно упростит и сократит время обработки в прерывании

Насчет pcint тоже не вижу проблем. На каждый пин делается флаг для запоминания предыдущего состояния пина. Сравнивая его с текущим можно легко понять по какому пину пришло прерывание и так же определить его характер (с нуля на единицу или с единицы на ноль)

dhog1
Offline
Зарегистрирован: 01.03.2016

Ув. axill,

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

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

Возражение только одно. Кнопка нажимается один раз в сто (100) лет. "Аналоговый" пин в режиме FreeRun опрашивает ADC (на самом деле заставляет произвести измерение) _непрерывно_по_мере_готовности_. Чем _ниже_ точность, чем _чаще_ (время измерения короче). Да и пес бы с ним (само измерение автономно), но мы хотим результата измерения, а это прерывание ADC Complete. МК дергается, с моей т.з. слишком часто. Разумеется это все имеет место, если кнопки работают "как мне кажется" верно, т.е. независимо от всего, что происходит в приложении, а не опрашиваются "на авось" по прохождению loop "когда-нибудь".

Ну и по поводу PCINT у нас с вами никаких разногласий нет (что радует, ну просто так). Следует только упомянуть - все работает именно так (обработка быстро и без накладных расходов) в случае, когда события на пины бедного порта поступают последовательно и каждое заканчивается прежде, чем поступает очередное событие. Ну как мы обычно (последовательно) кнопки нажимаем.

Спасибо за отклик и дискуссию.

faeton
faeton аватар
Offline
Зарегистрирован: 21.03.2016

Зачем цикл и case, если тут 4 шага всего? И ничего на выход включать не требуется, всё всегда на чтение, только переключаются подтягивающие резисторы, определяющие состояние входя без нажатия кнопки.


int Key = 0;

pinMode(Pin1,INPUT_PULLUP);
pinMode(Pin2,INPUT_PULLDOWN);
if (digitalRead(Pin1)==0)  {
   Key=1;
} else {
   if (digitalRead(Pin2)==1) {
     Key=2;
   } else {
      pinMode(Pin1,INPUT_PULLDOWN);
      pinMode(Pin2,INPUT_PULLUP);
      if (digitalRead(Pin1)==1) {
         Key=3;
      } else {
         if (digitalRead(Pin2)==0) {
            Key=4;
         }
      }
   }
}

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

А уж городить огород с чтением кондёров и внешней логикой - совсем мрачно. Уж тогда R2R цепочку на анлоговый вход или специальную бисинку, отдавающую номер нажатой кнопки по I2C или 1Wire.

faeton
faeton аватар
Offline
Зарегистрирован: 21.03.2016

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

Алгорит другой: делаем порт1 с 1 по умолчанию, порт2 - 0. Подтягивающую единицу в порт1 кнопка давит на землю прямиком, а "парная" кнопка на входе2 снимает 0, подав на него через диод подтягивающую 1 с вход1.

dhog1
Offline
Зарегистрирован: 01.03.2016

Ув. faeton

(весело) то, что вы не въехали во флуд, который не желая того я развел, это нормально. Этот флуд не имеет отношения к проблемам ТС, к сожалению.

"Уж тогда R2R цепочку на анлоговый вход" - это уже обсосано, хоть и не по теме, но вот ЧТО ТАКОЕ "или специальную бисинку, отдавающую номер нажатой кнопки по I2C или 1Wire"? Если не составит труда, скажите что это такое? Хочу "бисинку" такую, чтоб по 1Wire, чтобы это ни было.