Управление новогодней гирляндой c .NET Micro Framework

Опубликовано 27 ноября 08 20:58:00 | Coding4Fun

   

Автор:

Роб Майлз (Rob Miles): www.robmiles.com

Загрузки:

https://coding4fun.net/source/festivelights1.0.zip

ПО:

Visual Studio 2008 Express Edition или коммерческая версия, .NET Micro Framework 3.0

Оборудование:

Digi Connect-ME GHI Electronics Embedded Master Devices Solutions Tahoe II

Необходимое время:

3 часа

Затраты:

30 долларов на гирлянду плюс устройство с .NET Micro Framework

Micro Framework — один из самых молодых членов семейства .NET, чудесно себя зарекомендовавший. Эта платформа делает доступным разработку встроенных систем любому C#-программисту. Если вы знаете C# и любите Visual Studio, теперь можете начать конструировать свои устройства и управлять ими из собственных программ. Вы можете даже осуществить давнишнюю мечту любого уважающего себя программиста — управлять с помощью своей программы огнями новогодней гирлянды. Именно эту возможность демонстрирует описываемый здесь проект. Однако он имеет еще одно волшебное свойство — заставлять мигать вашу гирлянду красными огоньками каждый раз, когда я, Роб Майлз, опубликовал новый пост на самом интересном из блогов, www.robmiles.com.

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

Если вы хотите просто поиграть с .NET Micro Framework и прочувствовать, как просто создавать программы для маленьких устройств, вам не потребуется покупать никакого оборудования. Проект содержит эмулятор гирлянды, так что вы сможете исследовать процесс взаимодействия аппаратуры и программ на компьютере, не обжигаясь паяльником.

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

Для работы вам понадобятся некоторые аппаратные и программные средства. Давайте разберемся с каждым из них.

Оборудование

Процессор

.NET Micro Framework позволяет запускать C#-программы на миниатюрных встраиваемых устройствах. На сегодняшний день есть достаточно много таких устройств, причем они с каждым днем дешевеют. Для реализации данного проекта подойдет любое устройство с .NET Micro Framework, содержащее сетевой порт и не менее трех выходных портов. Я могу рекомендовать следующие:

Я использовал Digi Connect, но приведенный код можно адаптировать к любому из перечисленных устройств. Прелесть .NET Micro Framework в том, что вам вряд ли потребуется изменять свою программу даже при полной замене аппаратуры, с которой она работает. Вам надо будет лишь учесть в коде использование других выходных контактов. Я обращу ваше внимание на соответствующее место в программе, когда мы до этого дойдем.

Поставляемый проект работает на эмуляторе, который выполняется на ПК и имитирует функционирование устройства .NET Micro Framework с подключенными к нему лампочками, так что вы сможете сразу начать исследование кода.

Лампочки

В своем проекте я применял только низковольтные устройства. Так что устройства для данного проекта может смастерить малыш любого возраста, не рискуя сунуть пальцы в розетку. Используемая мной гирлянда представляет собой двадцать светодиодов, припаянных по пять штук на четыре провода, скрученных вместе. На каждом проводе припаяны светодиоды одного цвета. Провода были соединены с коробочкой для трех батареек AA и маленьким контроллером. Я отпаял провода от коробочки и соединил их со своим устройством.

clip_image002

Рис. 1. Мои лампочки, подключенные к батарейкам

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

Покупка лампочек

Лампочки, которые я использовал, в Великобритании можно купить в Lights4Fun: www.lights4fun.co.uk. Они называются «C-LED-4.5-M 20 Multi Coloured Battery Operated LED Fairy Lights». Контроллер и отсек для батареек я убрал и подключил формирователь тока на сборках Дарлингтона. В качестве источника питания я использовал старое пятивольтовое зарядное устройство для мобильника. Если вы поищете на eBay «led christmas lights battery», наверняка найдете достаточно продавцов чего-то подобного.

Управляющее устройство

Устройство, поддерживающее .NET Micro Framework, мы не можем подключить прямо к гирлянде. Причин две:

  1. Устройство Micro Framework не сможет управлять цепью с тем током, который нам нужен для наших лампочек.
  2. У устройства Micro Framework нет необходимого количества выходных контактов для управления большим числом лампочек.

Следовательно, нам надо сделать некоторое устройство и подключить его между процессором и гирляндой. На самом деле я не вижу в этом проблемы. Мастерить «железо» весьма забавно. Нам надо сделать устройство, которое позволит управлять многими сотнями светодиодов с помощью единственной платы с Micro Framework. Это означает и коммутацию достаточно большого тока. Мы собираемся использовать два полупроводниковых компонента: сдвиговый регистр и фиксатор CD4094, а также восьмиразрядный формирователь Дарлингтона ULN2803 (Octal Darlington Driver). Пара таких схем позволит управлять восемью выходами. Если вы хотите управлять большим числом схем, возьмите больше микросхем и соедините их между собой. Я в своем проекте использовал по одной схеме. Нашел я их в Maplin: www.maplin.co.uk. Шифры этих компонентов следующие: QW54J 4094 Shift Register, QY79L ULN2803A Darlington Driver. В США можно купить их в Digi-Key: www.digikey.com.

Последовательные и параллельные данные

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

Мы будем использовать три сигнала, которые назовем тактовый импульс (clock), данные (data) и фиксатор (latch). Устройство .NET Micro Framework, управляемое нашей программой, может установить высокое (есть напряжение) или низкое (нет напряжения) значение для каждого их этих сигналов. Эти сигналы поступают на одноименные входы сдвигового регистра CD4094, так что программа может взаимодействовать с ним.

Тактовый сигнал заставляет сдвиговый регистр делать следующие две вещи:

  1. Освободить место, сдвинув все биты.
  2. Получить значение сигнала данных и сохранить его в освобожденной ячейке.

Это проиллюстрировано рис. 2. На нем вы видите сдвиговый регистр с некоторой комбинацией битов. Эта комбинация — 01100001. Обратите внимание: хотя это есть представление некоторого числа, мы рассматриваем именно последовательность нулей и единиц в регистре. Значение 0 соответствует нулю вольт, а значение 1 — некоторому ненулевому напряжению. Эти сигналы будут использоваться для управления свечением лампочек. Будем называть их 0 и 1. Сдвиговый регистр имеет сигналы Clock, Data и Latch, каждый из которых установлен в 1. Сигнал фиксации (latch) мы пока не рассматриваем.

clip_image004

Рис. 2. Сдвиговый регистр и фиксатор с некоторыми данными

При изменении тактового сигнала с 1 в 0, регистр выполняет указанные выше действия. Сначала данные сдвигаются вправо. При этом в начале регистра образуется «пустое» место, а самый правый разряд «отваливается» и исчезает из регистра. Это показано на рис. 3.

clip_image006

Рис. 3. Сдвиг данных в регистре

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

clip_image008

Рис. 4. Сохранение нового бита данных

Повторив этот процесс восемь раз, регистр получает новую восьмиразрядную комбинацию. Наступает время зафиксировать получившееся новое значение для управления лампочками. В этот момент их свечение может измениться. Когда значение сигнала фиксации измениться с 0 в 1 значение из сдвигового регистра копируется в фиксатор (рис. 5).

clip_image010

Рис. 5. Копирование комбинации битов из знакового регистра в фиксатор

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

Чтобы обеспечить необходимую последовательность сигналов, нам потребуется написать немного кода на C#. Благодаря .NET Micro Framework, он будет весьма прост. Показанный ниже метод displayByte отправляет 8-битное значение в сдвиговый регистр и затем фиксирует его на выходе. Посмотрите, как в коде устанавливаются высокие (true) и низкие (false) значения сигналов, а затем инициируется фиксация. Входным параметром является 8-разрядное значение типа байт, а для побитного формирования выходных данных используется маска.

    1: private static void displayByte(byte value)
    2: {
    3:     latchPort.Write(false);
    4:     clockPort.Write(false);
    5:  
    6:     byte mask = 1;
    7:  
    8:     for (int i = 0; i < 8; i++)
    9:     {
   10:         if ((value & mask) > 0)
   11:         {
   12:             dataPort.Write(true);
   13:         }
   14:         else
   15:         {
   16:             dataPort.Write(false);
   17:         }
   18:         clockPort.Write(true);
   19:         clockPort.Write(false);
   20:         mask <<= 1;
   21:     }
   22:     latchPort.Write(true);
   23:     latchPort.Write(false);
   24: }

Переменные dataPort, clockPort и latchPort являются экземплярами класса OutputPort .NET Micro Framework, который предоставляет метод Write, с помощью которого можно управлять выходным сигналом. Мы рассмотрим это немного ниже.

В демонстрационном коде данного проекта есть программный эмулятор CD4094, демонстрирующий его работу. На рис. 6 показан процесс формирования новой комбинации в регистре, в то время как фиксатор содержит предыдущее значение. Уровень сигналов Clock и Data высокий и при выполнении следующего оператора сигнал Clock будет сброшен и следующий бит будет добавлен в формируемую комбинацию.

clip_image002[4]

Рис. 6. Эмулятор гирлянды

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

Описанный подход можно применять всегда, когда требуется управлять большим количеством выходных сигналов посредством всего нескольких выходных контактов. У CD4094 есть «шлейфовые» входы и выходы, позволяющие соединять несколько таких устройств в цепочку. С помощью двух схем можно управлять шестнадцатью разрядами, трех — 24 и т. д. Изменения в программе потребуются минимальные.

Выходной формирователь

Устройство CD4094 выдает выходные сигналы, но их мощность не достаточна для работы со световыми элементами. Для них нам нужен некий усилитель, в качестве которого вполне подходит восьмеричный формирователь Дарлингтона ULN2803 Octal Darlington Driver. Он представляет собой одну микросхему с восемью парами транзисторов. Каждая пара представляет собой сборку Дарлингтона, которая может использоваться как коммутатор, управляемый выходным сигналом CD4094. Включенные транзисторы позволяют току протекать через них, благодаря чему светодиоды будут светиться. Я использовал схему подключения световых элементов «с заземлением». Один контакт каждого светодиода подключен к общей линии, на которую подается «плюс» питания. Чтобы цепь с подключенными светодиодами замкнулась и они загорелись, другой конец надо заземлить. Это распространенная схема подключения для подобной иллюминации. В устройстве ULN2803 транзисторы подключены таким образом, что можно пропустить сигнал «на землю». Это показано на рис. 7. Резисторы на самом деле подключены по одному к каждому светодиоду.

clip_image004[4]

Рис. 7. Зажигание светодиодов

Схема целиком

В общей схеме, показанной на рис. 8, каждый выход сдвигового регистра CD4094 подключен ко входу дарлингтоновского формирователя ULN2803. Показанная схема является общим макетом.

clip_image006[4]

Рис. 8. Схема целиком

На диаграмме показаны лишь пять светодиодов, подключенных к выходу OUT1, соответствующему контакту 18 схемы ULN2803; остальные семь каналов подключены аналогичным образом. Какие-то контакты можно оставить незадействованными. Контакты 9 и 10 схемы CD4094 требуются лишь при каскадном подключении нескольких сдвиговых регистров, а контакт десятой схемы ULN2803 не используется. К входам Latch, Data и Clock CD4094 подключены выходные сигналы устройства Micro Framework.

На рис. 9 показана собранная схема, построенная по описанному макету. Слева расположена микросхема CD4094, справа — ULN2803.

clip_image008[4]

Рис. 9. Собранная схема

Красные провода соединены с «плюсом» источника питания, который подключен слева вверху. Зеленые провода — это «земля». Провода других цветов используются для сигналов. Сигналы Clock, Latch и Data заведены на разъем, которым данная схема подключается к плате Digi-ME.

Выбор лампочек

Каждый бит числа, передаваемого методу displayByte, а затем в сдвиговый регистр, будет соответствовать определенной цепочке лампочек. Для установления соответствия используются программные константы. Красным лампочкам я назначил выходы 4 и 8. Чтобы зажечь лишь красные лампочки, используется константа 0x88, соответствующая необходимой комбинации разрядов. Вот константы для всех используемых цветов:

    1: const byte GREEN = 0x11;
    2: const byte BLUE = 0x22;
    3: const byte YELLOW = 0x44;
    4: const byte RED = 0x88;

Настройка оборудования

В .NET Micro Framework есть набор классов, который можно использовать для представления оборудования в системе. Наша программа использует экземпляры класса OutputPort для представления выходных контактов. Они порождаются в методе настройки оборудования:

    1: const Cpu.Pin clockPin = Cpu.Pin.GPIO_Pin0;
    2: const Cpu.Pin dataPin = Cpu.Pin.GPIO_Pin1;
    3: const Cpu.Pin latchPin = Cpu.Pin.GPIO_Pin2;
    4:  
    5: static OutputPort clockPort;
    6: static OutputPort dataPort;
    7: static OutputPort latchPort;
    8:  
    9: private static void setupOutputs()
   10: {
   11:     clockPort = new OutputPort(clockPin, false);
   12:     dataPort = new OutputPort(dataPin, false);
   13:     latchPort = new OutputPort(latchPin, false);
   14: }

В текущей версии своей схемы я подключаю контакт 0 процессора к тактирующему сигналу, контакт 1 — к данным, а 2 — к фиксатору. Если у вас другая комбинация контактов, измените настройки.

Программы

Разобравшись с аппаратурой, перейдем к рассмотрению ПО. Обратите внимание: рассматриваемая версия работает корректно, но в ней нет обработчика исключений, который делает программу действительно надежной. Я отказался от него, чтобы облегчить объяснение программы. Имеет смысл рассматривать программу, имея ее копию.

Вы можете работать с ее кодом в Visual Studio 2008 версии Express, которую можно загрузить здесь: https://www.microsoft.com/express/ru/.

Программы написаны для .NET Micro Framework 3.0, которую можно загрузить здесь: https://www.microsoft.com/downloads/details.aspx?FamilyID=9356ed6f-f1f0-43ef-b21a-4644dd089b4a&displaylang=en

Чтение блога

Программа читает RSS-канал блога и ищет значения <pubDate> . Оно указывает дату последнего изменения канала. При изменении этого значения гирлянда должна несколько секунд помигать красным светом, а потом продолжить случайное мигание. Пользователи полной версии .NET Framework для этих целей могут использовать класс HTTPRequest для построения команды GET, отправляемой серверу. К сожалению, в .NET Micro Framework это не поддерживается, так что мы реализуем доступ к веб-каналу с помощью сокетов. Данная часть программы построена на основе примера SocketClient, поставляемого с .NET Micro Framework.

    1: // Данный метод запрашивает страницу с указанного сервера.
    2: private static String GetWebPage(String server, string webPage)
    3: {
    4:     const Int32 c_httpPort = 80;
    5:     const Int32 c_microsecondsPerSecond = 1000000;
    6:  
    7:     // Создание сокет-соединения с указанным сервером и портом.
    8:     using (Socket serverSocket = ConnectSocket(server, c_httpPort))
    9:     {
   10:         // Отправка запроса серверу.
   11:         String request = "GET "+ webPage + 
   12:              " HTTP/1.1\r\nHost: " + server + 
   13:              "\r\nConnection: Close\r\n\r\n";
   14:         Byte[] bytesToSend = Encoding.UTF8.GetBytes(request);
   15:         serverSocket.Send(bytesToSend, bytesToSend.Length, 0);
   16:  
   17:         // Выделение буфера для получения HTML-данных 
   18:         Byte[] buffer = new Byte[1024];
   19:  
   20:         // 'page' указывает на формируемые данные HTML.
   21:         String page = String.Empty; 
   22:  
   23:         // 30 секунд ожидаем начальные данные 
   24:         // Если  соедниение закрыто, будет исключение
   25:         DateTime timeoutAt = DateTime.Now.AddSeconds(30);
   26:         while (serverSocket.Available == 0 && 
   27:                DateTime.Now < timeoutAt)
   28:         {
   29:             System.Threading.Thread.Sleep(100);
   30:         }
   31:  
   32:         // Опрашиваем сокет 30 секунд
   33:         // При получении данных возвращается true и соединение закрывается
   34:         while (serverSocket.Poll(30 * c_microsecondsPerSecond,
   35:                                  SelectMode.SelectRead))
   36:         {
   37:             // Обнуляем многократно используемый буфер
   38:             Array.Clear(buffer, 0, buffer.Length);
   39:  
   40:             // Читаем HTML-данные по размеру буфера
   41:             Int32 bytesRead = serverSocket.Receive(buffer);
   42:  
   43:             // Если в буфере 0 байт, соединение закрывается 
   44:             // или возникает тайм-аут
   45:             if (bytesRead == 0)
   46:                 break;
   47:  
   48:             // Добавляем данные к строке
   49:             page += new String(Encoding.UTF8.GetChars(buffer));
   50:         }
   51:  
   52:         return page;   // Возвращаем всю строку
   53:     }
   54: }

Этот метод вызывается для получения RSS-канала journal из моего блога:

    1: string address = "www.robmiles.com";
    2: string name = "/journal/rss.xml";
    3:  
    4: string html = GetWebPage(address, name);

Эта программа может использоваться для чтения любой страницы или RSS-канала. Если страницу прочитать невозможно, выдается исключение. Однако в данной версии обработка исключений не предусмотрена.

Получение даты публикации

Дата представлена в следующей форме:

    1: <pubDate>Wed, 05 Nov 2008 22:38:52 +0000</pubDate>

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

    1: private static DateTime getRecentPubDate(string html, string startTag)
    2: {
    3:     int index = html.IndexOf(startTag) ;
    4:  
    5:     if (index < 0) 
    6:         throw new Exception("Missing tag " + startTag);
    7:  
    8:     index += startTag.Length;
    9:  
   10:     // Перемещаемся на позицию, следующую за названием дня
   11:     while (index < html.Length && html[index] != ',') index++;
   12:     if (index == html.Length) 
   13:         throw new Exception("Short publish date");
   14:  
   15:     int dayValue = getInt(html, ref index, ' ');
   16:  
   17:     string monthName = getString(html, ref index, ' ');
   18:     int monthValue = getMonth(monthName);
   19:  
   20:     int yearValue = getInt(html, ref index, ' ');
   21:     int hourValue = getInt(html, ref index, ':');
   22:     int minuteValue = getInt(html, ref index, ':');
   23:     int secondValue = getInt(html, ref index, ' ');
   24:  
   25:     return new DateTime(yearValue, monthValue, dayValue, 
   26:                         hourValue, minuteValue, secondValue);
   27: }

В моей основной программе этот метод вызывается для извлечения даты из данных RSS-канала. При обнаружении новой даты наступает время мигания лампочками.

Реализация потоков

Первая версия моей программы вызывала мигание лампочек в течение некоторого времени, а затем проверяла поступление новой публикации поста. Это работало, но на загрузку RSS-канала с сервера и анализ дат требовалось несколько секунд, и в это время мигание гирлянды замирало. Выглядело это не очень красиво, и я решил использовать два потока в своей программе. Один отвечает за мигание лампочек в случайном порядке, а другой — за чтение RSS-канала моего блога и проверки времени последней публикации.

Обратите внимание: применение потоков здесь полностью совпадает с полной версией .NET Framework.

Взаимодействие потоков

Два потока взаимодействуют посредством единственной булевской переменной, значение которой устанавливается в true при наличии изменения в блоге. Поток, работающий с лампами, анализирует этот флаг и при необходимости вызывает мигание гирлянды красным светом.

    1: public static bool alert = false;
    2:  
    3: public static void flasher()
    4: {
    5:     setupOutputs();
    6:  
    7:     while (true)
    8:     {
    9:         randomDisplay(400, 10);
   10:         if (alert)
   11:         {
   12:             alert = false;
   13:             flashRed(600, 20);
   14:         }
   15:     }
   16: }

Методы randomDisplay и flashRed делают именно то, что от них и ожидается. Каждый из них для управления лампочками получает два числа. Первое число задает задержку между включениями ламп в миллисекундах (лучший результат достигается, когда эта задержка составляет несколько сот миллисекунд). Второе число указывает, сколько раз лампочки должны мигнуть до завершения метода. RandomDisplay вызывает разноцветное мигание, а flashRed заставляет мигать только красные светодиоды. Перед миганием красных огней флаг alert очищается, так что система переходит к обычному миганию после обработки уведомления от блога.

Во втором потоке реализована загрузка блога и проверка времени. Это делается в методе Main:

    1: public static void Main()
    2: {
    3:     flashThread = new System.Threading.Thread(flasher);
    4:  
    5:     flashThread.Start();
    6:  
    7:     string address = "www.robmiles.com";
    8:     string name = "/journal/rss.xml";
    9:  
   10:     String html = GetWebPage(address, name);
   11:  
   12:     DateTime lastUpdate = getRecentPubDate(html, "<pubDate>");
   13:  
   14:     Debug.Print("Initial Update Value : " + lastUpdate.ToString());
   15:  
   16:     while (true)
   17:     {
   18:         System.Threading.Thread.Sleep(10000);
   19:  
   20:         html = GetWebPage(address, name);
   21:  
   22:         DateTime blogUpdate = getRecentPubDate(html, "<pubDate>");
   23:  
   24:         if (!blogUpdate.Equals(lastUpdate))
   25:         {
   26:             Debug.Print("Updated at : " + blogUpdate.ToString());
   27:             lastUpdate = blogUpdate;
   28:             alert = true;
   29:         }
   30:     }
   31: }

Чтобы не перегружать сеть, система опрашивает блог раз в 10 секунд.

Дальнейшие планы

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

Благодарности

Благодарю Яна Митчелла (Ian Mitchell) из Ormston Technology (https://www.ormtec.co.uk/) за помощь в разработке и монтаже аппаратуры.