23.04.2011

Arduino + Ethernet + mircoSD

После выхода новой Arduino Ethernet Shield 05 прошло довольно много времени, но недавно  мне случилось снова обратиться к этой теме, изучая возможности платы Freeduino EthernetSD Shield:




Отправная точка для ее создания сходна с Freeduino One - попытка собрать ограниченную партию для собственных нужд из доступной комплектации. Но попутно выяснилось много маленьких, но интересных фактов.

Например - каким образом новая плата оказалась совместима с Mega?

Напомню историю проблемы: в Arduino обмен с Ethernet-чипом W5100 происходит по шине SPI, и сначала сигналы на Arduino Ethernet Shield были разведены прямо от колодки цифровых входов 11,12 и 13, к которым и был привязан аппаратный SPI на ATmega168/328. Но после триумфального выхода Arduino Mega все вдруг с опозданием спохватились, что аппаратный SPI "переехал" на 50,51 и 52 - и это стало причиной несовместимости MEGA не только с EthernetShield, но и вообще со всеми шилдами, использовавшими аппаратный SPI.

Именно в результате этого недоразумения SPI теперь принято разводить с колодки ICSP, которая, к огромному счастью, сохранила свое позиционное расположение во всех оригинальных платах Arduino. Заглянем на обратную сторону EthernetSD шилда:



Таким образом, SPI-сигналы MISO, MOSI и SCK будут всегда правильными:
  • в обычных (не-MEGA) платах будут использоваться digital 11,12,13;
  • в Mega и Mega2560 - 50,51,52.
Единственный сигнал, которого нет на вилке ICSP - это SS (Slave Select). Низким уровнем он выбирает ведомое устройство на шине SPI, с которым будет общаться ведущее (в нашем случае - ATmega):
  • для выбора Ethernet используется digital 10;
  • для выбора SD-карты используется digital 4.
Соответственно, использовать digital 10 и 4 в схемах с EthernetSD Shield для какого-то другого применения - нельзя.


Ради интереса можете сравнить решение по доступу к SD с вариантом от libellium, котрое я разбирал в статье MicroSD Shield (в основе - тот же резистивный делитель для согласования уровней сигналов). В старой версии Ethernet Shield разъем SD никогда не напаивался (лишь гордо блестели залуженные площадки из-под маски, напоминая о былых ошибках и вселяя ложную надежду в сердца тех, кто рассчитывал напаять разъем и получить какие-то дополнительные функции) - то ли из-за отсутствия согласования сигналов по уровню 3,3В, а может - из-за отсутствия программной поддержки. Да и сигналы эти были разведены на другие пины. Но теперь SD и Ethernet физически разделяют одну шину SPI, одинаково используя преимущества аппаратной поддержки в ATmega.

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

На оригинальной схеме Arduino Ethernet Shield 05 сигналы детекта карты и защиты от записи почему-то оказались не разведены на разъем microSD, но сохранены для полноразмерного SD-разъема (они даже имеют подтяжки 10К к Vcc). Я некоторое время колебался - но в итоге оставил эту странность в покое ради полной совместимости. Ведь представьте себе - установка/изъятие SD-карты влечет изменение уровня на analog0 и автоматически приводит к невозможности его использования для других целей.

Авторы по прежнему сохраняют разорванную перемычку INT, которая может соединить выход прерывания W5100 c digital2 и использовать для ввода/вывода механизм прерываний. Однако, поддержка прерываний с W5100 так до сих пор и не реализована: библиотека работает традиционным для Arduino методом пулинга (постоянного опроса состояния буфера из скетча).



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

Заслуживает нескольких слов и программная поддержка, над которой сообщество также изрядно потрудилось.

Когда-то я мучительно плодил классы для работы с UDP, в конечном итоге добиваясь возможности синхронизации времени по протоколу NTP. Но прогресс не стоит на месте и теперь UDP включен в библиотеку вEthernet в составе ArduinoIDE 0022, ничего изобретать не надо. Кроме того, энтузиастами написана поддержка следующих возможностей:

  • EthernetDHCP, позволяет получать IP-адрес динамически, поддерживается блокирующий и неблокирующий режим вызова функций; есть возможность обновления lease (и перехода на другой IP, если его поменяют "на лету"). Более, чем достаточно!
  • EthernerDNS, позволяет преобразовывать символические имена в реальные IP-адреса. Необходимо только знать IP-адрес своего DNS-сервера - например, взять из DHCP lease (см. выше);
  • EthernetBonjour,  поддержка протокола Bonjour/Zeroconf, реализующего MulticastDNS и DNS Service Discovery - при помощи него вы можете донести до остальных пользователей информацию о том, какие сервисы запущены на вашем Arduino (поклонники Apple быстрее остальных поймут, о чем речь ;)
  • WebDuino, библиотека с каркасом для построения веб-серверов на основе Arduino - включает в себя парсинг URL и прочие полезные функции. Почему-то напомнила CGI.pm ;)
  • RadiusClient, библиотека для поддержки авторизации по протоколу Radius (видимо, Diametr должен быть на подходе ;)
  • UdpNtpClient, пример из стандартной библиотеки, запрашивающий по NTP время и расшифровывающий ответ в виде ЧЧ:ММ:СС. Что я могу сказать... весьма грубо усеченный, но рабочий вариант.
Недавно мне задали вопрос: "Насколько сложно программировать для EthernetSD Shield?" В качестве демонстрации ответа, приведу решение задачи, которую один мой знакомый сформулировал так: "у меня есть компьютер с портом RS232, надо дать к нему доступ через Internet". При этом от предложений на основе PC+Linux он отказался в пользу Arduino+Ethernet Shield. Учтите, выставлять в public internet сервер Telnet - чистое безумие не совсем безопасно! Так что используйте на свой страх и риск:


//  Copyright (C) 2011 Ilya Danilov
//  http://mk90.blogspot.com
//
//  TelnetServer is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Lesser General Public License as
//  published by the Free Software Foundation, either version 3 of
//  the License, or (at your option) any later version.
//
//  TelnetServer is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Lesser General Public License for more details.
//
//  Illustrates how to make simple Telnet Server based on Ethernet Shield.

#if defined(ARDUINO) && ARDUINO > 18
#include <SPI.h>
#endif
#include <Ethernet.h>
#include <EthernetDHCP.h>
#include <SD.h>

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEC };

#define MAXPASSLEN  16
#define READ_TIMEOUT_IN_MS 60000L
char enteredPassword[MAXPASSLEN];
char cardPassword[MAXPASSLEN];

Server server(23);

// Just a utility function to nicely format an IP address.
const char* ip_to_str(const uint8_t* ipAddr)
{
  static char buf[16];
  sprintf(buf, "%d.%d.%d.%d\0", ipAddr[0], ipAddr[1], ipAddr[2], ipAddr[3]);
  return buf;
}

void setup()
{
  pinMode(10, OUTPUT);
  pinMode(4, OUTPUT);
  digitalWrite(10, HIGH);
  digitalWrite(4, HIGH);
  
  SD.begin(4);
  digitalWrite(4, HIGH);
    
  Serial.begin(9600);
  
  Serial.println("Attempting to obtain a DHCP lease...");
  EthernetDHCP.begin(mac);

  const byte* ipAddr = EthernetDHCP.ipAddress();
  const byte* gatewayAddr = EthernetDHCP.gatewayIpAddress();
  const byte* dnsAddr = EthernetDHCP.dnsIpAddress();
  
  Serial.println("A DHCP lease has been obtained.");

  Serial.print("My IP address is ");
  Serial.println(ip_to_str(ipAddr));
  
  Serial.print("Gateway IP address is ");
  Serial.println(ip_to_str(gatewayAddr));
  
  Serial.print("DNS IP address is ");
  Serial.println(ip_to_str(dnsAddr));
  
  server.begin();
}


boolean authenticateClient(Client &client) {
  int i=0, k=0;
  unsigned long timeoutTime;
  
  delay(500);
  client.flush(); // kill telnet handshake junk
  memset(enteredPassword,0,sizeof(enteredPassword));
  
  for (i=0;i<3;i++) {
    client.println("Password:");
    k = 0;
    timeoutTime = millis() + READ_TIMEOUT_IN_MS;
    while ( (k<MAXPASSLEN) && client.connected() && (millis() < timeoutTime) ) {
      char ch = client.read();
      if (ch != -1) {
        if (ch == '\r') continue;
        if (ch == '\n') break;
        enteredPassword[k++] = ch;
      } else delay(10);
    }
    
    if (millis() >= timeoutTime) return false;  
    if (!client.connected()) return false;  
        
    // read password from SD
    if (enteredPassword[0]) {
      if (SD.exists("passwd")) {
        memset(cardPassword,0,sizeof(cardPassword));
        k = 0;
        File f = SD.open("passwd"); 
        while (f.available() && k<MAXPASSLEN ) {
          char ch = f.read();
          if (ch == '\r') continue;
          if (ch == '\n') break;
          cardPassword[k++] = ch;
        }
        f.close();
        digitalWrite(4, HIGH);
        
        // compare
        if (!strcmp(cardPassword,enteredPassword)) 
          return true;
        else {
          client.println("Password mistmatch, try again!"); 
        }
      } else {
        client.println("Password file not found\n");
      } 
    } 
  } 
  return false;
}

void loop()
{
  boolean authenticated = false;
  //EthernetDHCP.maintain();
  
  // listen for incoming clients
  Client client = server.available();
  if (client) {
    while (client.connected()) {
      if (!authenticated) {
        if (!authenticateClient(client)) {
          client.println("Authorization failed.");
          client.stop();
          break;
        } else authenticated = true;
      } else { //auth passed
        if (client.available()) {
          char c = client.read();
          Serial.print(c);
        }
        if (Serial.available() >0 ) {
          char c = Serial.read();
          client.print(c);
        }
      }
    }
  }
}



После старта Arduino получает адрес динамически и выводит его через Serial-порт, чтобы было понятно, на какой IP ломиться telnet-клиентом. По входящему соединению выдается запрос "Password:", полученная в ответ комбинация символов сравнивается с содержимым простого текстового файла passwd, который располагается в корне SD-карты (внутри файла просто пароль, не путать с /etc/passwd :).

И как водится во всех сказках, на ввод правильного пароля дается три попытки ;)

Если выкинуть авторизацию, скетч займет один экран. Сложно? Не думаю, хотя для серьезных задач потребуется изучение нюансов. В итоге: после компиляции скетч занял 23К в памяти, переключение между SD-картой и Ethernet работает нормально.

Как всегда, часть плат пока еще доступна здесь.

16.04.2011

Сеть 1-Wire в "полевых условиях"

О практическом применении сети 1-Wire и температурных датчиков DS18B20 в частности, написано много и  подробно. Цель этой статьи - рассказать, как использовать эти датчики (или другие устройства сети) в суровых "полевых условиях". Не секрет, что на столе под лампой светлой цифровой датчик DS18B20 или его бюджетный брат DS18S20 замечательно работает  с минимальным обвязом со стороны микроконтроллера в т.н. двухпроводной схеме:



Фактически, весь "обвяз" состоит из резистора 4К7, между шиной питания VCC (+5В) и шиной данных VDO, который и позволяет датчику паразитно питаться от этой же шины. Схема проста, наглядна и кроме этого, позволяет экономить на одном проводе в кабеле сети. Для расстояний менее 10 метров - вполне оправдано, правда точность преобразования будет не лучше 2°C (разрядность АЦП датчика DS18B20 снижается с 12 до 10 бит), что во многих случаях будет вполне достаточно.

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




Дополнительная выделенная линия питания сулит нам следующие "бонусы":
  1. Длина сетевого кабеля 100 метров и более;
  2. Количество одновременно подключенных датчиков - не менее 32шт.;
  3. Разрешающая способность АЦП - 0,0625°C и точность измерения - 1°C.
Однако, еще остается борьба с помехами на длинных линиях связи. Простейшей защитой является включенный в обратном направлении диод Шоттки между линией данный и общим проводом, именно так советует поступать Brian C. Lane, автор популярного проекта DigiTemp. Мы лишь немного расширим данное решение для трехпроводной схемы включения:


D1 - сборка быстродействующих диодов Шоттки BAV199, механизм действия такой защиты подробно описан в блоге Уважаемого киберсатаниста DI HALT;
L1, L2 - фербиды BLM21AG221SN1D - индуктивности для защиты от высокочастотных помех, возникающих при коммутации сильноточных потребителей;
C1 - керамический конденсатор, естественный спутник ножек питания цифровой микросхемы;
IC1 - собственно цифровой датчик температуры DS18B20Z в корпусе SOIC8.

Все компоненты - SMD (0805 и SOT23) для уменьшения размера платы датчика:



После сборки, плата температурного датчика выглядит следующим образом:


Обязательно защищаем плату датчика от влаги (цапонлаком или акриловым лаком):


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

Контактные площадки для пайки кабеля сети 1-Wire сознательно сделаны крупными и вот почему...

Трактат о проводочках кабеля

Самой распространенной ошибкой при построении сети 1-Wire является выбор в пользу Ethernet-кабеля Cat.5! Подавляющее большинство читателей скажет - "у нас все очень хорошо и бодро работает на обрезках сетевухи". Не спорю ни в коем случае, кабель Cat.5 длиной 10..30 м вполне годится для 3-х проводного способа подключения датчиков, более того - вот вам рекомендованная схема использования народного кабеля, которую и сам использую на даче для водоснабжения дома:




"Ну таки и в чем дело?" - скажет проницательный читатель. А вот в чем: в кардинальном различии "физики и логики" сетей Ethernet и 1-Wire. Не вдаваясь в сложности организации сети Ethernet, просто прошу поверить (и с мультиметром проверить) в то, что из-за значительного падения напряжения на длинных и весьма тонких проводах кабеля Cat.5e датчику сети 1-Wire банально не хватает напряжения питания!

Вывод напрашивается простой и логичный - использовать кабель с проводами бОльшего сечения и желательно - экранированный! Для своих целей, я выбрал вполне доступный по цене (и наличию в магазинах) кабель МКЭШ-3х0.5 - схема подключения датчиков будет выглядит так:


Несколько худший, но вполне приемлемый результат, можно получить с кабелем МКЭШ-2х0.35 и следующей схемой подключения:




Наконец, можно использовать вполне приличный провод от торшера - ПВС3х0.75...ПВС3х1

Заключение


Нынешний владелец торговой марки 1-Wire - компания Maxim, для защиты нежных ножек микропроцессора от "суровых полевых условий", предлагает приборчик DS9503, который по сути - просто быстродействующий диод Шоттки + токоограничивающие резисторы в линиях питания и данных. Сам я его еще в руках не держал, но как только это случиться - немедленно опишу полученные впечатления.

Послесловие


C огромным удовольствием - благодарю Илью Данилова за помощь словом и хардвером в освоении платформы Ардуино, конструктивные замечания и неоценимую поддержку в разработке проекта ОткрытогоПЛК!


  • Скачать проект платы-подавителя помех в формате Eagle.

10.04.2011

LoL Shield по-русски

На досуге забил русский знакогенератор в стандартную библиотеку, получилось приблизительно так:



Выкладываю раннюю альфу для тех, кому надо срочно заставить LoL-шилд говорить по-русски. Несколько ограничений:

  • работает только горизонтальный скроллинг;
  • буквы могут быть только заглавными.
Автор модуля Font.cpp Benjamin Sonntag оставил в коде забавный коммент "STRANGE BUG : If I put these array too (low-case fonts) the code don't work, and I don't know why ...". Ну что же... зато теперь я могу ответить - Benjamin, try to use flash mem instead of RAM. То бишь, наш французский друг разместил знакогенератор прямо в оперативной памяти, поэтому даже подключение безобидного Serial вызывало железное повисание скетча.

Тестовый скетч занимает после компиляции ~5300 байт, но есть возможности для оптимизации (спасибо Бенжамину ;)


07.04.2011

Freeduino ONE

С самого начала, Arduino Uno восхищал меня возможностью переделки в любое USB-устройство при помощи смены прошивки ATmega8u2. Но экспериментировать с оригинальной итальянской платой было откровенно жалко, и как-то постепенно я пришел к идее сделать свою собственную модификацию - Freeduino One. В итоге получился полный аналог Arduino Uno:


Я честно исходил из того, что использованные компоненты должны быть доступны - иначе, какой смысл? Именно поэтому вы и наблюдаете на фото:

  • ATmega8u2 в TQFP-корпусе - по крайней мере он без труда поддается пайке;
  • Оба резонатора - кварцевые, не ловлю кайф на миниатюризации, тем более с потерей точности;
  • Миниатюрные сборки резисторов мощностью 0,065 Вт заменены на резисторы типоразмера 0603 - кроме той, что у разъема USB - уж больно красиво смотрится;
  • У регулятора 3,3В появился шунтирующий конденсатор (как и положено по datasheet-у) - собственно, это самое серьезное изменение в схеме.
В целом - получилось вполне симпатично. И, конечно же - без пижонства я не смог. Не в свою сторону, просто за державу теперь уже не обидно: 



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

Схема (кликабельно для увеличения, как и все фотки в этой статье):