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 работает нормально.

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

27 комментариев:

  1. пример интересный, но ваш знакомый все-таки очень странный человек - плата ардуино + шилд примерно 2000-2200р

    при этом например такой роутер ( http://wiki.openwrt.org/toh/tp-link/tl-wr741nd ) стоит 660р

    прекрасно перешивается на openwrt, имеет внутри нераспаянный порт консоли и ssh "из коробки"

    один из вариантов применения (в паре с ардуино)
    http://www.instructables.com/id/Home-automation-server-with-router/#step1

    ОтветитьУдалить
  2. Человек ищет решение, но не всегда знает о всех путях его осуществления. Это как в perl - "есть много способов сделать это".

    Но, тем не менее - это правда, т.е. запрос про именно такое решение мне озвучивали. Ах, знали бы Вы, что мне вообще озвучивали! ;)

    ОтветитьУдалить
  3. Кстати интересно а какую реально скорость может обеспечить такой Ethernet шилд?

    Понятно что SPI будет тормозом и 100Мбит/c даже близко не получится, но все же сколько можно из нее реально вытащить?

    ОтветитьУдалить
  4. Забавно, но и я этим вопросом задавался (по работе). Минимальный период SCLK - 70 нс, значит байт передается не быстрее 560 нс и т.д., если я ничего не путаю - полмегабайта в секунду (на пределе возможностей).

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

    ОтветитьУдалить
  5. Планирую приобрести шилд для Mega, как по внешнему виду быть уверенным, что они будут совместимы? Должен быть распаян SD и присутствовать мама-SPI?
    Приобретать, скорее всего, буду на ebay.

    ОтветитьУдалить
  6. SD может быть и не распаян, а вот мама-SPI на тыльной стороне - да, должна быть.

    ОтветитьУдалить
  7. Правильно ли я понимаю, что для Mega надо использовать
    pinMode(53, OUTPUT) ?
    Кстати, есть ли где-то возможность автоматического определения "платформы" Т.е. где-то в библиотеке определять 10 или 53 используется в конкретном случае, что что бы не править каждый раз код перед заливкой на разные платы?

    ОтветитьУдалить
  8. Нет, неправильно понимаете. Просто всегда используйте 10, потому что выбор SD висит именно на нем, а не на аппаратном SS.

    ОтветитьУдалить
  9. Сегодняшняя моя истоия: http://mircobot.blogspot.com/2011/11/micro-sd-ethernet-shield-05.html

    ОтветитьУдалить
  10. Наводящий вопрос: на шилде написано "mk90.ru" или "mk90.blogspot.com"?

    ОтветитьУдалить
  11. На шилде написано mega compatible :)

    made in china, мама-SPI присутствует.

    ОтветитьУдалить
  12. Тогда извиняюсь - подумал, что речь про мой шилд или про оригинальный Arduino-вский. Видимо, китайцы даже схему правильно скопировать не могут :( Но на будущее - спасибо, буду знать - что и такое бывает ;)

    При появлении MEGA проблема четко обозначилась для EthernetShield из-за "переезда" пинов, отвечающих за аппаратный SPI. Тогда и было решено брать их надо исключительно с вилки ISCP6. Сигнал SS на ней отсутствует, но для устройства-мастера его аппаратная поддержка уж точно не критична, поэтому его можно взять с любого general-пина.

    А у Вас точно не работает скетч из статьи? Все никак не могу поверить :(

    ОтветитьУдалить
  13. Классический SD-скетч заработал, когда я вписал pinMode(53, OUTPUT);

    Даже в комментариях там написано:
    // On the Ethernet Shield, CS is pin 4. It's set as an output by default.
    // Note that even if it's not used as the CS pin, the hardware SS pin
    // (10 on most Arduino boards, 53 on the Mega) must be left as an output
    // or the SD library functions will not work.

    Ваш скетч у меня заработал (работу с SD я выбросил для упрощения).

    Причем пришлось оставить:
    oid setup() {
    pinMode(10, OUTPUT);
    pinMode(4, OUTPUT);
    digitalWrite(10, HIGH);
    digitalWrite(4, HIGH);

    // SD.begin(4);
    digitalWrite(4, HIGH);
    delay(500);

    Если последний digitalWrite(4, HIGH); убрать, то не происходит подключение к dhcp серверу 8).

    ОтветитьУдалить
  14. Гм. Тогда зря я ругал кетайцев ;)

    ОтветитьУдалить
  15. Ни как не могу разобраться что сделать что бы у меня одновременно заработало SD и Ethernet.
    Какой должен быть алгоритм? После работы с sd вызывать digitalWrite(4, HIGH); ?

    ОтветитьУдалить
  16. Так Вы же говорите, что скетч из статьи работает? Там и SD, и Ethernet используются, попеременно.

    ОтветитьУдалить
  17. Я написал, что убрал из вашего скетча SD. Нельзя ли где-то пообщаться в более удобном месте. Каждый раз капчу вводить надоело, да и код не удобно будет в комметах читать.

    ОтветитьУдалить
  18. Готово: http://mk90.ru/forum/viewtopic.php?f=4&t=19

    ОтветитьУдалить
  19. Помогите с этим шилдом.
    Все в него заливается, мигает посекундно сверху пару диодов(+ зеленый слева на коннекторе витой пары).
    Например Определить свой IP внутри DHCP, ничего не выдает.
    На роутере ,свиче, лампочка коннекта не горит.
    Думал кабель - вставляю его в комп с этим кабелем - работают.
    Может что-то не правильно сделал?

    ОтветитьУдалить
    Ответы
    1. Я перепробовал несколько различных библиотек пока что-то наконец-то заработало. Так же пришлось периодически менять состояние 4 и 10 пинов. Об этом написано выше. Причем на Mega 2560 это работает, а UNO стабильно вешается через какое-то время. Причину не исследовал.

      Удалить
    2. А обращаться в нашу поддержку пробовали? Можно сразу через форум.

      Мой личный совет - попробуйте для начала что-то простое: со статическим ip, при непосредственном соединении с компом. Скажем, из стандартных примеров - WebServer. Отключите от Arduino все схемы, кроме самого EtherSD шилда. И SD-карту тоже извлеките, если она вдруг вставлена. С Arduino 1.0.1 должно работать, без всяких шаманств - в итоге должно получиться зайти на ардуинку браузером по порту 80.

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

      Удалить
    3. Проблема в том, что на работе, интернет-клубе через обычные счичи все примеры по Етернет-шилде работают ,а дома DIR-615, не ребоатает.
      Даже лампочки не светятся о том, что на ЛАН подлючен провод. На Ардуино мигает зеленая а на роутере - ноль эмоций.
      Не знаю даже как подружить. Ведь дома хочу работать с Ардуино, а не на работе, когда занят.

      Удалить
  20. имеем:
    1)arduino mega 2560 R3
    2)ethernet shield

    Система win7, arduino ide = arduino-1.5.4-r2-windows.
    При подключении одной ардуины - видится порт с ней, скетчи заливаются.
    Если навесить ethernet shield - теряется порт или видится, но вкл/отключив подключение дуины к компу кабелем USB - порта нет.

    Или при заливке вываливается ошибка:
    КОД: ВЫДЕЛИТЬ ВСЁ
    avrdude: stk500v2_ReceiveMessage(): timeout
    avrdude: stk500v2_ReceiveMessage(): timeout
    avrdude: stk500v2_ReceiveMessage(): timeout
    avrdude: stk500v2_ReceiveMessage(): timeout
    avrdude: stk500v2_ReceiveMessage(): timeout
    avrdude: stk500v2_ReceiveMessage(): timeout
    avrdude: stk500v2_getsync(): timeout communicating with programmer



    что делать?
    странность в том,что без шилда, ок все, с ним - косяк.

    ОтветитьУдалить
    Ответы
    1. Чаще всего в такую ситуацию можно попасть, устроив на шилде короткое замыкание (замкнув GND и VCC, или же дав по нему чрезмерное потребление). Еще может быть "сюрприз", если ethernet shield довольно стар и не совместим с Mega.

      Удалить
    2. спасибо)
      тут еще ситуация такая: если с шилдом, то ардуинка видна в устройствах на комп., но не видна в arduino ide, без шилда и там и там и работает)

      прозвонить с шилдом GND и VCC?
      китайцам отписался, предлагают выпаять 2 элемента возле кнопки на шилде)
      что-то боязно)))

      Удалить
    3. А если попробовать разобраться, что это за элементы и зачем их выпаивать? Схемы, конечно же, нет?

      Удалить