Arduino+Ethernet Shield+SD (ITA)
Usare Arduino per accedere ai file di una SD card da remoto
A cura di Fabrizio Schiano
Ethernet shield
Comincio scrivendovi uno dei link al quale potrete trovare una Ethernet shield:
Arduino Ethernet shield with a MicroSD card slot.
Il progetto riportato di seguito e’ un progetto semplice che dimostra cosa si puo’ fare con una ethernet
shield, ma non e’ perfetto al 100%, quindi se vorrete fare altro dovrete modificarlo leggermente.
Questo e’ un buon inizio per un web monitor con login o un sistema di salvataggio di dati in remoto.
Prendere Familiarita’ con l’ethernet shield
Prima di iniziare con questo tutorial dovrai entrare in familiarita’ con quello su cui stai lavorando. Questo
non e’ un tutorial per principianti, ma e’ meglio che venga utilizzato da persone che hanno gia’ una certa
dimistichezza con Arduino o con un microcontrollore in generale e siano abbastanza sciolti nell’utilizzo di
C/C++/Java.
Inoltre potrete leggere Ethernet library per capire qualcosa sulla libreria Ethernet apposita per l’IDE Arduino.
Inizializzare una Micro-SD Card su una Ethernet Shield
L’ultima Arduino Ethernet shield e’ corredata di una utile slot per MicroSD card in modo tale che l’utente possa
salvare dei dati attraverso la shield. Quindi capiamo come interfacciarci con la card.
Dovete essere sicuro di avere l’ultima versione di SdFatLib (libreria dell’IDE Arduino necessaria per
interfacciarsi alle SD card) in quanto avrete bisogno di alcune delle nuove caratteristiche.
La prima cosa da notare e’ che il pin SS (Slave Select) per la card e’ il pin digitale 4, sebbene nella scrittura di
questo tutorial questa cosa non sia stata aggiornata negli schemi (schematic).
Aprire lo sketch di esempio SdFatInfo e cambiare il rigo in loop() da:
uint8_t r = card.init(SPI_HALF_SPEED);
a:
pinMode(10, OUTPUT);
(necessary!)
// set the SS pin as an output
digitalWrite(10, HIGH);
// but turn off the W5100 chip!
uint8_t r = card.init(SPI_HALF_SPEED, 4);
// Use digital 4 as the SD SS line
Siate sicuri di aggiungere queste linee di codice prima di mettere mano al tutto! Esse abilitano l’interfaccia SPI.
Se siete su un Arduino Mega usate il pin 53 al posto del 10.
Ora effettuate l’upload e testate la card. Dovreste vedere qualcosa del genere:
Cosa che indica che avete parlato in modo corretto con la card.
List files
Inserite alcuni file di testo sulla vostra SD card, usando un computer, in modo tale da avere qualche dato da
leggere. Siate sicuri che essi siano nella root directory e non in una cartella qualunque.
Ora eseguite lo sketch di esempio SdFatLs dalla libreria SdFat , dovreste vedere che esso elenca tutti i file che
avete sulla card. Ancora, dovrete fare dei cambiamenti come in precedenza per aggiornare la parte card.init() al
nuovo SS pin.
Per esempio, la card che sto utilizzando ha due file su di essa che sono il risultato di un precedente datalogging.
Accorpare WebServer + SdFatLs
Cominceremo dal combinare il WebServer (lo sketch di esempio che e’ contenuto nella libreria Ethernet)
e SdFatLs per costruire un web server che elenchi i file presenti sulla SD card.
Potete scaricare i file da qui: DOWNLOAD.
La prima parte e’ formata dagli oggetti Ethernet e SD card a una semplice funzione di errore
( void error_P(const char* str) ) che stampa gli errori e blocca il programma se ci sono dei problemi
seri.
Dovreste, naturalmente, settare il vostro mac e ip in modo adeguato, usate l’ip e il mac che ha funzionato nelle
vostre esperienze precedent con l’Ethernet Shield.
La card, volume, e root sono oggetti che ci aiutano attraverso la complessa struttura di una SD card.
La funzione di errore non e’ cosi’ eccitante, essa semplicemente stampa l’errore e si mette in un
loop while(1); .
/*
* This sketch will list all files in the root directory and
* then do a recursive list of all directories on the SD card.
*
*/
#include <SdFat.h>
#include <SdFatUtil.h>
#include <Ethernet.h>
/************ ETHERNET STUFF ************/
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte ip[] = { 192, 168, 1, 177 };
Server server(80);
/************ SDCARD STUFF ************/
Sd2Card card;
SdVolume volume;
SdFile root;
// store error strings in flash to save RAM
#define error(s) error_P(PSTR(s))
void error_P(const char* str) {
PgmPrint("error: ");
SerialPrintln_P(str);
if (card.errorCode()) {
PgmPrint("SD error: ");
Serial.print(card.errorCode(), HEX);
Serial.print(',');
Serial.println(card.errorData(), HEX);
}
while(1);
}
La parte 2 e’ la funzione setup(). Essa setta l’oggetto Serial in modo tale che noi possiamo effettuare un
debug della connessione in real time. Essa inoltre stampa l’utilizzo della RAM. Naturalmente avrete bisogno
di un Arduino con a bordo un Atmega328 per fare questi esperimenti, e dovrete vedere come minimo 1000
bytes di RAM libera altrimenti rischierete di andare troppo lenti.
Ora vedremo il trucco con il quale, settando il pin SS 10 come OUTPUT e HIGH, disabiliteremo il chip wiznet
per controllare il contenuto della SD card. Se state utilizzando un Arduino Mega, cambiate questo pin, come
detto in precedenza, da 10 a 53. Poi inizializzeremo la card, cosa che dovrebbe andare a buon fine se non e’
mai stata testata prima.
Poi verificheremo la struttura della card, stamperemo tutti i file, e stamperemo “Done!”. Alla fine ci
occuperemo del codice di inizializzazione della Ethernet shield. Quindi abbiamo sia la Ethernet Shield che la
SD card entrambe funzionanti.
void setup() {
Serial.begin(9600);
PgmPrint("Free RAM: ");
Serial.println(FreeRam());
// initialize the SD card at SPI_HALF_SPEED to avoid bus errors with
// breadboards. use SPI_FULL_SPEED for better performance.
pinMode(10, OUTPUT);
// set the SS pin as an output
(necessary!)
digitalWrite(10, HIGH);
// but turn off the W5100 chip!
if (!card.init(SPI_HALF_SPEED, 4)) error("card.init failed!");
// initialize a FAT volume
if (!volume.init(&card)) error("vol.init failed!");
PgmPrint("Volume is FAT");
Serial.println(volume.fatType(),DEC);
Serial.println();
if (!root.openRoot(&volume)) error("openRoot failed");
// list file in root with date and size
PgmPrintln("Files found in root:");
root.ls(LS_DATE | LS_SIZE);
Serial.println();
// Recursive list of all directories
PgmPrintln("Files found in all dirs:");
root.ls(LS_R);
Serial.println();
PgmPrintln("Done");
// Debugging complete, we start the server!
Ethernet.begin(mac, ip);
server.begin();
}
Ora andremo avanti con la funzione loop() dove aspettiamo i clients (controllando attraverso la
funzione server.available() ) e poi leggiamo la richiesta del client prima di rispondere. Questo
riportato di seguito e’ semplicemente un copia e incolla di quanto si trova nello sketch di esempio
Webserver incluso nella libreria Ethernet (in verita’ qualcosa di diverso c’e’ ).
C’e’ un piccolo trucco nel quale, per semplificare il codice, lo scrittore di questo sketch non controlla quali
file il web browser vuole, esso sputa fuori sempre la stessa cosa. In questo caso, noi facciamo si’ che esso ci
sputi fuori i file attraverso una funzione di aiuto chiamata ListFiles(client, 0) che esamineremo
nel seguito della trattazione. Lo 0 come secondo argomento della funzione semplicemente indica alla
funzione se stampare o meno le dimensioni dei file in gioco.
void loop()
{
Client client = server.available();
if (client) {
// an http request ends with a blank line
boolean current_line_is_blank = true;
while (client.connected()) {
if (client.available()) {
char c = client.read();
// if we've gotten to the end of the line (received a newline
// character) and the line is blank, the http request has ended,
// so we can send a reply
if (c == '\n' && current_line_is_blank) {
// send a standard http response header
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/html");
client.println();
// print all the files, use a helper to keep it clean
//ListFiles(client, 0);
client.println("<h2>Files:</h2>");
ListFiles(client, 0);
break;
}
if (c == '\n') {
// we're starting a new line
current_line_is_blank = true;
} else if (c != '\r') {
// we've gotten a character on the current line
current_line_is_blank = false;
}
}
}
// give the web browser time to receive the data
delay(1);
client.stop();
}
}
Ora faremo un passo indietro ed esamineremo la funzione ListFiles. Questo risultera’ alquanto tesioso, ma
e’ meglio farlo.
Noi abbiamo semplificato la trattazione rimuovendo l’elencazione ricorsiva, cio’ significa che non
elenchiamo i file in alcuna sottodirectory.
L’oggetto dir_t p e’ un detentore di "Directory Entry" cioe’ esso memorizzera’ l’informazione per ogni voce
nella Directory.
Prima di tutto resettiamo la root directory con la funzione rewind(). Poi leggiamo i file della directory uno
ad uno. Alcuni file sono inutilizzati o sono i link “.” e “..” (up directory). We also only list FILEs or
SUBDIRectories. Poi stampiamo il nome andando attraverso tutti gli 11 caratteri (ricordare che i nomi dei
file sono in un formato 8.3) e ignorando lo spazio. Piantiamo anche il '.' tra i primi 8 e gli ultimi 3 caratteri.
Se e’ un file di tipo directory, ci mettiamo uno slash alla fine per indicarlo. Se non lo e’ possiamo stampare
la grandezza del file in bytes.
Alla fine, dopo ogni nome del file scriveremo un “<br>" che ha lo scopo di fare andare a capo in una pagina
web.
void ListFiles(Client client, uint8_t flags) {
// Questo codice e’ semplicemente copiato da Sdfile.cpp della SDFat Library e
// modificato in modo da stampare l’output del client in html!
dir_t p;
root.rewind();
while (root.readDir(p) > 0) {
// done if past last used entry
if (p.name[0] == DIR_NAME_FREE) break;
// skip deleted entry and entries for . and ..
if (p.name[0] == DIR_NAME_DELETED || p.name[0] == '.') continue;
// only list subdirectories and files
if (!DIR_IS_FILE_OR_SUBDIR(&p)) continue;
// print file name with possible blank fill
//root.printDirName(*p, flags & (LS_DATE | LS_SIZE) ? 14 : 0);
for (uint8_t i = 0; i < 11; i++) {
if (p.name[i] == ' ') continue;
if (i == 8) {
client.print('.');
}
client.print(p.name[i]);
}
if (DIR_IS_SUBDIR(&p)) {
client.print('/');
}
// print modify date/time if requested
if (flags & LS_DATE) {
root.printFatDate(p.lastWriteDate);
client.print(' ');
root.printFatTime(p.lastWriteTime);
}
// print size if requested
if (!DIR_IS_SUBDIR(&p) && (flags & LS_SIZE)) {
client.print(' ');
client.print(p.fileSize);
}
client.println("<br>");
}
}
Dopo tutto questo lavoro, possiamo effettuare l’upload di questo sketch su Arduino! Siate sicuri di avere il
corretto indirizzo ip per la vostra rete, quindi usate un browser sulla stessa rete per visitare il vostro sito
web.
Questo e’ quello che vedo – ci sono quei due file dai miei esperimenti precedenti.
Dare un’occhiata ai file
Naturalmente, dovremmo fare in modo che tu possa cliccare i nomi di quei file. Bene! Questo e’ quello che
faremo nel prossimo sketch, puoi scaricare l’ultima versione di githup da qui (click Download Source)
nell’angoo in alto a destra!
Aggiustate l’indirizzo ip in accordo con la vostra rete , e il pin SS (se siete su un Mega).
Non cambia molto dal codice precedente, il setup e’ lo stesso, i grandi cambiamenti stanno nel codice del
loop(). La struttura e' la stessa - aspettiamo nuove connessioni da client (n.d.r. inteso come qualcosa che si
connette al server). Questa volta pero', leggiamo la richiesta del client e la scriviamo in un buffer di
caratteri fino a che non ci viene spedito un carattere di "ritorno a capo" come \n o \r .Questo indica che
abbiamo letto una intera linea di testo. Per terminare la stringa, mettiamo un carattere ‘null’ (0) alla fine.
In seguito utilizziamo strstr che cerca le sottostringhe. Se riceviamo una richiesta “GET/http” per la root
directory , facciamo la stessa cosa fatta in precedenza stampando la lista dei files.
Se non abbiamo spazio dopo il “GET/” questo significa che c’e’ qualcosa tipo “GET/file” che significa che
dovremo estrarre il nome del file. Creiamo un puntatore alla stringa e lo inizializziamo alla posizione subito
successiva il primo slash. Quindi cerchiamo l’inizio della stringa “HTTP/1.1” che segue la richiesta del nome
del file e trasforma il primo carattere in un terminatore di stringa. Ora abbiamo quindi il nome del file che
vogliamo cercare di aprire.
Se commettiamo un errore nell’aprire un file, ritorneremo un 404, altrimenti stampiamo tutti i contenuti
del file.
// Il nostro buffer quanto deve essere grande? 100 e’ piu’ che sufficiente
#define BUFSIZ 100
void loop()
{
char clientline[BUFSIZ];
int index = 0;
Client client = server.available();
if (client) {
// una richiesta http termina con una linea bianca
boolean current_line_is_blank = true;
// reset del buffer di input
index = 0;
while (client.connected()) {
if (client.available()) {
char c = client.read();
// Se non e’ una nuova linea, aggiungi il carattere al buffer
if (c != '\n' && c != '\r') {
clientline[index] = c;
index++;
// siamo troppo grandi per il buffer? Comincia a buttare qualche dato
if (index >= BUFSIZ)
index = BUFSIZ -1;
// continua a leggere piu’ dati!
continue;
}
// got a \n or \r new line, which means the string is done
clientline[index] = 0;
// Print it out for debugging
Serial.println(clientline);
// Look for substring such as a request to get the root file
if (strstr(clientline, "GET / ") != 0) {
// send a standard http response header
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/html");
client.println();
// print all the files, use a helper to keep it clean
client.println("<h2>Files:</h2>");
ListFiles(client, LS_SIZE);
} else if (strstr(clientline, "GET /") != 0) {
// this time no space after the /, so a sub-file!
char *filename;
filename = clientline + 5; // look after the "GET /" (5 chars)
// a little trick, look for the " HTTP/1.1" string and
// turn the first character of the substring into a 0 to clear it out.
(strstr(clientline, " HTTP"))[0] = 0;
// print the file we want
Serial.println(filename);
if (! file.open(&root, filename, O_READ)) {
client.println("HTTP/1.1 404 Not Found");
client.println("Content-Type: text/html");
client.println();
client.println("<h2>File Not Found!</h2>");
break;
}
Serial.println("Opened!");
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/plain");
client.println();
int16_t c;
while ((c = file.read()) > 0) {
// Togliere il comment dalla linea successive per il debugo(LENTO!)
//Serial.print((char)c);
client.print((char)c);
}
file.close();
} else {
// qualunque altra cosa e’ un 404
client.println("HTTP/1.1 404 Not Found");
client.println("Content-Type: text/html");
client.println();
client.println("<h2>File Not Found!</h2>");
}
break;
}
}
// dare al web browser il tempo di ricevere il dato
delay(1);
client.stop();
}
}
Ora diamo un’occhiata al nuovo codice per l’elenco dei file, anche se e’ molto simile al vecchio. In questo
codice abbiamo aggiunto un po’ di HTML in modo tale che ogni file e’ parte di una lista <ul> e il nome e’ un
link URL.
void ListFiles(Client client, uint8_t flags) {
// Questo codice e’ semplicemente copiato da SdFile.cpp nella SDFat library
// e modificato in modo tale da stampare nell’output del client in html!
dir_t p;
root.rewind();
client.println("<ul>");
while (root.readDir(p) > 0) {
// done if past last used entry
if (p.name[0] == DIR_NAME_FREE) break;
// skip deleted entry and entries for . and ..
if (p.name[0] == DIR_NAME_DELETED || p.name[0] == '.') continue;
// only list subdirectories and files
if (!DIR_IS_FILE_OR_SUBDIR(&p)) continue;
// print any indent spaces
client.print("<li><a href=\"");
for (uint8_t i = 0; i < 11; i++) {
if (p.name[i] == ' ') continue;
if (i == 8) {
client.print('.');
}
client.print(p.name[i]);
}
client.print("\">");
// print file name with possible blank fill
for (uint8_t i = 0; i < 11; i++) {
if (p.name[i] == ' ') continue;
if (i == 8) {
client.print('.');
}
client.print(p.name[i]);
}
client.print("</a>");
if (DIR_IS_SUBDIR(&p)) {
client.print('/');
}
// print modify date/time if requested
if (flags & LS_DATE) {
root.printFatDate(p.lastWriteDate);
client.print(' ');
root.printFatTime(p.lastWriteTime);
}
// print size if requested
if (!DIR_IS_SUBDIR(&p) && (flags & LS_SIZE)) {
client.print(' ');
client.print(p.fileSize);
}
client.println("</li>");
}
client.println("</ul>");
}
Ok, effettuiamo l’upload dello sketch. Ora vedrete che i nomi dei file sono diventati link (e abbiamo anche
aggiunto la grandezza dei file in byte).
Clicckare sul nome del file per vederlo.
Se provate a vedere un file che non e’ sulla sd card, avrete un errore 404 (file not found)
Questo articolo e’ semplicemente una traduzione di un tutorial che ho trovato sul sito Ladyada.net. La
versione originale dell’articolo la potete trovare qui:
http://www.ladyada.net/learn/arduino/ethfiles.html
Ho pensato che tradurlo sarebbe stato utile alle persone che con l’inglese fanno un po’ piu’ fatica di me.
Spero che qualcuno lo trovi utile.
Grazie per l’attenzione,
Fabrizio Schiano.
Scarica

Arduino+Ethernet Shield+SD (ITA)