Автоматическая подсветка лестницы. Своими руками.

Опубликовал | 20.11.2015

  • Цена: $15 — $25

Итого ещё около $10. Самая заметная по цене позиция – это алюминиевые радиаторы для силовых транзисторов TIP-122 (в корпусе TO-220). Поскольку транзисторы выбраны с некоторым запасом и способны без радиатора выдержать некоторую нагрузку (скажем, 0,5 ампера) без «посторонней помощи», Вам решать, целесообразно ли использовать радиаторы. Я использовал. В том числе, для электрической изоляции между коллекторами, поскольку в комплекте к радиаторам идут теплопроводящие диэлектрические «резинки» и пластиковые втулки для винтов.

Пин хедеры нужны для подключения сонаров и более «культурного» подключения Arduino. Искать уже нарезанные по 15 контактов хедеры конкретно под Arduino крайне нецелесообразно в финансовом плане – реально очень дорого. Необоснованно дорого. Поэтому один 40-контактный хедер при помощи ножа и надфиля легко превращается в два 15-контактных хедера и остаётся ещё небольшой кусочек. Такая же история и про хедеры для сонаров: ножик – «наше всё».

Резистор на 2 кОм нужен только для уставки тока драйвера TLC5940. Два последовательно соединённых резистора по 1кОм из предыдущего, «основного» списка, прекрасно могут справиться с этой проблемой.

Конденсаторы на 100нФ и 47мкФ нужны для более стабильного питания драйвера. На практике они не обязательны, но крайне желательны, поскольку их отсутствие может спровоцировать стробоскопическое мигание нагрузки. У меня такое было на брэд-борде. На готовой плате – не было. Но в готовый вариант конденсаторы я всё же добавил.

В панелях острой необходимости нет, но внутреннее чувство прекрасного, подсказывающее, что без них будет выглядеть депрессивно-дёшево, заставило эти панели добавить. Другими словами, причина их использования глубоко субъективна.

Что ж, с деталями почти закончили, осталась только печатная плата.

Печатная плата.

Первая версия печатной платы, сделанной самостоятельно, была 150 мм на 110 мм. Односторонняя. Я использовал фоторезист, получилось на первый взгляд неплохо. Но когда я понял, сколько отверстий мне придётся сверлить, едва не психанул. Но просверлил. Получилось «так себе» — угол сверления «гулял», сверло слегка «сползало». Результат глаз не радовал вообще. Дальше хуже: я заметил, что «накосячил» со всеми 74LS04 – неправильно развёл входы и выходы на вторых сторонах микросхем. И вот тут я психанул. Радикально.

Вторая версия платы уложилась в размер 100 мм на 100 мм, была двусторонней, с паяльной маской и металлизацией между сторонами. Красота! :) Изготовлена, разумеется, не самостоятельно, а фирмой из Айовы (США). С подрядчиком в Гонконге. Так как на MySKU было несколько обзоров на заказы плат в Китае, решил и я вынести заказ платы в отдельный обзор – реально очень много информации. Если бы я начал рассказывать все подробности здесь, в этом обзоре, то закончили бы Вы его читать еще очень, очень не скоро. Поэтому здесь я приведу только фотографии.



Надписи о производителе наверняка кого-нибудь позабавят. Что ж, сначала хотел иронично написать «Made in Belarus», но не стал этого делать по личным мотивам. Про производство самой платы я написал умышленно, дабы акцентировать на этом внимание читателей MySKU. Подробнее об этом – в обзоре про печатные платы.

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

Схема проекта.

Представленная схема подразумевает только 12 каналов. Так изначально требовалось. В связи с этим, а также в связи с нежеланием визуально усложнять схему решил оставить, как есть. Уверен, что Вы разберётесь, как задействовать все 16, а то и 32, 48, 64 и т.д. каналов.

Скетч для Arduino («прошивка»).

Как я уже упоминал, Wiring, основанный на Си(++) – не мой «родной» язык. Поэтому частенько приходилось «наступать на грабли» самостоятельно. А иногда даже и «прыгать на них с разбегу». Именно поэтому прошу не злиться на меня за, возможно, отсутствие простых решений в некоторых ситуациях, специфичных для опыта тех людей, у кого Си – язык «родной». Так или иначе, я старался максимально подробно комментировать весь код, вплоть до нудных и очевидных вещей. Думаю, только так можно доступно пояснить всю суть даже не программистам. Возможно, это станет отправной точкой для потенциального новичка в программировании.

Итак, сначала на словах постараюсь объяснить алгоритм работы контроллера, а также объяснить, что же такого принципиально иного в моём подходе к написанию «прошивки», что это даже послужило одним из самых важных мотивов к сознанию своего, альтернативного проекта.

В первую очередь, нужно понимать, что Arduino (в дальнейшем «контроллер») – это лишь «с большего окультуренная», упрощённая реализация контроллеров AtMega, со всеми «вытекающими». Но, со своей средой написания прошивок («скетчей»), где всё значительно (!) упрощено для программиста. Вплоть даже до потери части функционала по сравнению с «родными» средами программирования ATMEL в угоду простоте. Но работа постоянно идёт, проект Arduino развивается, и дополнительный, в том числе «потерянный», функционал добавляется. Не буду обсуждать перспективы проекта Arduino, поскольку вокруг много «противников» проекта в силу разных причин, но хочу поделиться впечатлениями по поводу IDE Arduino (средой программирования). На мой взгляд, она ужасна. Просто немного «доработанный» блокнот с функцией монитора COM-порта. Начав писать свой проект, уже через час я понял, что пользоваться этим «блокнотом» практически невозможно. Именно поэтому я отвлёкся и занялся поиском нормальной среды, в которой можно комфортно писать код. Критериями поиска были наличие Autocomplete (автозавершение «фраз»), наличие всплывающих подсказок по требуемым аргументам функций (подсказывает, какие аргументы вводить), наглядное форматирование, возможность «схлопнуть» тело функций для удобства навигации по коду, отсутствие «тяжеловесности» среды (очень не хотелось «монструозных, неповоротливых» продуктов). Рассмотрел несколько вариантов, доступных в интернете. Больше всего устроила среда Code::Blocks for Arduino (также доступно в архиве в конце обзора).


После стандартной Arduino IDE, это – словно откровение. Эта среда бесплатна, практически полностью устраивает по функционалу. Единственное, чем не занимался, так это настройкой среды для компиляции готового кода. Вроде, это возможно. Но я использую «копи-пейст» готового кода в стандартную среду Arduino и уже в ней компилирую код и прошиваю контроллер. Профит :).

Ну и во-вторых, непосредственно о логике работы контроллера.

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

Центральной идеей является использование быстрых операций с флагами (переменными) и очень скорой дальнейшей работы контроллера «дальше по списку» вместо непосредственных операций, которые я и заменил этими флагами. И только когда будет «удобное» время, порциями выполнять требуемые действия в соответствии с флагами. Именно порциями. Так удалось достигнуть относительной плавности «загорания» светодиодных лент. Второй причиной использования флагов и «порционности» стала необходимость как-то отслеживать состояние сонаров уже в процессе «загорания» ступенек. В моём случае порция, а следовательно, и время реакции, составляет 400 мс – 4 операции по 100 мс. За синхронизацию состояния флагов (а также буфера со значениями яркости каждой ступеньки) с «реальной жизнью» (фактическим требуемым состоянием яркостей на каждой из ступенек) отвечает функция sync2RealLife. Все дополнительные, отдельные функции (процедуры) старался называть максимально логично. Следовательно, разобраться в коде должно быть нетрудно. Но думаю, нужно хотя бы вкратце поговорить о каждой из них:

startBottomTop() – процедура поочерёдного включения ступенек (точнее, манипуляции с флагами) по направлению снизу вверх.   stopBottomTop() – процедура поочерёдного отключения ступенек по направлению снизу вверх.  startTopBottom() – процедура поочерёдного включения ступенек по направлению сверху вниз.  stopTopBottom() – процедура поочерёдного отключения ступенек по направлению сверху вниз.  sync2RealLife() – процедура, обеспечивающая синхронизацию данных из каждой ячейки массива с реальной яркостью каждой ступеньки.  sonarPrepare(byte sonarNo) – процедура первоначальной "инициализации" сонаров. Служебная процедура, выполняется один раз, при старте. Код вынесен в отдельную процедуру для улучшения «читабельности» остального кода.  sonarTrigged(byte sonarNo) – функция проверки, сработал ли сонар, с отслеживанием "подвисания" сонаров. Так же, как и в предыдущем случае, код вынесен в отдельную процедуру для улучшения «читабельности» остального кода.  sonarReset(byte sonarNo) – процедура переинициализации подвисшего сонара. На практике "отбирает" у него питание на 100 мс. Вынужденная мера для борьбы с не особо качественными сонарами.  sonarDisable(byte sonarNo) – процедура погружения сонара «в анабиоз» на некоторое время. Нужно, в том числе, для реализации режима встречного и попутного движения нескольких людей по лестнице.  sonarEnabled(byte sonarNo) – функция, проверяющая, можно ли уже пользоваться данными с сонара. Работает в связке с предыдущей процедурой.  

Также хочу обратить Ваше внимание, что, имея всего 2 сонара по краям лестницы, очень сложно симулировать и предсказать модель поведения людей на лестнице. Очень сложно. Поэтому приходилось постоянно упрощать модель до допущений. Именно поэтому так важно правильно подобрать и настроить значение констант в самом начале кода. Даже так: жизненно необходимо, иначе работать будет «криво». Это ещё одна из причин, по которой я отказался от готового решения, поскольку в том случае я буду зависеть от «пряморукости» программиста, написавшего прошивку, и/или от установщика, который «за меня» будет решать, «как правильно», не вдаваясь в подробности.

Итак, вкратце назначение всех процедур пояснил, про нюансы рассказал. Напомню, более конкретные комментарии к ним доступны в теле кода. Ну и сам код.

Скетч для контроллера
#include "Tlc5940.h"    const byte sonar1trig = 8;  const byte sonar1echo = 2;  const byte sonar1resetpin = 4;  const byte sonar2trig = 6;  const byte sonar2echo = 7;  const byte sonar2resetpin = 5;    const byte button1pin = 12;  const byte button2pin = 7;    const byte sonar1minLimit = 30;//см, если обнаружена дистанция меньше, чем это число, то сонар считается сработавшим  const byte sonar2minLimit = 30;//см, если обнаружена дистанция меньше, чем это числож, то сонар считается сработавшим  const word sonarInactiveTime = 1500;//мс, время после срабатывания сонара, в течение которого сонар игнорит следующие срабатывания    const byte stairsCount = 12;//количество ступенек  const byte initialPWMvalue = 2;// only 0...5 (яркость первой и последней ступенек в ожидании)  const word waitForTurnOff = 7000;//мс, время задержки подсветки во вкл состоянии после включения последней ступеньки    byte stairsArray[12];//массив, где каждый элемент соответствует ступеньке. Все операции только с ним, далее sync2realLife  byte direction = 0;//0 = снизу вверх, 1 = сверху вниз  byte ignoreSonar1Count = 0;//счетчик-флаг, сколько раз игнорировать срабатывание сонара 1  byte ignoreSonar2Count = 0;//счетчик-флаг, сколько раз игнорировать срабатывание сонара 2    boolean allLEDsAreOn = false;//флаг, все светодиоды включены, требуется ожидание в таком состоянии  boolean need2LightOnBottomTop = false;//флаг, требуется включение ступеней снизу вверх  boolean need2LightOffBottomTop = false;//флаг, требуется выключение ступеней снизу вверх  boolean need2LightOnTopBottom = false;//флаг, требуется включение ступеней сверху вниз  boolean need2LightOffTopBottom = false;//флаг, требуется выключение ступеней сверху вниз  boolean sonar1trigged = false;//флаг, участвующий в реакциях на сратабывание сонара в разных условиях  boolean sonar2trigged = false;//флаг, участвующий в реакциях на сратабывание сонара в разных условиях  boolean nothingHappening = true;//флаг, указывающий на "дежурный" режим лестницы, т.н. исходное состояние    unsigned long sonar1previousTime;//время начала блокировки сонара 1 на sonarInactiveTime миллисекунд  unsigned long sonar2previousTime;//время начала блокировки сонара 2 на sonarInactiveTime миллисекунд  unsigned long allLEDsAreOnTime;//время начала горения ВСЕХ ступенек    void setup(){//подготовка  for (byte i = 1; i <= stairsCount-2; i++) stairsArray[i] = 0;//забить массив начальными значениями яркости ступенек  stairsArray[0] = initialPWMvalue;//выставление дефолтной яркости первой ступеньки  stairsArray[stairsCount-1] = initialPWMvalue;//выставление дефолтной яркости последней ступеньки  Tlc.init();//инициализация TLC-шки  delay(1000);//нужно, чтобы предыдущая процедура (инициализация) не "подвесила" контроллер  sync2RealLife();//"пропихнуть" начальные значения яркости ступенек в "реальную жизнь"  sonarPrepare(1);//подготавливаем сонар 1  sonarPrepare(2);//подготавливаем сонар 2  }    void loop(){//бесконечный цикл    sonar1trigged = sonarTrigged(1);//выставление флага сонара 1 для последующих манипуляций с ним  sonar2trigged = sonarTrigged(2);//выставление флага сонара 2 для последующих манипуляций с ним  nothingHappening = !((need2LightOnTopBottom)||(need2LightOffTopBottom)||(need2LightOnBottomTop)||(need2LightOffBottomTop)||(allLEDsAreOn));    if (nothingHappening){//если лестница находится в исходном (выключенном) состоянии, можно сбросить флаги-"потеряшки" на всякий случай    ignoreSonar1Count = 0;//сколько раз игнорировать сонар 1    ignoreSonar2Count = 0;//сколько раз игнорировать сонар 2  }    //процесс включения относительно сложен, нужно проверять кучу условий    //процесс ВКЛючения: сначала - снизу вверх (выставление флагов и счетчиков)    if ((sonar1trigged) && (nothingHappening)){//простое включение ступенек снизу вверх из исходного состояния лестницы    need2LightOnBottomTop = true;//начать освение ступенек снизу вверх    ignoreSonar2Count++;//игнорить противоположный сонар, чтобы при его срабатывании не запустилось "загорание" сверху вниз  }  else if ((sonar1trigged) && ((need2LightOnBottomTop)||(allLEDsAreOn))){//если ступеньки уже загоряются в нужном направлении или уже горят    sonarDisable(1);//просто увеличить время ожидания полностью включенной лестницы снизу вверх    ignoreSonar2Count++;//игнорить противоположный сонар, чтобы при его срабатывании не запустилось "загорание" сверху вниз    direction = 0;//направление - снизу вверх  }  else if ((sonar1trigged) && (need2LightOffBottomTop)){//а уже происходит гашение снизу вверх    need2LightOffBottomTop = false;//прекратить гашение ступенек снизу вверх    need2LightOnBottomTop = true;//начать освещение ступенек снизу вверх    ignoreSonar2Count++;//игнорить противоположный сонар, чтобы при его срабатывании не запустилось "загорание" сверху вниз  }  else if ((sonar1trigged) && (need2LightOnTopBottom)){//а уже происходит освещение сверху вниз    need2LightOnTopBottom = false;//прекратить освещение ступенек сверху вниз    need2LightOnBottomTop = true;//начать освение ступенек снизу вверх    ignoreSonar2Count++;//игнорить противоположный сонар, чтобы при его срабатывании не запустилось "загорание" сверху вниз  }  else if ((sonar1trigged) && (need2LightOffTopBottom)){//а уже происходит гашение сверху вниз    need2LightOffTopBottom = false;//прекратить гашение ступенек сверху вниз    need2LightOnBottomTop = true;//начать освение ступенек снизу вверх    ignoreSonar2Count++;//игнорить противоположный сонар, чтобы при его срабатывании не запустилось "загорание" сверху вниз  }    //процесс ВКЛючения: теперь - сверху вниз (выставление флагов и счетчиков)    if ((sonar2trigged) && (nothingHappening)){//простое включение ступенек сверху вниз из исходного состояния лестницы    need2LightOnTopBottom = true;//начать освещение ступенек сверху вниз    ignoreSonar1Count++;//игнорить противоположный сонар, чтобы при его срабатывании не запустилось "загорание" снизу вверх  }  else if ((sonar2trigged) && ((need2LightOnTopBottom)||(allLEDsAreOn))){//если ступеньки уже загоряются в нужном направлении или уже горят    sonarDisable(2);//обновить отсчет времени для освещения ступенек сверху вниз    ignoreSonar1Count++;//игнорить противоположный сонар, чтобы при его срабатывании не запустилось "загорание" снизу вверх    direction = 1;//направление - сверху вниз  }  else if ((sonar2trigged) && (need2LightOffTopBottom)){//а уже происходит гашение сверху вниз    need2LightOffTopBottom = false;//прекратить гашение ступенек сверху вниз    need2LightOnTopBottom = true;//начать освещение ступенек сверху вниз    ignoreSonar1Count++;//игнорить противоположный сонар, чтобы при его срабатывании не запустилось "загорание" снизу вверх  }  else if ((sonar2trigged) && (need2LightOnBottomTop)){//а уже происходит освещение снизу вверх    need2LightOnBottomTop = false;//прекратить освещение ступенек снизу вверх    need2LightOnTopBottom = true;//начать освение ступенек сверху вних    ignoreSonar1Count++;//игнорить противоположный сонар, чтобы при его срабатывании не запустилось "загорание" снизу вверх  }  else if ((sonar2trigged) && (need2LightOffBottomTop)){//а уже происходит гашение снизу вверх    need2LightOffBottomTop = false;//прекратить гашение ступенек снизу вверх    need2LightOnTopBottom = true;//начать освение ступенек сверху вниз    ignoreSonar1Count++;//игнорить противоположный сонар, чтобы при его срабатывании не запустилось "загорание" снизу вверх  }    //процесс ВЫКлючения относительно прост - нужно только знать направление, и выставлять флаги    if ((allLEDsAreOn)&&((allLEDsAreOnTime + waitForTurnOff) <= millis())){//пора гасить ступеньки в указанном направлении    if (direction == 0) need2LightOffBottomTop = true;//снизу вверх    else if (direction == 1) need2LightOffTopBottom = true;//сверху вниз  }    //непосредственная обработка флагов с "пропихиванием" массива ступенек в "реальность"    if (need2LightOnBottomTop){//увеличим яркость за 4 итерации, уложившись в 400мс (BottomTop - снизу вверх)    for (byte i=0; i<=3;i++){      startBottomTop();      sync2RealLife();      delay(100);    }//for  }//if    if (need2LightOffBottomTop){//уменьшим яркость за 4 итерации, уложившись в 400мс (BottomTop - снизу вверх)    for (byte i=0; i<=3;i++){      stopBottomTop();      sync2RealLife();      delay(100);    }//for  }//if    if (need2LightOnTopBottom){//увеличим яркость за 4 итерации, уложившись в 400мс (TopBottom - сверху вниз)    for (byte i=0; i<=3;i++){      startTopBottom();      sync2RealLife();      delay(100);    }//for  }//if    if (need2LightOffTopBottom){//уменьшим яркость за 4 итерации, уложившись в 400мс (TopBottom - сверху вниз)    for (byte i=0; i<=3;i++){      stopTopBottom();      sync2RealLife();      delay(100);    }//for  }//if  }//procedure    void startBottomTop(){//процедура ВКЛючения снизу вверх    for (byte i=1; i<=stairsCount; i++){//обработка всех ступенек по очереди, добавление по "1" яркости для одной ступеньки за раз      if (stairsArray[i-1] <=4){//узнать, какой ступенькой сейчас заниматься         stairsArray[i-1]++;//увеличить на ней яркость         return;//прямо сейчас "свалить" из процедуры      }//if      else if ((i == stairsCount)&&(stairsArray[stairsCount-1] == 5)&&(!allLEDsAreOn)){//если полностью включена последняя требуемая ступенька        allLEDsAreOnTime = millis();//сохраним время начала состояния "все ступеньки включены"        allLEDsAreOn = true;//флаг, все ступеньки включены        direction = 0;//для последующего гашения ступенек снизу вверх        need2LightOnBottomTop = false;//поскольку шаг - последний, сбрасываем за собой флаг необходимости        return;//прямо сейчас "свалить" из процедуры      }//if    }//for  }//procedure    void stopBottomTop(){//процедура ВЫКЛючения снизу вверх    if (allLEDsAreOn) allLEDsAreOn = false;//уже Не все светодиоды включены, очевидно      for (byte i=0; i<=stairsCount-1; i++){//пытаемся перебрать все ступеньки по очереди      if ((i == 0)&&(stairsArray[i] > initialPWMvalue)){//если ступенька первая, снижать яркость до "дежурного" уровня ШИМ, а не 0        stairsArray[0]--;//снизить яркость        return;//прямо сейчас "свалить" из процедуры      }      else if ((i == stairsCount-1)&&(stairsArray[i] > initialPWMvalue)){//если последняя, то снижать яркость до дежурного уровня ШИМ, а не 0        stairsArray[i]--;//снизить яркость        if (stairsArray[stairsCount-1] == initialPWMvalue) need2LightOffBottomTop = false; //если это последняя ступенька и на ней достигнута минимальная яркость        return;//прямо сейчас "свалить" из процедуры      }      else if ((i != 0) && (i != (stairsCount-1)) && (stairsArray[i] >= 1)){//обработка всех остальных ступенек        stairsArray[i]--;//снизить яркость        return;//прямо сейчас "свалить" из процедуры      }//if i == 0    }//for  }//procedure    void startTopBottom(){//процедура ВКЛючения сверху вниз    for (byte i=stairsCount; i>=1; i--){//обработка всех ступенек по очереди, добавление по "1" яркости для одной ступеньки за раз      if (stairsArray[i-1] <=4){//узнать, какой ступенькой сейчас заниматься         stairsArray[i-1]++;//уменьшить на ней яркость         return;//прямо сейчас "свалить" из процедуры      }//if      else if ((i == 1)&&(stairsArray[0] == 5)&&(!allLEDsAreOn)){//если полностью включена последняя требуемая ступенька        allLEDsAreOnTime = millis();//сохраним время начала состояния "все ступеньки включены"        allLEDsAreOn = true;//флаг, все ступеньки включены        direction = 1;//для последующего гашения ступенек снизу вверх        need2LightOnTopBottom = false;//поскольку шаг - последний, сбрасываем за собой флаг необходимости        return;//прямо сейчас "свалить" из процедуры      }//if    }//for  }//procedure    void stopTopBottom(){//процедура ВЫКЛючения сверху вниз    if (allLEDsAreOn) allLEDsAreOn = false;//уже Не все светодиоды включены, очевидно      for (byte i=stairsCount-1; i>=0; i--){//пытаемся перебрать все ступеньки по очереди      if ((i == stairsCount-1)&&(stairsArray[i] > initialPWMvalue)){//если ступенька первая, то снижать яркость до дежурного уровня ШИМ, а не 0        stairsArray[i]--;//снизить яркость        return;//прямо сейчас "свалить" из процедуры      }      else if ((i == 0)&&(stairsArray[i] > initialPWMvalue)){//если последняя, то снижать яркость до дежурного уровня ШИМ, а не 0        stairsArray[0]--;//снизить яркость        if (stairsArray[stairsCount-1] == initialPWMvalue) need2LightOffTopBottom = false; //если это последняя ступенька и на ней достигнута минимальная яркость        return;//прямо сейчас "свалить" из процедуры      }      else if ((i != 0) && (i != (stairsCount-1)) && (stairsArray[i] >= 1)){//обработка всех остальных ступенек        stairsArray[i]--;//снизить яркость        return;//прямо сейчас "свалить" из процедуры      }//if i == 0    }//for  }//procedure    void sync2RealLife(){//процедуры синхронизации "фантазий" массива с "реальной жизнью"  for (int i = 0; i < stairsCount; i++) Tlc.set(i, stairsArray[i]*800);//0...5 степени яркости * 800 = вкладываемся в 0...4096  Tlc.update();  }//procedure    void sonarPrepare(byte sonarNo){//процедура первоначальной "инициализации" сонаров  if (sonarNo == 1){    pinMode(sonar1trig, OUTPUT);    pinMode(sonar1echo, INPUT);    pinMode(sonar1resetpin, OUTPUT);    digitalWrite(sonar1resetpin, HIGH);//всегда должен быть HIGH, для перезагрузки сонара кратковременно сбросить в LOW  }  else if (sonarNo == 2){    pinMode(sonar2trig, OUTPUT);    pinMode(sonar2echo, INPUT);    pinMode(sonar2resetpin, OUTPUT);    digitalWrite(sonar2resetpin, HIGH);//всегда должен быть HIGH, для перезагрузки сонара кратковременно сбросить в LOW  }  }//procedure    void sonarReset(byte sonarNo){//процедура ресета подвисшего сонара, на 100 мс "отбирает" у него питание    if (sonarNo == 1){      digitalWrite(sonar1resetpin, LOW);      delay(100);      digitalWrite(sonar1resetpin, HIGH);    }    else if (sonarNo == 2){      digitalWrite(sonar2resetpin, LOW);      delay(100);      digitalWrite(sonar2resetpin, HIGH);    }//if  }//procedure    void sonarDisable(byte sonarNo){//процедура "запрета" сонара    if (sonarNo == 1) sonar1previousTime = millis();    else if (sonarNo == 2) sonar2previousTime = millis();  }    boolean sonarEnabled(byte sonarNo){//функция, дающая знать, не "разрешен" ли уже сонар      if ((sonarNo == 1)&&((sonar1previousTime + sonarInactiveTime) <= millis())) return true;    else if ((sonarNo == 2)&&((sonar2previousTime + sonarInactiveTime) <= millis())) return true;    else return false;  }    boolean sonarTrigged(byte sonarNo){//процедура проверки, сработал ли сонар (с отслеживанием "подвисания" сонаров)    if ((sonarNo == 1)&&(sonarEnabled(1))){      digitalWrite(sonar1trig, LOW);      delayMicroseconds(5);      digitalWrite(sonar1trig, HIGH);      delayMicroseconds(15);      digitalWrite(sonar1trig, LOW);      unsigned int time_us = pulseIn(sonar1echo, HIGH, 5000);//5000 - таймаут, то есть не ждать сигнала более 5мс      unsigned int distance = time_us / 58;      if ((distance != 0)&&(distance <= sonar1minLimit)){//сонар считается сработавшим, принимаем "меры"          if (ignoreSonar1Count > 0) {//если требуется 1 раз проигнорить сонар 2          ignoreSonar1Count--;//проигнорили, сбрасываем за собой флаг необходимости          sonar1previousTime = millis() - 1000;//запретить сонар 2 на sonarInactiveTime - 1500 мсек          Serial.println("Sonar 1 ignored");          return false;        }        Serial.println("Sonar 1 trigged");        sonarDisable(1);//временно "запретить" сонар, иначе каждые 400мс будут "ходить всё новые люди"        return true;      }      else return false;    }//if sonar 1      if ((sonarNo == 2)&&(sonarEnabled(2))){      digitalWrite(sonar2trig, LOW);      delayMicroseconds(5);      digitalWrite(sonar2trig, HIGH);      delayMicroseconds(15);      digitalWrite(sonar2trig, LOW);      unsigned int time_us = pulseIn(sonar2echo, HIGH, 5000);//5000 - таймаут, то есть не ждать сигнала более 5мс      unsigned int distance = time_us / 58;      if ((distance != 0)&&(distance <= sonar2minLimit)){//сонар считается сработавшим, принимаем "меры"          if (ignoreSonar2Count > 0) {//если требуется 1 раз проигнорить сонар 2          ignoreSonar2Count--;//проигнорили, сбрасываем за собой флаг необходимости          sonar2previousTime = millis() - 1000;//запретить сонар 2 на sonarInactiveTime - 1500 мсек          Serial.println("Sonar 2 ignored");          return false;        }        Serial.println("Sonar 2 trigged");        sonarDisable(2);//временно "запретить" сонар, иначе каждые 400мс будут "ходить всё новые люди"        return true;      }      else return false;    }//if sonar 2  }//procedure  

Демонстрация работы.


Никто из Вас не отважится на повторение проекта, если не будет чётко себе представлять, что он получит «на выходе». К моему сожалению, мой проект пока не интегрирован в лестницу дома моей сестры в связи с вялотекущим ремонтом последнего. Следовательно, всё, что я могу предоставить в доказательство работоспособности и жизнеспособности проекта, это видео работы устройства, не интегрированного пока в лестницу. Другими словами, со светодиодными панелями, разложенными на столе. Извините за качество видео, снимать его пришлось на «тапок». Ленты на видео частично прикрыты для того, чтобы камера не «ослеплялась» и видео не становилось тёмным, потому что ленты очень яркие. Более того, для этого видео мне пришлось снизить питание до 10 вольт вместо 12, по той же причине.


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

Итого.


Своей цели – получить базовые навыки в Arduino – я добился. Равно как и удовлетворил желание реализовать проект по-своему. Разумеется, я применяю полученный опыт и в других проектах и намерен продолжать саморазвитие. А вот достиг ли я самой важной цели – привнёс ли что-то полезное в жизнь каждого из читателей – покажет только время. Цель обзора, как Вы понимаете, именно в этом.

Ссылки Dropbox – (сам скетч, библиотека к нему, макет платы в Sprint Layout 6 и среда Code::Blocks).

Спрашивайте, что непонятно. Исправляйте, если что-то не так, ведь проект, как я уже упоминал, является моим первым «блином».

Всем добра. Сегодня это, как никогда, важно.

(c) 2015 Источник материала.

Рекламные ссылки