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.