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

image

Внутри — пара замыкающих или переключающих контактов и светодиод (опционально — уже с гасящим резистором на выбор для питания от 5, 12, или 24 В). Все бы с ней хорошо, но разместить кнопку планировал на приборной панели автомобиля, а управляющий блок — в моторном отсеке. А тянуть отдельные провода для контакта и для индикации уж очень не хотелось.

Как это решить, и что для этого нужно — под катом

В итоге в голову пришло вот такое решение:



Работать будет с портами ВВ микроконтроллера, для которых есть:
a) двунаправленный режим
б) безопасное ограничение по выходному току (в STM32 ЕМНИП регулируется программно)
в) виртуальный порт, позволяющий считывать сигнал прямо с выводов (в AVR PINx)

Алгоритм такой:

1. периодически проверяем бит порта PIN, к которому подключена кнопка (например, раз в 20 мс)
1а. на время проверки, если выходной PORT был равен 0, ставим его в 1 (сама проверка — несколько микросекунд — глазом не заметно)

2. если на PIN обнаружили 0, значит кнопка нажата — запоминаем факт первичного обнаружения (для алгоритма антидребезга)
2a. если при этом на PORT был высокий уровень и мы обнаружили нажатие, снимаем его до тех пор, пока периодическая проверка п.1 не покажет устойчивое отжатие кнопки (3-5 проверок подряд) — так как светодиод и так не будет светиться из-за шунтирования контактами кнопки, а уменьшить выходной ток через порт, ограниченный при нажатии кнопки лишь выходным каскадом, очень полезно во всех смыслах

3. если подтвердили нажатие или отпускание в течение нескольких циклов проверки — обрабатываем его в программе, и зажигаем соответственно светодиод через PORT

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

Теперь для МК, которые не умеют отслеживать состояние непосредственно выводов порта, либо не имеют средств ограничения выходного тока (например, старые семейства 8051 и их клоны):



Здесь придется пожертвовать еще одной линией ВВ, настроенной на вход, и разделить гасящий резистор на две части. Алгоритм при этом остается прежним, разве что переводить выходной порт в 0 на все время, пока обнаружено нажатие кнопки, не обязательно (но по-прежнему желательно для автономных систем с точки зрения уменьшения потребляемого тока). Пропорцию значений резисторов можно выбрать и не такую экстремальную (сажать выход на землю через 39 Ом, даже на миллисекунды для некоторых МК может оказаться жестковато) — важно только, чтобы падение напряжения на светодиоде + его нижней половине ограничительного резистора при высоком уровне выходного порта было с запасом больше, чем Vcc/2

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



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

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

Комментарии (44)


  1. eta4ever
    05.06.2015 12:57
    +23

    А я поначалу подумал, что действительно 1-Wire, что в кнопку был имплантирован контроллер…


    1. gleb_l Автор
      05.06.2015 13:00

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


      1. eta4ever
        05.06.2015 13:08

        Я так поглядел, 1-Wire Slave довольно костыльно реализуется, а жаль.


      1. gleb_l Автор
        05.06.2015 15:34

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

        А вообще, в данном случае достаточно именно «проволочного» решения на «клиентской стороне» — без всяких контроллеров в кнопках as long as все можно сделать аппаратно-программно на «сервере»


      1. propell-ant
        05.06.2015 17:13

        так, на всякий случай, если кому-то интересно
        человечек сделал клиента 1w на avr
        avr.ru/ready/measure/mass/debet/part3


        1. ploop
          05.06.2015 17:44
          +1

          Да их кто только не делал, и я в том числе (это следующий уровень после миганий светодиодами). Дать код на ассемблере? :)


          1. propell-ant
            05.06.2015 17:58

            дать!
            хабр индексируется лучше очень многих ресурсов — легче найти новичкам


            1. ploop
              05.06.2015 18:09

              Только вряд ли кому поможет кусок, вырванный из контекста, но ладно:

              Код
              ; задержка в 1 микросекунду (максимально достижимой точности!!!)               
              ; учитывается длительность RCALL и RET                                         
              ; НЕ ДОЛЖНА ИСПОЛЬЗОВАТЬ НИ ОДНОГО РЕГИСТРА !!!!                               
              ; для низкой тактовой частоты, возможно, придется заменить на макрос из NOP-ов 
              delay1us:                                                                   
              ;          +3                     
                nop      ;1                  
                push r0  ;2                                                                  
                pop r0   ;2              
                push r0  ;2               рассчитано ДЛЯ 20МГц!!!!!!
                pop r0   ;2               
                push r0  ;2              
                pop r0   ;2              
                ret      ;4               = 20 тактов (1 микросекунда)
              ;-------------------------------------------------------------------------------
              delay10us:
              ; Задержка в 10 микросекунд (основана на delay1us) - повышенной точности!      
              ;                    + 3
                push  wiretemp     ; 2
                ldi  wiretemp, 9   ; 1     << для 20МГц!!! 
                d_loop:            ;                        
                rcall  delay1us    ; 8 
                dec  wiretemp      ; 1 
                brne  d_loop       ; 2/1 
                pop  wiretemp      ; 2  
                nop                ; 1  
                nop                ; 1 
                nop                ; 1 
                ret                ; 4 
              ;-------------------------------------------------------------------------------
              ; задержка в wiretemp десятков микросекунд - повышенной точности не требуется
              ; вход: WireTemp = число десятков микросекунд для задержки (регистр портится)
              ;-------------------------------------------------------------------------------
              delayus:
                dec wiretemp
              delayus1:
                rcall delay10us
                dec wiretemp
                brne delayus1
                rcall delay1us
                rcall delay1us
                rcall delay1us
                rjmp delay1us
              ;-------------------------------------------------------------------------------
              ; выполняет выдачу сброса в линию 1-wire и возврат состояния PRESENCE
              ; вход: ничего
              ; выход: флаг Z установлен, если был PRESENCE
              ; так как длительность RESET может быть любой, прерывания не запрещаются, 
              ; если это не необходимо
              ;-------------------------------------------------------------------------------
              reset1w:
                wire1
                sbis wirepin,wirebit            ; проверим наличие 1 в линии
                rjmp presence_fault             ; если нет - это ошибка
                wire0                           ; давим линию в 0
                ldi wiretemp,49
                rcall delayus                   ; 480 микросекунд задержка
                wire1                           ; линию в 1
                ldi wiretemp,6
                rcall delayus                   ; 60 микросекунд задержка
              ; начинаем ждать PRESENCE
                ldi wiretemp,250                ; ждем с запасом
              rw1_wait:
                sbis wirepin,wirebit
                rjmp presence_ok
                rcall delay1us
                dec wiretemp
                brne rw1_wait
              presence_fault:
              ; сюда мы попадаем, если не дождались PRESENCE
                clz                             ; сбрасываем Z-флаг
                ret
              presence_ok:
                ; PRESENCE получен
                sez
                ldi wiretemp,49
                rcall delayus
                ret                             ; Z-флаг и так стоит
              ;-------------------------------------------------------------------------------
              ; выполняет прием байта из линии
              ; вход: ничего
              ; выход: r0 - принятый байт
              ;-------------------------------------------------------------------------------
              resive1w:
                ser wiretemp
                mov r0,wiretemp
              ;-------------------------------------------------------------------------------
              ; выполняет передачу байта в линию
              ; вход: R0 - выводимый байт
              ; выход: R0 - то, что удалось ВВЕСТИ из линии 
              ; портит: R0
              ; примечание: если делать ВЫВОД 0хFF, то на выходе будет ПРИНЯТЫЙ байт
              ;-------------------------------------------------------------------------------
              send1w:
                ldi wiretemp,8                  ; число битов в байте (надо же :) )
              
              sw1_next:                         ; сохраним флаги, 
                pushf                           ; т.к. будем запрещать прерывания
                cli                             ; запрещаем прерывания
                wire0                           ; давим линию в 0
                rcall delay1us                  ; 1 мкс задержки
                ror r0                          ; выталкиваем выводимый бит
                brcc s0                         ; если бит C = 0 - обход 
                wire1                           ; линию в 1
              s0:
                push wiretemp
                ldi  wiretemp,9                 ; в некоторых случаях может потребоваться
                                                ; увеличить это значение, но не более 13!
              sw1_wait:
                rcall delay1us
                dec wiretemp
                brne sw1_wait
                clc  
                sbic wirepin,wirebit
                sec
                ror wireres
                ldi wiretemp,9
                rcall delayus                   ; 90 микросекунд - длительность тайм-слота
                wire1  
                pop wiretemp
                popf
                dec wiretemp
                brne sw1_next    
                mov r0,wireres
                ret  
              ;-------------------------------------------------------------------------------
              ; считывает 9 байтов в буфер, подсчитывает CRC
              ; портит r0 и SREG, регистры X и Z
              ;-------------------------------------------------------------------------------
              readbytes1w:
                clr wiretemp
                sts CRC,wiretemp                ; обнуляем CRC
                ldiX Buffer
                ldi wiretemp,9
              r1w_next:
                push wiretemp
                rcall resive1w                  ; считываем 1 байт
                ; r0=считанному байту, начинаем подсчет CRC
                st X+,r0                        ; сохраняем принятый байт
                rcall crc8                      ; считаем контрольную сумму
                pop wiretemp
                dec wiretemp
                brne r1w_next
                ret
              ;-------------------------------------------------------------------------------
              ;  выполняет подсчет CRC по алгоритму 1-Wire
              ;  вход: r0 - считанный байт
              ;  выход: CRC - содержит подсчитанную сумму
              ;  портит: регистр Z
              ;  примечание: перед первым вызовом CRC необходимо обнулить
              ;-------------------------------------------------------------------------------
              crc8:
                push r0
                ldi wiretemp,8
                mov wireres,wiretemp
              r1w_loop_crc:
                lds wiretemp,CRC
                eor r0,wiretemp
                ror r0
                mov r0,wiretemp
                brcc zero
                ldi wiretemp,0x18
                eor r0,wiretemp
              zero:
                ror r0
                sts CRC,r0
                pop r0
                ; 4 следующие команды делают циклический сдвиг r0
                push r0
                ror r0
                pop r0
                ror r0
                ; сдвиг закончен - очень удобно, не правда ли?!
                push r0
                dec wireres
                brne r1w_loop_crc
                pop r0
                lds r0,CRC
              ret
              ;-------------------------------------------------------------------------------
              ; Выбор датчика. Ссылка на массив с уникальным ID в Z
              ;-------------------------------------------------------------------------------
              sel_sensor:
                Send_1w CMD_MATCH_ROM           ; Обращение к конкретному устройству
                ldi r16,8
                ss01:
                  lpm r0,Z+
                  rcall Send1w
                  dec r16
                  brne ss01
              ret
              


        1. eta4ever
          05.06.2015 20:18

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

          Где три провода, там и четыре, а где четыре — там и i2c или rs-485, без велосипедов.


  1. shushu
    05.06.2015 13:46

    Я по подобному принципу лет 15 назад светодиод на выключатель освещения в квартире цеплял :)


  1. KonstantinSoloviov
    05.06.2015 15:59

    А зачем вы шунтируете кнопкой и диод и резистор? Может только диод?


    1. gleb_l Автор
      05.06.2015 16:04

      смысл в том, чтобы пересилить выход порта и притянуть его к нулю, и это притягивание заметил МК. Если просто закоротить диод, то резистор не позволит этого сделать — просто чуть увеличится выходной ток.


      1. KonstantinSoloviov
        05.06.2015 16:16

        а почему бы не инвертировать режим работы вывода (т.е. переключить выход на вход) на те самые тестовые 20мс? и «перетягивать» ничего не придется


        1. gleb_l Автор
          05.06.2015 16:24

          не получится — так как светодиод с резистором и так притянут вход к нулю, даже если включить внутренний pull-up — так как сопротивление pull-up'а на два-три порядка больше, чем гасящего резистора — в итоге, на нем почти ничего не упадет, плюс светодиод под микротоком (ну допустим, 1 вольт) — и МК не почувствует разницы между закороченным и свободным светодиодом — оба уровня будут ниже входного порога


          1. KonstantinSoloviov
            05.06.2015 16:30

            пожалуй, а как на счет АЦП? должна же быть заметна разница!


            1. gleb_l Автор
              05.06.2015 16:39
              +1

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


          1. gleb_l Автор
            05.06.2015 16:37

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


  1. eps
    05.06.2015 16:57

    У Maxim есть DS2413, микросхема с 2 вводами-выводами, работающая по тому самому 1-Wire.

    Используется, например, в Эппловском разъёме MagSafe, где управляет 2 светодиодами. Стоит чуть больше доллара оптом.


    1. ploop
      05.06.2015 16:59
      +1

      У автора не 1-Wire, а «1-Wire», т.е. один провод. Всем известный протокол тут не при чём.


      1. eps
        05.06.2015 17:03

        Именно. И у Maxim, создателя настоящего 1-Wire, есть микросхема, выполняющая функции, которые нужны автору.

        При этом — все преимущества 1-Wire — готовые библиотеки, шина для нескольких устройств с авто-адресацией и, конечно, надёжность. Это всё оттестировано до нас и работает.


        1. gleb_l Автор
          05.06.2015 17:20

          А еще одного провода для собственного питания DS2413 часом не требует? ;)


          1. eps
            05.06.2015 17:28
            +1

            Нет, там паразитное питание через шину данных. В datasheet пишут, что может давать на нагрузку ток в 20 мА


            1. gleb_l Автор
              05.06.2015 17:37

              Это прекрасно, а сам светодиод питать тоже паразиткой с шины? Чип-то получит команду на его включение, а кто даст энергию ему гореть? Или 1-wire переживет шунтирование светодиодом с резистором? ;)


              1. eps
                05.06.2015 18:20

                Немного тока с неё взять получится, но, говорят, мало. Единицы милиампер и 5 вольт. На самом деле, вообще никакой конкретной информации нет.

                Можно взять напряжение для нагрузки на месте (всё-таки это автомобиль) — но это не так интересно.

                (не заметил, что там всего лишь открытый сток)


  1. Big-Boss
    05.06.2015 22:47

    Просто вставлю…

    www.youtube.com/watch?v=z3XZDRLULs4


    1. gleb_l Автор
      05.06.2015 23:16

      Ага, примерно так :)


  1. imwode
    06.06.2015 05:16
    +2


    Проблема высосана из пальца, извините


    1. imwode
      06.06.2015 06:35

      Вот код для дуни:

      int state = 3; // просто индикатор состояния кнопки
      int one_wire = 5; //"1 вайр"
      
      // the setup routine runs once when you press reset:
      void setup() {
        // initialize the digital pin as an output.
        pinMode(state, OUTPUT);
        pinMode(one_wire, OUTPUT);
        digitalWrite(one_wire, LOW);
      }
      
      // the loop routine runs over and over again forever:
      void loop() {
        pinMode(one_wire, INPUT);
        digitalWrite(state, !digitalRead(one_wire));
        pinMode(one_wire, OUTPUT);
        delay(20);
      }
      

      Симулятор: 123d.circuits.io/circuits/851507-pb-led


    1. gleb_l Автор
      06.06.2015 09:11

      Не извиняю — посчитайте количество проводов, связывающих кнопку-индикатор с МК системой в вашем случае


      1. imwode
        06.06.2015 10:08

        ээээ, 1?
        Хочешь сказать, три? В твоем тогда два. В чем проблема цапануть +12В под приборкой для постоянной индикации без всяких выкрутасов?


        1. ploop
          06.06.2015 18:24

          В чем проблема цапануть +12В под приборкой для постоянной индикации без всяких выкрутасов?

          Только вот надо +5, а не +12.


          1. imwode
            06.06.2015 18:33

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


            1. ploop
              06.06.2015 18:38

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


  1. k0der1
    06.06.2015 17:12
    +1

    Можно узнать модель кнопки. Спасибо.


  1. Muzzy0
    06.06.2015 20:22

    Последнюю схему не понял. Как при одном входе понять, какая кнопка нажата?


    1. ploop
      06.06.2015 23:41

      Сканировать по очереди, например.
      Но если кнопок много, то и линий много, проще коммутатор сделать и передавать уже настоящей 1-wire


      1. Muzzy0
        06.06.2015 23:43

        Так а что сканировать по очереди, если физический вход один? Например, что мы получим в случае, если нажато несколько кнопок одновеременно?


        1. ploop
          07.06.2015 00:25

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


          1. Muzzy0
            07.06.2015 00:35

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


            1. ploop
              07.06.2015 00:40

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


  1. KurilkaRymin
    06.06.2015 23:59

    image


    1. Muzzy0
      07.06.2015 00:33

      Это больше похоже на правду :) в исходном посте про АЦП ни слова.


  1. Alexeyslav
    08.06.2015 08:50
    +1

    Ха… все выходные не мог дочитать статью до конца, а особенно комментарии…
    Товарищи! Вы где усыпили своего инженера?
    Не надо ничего замыкать! Подключите кнопкой параллельно светодиоду и резистору… конденсатор! Он не шунтирует цепь, по крайней мере надолго, и если ограничить ток его заряда резистором в 100 Ом он вообще не будет создавать нагрузку порту и мешать светодиоду. Зато его наличие легко определяется по инерционности — когда светодиод включен, короткое отключение порта в режим входа и через определенное время смотрим — есть напряжение, значит кнопка замкнута нет напряжения — кнопка разомкнута.
    Если светодиод погашен — выдаем короткий импульс «1» по окончанию импульса через несколько микросекунд смотрим — есть напряжение(паразитная емкость быстро зарядится, но будет держать заряд еще считанные микросекунды) значит кнопка разомкнута, если зарядится не успеет — копка замкнута и подключен значительно больший конденсатор, которому не хватило времени чтобы зарядится.

    Немного усложнит опрос кнопки но… преимущества на лицо.


    1. gleb_l Автор
      08.06.2015 12:26

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

      И чтобы не писать несколько ответов — кнопки такие продаются на aliexpress.com, а вариант схемы с одним входом, развязанном диодами, конечно, работать не будет — это я загнул. Нужно будет использовать первый или второй вариант

      BTW, некоторые кнопки имеют НЗ контакты (но они вроде как дороже, чем НР) — их можно включить последовательно со светодиодом/резистором, и проверять порт в режиме чтения на 1