Маленькие изыскания в вопросе использования одного пина Arduino для индикации. Причем я решил ограничиться вопросом подключения ТОЛЬКО светодиодов. Варианты с 1-wire или RS протоколами, а так же использование пина для ввода данных в данном опусе рассматриваться не будут
Человеческий мозг устроен таким образом, что самые интересные и невероятные решения проблемы рождаются в экстремальных ситуациях. Экстрима нам конечно же не нужно, мы просто смоделируем такую ситуацию. Представьте себе, что при проектировании некого устройства, у вас остался свободным только один пин ардуинки. Как же его по максимуму задействовать?. Первое, что приходит в голову — подключить светодиод. Не спорю, даже один светодиод весьма много чего может сказать: горит, не горит, определенным образом подает вспышки и т.п. Вот пример. Все это имеет право на жизнь, но два светодиода — все же гораздо нагляднее.
Ага, два светодиода и всего один пин. Ну посмотрим….
Для удобства отладки и тестирования, я на скорую руку набросал отладочный cкетч и собрал простейшую схему на макетке с одной кнопкой. При нажатии на кнопку соответствующий выход меняет свое состояние. (Позже в перечень режимов добавился и Hi-Z). Чтобы понимать «что сейчас»- при режиме Hi-Z дополнительно светится встроенный светодиод на 13 пине.
//************************************************** // Отладочный скетч опуса "И один в поле воин..." // для robocraft.ru, кибервесна-2016 // при нажатии кнопки последовательно происходит переключение // пина между след. состояниями: Hi-Z, HIGH, LOW //************************************************** #define ledPin 3 #define BUTTON_PIN 5 void setup() { pinMode(13, OUTPUT); pinMode(BUTTON_PIN, INPUT); // устанавливаем пин как вход } enum mode{p_HIGH, p_LOW, p_HZ, END}; // именнованные режимы byte currentMode = p_HZ; //==== Основной цикл ==== void loop() { if (digitalRead(BUTTON_PIN)) { currentMode++; if (currentMode == END) { currentMode =0; } } // end if if (currentMode == p_HZ) { digitalWrite(13, HIGH); } else { digitalWrite(13, LOW); } switch (currentMode) { case p_HIGH: pin_High(); break; case p_LOW: pin_Low(); break; case p_HZ: pin_Hz(); break; }// end switch delay(200); } //end main loop //=============== void pin_High() { pinMode(ledPin, OUTPUT); digitalWrite(ledPin, HIGH); } //=============== void pin_Low() { pinMode(ledPin, OUTPUT); digitalWrite(ledPin, LOW); } //=============== void pin_Hz() { pinMode(ledPin, OUTPUT); digitalWrite(ledPin, LOW); pinMode(ledPin, INPUT); }
Напомню, что пин может находится в состоянии LOW или HIGH. Попросту говоря, если в пин записан «0», то пин Arduino выступает в роли земли, а если «1» — в роли источника питания (правда весьма слабенького, с допустимым током до 40 мА). Записывая в пин то или иное состояние, мы реально сможем включать нужный нам светодиод. А что же со вторым светодиодом? Как его подключить? Первое, что пришло на ум — первый светодиод подключаем непосредственно к пину, а дальше использовать транзистор в качестве инвертора и второй светодиод подключить к инвертированному выходу.
Давайте и попробуем этот вариант.
Как это работает?
Когда в пине выставлен «0» транзистор T1 закрыт и ток течет по цепочке [+Vсс -> R3 -> Led2] и, соответственно, светится только красный светодиод. При записи в пин «1» — загорается зеленый светодиод, открывается транзистор и ток начинает течь по линии [+Vсс -> R3 -> T1(кэ)]. Налицо крупный недостаток такого варианта: при подаче уровня HIGH, кроме горящего нужного нам светодиода, транзистор T1 ТОЖЕ будет находиться в открытом состоянии и через него будет НАПРАСНО течь ток, как через байпас.
Но, несмотря на распространенность n-p-n транзисторов, свет клином на них не сошелся. Может не совсем это верно, но я почему-то запомнил такую фразу:»n-p-n транзистор нужен, чтобы тянуть к земле, а p-n-p — к питанию«. Автора этой фразы уже не установить, но я достаточно часто ею пользуюсь. А давайте попробуем вариант с p-n-p транзистором.
Как это работает?
Когда в пине выставлен «0» транзистор открыт и ток течет по цепочке [+Vсс -> Т1(эк) -> R3 -> Led2] и светится только красный светодиод. При записи в пин «1» — ток течет из пина Ардуино через резистор R1 и загорается зеленый светодиод led1, транзистор T1 закрыт. Вот, уже ГОРАЗДО лучше…
Однако, есть более простой вариант: два последовательно включенных светодиода с токоограничивающими резисторами. Кстати, такое решение я впервые увидел в подборке типовых схем подключения — ABC ArduinoBasicConnection
Как это работает?
Тут все просто. При «1» на пине, ток будет течь по цепочке [pin -> led2 -> R2]. В случае «0» на пине — ток пойдет по цепи [+Vcc -> R1 -> led1 -> pin]. Что же, вполне неплохо и работоспособно.
Все вышеперечисленные варианты схем имели один очень важный недостаток. У нас получалось, что либо один светодиод горит, либо другой. Выключить оба светодиода (в описанных выше схемах) не представляется возможным. Или нет? Может как нибудь можно? Да-да-да…напрягаем извилины. Пин может находиться еще и в состоянии Hi-Z pinMode(pin, INPUT). С точки зрения схемотехники я рассматриваю это состояние как будто этой части с Ардуино вообще в схеме и нет. Проверяем на последней схеме. Блин! А при таком состоянии пина (INPUT) у нас оба светодиода тускло подсвечиваются, значит по всей нашей цепочке течет, пусть и незначительный, но ток, достаточный для свечения светодиодов. Не есть хорошо 🙁
А давайте попробуем немного изменить последнюю схему, добавив парочку диодов. Ну и что мы имеем в таком варианте?
Как это работает?
Чтобы диод начал открываться, необходима разность потенциалов между анодом и катодом порядка 0.5 вольт минимум. Для светодиода эта величина составляет >1.5 вольта (в зависимости от типа и цвета светодиода). При напряжении питания 5 вольт, мы получаем 0.5В х 4+1.5В х 2 = 5 вольт. Следовательно, все диоды находятся в закрытом состоянии. При выставлении на пине «1» ток будет течь по цепи D3 -> D4 -> R2 -> LED2. Круто! Теперь есть возможность включать (правда, по отдельности) каждый светодиод и гасить оба.
А давайте задействуем этот режим по полной. Ведь можно к одному пину подключить 3 светодиода и управлять включением каждого из них. Забегая вперед, скажу, что к сожалению в таком варианте выключить все светодиоды ТЕПЕРЬ ТОЧНО не получится. Думаю, что в ряде случаев, этим недостатком вполне можно пренебречь. По сути, эта схема — это чуть расширенная версия предыдущей. Добавлен узел, который будет активен когда pin Ардуино будет находится в состоянии Hi-Z.
Как это работает?
пин = HIGH. Т1 — закрыт, Т2 — открыт. Ток протекает по цепи [Pin -> D3 -> D4 -> R5 -> Led3]
пин = LOW. Т1 — открыт, Т2 — закрыт. Ток протекает по цепи [+Vcc -> D1 -> D2 -> R4 -> Led2]
пин = INPUT. Диоды D1, D2, D3, D4 — закрыты и незначительный микроток не достаточен для зажигания Led2 и Led3. Т1 и Т2 — открыты. Ток протекает по цепи [+Vcc -> T1(эк) -> R1 -> Led1 -> T2(кэ)]
Кстати, последний вариант подключения 3-х светодиодов мне ОЧЕНЬ понравился и я произвел для него замеры напряжений на разных участках цепи.
Небольшое видео работы такого варианта схемы.
А теперь ВООБЩЕ трэш! А восемь светодиодов можем? Можем, да хоть 16… правда необходимо задействовать микросхему 74HC164 (или несколько, если хотим каскадировать включение). Этот вариант схемы подсмотрен у небезызвестного DiHalt-а. Итак, эта микросхема — ни что иное, как сдвиговый регистр. Но, в отличии от горячо мною любимой микросхемы 74HC595, у 74HC164 нету “защелки”. Т.е. данные СРАЗУ попадают на выходы. Это и хорошо и плохо… Хорошо: не нужен дополнительный сигнал на “защелку”; плохо: все продвижения данных будут видны красивыми “переливами”.
Линию RESET мы использовать не будем, а сразу заблокируем ее подтянув к питанию. Два входа данных (A, B) чаще всего объединяют, и тактовым входом (Clock, он же CLK) обеспечивают продвижение битиков по регистру. Ага, все же нужно два пина как минимум: 1-для выставления данных, 2-для тактирования. И тут на помощь к нам приходит “Интегрирующая RC-цепочка”
Выходным напряжением в интегрирующей цепи является напряжение на конденсаторе. Естественно, если конденсатор разряжен, оно равно нулю. При подаче импульса напряжения на вход цепи, конденсатор начнет накапливать заряд, и накопление будет происходить по экспоненциальному закону, соответственно и напряжение на нем будет нарастать по экспоненте от нуля до своего максимального значения. Более научную версию с формулами можно прочитать тут. Резистор ограничивает ток заряда конденсатора, поэтому чем больше его сопротивление, тем больше время заряда конденсатора. Также и для конденсатора — чем больше емкость, тем большее время он заряжается.
Если мы пустим через такую RC-цепочку длинный импульс, то конденсатор успеет как полностью зарядиться, так и полностью разрядиться. Если подадим короткий импульс, по времени намного меньше чем постоянная времени t, то напряжение на конденсаторе изменится совершенно незначительно. Я бы здесь провел аналогию с автомобилем: если при движении по дороге резко нажать на педаль газа и отпустить, то стрелка тахометра просто “взлетит”, а вот стрелка спидометра лишь немного изменить свое положение.
Как это работает?
Выставляем на пин нужный нам битик и ждем пока значение на выходе RC-цепочки не «устаканится» до нужного значения и делаем весьма короткий тактовый “дрыг”. Выставленный бит попадает на выход регистра. И так 8 раз 🙂 Понятное дело, что тактовый импульс снимается до RC цепи, а данные — после.
Схема такая:
Данный вариант схемы я решил собирать не на макетке, а в виде реальной платы (шутка ли, такой удобный вышел модуль для той же отладки). Печатка получилась примитивная до безобразия 🙂
К сожалению, у меня не оказалось одинарного 7-ми сегментного индикатора, зато “до фига” сдвоенных. Я оставил место под перемычку, запаяв которую, можно сделать вывод одинаковой информации сразу на две половинки индикатора.
Итого: три провода (питание, земля и управляющий) 🙂 Ниже код, который демонстрирует банальный отсчет от 0 до 9
//задержка нужная для заряда-разряда конденсатора (подобрана экспериментально) #define led_delay 10 //Пин подключения платы индикации #define outPin 8 // таблица кодов для индикации 7-сегментника // 0 1 2 3 4 5 6 7 8 9 const byte PROGMEM Encode7seg[]={252,192, 181, 213, 201, 93, 125, 196, 253,221}; void setup() { pinMode(outPin,OUTPUT); } //демонстрашка простого отсчета от 0 до 9 void loop() { for (byte z=0;z<10;z++) { byte num_out=pgm_read_byte(&Encode7seg[z]); out7seg(num_out); delay(500); } }// end main loop //===================== // void output 7segment void out7seg(byte out_byte) { for (byte i=0;i<8;i++) { digitalWrite(outPin,((out_byte>>i) & 0x01)); //выделяем нужный бит и выставляем на линию delay(led_delay);//ждем, пока RC цепочка устаканится в нужном значении digitalWrite(outPin,LOW);//делаем быстрый синхроимпульс digitalWrite(outPin,HIGH); }//end for }
А ниже можно посмотреть результат работы
Ну вот, пожалуй, и все. Естественно, далеко не все варианты применения одного пина мне удалось рассмотреть в этой статье. Оставим небольшой задел на будущее…
P.S. Буду очень-очень рад, если кому-то этот материал будет полезен 🙂