После выхода новой 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 работает нормально.
И как водится во всех сказках, на ввод правильного пароля дается три попытки ;)
Если выкинуть авторизацию, скетч займет один экран. Сложно? Не думаю, хотя для серьезных задач потребуется изучение нюансов. В итоге: после компиляции скетч занял 23К в памяти, переключение между SD-картой и Ethernet работает нормально.
Как всегда, часть плат пока еще доступна здесь.