[Из песочницы] Светодиодные часы Shadowplay на платформе Arduino Uno

Вместо вступления


48b4514a6fd144ba8e19798a1588bd87.JPG

Итак, перед нашей командой из трех человек стояла задача: в очень сжатые сроки собрать небольшой аппаратный проект, желательно на платформе Arduino. Стоит оговориться, что до того момента со схемотехникой мы были знакомы, по большей части, в теории. А это значит — ни опыта работы с паяльником (практически), ни, тем более, опыта работы с Arduino.

Неожиданно мы наткнулись на статью, посвященную проекту Shadowplay Clock. Это настенные часы, разработанные командой венских дизайнеров, время по которым можно увидеть, дотронувшись пальцем до их центра. Светодиоды загораются в таком порядке, чтобы тень от пальца в центре показывала время. Было решено создать такие же (или очень похожие), но в домашних условиях. Вышеуказанная статья, как можно заметить, не содержит подробного описания проекта. Из всего этого следовало, что нам самим предстояло разобраться, как работает это устройство, и воплотить его в жизнь. Чем мы, собственно, и занялись.

Материалы


Для создания часов необходимы:

  • заготовки из ДВП
  • светодиодная лента на 60 диодов
  • Arduino Uno
  • модуль часов реального времени RTC DS1307
  • кнопка
  • макетная плата
  • сдвиговый регистр 74HC595 (x2)
  • 8-разрядный регистр-защелка 74HC573N (x8)
  • дешифратор 4–16 К155ИД3
  • инвертор с открытым стоком IN74HC05AN (x10)
  • блок питания


Приступим


Итак, алгоритм работы устройства:

  1. При подаче питания, светодиоды включаются в заданной комбинации. В оригинале Shadowplay загорались все светодиоды, но нам показалось, что интереснее будет запустить какую-нибудь комбинацию в качестве заставки.
  2. При нажатии кнопки (да, мы также отошли от оригинала и вставили в центр маленькую кнопку) считывается время из модуля RTC.
  3. Считанное время преобразуется в двоичный код (маску) и заносится в регистры.
  4. В зависимости от маски, зажигается необходимый диод.


Аппаратная часть


Когда мы, наконец, определились с идеей проекта, то первым делом попытались мысленно набросать примерные варианты схем для его реализации. Основной вопрос стоял в том, каким образом адресовать 60 светодиодов. Собственно говоря, ответ на этот вопрос и определял способ построения практически всей схемы.

Первый пришедший на ум вариант был связан с использованием дешифраторов. Составленная схема представляла собой каскад из четырёх дешифраторов 4 — 16 и одного дешифратора 2 — 4, и те и другие с входами разрешения дешифрации. Такой каскад позволял обеспечивать адресацию на 64 выхода, чего с избытком хватало для подключения 60 светодиодов.

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

Этот недостаток заставил нас отказаться от идеи с каскадом дешифраторов. К тому же теперь у нас появилось ещё одно требование к будущей схеме — поддержка одновременной работы разного количества светодиодов.

Чтобы удовлетворить это требование мы подумали, что можно дать возможность каждому светодиоду хранить своё состояние. Для этой цели хорошо подходят регистры, где каждый отдельно взятый разряд соответствует состоянию одного из светодиодов. Мы решили использовать регистры на 8 разрядов, так как они более распространённые и более практичные. Соответственно в нашей схеме нам понадобится 8 таких регистров, чтобы обеспечить поддержку 60 светодиодов.

Дальше мы задумались о том, как осуществлять управление состоянием светодиодов с Arduino через регистры. Каждый регистр для нормальной работы должен получать все 8 бит целиком. Arduino Uno, конечно, предоставляет достаточно выходов для передачи нескольких бит одновременно, но такой подход будет не рациональным. Кроме того в схеме всего 8 регистров, а значит нужно их как-то адресовать. Для этой цели мы добавили в схему дешифратор и два 8 разрядных сдвиговых регистра, соединённых каскадом. Один сдвиговый регистр хранит 8-битую маску состояний, которая будет загружена в один из 8 обычных регистров, номер которого хранится во втором сдвиговом регистре. Соответственно, ко второму сдвиговому регистру подключён дешифратор. Для этих целей вполне хватит дешифратора 3 на 8.

Чтобы убрать инверсию с нужного нам количества выходов мы использовали две микросхемы инверторов КР1533ЛН1. Это, конечно, несколько усложнило схему.

Ещё одной задачей стало рабочее напряжение светодиодов равное 12 вольтам по сравнению с 5 вольтами логических микросхем. Предложенное решение заключалось в применении инвертора с открытым стоком. Такая микросхема исполняет роль ключа, который замыкает (при логической 1) или размыкает (при логическом 0) один из контактов светодиода с землёй, тем самым включая или отключая светодиод. Схема предполагает работу от 12 вольт, в соответствии с рабочим напряжением светодиодов, поэтому чтобы получить 5 вольт для логических микросхем в схему был добавлен стабилизатор КР142ЕН5А с двумя конденсаторами.

Некоторые входы определённых микросхем подразумевают константное значение на входе, поэтому были заведены на землю или источник питания. В данном случае это следующие входы:

  • Инверсный вход сброса MR в обоих сдвиговых регистрах через нагрузочный регистр подключён к выходу стабилизатора на 5 вольт.
  • Инверсный вход разрешения выхода OE в обоих сдвиговых регистрах подключён напрямую к земле.
  • Инверсный вход разрешения дешифратора E0 подключён к земле

9947dc3c61754b44b034f4670f846fbc.png

Управление схемой осуществляется четырьмя входами (E1, SH, ST, DS). Назначение и уровни сигнала каждого из них более подробно рассмотрим ниже:

Вход E1 предназначен для включения дешифратора. В нашем случае, изначально на дешифраторе есть два управляющих входа E1, E0, и они оба являются инверсными. Выхода будет достаточно и одного, поэтому второй (E0) можно завести на землю. Состояние дешифратора «по умолчанию» — рабочее, пока на E1 не подать высокий уровень сигнала. Для того, чтобы сделать наоборот, мы подключили данный вход к инвертору. Без этого дешифратор может выдавать неверные управляющие сигналы регистрам, например, в момент обновления данных в сдвиговом регистре. Как уже говорилось, в схеме можно использовать дешифратор 3 на 8, у которого может быть один не инверсный управляющий вход, что позволит с лёгкостью решить все описанные выше проблемы без лишних проводов и паяльника.

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

Следующие три входа предназначаются уже для управления сдвиговыми регистрами. Начать стоит с самого простого — входа данных DS. Данный вход, как уже следует из названия, предназначен для передачи данных. Так как сдвиговые регистры в схеме соединяются каскадом, то DS представляет соответствующий вывод одного из них. Вход второго сдвигового регистра соединён с выводом последнего разряда первого регистра. В результате получается один сдвиговый регистр на 16 разрядов, из которых используются только 12 разрядов.

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

Последний вывод ST служит защёлкой данных на выходах регистра. На данный вход подаётся импульс, однако подаётся только тогда, когда данные в сдвиговом регистре загружены окончательно и требуется зафиксировать их на выходе регистров. Только после подачи данного сигнала загруженные данные внутри регистра на первом ряду триггеров попадают на второй ряд триггеров и становятся доступны на шине. ST представляет собой контакт соединённый с выводами STcp обоих регистров.

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

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

Ещё один не описанный выше контакт предназначен для снятия уровня сигнал с кнопки в центре часов, схема кнопки типична и представляет собой нагрузку и ключ, в зависимости от положения которого на Arduino подаётся низкий или высокий уровень сигнала. В зависимости от состояния кнопки, часы работают либо в режиме заставки, либо в режиме показа времени.
Подключение к Arduino не имеет особенностей, за исключением того что вывод SH в идеале подключаться к цифровому выводу SCK. Остальные выводы схемы могут подключаться к любому из доступных цифровых входов общего назначения. В нашем случае подключение имеет следующий вид:

  • Arduino pin13 (SCK) — вывод схемы SH
  • Arduino pin 11 — вывод схемы ST Arduino pin 8 — вывод схемы DS Arduino pin 5 — вывод схемы E1 Arduino pin 3 — вывод кнопки Arduino pin GND — земля схемы (также подключается и к земле блока питания)
    После того как с проектированием схемы было покончено, началась работа над основой для часов.

    Были сделаны заготовки из ДВП: круг диаметром 36 см — задняя часть часов; и кольцо размерами 36 см (внешний диаметр) \ 26 см (внутренний диаметр) — передняя часть. В оригинале Shadowplay имеет диаметр круга 72 см, но нам хватило и 36. На круг приклеивается светодиодная лента, предварительно разрезанная на 60 частей (диод + резистор). По границе круга просверлили отверстия. Через них провода, подключенные к светодиодам, будут соединяться с макетной платой, которая находится на обратной стороне круга.

    c463fff0b95e402eaf9b4274301f04ba.jpg

    На самом деле, светодиоды доставили немало головной боли. Просто приклеив их к поверхности круга, мы слегка не рассчитали. В итоге получилось, что тень светодиоды создавали недостаточно яркую. А потому пришлось потратить немало времени, чтобы «приподнять» их градусов на 50 — 60, подложив под каждый из них треугольные картонные подкладки. Да, 60 маленьких картонных треугольников. Поэтому замечание: не повторяйте наш горький опыт — запаситесь подкладками заранее.

    87164343503642ba802ead947d81ab6a.JPG

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

    Макетная плата и общий вид устройства сзади.
    f959ccf6d1c34b58ac31411eb3477a99.jpg

    Да-да, нам стыдно.

    Изначально планировалось сделать точную копию Shadowplay. Однако способ, которым палец пользователя детектируется в центре устройства, не описывался в статье. Поразмышляв, мы пришли к выводу, что возможно для этих целей используется фоторезистор. Однако вскоре отказались от этой идеи, т.к. у фоторезистора возможны случайные срабатывания. Устройство может находится в условиях разной степени освещённости, а значит, не всегда сможет детектировать палец со 100% вероятностью. Предпочтение было отдано кнопке, которую мы и поместили в центр конструкции. Она обладает еще одним достоинством помимо надежности. Несмотря на ее небольшие размеры, тень от нее идеально подходит на роль стрелки часов. Таким образом, по желанию можно запрограммировать устройство на постоянную работу в режиме часов и без прикладывания пальца.

    После того, как вся схема была спаяна (долгие бессонные ночи кропотливой работы паяльником и пинцетом), выводы инверторов соединены со светодиодами, настал торжественный момент — первая попытка включить устройство. И, о чудо, диоды действительно горели! Но далеко не все. Поначалу появилось мнение, что что-то все-таки было неправильно спаяно. Однако, как выяснилось далее, причина крылась в другом. Так как при первом запуске мы подключили не все управляющие входы логических микросхем, а только лишь самые необходимые, то на оставшихся не подключенных входах образовалась неоднозначность в уровнях напряжения. К тому же, схема собиралась на макетной плате, с большой кучей проводов, а не вытравливалась.Провода были значительно подвержены электромагнитному излучению.Следовательно, при малейшем скачке напряжения из-за наводки на не подключенных входах логический элемент мог сработать. В результате любой предмет, находящийся в непосредственной близости от платы, вызывал ЭМИ и, соответственно, непредсказуемое поведение логики, а с ней и светодиодов.

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

    Программная часть


    Итак, с аппаратной частью покончено (практически). Настал черед написания программы под Arduino. Для начала, необходимо сконфигурировать модуль RTC, а именно — занести в него время. Микросхема базируется на высокоточном модуле DS1307, интерфейс подключения — I2C. Внутренний календарь в нем рассчитан до 2100 года с учетом високосных лет. Благодаря заряду батареи модуль может работать автономно около одного года. Ниже показана схема подключения RTC к Arduino, обнаруженная на этом сайте. Здесь же была взята масса информации о модуле RTC.

    c761290840164f52a699e8f7d0c23a50.jpg

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

    Код
    #include 
    #include 
    
    char REG [8];
    tmElements_t te;
    int c,reset=1;
    
    void setup() {
      pinMode(13, OUTPUT); 
      pinMode(11, OUTPUT); 
      pinMode(8, OUTPUT);  
      pinMode(5, OUTPUT);  
      pinMode(3, INPUT);   
      Serial.begin(57600);
      
      //данный блок позволяет установить время в RTC используется однократно
      //te.Hour = 18;
      //te.Minute = 50;
      //te.Second = 0;
      //te.Day = 20; //день
      //te.Month = 4; // месяц
      //te.Year = CalendarYrToTm(2016); 
      //RTC.write(te);
    }
    
    void loop()
    { 
    if(digitalRead(3))                    // сработает, если будет нажата кнопка 
      {RTC.read(te);
       SetShadowTime(te.Hour,te.Minute,te.Second,2);        // рассчитать и установить время на часах   
       delay(900);
       reset=1;
      }
    
    else                                   // если кнопка не нажата, установить заставку
      {wait1();
       reset=1;
      }
                                             //сброс светодиодов 
      for(int j = 0; j<8 ; j++) SetUpLightByMask(j,0); 
       
     
    }
    
    
    //=======================================================================Вспомогательные функции
    
    void SetUpLightByMask(int RegNum, char LightMask) // функция подсветки светодиодов согласно полученной маске в заданном регистре
    {
      
      digitalWrite(5, LOW);
      digitalWrite(11, LOW);
      shiftOut(8, 13, MSBFIRST, LightMask);
      shiftOut(8, 13, LSBFIRST, RegNum);
      digitalWrite(11, HIGH);
      digitalWrite(5, HIGH);
    }
    
    void digitalClockDisplay() {  //Функция вывода времени из RTC в консоль, полезна при настройке RTC  
      RTC.read(te);
      Serial.print(te.Hour);
      Serial.print(" : ");
      Serial.print(te.Minute);
      Serial.print(" :");
      Serial.print(te.Second);
      Serial.print(" ");
      Serial.print(te.Day);
      Serial.print(" ");
      Serial.print(te.Month);
      Serial.print(" ");
      Serial.print(tmYearToCalendar(te.Year));
      Serial.println();
    }
    
    //Функция рассчёта теневых стрелок на часах, в качестве параметров принимает часы, минуты, секунды и в качестве последнего параметра комбинацию стрелок:
    //0 - только часы,1 - часы и минуты, 2 - часы минуты и секунды
    
    void SetShadowTime(int Hours, int Minutes, int Seconds, int param){   
      int h,hshift,m,s;
      for(int j = 0; j<8 ; j++) REG[j] = 0;
    
      if(Hours >= 12) Hours -= 12;
      h = Hours + 6;
      if(h >= 12) h -= 12;
        
      hshift = (int) Minutes / 12;
      
      REG[(int)(((h*5)+hshift)/8)] = REG[(int)(((h*5)+hshift)/8)] | 1<<(((h*5)+hshift)%8);
                 
       
      if(param == 1)
      {m = Minutes + 30;
       if(m >= 60) m -= 60;
       REG[(int)(m/8)] = REG[(int)(m/8)] | 1<<(m%8);
       }
    
      if(param == 2)
      {m = Minutes + 30;
       if(m >= 60) m -= 60;
       REG[(int)(m/8)] = REG[(int)(m/8)] | 1<<(m%8);
         
       s = Seconds + 30;
       if(s >= 60) s -= 60;
       REG[(int)(s/8)] = REG[(int)(s/8)] | 1<<(s%8);
      }
    
      for(int j = 0; j<8 ; j++) SetUpLightByMask(j,REG[j]);
      }
      
    void wait1() //один из вариантов функций заставки
    {for(int a = 0; a < 8; a++)
        {c=0;
         for(int b = 0; b < 8; b++)
          {c = c << 1;
           c = c | 1;
           SetUpLightByMask(a, c);  
           delay(10);   
          }
       }
      for(int a = 0; a < 8; a++)
        {c=255;
          for(int b = 0; b < 8; b++)
          {c = c << 1;
           SetUpLightByMask(a, c);
           delay(10);     
          }
        }  
    }
    
    


    Сборка


    Можно считать устройство практически готовым. Осталось лишь собрать все составные части вместе и запустить. Макетная плата и остальные запчасти (Arduino, RTC) прикрепились к задней стороне часов. Поверх круга со светодиодами закреплено кольцо, скрывающее детали реализации. Чтобы отвлечь внимание пользователя от несовершенств конструкции, кольцо расписали узором «а-ля микросхема». И наконец — включили в розетку. Результат ниже:

    Приносим извинения за качество фото.

    Вот небольшой пример того, какие комбинации можно запустить в качестве «заставки»:

    А вот, собственно, часы в рабочем состоянии:

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

    И да, предвкушая вопрос, который вы наверняка хотите задать. Как отличать часовую, минутную и секундную стрелки? Было немало споров на эту тему. Предлагались разнообразные решения данной проблемы: от разноцветной подсветки стрелок до поочередного включения стрелок (сначала часовая, через небольшой промежуток времени минутная, и т.д.). Однако, посмотрев на оригинал, мы поняли, что венские дизайнеры в принципе не озаботились данным вопросом. Было решено закрыть глаза на этот маленький недостаток. Вероятно, в будущем он будет устранен.

    Итог


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

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

    Спасибо за внимание!

© Geektimes