Именно в результате этого недоразумения SPI теперь принято разводить с колодки ICSP, которая, к огромному счастью, сохранила свое позиционное расположение во всех оригинальных платах Arduino. Заглянем на обратную сторону EthernetSD шилда:
Единственный сигнал, которого нет на вилке 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 работает нормально.
Как всегда, часть плат пока еще доступна
здесь.