Operazioni di base su un modulo wifi ESP8266 di Arduino
Quando espresso ha lanciato i primi moduli sul mercato Wi-Fi con l'integrato ESP8266 e il firmware con cui gestirlo utilizzando i comandi AT, ciò che interessava a noi utenti era integrarlo negli assiemi microcontrollori e i problemi si riducevano a conoscere il (precedentemente) oscuro Tabella dei comandi AT ESP8266, esigenze alimentari o Aggiornamento del firmware ESP8266.
Poi sono arrivate rapidamente le alternative per programmare il ESP8266 e implementazioni dei moduli Wi-Fi di formati molto diversi che hanno sollevato altre preoccupazioni: quale modulo wifi ESP8266 scegliere a seconda della portata delle diverse antenne (comprese quelle esterne) o dell'integrazione fisica di questi nuovi moduli nei nostri assemblaggi.
Sicuramente, a causa di tutti questi cambiamenti, l’enfasi potrebbe non essere stata posta sugli aspetti più elementari, sulla gestione più elementare del Modulo Wi-Fi ESP8266. Sebbene polarità.es È possibile trovare informazioni sull'uso di ESP8266 ed esistono alcune applicazioni destinate a spiegare in modo generico il funzionamento del Modulo Wi-Fi ESP8266 utilizzando i comandi AT, in particolare nell'articolo su libreria per effettuare query HTTP da Arduino con il modulo wifi ESP8266, le impressioni dei lettori suggeriscono che sarebbe utile aggiungere qualche informazione di base per aiutare gli utenti del ESP8266 per realizzare le proprie implementazioni.
Discutere le operazioni di base per lavorare con ESP8266 e proporre soluzioni generiche è un obiettivo di più parti molto diverse; Per facilitare la comprensione del contenuto dell'articolo, il seguente indice può fungere da guida:
- Controlla il modulo wifi ESP8266 dal computer tramite la porta seriale
- Aggiorna il firmware con esptool
- Invia ordini al modulo
- Ricevi dati dall'ESP8266
- Analizzare la risposta cercando testi nel contenuto
- Limitare il tempo di attesa per ricevere la risposta
- Esegui un'operazione complessa definita da più comandi AT
Controlla il modulo wifi ESP8266 dal computer tramite la porta seriale
Da un piatto Arduino e utilizzando il tuo IDE è possibile monitorare il funzionamento di a Modulo Wi-Fi ESP8266, Invia il Comandi AT ESP8266 e vedi la risposta ma è molto più comodo farlo da un computer con un'applicazione di tipo terminale.
A seconda di quale scheda Arduino utilizzata, potrebbe essere disponibile solo una porta seriale hardware, il che aggiunge un piccolo inconveniente all'invio e alla ricezione. La modifica della velocità di comunicazione è molto più comoda in un'applicazione di comunicazione seriale da un computer e da alcune schede madri. Arduino (e in alcune circostanze) non supportano bene le velocità più elevate delle comunicazioni seriali, in particolare 115200 baud, che è la velocità predefinita delle ultime versioni del firmware.
Su Quale programma utilizzare per monitorare il ESP8266 utilizzando la porta seriale, ce ne sono tanti tra cui scegliere a seconda delle esigenze e delle preferenze; ultimamente sto usando di più il classico carinoCom (quello nello screenshot qui sopra) perché per me è molto comodo ripeterne alcuni Ordini AT per modulo wifi ESP8266 nella sperimentazione del progetto.
Qui sono già state fornite alcune raccomandazioni sui programmi che funzionano come console seriale; Ad esempio, quando si parla di PuTTY per il controllo dei dispositivi seriali UART dal computer. PuTTYOltre ad essere un'ottima applicazione, è disponibile per la maggior parte dei sistemi operativi desktop. Inoltre, come PuTTY può essere utilizzato per fungere da console sia con la porta seriale che con Famiglia di protocolli Internet (TCP/IP), compresi quelli che operano su TLS, diventa uno strumento comune che più che ripaga il (poco) tempo impiegato a configurarlo e ad abituarsi al suo utilizzo.
Oltre al software di comunicazione seriale, per collegare il Modulo Wi-Fi ESP8266 al porto USB Un computer richiede anche un convertitore USB alle serie TTL. Come nel caso del software, esistono diverse versioni, dalle quali vengono utilizzate solo per convertire la porta USB su una porta seriale TTL (ottenibile con un Euro) a quelli che possono emulare protocolli diversi (come SPI o I2C).
Proprio come un programma che funziona come una console seriale, l'hardware tramite cui comunicare con il computer USB con un circuito logico (non solo il ESP8266) sarà uno strumento comune nel lavoro di uno sviluppatore di applicazioni microcontrollate, vale la pena averlo nella cassetta degli attrezzi il prima possibile e lavorarci Modulo Wi-Fi ESP8266 È un'eccellente opportunità per ottenerne uno.
Il convertitore USB a bus digitale UART TTL Può anche essere utilizzato per monitorare il comportamento di un circuito che utilizza il ESP8266, per fare ciò, le uscite che si vogliono monitorare sono collegate in serie all'ingresso dati (RX) del convertitore con un diodo veloce (il 1N4148, ad esempio) e un resistore (ad esempio 2K2) in parallelo tra loro. Tale configurazione funziona come uno sniffer seriale hardware.
Anche se lo sniffer nell'immagine sopra è sicuramente rudimentale (tra l'altro non ha bufferizzare) è sufficiente per monitorare il funzionamento di un assieme Arduino e il ESP8266.
Togliendo lo sniffer dallo schema precedente, il schema che mostra come collegare a Modulo Wi-Fi ESP8266 ad un piatto Arduino. Oltre ad alimentarlo a 3V3, il pin di reset ed il pin di attivazione dell'integrato devono essere collegati a livello alto (enable). Naturalmente, il pin RX di uno deve connettersi al TX dell'altro.
Per semplificare lo schema precedente è stata rappresentata una piastra Arduino alimentato a 3V3 e per il quale si presuppone anche una tensione sulla porta seriale pari a 3V3. Se usi a microcontrollore con un livello di segnale diverso sulla porta seriale (tipicamente 5 V) sarà necessario, per non danneggiare il ESP8266, usare un convertitore di livello come quelli negli schemi seguenti. Questo circuito si trova spesso in molte implementazioni di moduli commerciali standard.
Aggiorna il firmware ESP8266
Le Comandi AT ESP8266, la sua terminazione, la velocità predefinita del modulo... dipendono dalla versione del file Firmware ESP8266. È meglio assicurarsi di avere la stessa versione in tutti i moduli e, se possibile, che sia la versione più recente.
Sfortunatamente, la maggior parte dei Modelli di moduli Wi-Fi ESP8266 Hanno solo 4Mbit, quindi non è possibile installare la versione più recente. L'ultima versione (ufficiale) del firmware su cui è possibile installare Moduli Wi-Fi ESP8266 con 4 Mbit (la maggior parte) è 0.9.4 che include la versione 0.2 del Comandi AT ESP8266.
In sintesi, per aggiornare il firmware è necessario:
-
Scarica la versione firmware corrispondente. il ultima versione (ufficiale) per un modulo con 4Mbit di memoria, che si trova nella cartella Espressif su github. Nel Sito dell'Espressif È possibile scaricare la versione più recente del firmware, ma è molto importante verificare che il modulo su cui è installato disponga di memoria sufficiente.
-
Scarica la versione più recente dello strumento di installazione del firmware. Il mio preferito è pioppo tremulo che c'è scritto Python, quindi funziona su qualsiasi piattaforma. Oltre ad essere scaricato, può anche essere installato con
pip install esptool
(opip2
opython -m pip
…). Ovviamente, espresso Offre anche il proprio strumento ma attualmente è disponibile solo per Windows. -
Preparare i file scaricati; decomprimeteli in una cartella accessibile e, se necessario, rendete eseguibile lo strumento pioppo tremulo, nel mio caso, da allora GNU / Linux, Con
chmod +x esptool
-
Collega il modulo al computer utilizzando un convertitore USB bus digitale UART TTL che funziona a 3V3 oppure utilizzare un convertitore di livello se funziona a 5 V. Oltre all'alimentazione, dovrai collegare TX a RX del convertitore USB bus digitale UART TTL, RX to TX, GPIO0 a basso livello (GND) e forse GPIO2 ad alto livello (nei miei test ha funzionato sia collegandolo a basso livello che disconnettendolo). Se il modulo ha la connessione GPIO15 libera (come avviene nell'ESP-12) deve essere collegato a livello basso. Il RESET, che normalmente sarebbe a livello alto durante il funzionamento, può essere lasciato scollegato oppure collegato a livello alto tramite una resistenza (10K, ad esempio), poiché prima di iniziare la registrazione potrebbe essere necessario resettare il dispositivo collegandolo ad un livello basso.
Accendendo il modulo sarà disponibile per l'aggiornamento ma, Se viene visualizzato un errore di connessione, sarà necessario reimpostarlo collegando RESET a livello basso per un istante e poi lasciandolo in onda (senza connettersi) per il processo di aggiornamento.
Il modulo ha picchi di consumo di mezzo ampere (fino a 600 mA, secondo alcuni utenti) quindi è importante utilizzare un alimentatore in grado di supportare questo consumo, soprattutto per l'aggiornamento del firmware. -
Eseguire lo strumento per aggiornare il firmware. Nel mio caso, ho salvato i documenti dello strumento e del firmware nel passaggio 3 nella stessa cartella, quindi eseguo dalla console:
cd ~/Datos/firmwareESP8266
(passare alla cartella contenente lo strumento e il firmware)./esptool.py --baud 115200 --port /dev/ttyUSB0 write_flash \
0x00000 ./boot_v1.1.bin \
0x01000 ./user1.bin \
0x7C000 ./esp_init_data_default.bin \
0x7E000 ./blank.bin
--baud
imposta la velocità di ESP8266 (115200 baud nel mio caso) e--port
la porta seriale a cui si collega (nel mio caso, emulata, la prima USB). I diversi documenti che compongono il firmware vanno dietrowrite_flash
preceduto dall'indirizzo, con il documento user1.bin contenente il payload dell'aggiornamento.
Invia comandi al modulo Wi-Fi ESP8266
Per controllare il ESP8266 da un computer dovremo iniziare configurare l'app per cui basterà ① scegliere la porta a cui è collegato il convertitore USB bus digitale UART TTL, qualcosa di simile a /dev/USB0
in GNU/Linux e simili o qualcosa del genere COM6
in Windows, ② scegli la velocità alla quale ESP8266, probabilmente 115200 baud, ③ imposta 8 bit di dati più un bit di stop, senza parità o handshake, e ④ imposta la fine della linea, a seconda del firmware, quasi sempre CR+LF.
Una volta che l'applicazione è configurata (o, se del caso, memorizzata e selezionata), lo è aprire la connessione ("apri dispositivo" e "apri", rispettivamente, negli screenshot degli esempi sopra con carinoCom y PuTTY) e puoi iniziare a inviare ordini a ESP8266.
Come si può vedere nel Tabella dei comandi AT ESP8266, il formato per attivare, disattivare, impostare un valore e fare riferimento ad esso è abbastanza prevedibile, ma in generale non è facile ricordarli tutti e probabilmente sarà necessario averlo a portata di mano per consultarlo.
La forma di inviare Agli ordini al Modulo Wi-Fi ESP8266 da Arduino è molto semplice: ① configurare le comunicazioni con Serial.begin(115200);
(o Serial1, Serial2… su schede con più porte seriali hardware) e ② inviare i comandi utilizzando il formato Serial.print(orden+"\r\n");
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #define PUERTO_SERIE Serial // Objeto serie que corresponde a puerto serie hardware al que está conectado el módulo wifi ESP8266 #define VELOCIDAD_ESP8266 115200 // Velocidad, en baudios, a la que está configurado el ESP8266 #define IDENTIFICADOR_WIFI “polaridad.es” // SSID (Service Set Identifier) #define CLAVE_WIFI “54lLij1RiTn3MEd3v41C” // Clave del punto de acceso wifi al que se conecta el ESP8266 void setup() { PUERTO_SERIE.begin(VELOCIDAD_ESP8266); PUERTO_SERIE.print ( “AT+CWJAP=\””+ String(IDENTIFICADOR_WIFI)+ “\”,\””+ String(CLAVE_WIFI)+ “\”\r\n” ); } void loop() { } |
L'esempio sopra mostra come inviare il file Ordini AT per modulo wifi ESP8266 da Arduino. In questo caso è illustrato AT+CWJAP
, che viene utilizzato per connettersi a un punto di accesso. Questo comando utilizza come argomenti l'identificatore del punto di accesso (SSID) e la chiave, entrambe tra virgolette, in modo che diventino un oggetto Srtring
e racchiuderli tra virgolette utilizzando il codice escape (\"
). Per completare l'ordine utilizzare \r\n
che corrisponde a CR
y LF
.
Da ricordare che non sempre la porta seriale viene identificata con Serial
(su certi piatti può essere Serial1
, Serial2
…) è stato definito l'oggetto porta utilizzato assegnandolo alla macro PUERTO_SERIE
. Rilevare il tipo di scheda utilizzata potrebbe aggiungere un po' di intelligenza alla selezione della porta seriale; Successivamente esamineremo come scoprire il tipo di Arduino. Il resto delle definizioni sono le solite che permettono di "dare un nome" ai valori costanti per evitare di ripeterli (e di commettere errori) e rendere più semplice modificarli.
L'esempio sopra dovrebbe connettere il file Modulo Wi-Fi ESP8266 all'access point indicato ma era già connesso prima? La connessione ha funzionato? Per sapere questo, dobbiamo "ascoltare" ciò che il ESP8266
Ricevi dati dal modulo Wi-Fi ESP8266
Collegando lo sniffer di dati spiegato sopra al computer puoi vedere cosa Arduino ha inviato il ESP8266 e la sua risposta. Da leggere Arduino ed elaborare le informazioni in esso contenute che sarà necessario rilevare Serial.available()
se sono arrivati dei dati e in tal caso caricarli Serial.read()
. L'esempio seguente mostra come leggere la risposta da AT+CWJAP?
, che segnalerà se è presente una connessione a qualsiasi punto di accesso.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #define PUERTO_ESP8266 Serial // Objeto serie que corresponde a puerto serie hardware al que está conectado el módulo wifi ESP8266 #define VELOCIDAD_ESP8266 115200 // Velocidad, en baudios, a la que está configurado el ESP8266 char letra_recibida; void setup() { PUERTO_ESP8266.begin(VELOCIDAD_ESP8266); PUERTO_ESP8266.print(“AT+CWJAP?\r\n”); } void loop() { while(PUERTO_ESP8266.available()) { letra_recibida=PUERTO_ESP8266.read(); } } |
Come su un piatto Arduino Uno (e in altri) l'apertura del monitor seriale ripristina il programma, può essere utilizzato per vedere nella console seriale Arduino le informazioni a cui invii ESP8266 come mostra lo screenshot dell'immagine qui sotto.
Analizza la risposta inviata dal modulo wifi ESP8266
Abbiamo già visto come leggere le informazioni che arrivano Arduino dalla ESP8266. Il problema con cui devi fare i conti è che non sai quando inizierà ad arrivare, quanto tempo ci vorrà per arrivare, quanto durerà... e non è molto efficiente aspettare la risposta dal ESP8266 viene ricevuto senza lasciare che il microcontrollore svolgere altri compiti nel frattempo.
Un modo semplice per gestire questa circostanza è iterare sui dati ricevuti cercando risposte concrete con cui, ad esempio, attivare indicatori (flag o variabili booleane) che determineranno se proseguire la ricerca nel testo ricevuto e quali azioni compiere in base alle informazioni che arrivano dal ESP8266. Mentre arriva la risposta microcontrollore può dedicarsi ad altri compiti, ad esempio, ricevere dati dai sensori e elaborarli.
Cerca un testo nelle informazioni ricevute dall'ESP8266
Per cercare il testo che proviene da ESP8266 possono essere confronta ogni lettera ricevuta con quella che corrisponde al messaggio che stai cercando. Sarà necessario utilizzare un contatore (o un puntatore) che punti alla lettera da confrontare; Se il carattere che arriva dal ESP8266 è uguale a quello in esame nel messaggio, il contatore avanza, se è diverso viene inizializzato.
Per sapere se è stata raggiunta la fine viene consultato il carattere successivo del messaggio cercato, che sarà zero (\0
) oppure la lunghezza del messaggio memorizzato per, confrontandolo con il contatore, sapere se il confronto è terminato e quindi il Modulo Wi-Fi ESP8266 ha inviato il messaggio desiderato.
L'esempio seguente utilizza il comando AT+CWLAP
che restituirà un elenco di punti di accesso e al loro interno ne verrà cercato uno chiamato "wifi polaridad.es". Sebbene abbiamo scelto di verificare che l'ultimo carattere sia zero, poiché il bufferizzare Memorizza solo il testo cercato e se ne conosce la lunghezza, si può anche verificare se è stato ricevuto un numero tale di lettere corrette. Con un LED collegato al pin 2 viene segnalato che è stato trovato il testo atteso.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | #if defined(ARDUINO_AVR_LEONARDO)||defined(ARDUINO_AVR_MEGA2560) /* ¿Es una placa Arduino Mega 2560 o Arduino Leonardo? */ #define SERIE Serial1 /* Si es una placa Arduino Mega 2560 o Arduino Leonardo usar Serial1 */ #else /* En este proyecto solamente uso Placas Leonardo, Mega 2560 y Uno, así que tiene que ser un Arduino Uno si llega hasta aquí */ #define SERIE Serial /* Si es una placa Arduino Uno usar Serial */ #endif #define VELOCIDAD 115200 // Velocidad (en baudios) al que está configurado el módulo wifi ESP8266 (Cuidado con la placa utilizada, no todas o siempre son capaces de trabajar a una velocidad tan alta) #define ORDEN “AT+CWLAP\r\n” // Buscar los puntos de acceso wifi disponibles. (Dependiendo de la versión del firmware) Las órdenes terminan en CR+LF #define MENSAJE_BUSCADO “wifi polaridad.es” // Ver si está disponible el punto de acceso llamado “wifi polaridad.es” #define LONGITUD_MENSAJE 18 // Se necesita guardar, al menos, 17 letras y el terminador \0 #define PIN_LED_ENCONTRADO 2 // Pin al que se conecta el LED que informa de que se ha encontrado el texto (el punto de acceso buscado está disponible) #include <string.h> // strncpy boolean esperando=true; char buffer_mensaje; char mensaje[LONGITUD_MENSAJE]; byte posicion_mensaje=0; void setup() { pinMode(PIN_LED_ENCONTRADO,OUTPUT); digitalWrite(PIN_LED_ENCONTRADO,LOW); // Apagar el LED (por ahora no se ha encontrado el mensaje) strncpy(mensaje,MENSAJE_BUSCADO,LONGITUD_MENSAJE); // sizeof(mensaje) SERIE.begin(VELOCIDAD); // Configurar el puerto serie de Arduino a la velocidad del ESP8266 SERIE.print(ORDEN); // Enviar la orden (consultar los puntos de acceso disponibles) al módulo wifi ESP8266 } void loop() { if(esperando) // Buscará infinitamente hasta que llegue el texto esperado (y si no existe el punto de acceso nunca llegará ¡Es un ejemplo!) { while(SERIE.available()) // Si ha llegado algún dato por el puerto serie… { buffer_mensaje=SERIE.read(); // …almacenarlo en el buffer if(buffer_mensaje==mensaje[posicion_mensaje]) // Si el dato que ha llegado es igual al que correspondería del mensaje buscado… { posicion_mensaje++; // Pasar a la siguiente letra del mensaje if(mensaje[posicion_mensaje]==0) // ¿Ha terminado de analizarse todo el mensaje? (la última letra de la cadena de texto es \0) { esperando=false; // Si se ha terminado de analizar con éxito todo el mensaje ya no se está esperando digitalWrite(PIN_LED_ENCONTRADO,HIGH); // Encender el LED para indicar que se ha encontrado el texto buscado } } else // Si la letra que ha llegado por el puerto serie no corresponde con la buscada del mensaje… { posicion_mensaje=0; // …empezar desde la primera letra del texto buscado } } } } |
Nel codice dell'esempio precedente puoi anche vedere un modo per scegliere la porta seriale a seconda del tipo di scheda Arduino usato. Questo esempio presuppone che tu abbia tre tipi di schede per il progetto: uno Arduino Uno, One Arduino Mega2560 e Arduino Leonardo. Se lavori con a Arduino Uno verrà utilizzato Serial
e altrimenti Serial1
.
Se lavori con un piatto Arduino Leonardo È possibile utilizzare lo stesso metodo per arrestare il programma e attendere la console (la porta seriale associata a Serial
) è disponibile.
1 2 3 4 5 6 7 8 9 10 11 | #ifdef ARDUINO_AVR_LEONARDO #define SERIE Serial1 #define ESPERA_CONSOLA while(!Serial){} /* Esperar a la consola */ #else #define ESPERA_CONSOLA /* Si no es un Arduino Leonardo no hace falta esperar a la consola */ #ifdef ARDUINO_AVR_MEGA2560 #define SERIE Serial1 #else // En este proyecto solamente uso Placas Leonardo, Mega 2560 y Uno, así que tiene que ser un Arduino Uno si llega hasta aquí #define SERIE Serial #endif #endif |
Cerca vari testi nella risposta ESP8266
Il codice nell'esempio precedente viene utilizzato per cercare testo nelle informazioni inviate dal ESP8266 ma la risposta potrebbe includere informazioni diverse a seconda dell'operazione. Supponiamo, per iniziare con un caso semplice nel prossimo esempio, che il testo inviato da MCU ESP8266 es OK
quando l'operazione viene eseguita correttamente e ERROR
Altrimenti, come con l'ordine AT+CWJAP?
, che serve a verificare se il Modulo Wi-Fi ESP8266 è già connesso a un punto di accesso.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | #if defined(ARDUINO_AVR_LEONARDO)||defined(ARDUINO_AVR_MEGA2560) /* ¿Es una placa Arduino Mega 2560 o Arduino Leonardo? */ #define SERIE Serial1 /* Si es una placa Arduino Mega 2560 o Arduino Leonardo usar Serial1 */ #else /* En este proyecto solamente uso Placas Leonardo, Mega 2560 y Uno, así que tiene que ser un Arduino Uno si llega hasta aquí */ #define SERIE Serial /* Si es una placa Arduino Uno usar Serial */ #endif #define VELOCIDAD 115200 // Velocidad (en baudios) al que está configurado el módulo wifi ESP8266 (Cuidado con la placa utilizada, no todas o siempre son capaces de trabajar a una velocidad tan alta) #define ORDEN “AT+CWJAP?\r\n” // Verificar que está conectado a un punto de acceso #define CANTIDAD_MENSAJES 2 // Se distingue entre dos mensajes: acierto y error que se identificarán con true y false #define MENSAJE_ACIERTO “OK” #define MENSAJE_ERROR “ERROR” #define LONGITUD_MENSAJE 6 // Se necesita guardar, al menos, 5 letras y el terminador \0 (Un pequeño desperdicio al hacer una matriz en la que todos los elementos ocupan como el mayor. Es admisible porque son muy pocos elementos y así no se complica este ejemplo inicial) #define PIN_LED_ACIERTO 2 // Pin al que se conecta el LED que informa del acierto #define PIN_LED_ERROR 3 // Pin al que se conecta el LED que informa del error #include <string.h> // strncpy boolean esperando=true; // Todavía no se ha encontrado el final del mensaje char buffer_mensaje; // Para almacenar la última letra cargada desde el ESP8266 byte led_estado[CANTIDAD_MENSAJES]; // Un LED (pin) para cada estado char mensaje[CANTIDAD_MENSAJES][LONGITUD_MENSAJE]; // Mensajes de acierto y error byte posicion_mensaje[CANTIDAD_MENSAJES]; // Un contador de posición para cada mensaje void setup() { led_estado[true]=PIN_LED_ACIERTO; led_estado[false]=PIN_LED_ERROR; for(byte numero_mensaje=0;numero_mensaje<CANTIDAD_MENSAJES;numero_mensaje++) { pinMode(led_estado[numero_mensaje],OUTPUT); // Establecer el pin del LED digitalWrite(led_estado[numero_mensaje],LOW); // Apagar el LED posicion_mensaje[numero_mensaje]=0; // Inicializar a cero el número de letra a analizar de cada mensaje } strncpy(mensaje[true],MENSAJE_ACIERTO,LONGITUD_MENSAJE); // Preparar el mensaje de acierto strncpy(mensaje[false],MENSAJE_ERROR,LONGITUD_MENSAJE); // Preparar el mensaje de error SERIE.begin(VELOCIDAD); // Configurar el puerto serie de Arduino a la velocidad del ESP8266 SERIE.print(ORDEN); // Enviar la orden (verificar si existe conexión a un punto de acceso) al módulo wifi ESP8266 } void loop() { if(esperando) // Buscará infinitamente hasta que llegue el texto esperado (y si no existe el punto de acceso nunca llegará ¡Es un ejemplo!) { while(SERIE.available()) // Si ha llegado algún dato por el puerto serie… { buffer_mensaje=SERIE.read(); // …almacenarlo en el buffer for(byte numero_mensaje=0;numero_mensaje<CANTIDAD_MENSAJES;numero_mensaje++) { if(buffer_mensaje==mensaje[numero_mensaje][posicion_mensaje[numero_mensaje]]) // Si el dato que ha llegado es igual al que correspondería del mensaje buscado… { posicion_mensaje[numero_mensaje]++; // Pasar a la siguiente letra del mensaje if(mensaje[numero_mensaje][posicion_mensaje[numero_mensaje]]==0) // ¿Ha terminado de analizarse todo el mensaje actual? (la última letra de la cadena de texto es \0) { esperando=false; // Si se ha encontrado algún mensaje y ya no se está esperando digitalWrite(led_estado[numero_mensaje],HIGH); // Encender el LED correspondiente al mensaje encontrado } } else // Si la letra que ha llegado por el puerto serie no corresponde con la buscada del mensaje… { posicion_mensaje[numero_mensaje]=0; // …empezar desde la primera letra del texto buscado } } } } } |
Questa nuova implementazione dello stesso metodo, che ricerca una corrispondenza tra più messaggi possibili, permette di scegliere tra diverse azioni a seconda della risposta ricevuta dal ESP8266, è sufficiente accendere il LED quello corrisponde.
Limita il tempo necessario per ricevere una risposta
Finora non è stato fatto alcun riferimento ad una questione rilevante: l' tempo massimo di attesa (timeout) prima di considerare un'operazione fallita. Se per qualsiasi motivo la connessione con il Modulo Wi-Fi ESP8266, il modulo con il punto di accesso, il punto di accesso con Internet o, ad esempio, un ipotetico server non è disponibile, il programma potrebbe bloccarsi in un punto in attesa indefinitamente, quindi una risposta dovrà essere articolata a tali circostanze. È possibile configurare il tempo di attesa massimo per l'intera applicazione, solitamente sarà più "generoso" in quel caso, oppure è possibile programmare tempi di attesa individuali per ogni operazione.
Per verificare che sia trascorso (almeno) un certo intervallo di tempo Solitamente si sottrae il “tempo” del momento in cui si avvia il conto al “tempo” attuale e si verifica che la differenza sia maggiore del limite desiderato. Questo "tempo" non deve essere necessariamente tempo reale, di solito corrisponde all'intervallo trascorso dal MCU iniziare a contare il tempo; Ciò non influisce sul programma poiché ciò che interessa è il tempo trascorso e non il tempo assoluto.
Solitamente per verificare se è trascorso un certo intervallo si utilizza un'espressione del tipo:
1 | (unsigned long)(millis()–milisegundos_al_empezar)>intervalo_de_tiempo |
La variabile milisegundos_al_empezar
contiene il valore di millis()
di un certo momento dell'esecuzione da cui viene cronometrato, quindi non è insolito che il suo nome faccia riferimento alla parola "cronometro". La variabile intervalo_de_tiempo
contiene il numero massimo di millisecondi che rende vera l'espressione precedente, ovvero rappresenta il timeout; Solitamente è una costante (o una macro) e, come nel caso precedente, nel suo nome compare spesso la parola "TIMEOUT". Se lavori con intervalli molto brevi puoi usare micros()
al posto di millis()
(microsecondi invece di millisecondi) sebbene sia molto meno comune e molto meno preciso.
1 | (unsigned long)(millis()–cronometro)>TIMEOUT |
Un numero intero lungo in Arduino (unsigned long
) occupa 4 byte (32 bit), quindi il valore più grande che può rappresentare è 4294967295 (2 elevato a 32 meno uno, perché inizia da zero). su un piatto Arduino Durante il funzionamento continuo, il contatore dei millisecondi si azzererà (tornerà a zero) circa ogni 50 giorni. Quando si sottrae con tipi di dati senza segno viene riprodotto lo stesso comportamento (capovolgendo il contatore), quindi è possibile controllare il timeout indefinitamente.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | #if defined(ARDUINO_AVR_LEONARDO)||defined(ARDUINO_AVR_MEGA2560) /* ¿Es una placa Arduino Mega 2560 o Arduino Leonardo? */ #define SERIE Serial1 /* Si es una placa Arduino Mega 2560 o Arduino Leonardo usar Serial1 */ #else /* En este proyecto solamente uso Placas Leonardo, Mega 2560 y Uno, así que tiene que ser un Arduino Uno si llega hasta aquí */ #define SERIE Serial /* Si es una placa Arduino Uno usar Serial */ #endif #define VELOCIDAD 115200 // Velocidad (en baudios) al que está configurado el módulo wifi ESP8266 (Cuidado con la placa utilizada, no todas o siempre son capaces de trabajar a una velocidad tan alta) #define ORDEN “AT+CWJAP?\r\n” // Verificar que está conectado a un punto de acceso #define CANTIDAD_MENSAJES 2 // Se distingue entre dos mensajes: acierto y error que se identificarán con true y false #define MENSAJE_ACIERTO “OK” #define MENSAJE_ERROR “ERROR” #define LONGITUD_MENSAJE 6 // Se necesita guardar, al menos, 5 letras y el terminador \0 (Un pequeño desperdicio al hacer una matriz en la que todos los elementos ocupan como el mayor. Es admisible porque son muy pocos elementos y así no se complica este ejemplo inicial) #define PIN_LED_ACIERTO 2 // Pin al que se conecta el LED que informa del acierto #define PIN_LED_ERROR 3 // Pin al que se conecta el LED que informa del error #define TIMEOUT 5000 // Espera 5 segundos la respuesta del ESP8266 (y su análisis) antes de desistir #include <string.h> // strncpy boolean esperando=true; // Todavía no se ha encontrado el final del mensaje boolean encontrado=false; // Salvo que se encuentre el punto de acceso se considera que la operación ha fracasado char buffer_mensaje; // Para almacenar la última letra cargada desde el ESP8266 byte led_estado[CANTIDAD_MENSAJES]; // Un LED (pin) para cada estado char mensaje[CANTIDAD_MENSAJES][LONGITUD_MENSAJE]; // Mensajes de acierto y error byte posicion_mensaje[CANTIDAD_MENSAJES]; // Un contador de posición para cada mensaje unsigned long cronometro; void setup() { led_estado[true]=PIN_LED_ACIERTO; led_estado[false]=PIN_LED_ERROR; for(byte numero_mensaje=0;numero_mensaje<CANTIDAD_MENSAJES;numero_mensaje++) { pinMode(led_estado[numero_mensaje],OUTPUT); // Establecer el pin del LED digitalWrite(led_estado[numero_mensaje],LOW); // Apagar el LED posicion_mensaje[numero_mensaje]=0; // Inicializar a cero el número de letra a analizar de cada mensaje } strncpy(mensaje[true],MENSAJE_ACIERTO,LONGITUD_MENSAJE); // Preparar el mensaje de acierto strncpy(mensaje[false],MENSAJE_ERROR,LONGITUD_MENSAJE); // Preparar el mensaje de error SERIE.begin(VELOCIDAD); // Configurar el puerto serie de Arduino a la velocidad del ESP8266 SERIE.print(ORDEN); // Enviar la orden (verificar si existe conexión a un punto de acceso) al módulo wifi ESP8266 cronometro=millis(); } void loop() { if(esperando) // Buscará infinitamente hasta que llegue el texto esperado (y si no existe el punto de acceso nunca llegará ¡Es un ejemplo!) { while(SERIE.available()) // Si ha llegado algún dato por el puerto serie… { buffer_mensaje=SERIE.read(); // …almacenarlo en el buffer for(byte numero_mensaje=0;numero_mensaje<CANTIDAD_MENSAJES;numero_mensaje++) { if(buffer_mensaje==mensaje[numero_mensaje][posicion_mensaje[numero_mensaje]]) // Si el dato que ha llegado es igual al que correspondería del mensaje buscado… { posicion_mensaje[numero_mensaje]++; // Pasar a la siguiente letra del mensaje if(mensaje[numero_mensaje][posicion_mensaje[numero_mensaje]]==0) // ¿Ha terminado de analizarse todo el mensaje actual? (la última letra de la cadena de texto es \0) { encontrado=numero_mensaje; // (numero_mensaje!=0) Hay conexión con el punto de acceso esperando=false; // Si se ha encontrado algún mensaje y ya no se está esperando digitalWrite(led_estado[numero_mensaje],HIGH); // Encender el LED correspondiente al mensaje encontrado } } else // Si la letra que ha llegado por el puerto serie no corresponde con la buscada del mensaje… { posicion_mensaje[numero_mensaje]=0; // …empezar desde la primera letra del texto buscado } } } if((unsigned long)(millis()–cronometro)>TIMEOUT&&!encontrado) // Se ha superado el tiempo de espera y no hay conexión (se ha verificado que no hay o no ha llegado respuesta) { digitalWrite(PIN_LED_ERROR,HIGH); // Si ha superado el tiempo de espera encender el LED de error esperando=false; } } } |
Il codice sopra mostra a implementazione molto elementare della limitazione del timeout incorporando le righe segnate rispetto all'esempio che lo precede. Poiché la verifica del timeout viene eseguita dopo l'elaborazione dei dati provenienti dal file Modulo Wi-Fi ESP8266, l'operazione si può considerare andata a buon fine anche se la ricezione richiede un tempo superiore al tempo di attesa imposto.
Esegui un'operazione complessa definita da più comandi AT
Per avere un esempio di riferimento dello scopo dell'applicazione che sfrutta il Modulo Wi-Fi ESP8266, supponiamo che lo sia memorizzare le informazioni in un database a cui si accede tramite un servizio web per tenere traccia della temperatura. Il seguente codice legge un sensore collegato ad un ingresso analogico ogni certo intervallo di tempo, calcola il valore medio e, dopo un intervallo di tempo più lungo, lo invia al web server (stile IoT) attraverso a richiesta HTTP (POSTA, OTTIENI...).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | #define PIN_TEMPERATURA A0 // Pin analógico al que se conecta la salida del sensor de temperatura LM35 #define INTERVALO_LECTURA_TEMPERATURA 30000 // Leer la temperatura cada 30 segundos (30*1000) #define INTERVALO_GRABACION_TEMPERATURA 300000 // Grabar la media de la temperatura cada 5 minutos (5*60*1000) unsigned long muestras=0; // Número de veces que se ha medido la temperatura (para calcular la media) float temperatura; // de -55.0 °C a +150 °C | de -550 mV a +1500 mV | de 0 V a 2.050 V | analogRead*5.0/1023.0*100-55.0 -> analogRead/2.46-55.0 float media_temperaturas=0.0; unsigned long cronometro_lectura_temperatura; unsigned long cronometro_grabacion_temperatura; void setup() { Serial.begin(9600); cronometro_lectura_temperatura=millis(); cronometro_grabacion_temperatura=millis(); } void loop() { if((unsigned long)(millis()–cronometro_lectura_temperatura)>INTERVALO_LECTURA_TEMPERATURA) { cronometro_lectura_temperatura=millis(); temperatura=analogRead(PIN_TEMPERATURA)/2.46–55.0; muestras++; media_temperaturas=(float)temperatura/(float)muestras+media_temperaturas*(float)(muestras–1)/(float)(muestras); } if((unsigned long)(millis()–cronometro_grabacion_temperatura)>INTERVALO_GRABACION_TEMPERATURA) { cronometro_grabacion_temperatura=millis(); // Aquí iría la parte del código que graba la temperatura. Para verificar el funcionamiento, en este ejemplo simplemente se muestra en la consola Serial.println(“\nTemperatura media “+String(temperatura,DEC)+” °C (“+String((float)millis()/1000.0,DEC)+” s)\n”); } } |
In questo esempio di registrazione della temperatura si accede a un server web ogni cinque minuti. Sebbene la disponibilità non sia particolarmente elevata, è prevedibile che la proposta funzioni, ma se fosse necessaria una frequenza di registrazione più elevata, dovrebbero essere implementate altre risorse, ad esempio un buffer dei dati in attesa di essere inviato, per inviarne diversi quando il server può essere presente e memorizzarli per quando non è disponibile. Se la frequenza con cui i dati devono essere registrati fosse ancora maggiore, si dovrebbero proporre altri tipi di protocolli in alternativa a quello HTTP o addirittura sostituire TCP by UDP poter inviare la maggior parte dei dati alla velocità richiesta anche a costo di perderne alcuni.
Le operazioni che compongono l'attività da eseguire per inviare la temperatura sarebbero:
- Ripristina il modulo Wi-Fi
- Disconnettersi dal punto di accesso corrente (nel caso esista una connessione predefinita)
- Configura le impostazioni. Nell'esempio si presuppone che sia necessario configurare la modalità di connessione (semplice) e il ruolo nelle comunicazioni Wi-Fi (stazione).
- Connettersi al punto di accesso
- Verificare che la connessione sia corretta (in realtà questo è il punto di ingresso) Se non c'è connessione, riavviare il processo dall'inizio
- Connettiti al server
- Invia la richiesta HTTP con i dati da memorizzare
L'ordine delle operazioni non deve essere esattamente questo (anche se l'operazione lo è) e ogni passaggio potrebbe richiederne diversi Comandi AT ESP8266Ad esempio, la configurazione sopra elencata ne richiederebbe due: AT+CIPMUX=0
y AT+CWMODE=1
.
Una struttura dati per rappresentare le operazioni sull'ESP8266
Negli esempi precedenti, seppure in modo molto elementare, viene già suggerita una soluzione generica al problema: utilizzare una struttura dati che memorizza le possibili risposte e le azioni che devono essere intraprese in ciascun caso; invia un'azione, attendi una risposta e procedi in base al significato della risposta. Poiché ogni operazione complessa ne richiederà diverse Comandi AT ESP8266, la struttura dati deve collegare un'operazione con altre, successive o precedenti, che dovranno essere effettuate caso per caso a seconda della risposta del ESP8266.
Negli esempi precedenti, un messaggio veniva cercato all'interno della risposta del file ESP8266 ed è stato interpretato come un successo o un errore. Oltre ad una ricezione (e analisi) di tutto il testo ricevuto, Per avere un minimo generico è consigliabile occuparsi anche della compilazione del messaggio o, in altre parole, alla disponibilità del Modulo Wi-Fi ESP8266 per ricevere nuovi ordini. In questo modo, il passaggio ad uno stato che potremmo chiamare, ad esempio, "wifi disponibile", potrebbe essere ricevere il nome del punto di accesso e ricevere il testo ERROR
o il testo OK
significherebbe che il ESP8266 hai terminato la risposta e ora puoi inviare quella successiva Comando AT su ESP8266.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | // inicializar_operaciones.h // 0 Reiniciar el módulo wifi ESP8266 operacion[REINICIAR_ESP8266]=“AT+RST”; mensaje[REINICIAR_ESP8266][FALLO]=mensaje_fallo; mensaje[REINICIAR_ESP8266][ACIERTO]=“ready\r\n”; mensaje[REINICIAR_ESP8266][LITERAL]=mensaje_vacio; siguiente_operacion[REINICIAR_ESP8266][FALLO]=REINICIAR_ESP8266; siguiente_operacion[REINICIAR_ESP8266][ACIERTO]=DESCONECTAR_WIFI; siguiente_operacion[REINICIAR_ESP8266][LITERAL]=DESCONECTAR_WIFI; configuracion[REINICIAR_ESP8266]=ESPERAR_RESPUESTA|ACIERTO_TERMINA|FALLO_TERMINA; timeout[REINICIAR_ESP8266]=10000; // 1 Desconectar del punto de acceso por defecto (si fuera el caso) operacion[DESCONECTAR_WIFI]=“AT+CWQAP”; mensaje[DESCONECTAR_WIFI][FALLO]=mensaje_fallo; mensaje[DESCONECTAR_WIFI][ACIERTO]=mensaje_acierto; mensaje[DESCONECTAR_WIFI][LITERAL]=mensaje_vacio; siguiente_operacion[DESCONECTAR_WIFI][FALLO]=REINICIAR_ESP8266; siguiente_operacion[DESCONECTAR_WIFI][ACIERTO]=MODO_ESTACION; siguiente_operacion[DESCONECTAR_WIFI][LITERAL]=MODO_ESTACION; configuracion[DESCONECTAR_WIFI]=ESPERAR_RESPUESTA|ACIERTO_TERMINA|FALLO_TERMINA; timeout[DESCONECTAR_WIFI]=2500; // 2 Establecer el modo de estación (no punto de acceso) operacion[MODO_ESTACION]=“AT+CWMODE=1”; mensaje[MODO_ESTACION][FALLO]=mensaje_fallo; mensaje[MODO_ESTACION][ACIERTO]=mensaje_acierto; mensaje[MODO_ESTACION][LITERAL]=mensaje_vacio; siguiente_operacion[MODO_ESTACION][FALLO]=REINICIAR_ESP8266; siguiente_operacion[MODO_ESTACION][ACIERTO]=MODO_SIMPLE; siguiente_operacion[MODO_ESTACION][LITERAL]=MODO_SIMPLE; configuracion[MODO_ESTACION]=ESPERAR_RESPUESTA|ACIERTO_TERMINA|FALLO_TERMINA; timeout[MODO_ESTACION]=2500; // 3 Establecer el modo de conexión simple operacion[MODO_SIMPLE]=“AT+CIPMUX=0”; mensaje[MODO_SIMPLE][FALLO]=mensaje_fallo; mensaje[MODO_SIMPLE][ACIERTO]=mensaje_acierto; mensaje[MODO_SIMPLE][LITERAL]=mensaje_vacio; siguiente_operacion[MODO_SIMPLE][FALLO]=REINICIAR_ESP8266; siguiente_operacion[MODO_SIMPLE][ACIERTO]=CONECTAR_WIFI; siguiente_operacion[MODO_SIMPLE][LITERAL]=CONECTAR_WIFI; configuracion[MODO_SIMPLE]=ESPERAR_RESPUESTA|ACIERTO_TERMINA|FALLO_TERMINA; timeout[MODO_SIMPLE]=2500; // 4 Conectar al punto de acceso operacion[CONECTAR_WIFI]=“AT+CWJAP=\”polaridad.es\”,\”54lLij1RiTn3MEd3v41C\””; mensaje[CONECTAR_WIFI][FALLO]=mensaje_fallo; mensaje[CONECTAR_WIFI][ACIERTO]=mensaje_acierto; mensaje[CONECTAR_WIFI][LITERAL]=mensaje_vacio; siguiente_operacion[CONECTAR_WIFI][FALLO]=REINICIAR_ESP8266; siguiente_operacion[CONECTAR_WIFI][ACIERTO]=VERIFICAR_CONEXION; siguiente_operacion[CONECTAR_WIFI][LITERAL]=VERIFICAR_CONEXION; configuracion[CONECTAR_WIFI]=ESPERAR_RESPUESTA|ACIERTO_TERMINA|FALLO_TERMINA; timeout[CONECTAR_WIFI]=20000; // 5 Verificar si hay conexión operacion[VERIFICAR_CONEXION]=“AT+CIPSTATUS”; mensaje[VERIFICAR_CONEXION][FALLO]=mensaje_fallo; mensaje[VERIFICAR_CONEXION][ACIERTO]=mensaje_acierto; mensaje[VERIFICAR_CONEXION][LITERAL]=“STATUS:5”; siguiente_operacion[VERIFICAR_CONEXION][FALLO]=REINICIAR_ESP8266; siguiente_operacion[VERIFICAR_CONEXION][ACIERTO]=REINICIAR_ESP8266; siguiente_operacion[VERIFICAR_CONEXION][LITERAL]=CONECTAR_SERVIDOR; configuracion[VERIFICAR_CONEXION]=ESPERAR_RESPUESTA|ACIERTO_TERMINA|FALLO_TERMINA; timeout[VERIFICAR_CONEXION]=5000; // 6 Conectar al servidor operacion[CONECTAR_SERVIDOR]=“AT+CIPSTART=\”TCP\”,\”servidoriot.com\”,80″; mensaje[CONECTAR_SERVIDOR][FALLO]=mensaje_fallo; mensaje[CONECTAR_SERVIDOR][ACIERTO]=mensaje_acierto; mensaje[CONECTAR_SERVIDOR][LITERAL]=“CONNECT”; siguiente_operacion[CONECTAR_SERVIDOR][FALLO]=REINICIAR_ESP8266; siguiente_operacion[CONECTAR_SERVIDOR][ACIERTO]=INFORMAR_CANTIDAD; siguiente_operacion[CONECTAR_SERVIDOR][LITERAL]=INFORMAR_CANTIDAD; configuracion[CONECTAR_SERVIDOR]=ESPERAR_RESPUESTA|ACIERTO_TERMINA|FALLO_TERMINA; timeout[CONECTAR_SERVIDOR]=15000; // 7 Avisar de la cantidad de datos que se envían operacion[INFORMAR_CANTIDAD]=“AT+CIPSEND=”; mensaje[INFORMAR_CANTIDAD][FALLO]=mensaje_vacio; mensaje[INFORMAR_CANTIDAD][ACIERTO]=mensaje_acierto; mensaje[INFORMAR_CANTIDAD][LITERAL]=mensaje_vacio; siguiente_operacion[INFORMAR_CANTIDAD][FALLO]=REINICIAR_ESP8266; siguiente_operacion[INFORMAR_CANTIDAD][ACIERTO]=ENVIAR_CANTIDAD; siguiente_operacion[INFORMAR_CANTIDAD][LITERAL]=ENVIAR_CANTIDAD; configuracion[INFORMAR_CANTIDAD]=NO_ESPERAR_RESPUESTA; timeout[INFORMAR_CANTIDAD]=1000; // 8 Enviar cantidad //operacion[ENVIAR_CANTIDAD]=”123″; // Definido para cada envío mensaje[ENVIAR_CANTIDAD][FALLO]=sin_enlace; mensaje[ENVIAR_CANTIDAD][ACIERTO]=“>”; mensaje[ENVIAR_CANTIDAD][LITERAL]=mensaje_vacio; siguiente_operacion[ENVIAR_CANTIDAD][FALLO]=REINICIAR_ESP8266; siguiente_operacion[ENVIAR_CANTIDAD][ACIERTO]=ENVIAR_PREFIJO_PETICION; siguiente_operacion[ENVIAR_CANTIDAD][LITERAL]=ENVIAR_PREFIJO_PETICION; configuracion[ENVIAR_CANTIDAD]=ESPERAR_RESPUESTA|ACIERTO_TERMINA|FALLO_TERMINA; timeout[ENVIAR_CANTIDAD]=5000; // 9 Enviar el prefijo de la petición operacion[ENVIAR_PREFIJO_PETICION]=“GET /frigo03/almacenar_temperatura.php?temperatura=”; mensaje[ENVIAR_PREFIJO_PETICION][FALLO]=mensaje_vacio; mensaje[ENVIAR_PREFIJO_PETICION][ACIERTO]=mensaje_vacio; mensaje[ENVIAR_PREFIJO_PETICION][LITERAL]=mensaje_vacio; siguiente_operacion[ENVIAR_PREFIJO_PETICION][FALLO]=REINICIAR_ESP8266; siguiente_operacion[ENVIAR_PREFIJO_PETICION][ACIERTO]=ENVIAR_DATOS; siguiente_operacion[ENVIAR_PREFIJO_PETICION][LITERAL]=ENVIAR_DATOS; configuracion[ENVIAR_PREFIJO_PETICION]=NO_ESPERAR_RESPUESTA; timeout[ENVIAR_PREFIJO_PETICION]=10000; // 10 Enviar la temperatura //operacion[ENVIAR_DATOS]=”+000.00″; // Definido para cada envío mensaje[ENVIAR_DATOS][FALLO]=mensaje_vacio; mensaje[ENVIAR_DATOS][ACIERTO]=mensaje_vacio; mensaje[ENVIAR_DATOS][LITERAL]=mensaje_vacio; siguiente_operacion[ENVIAR_DATOS][FALLO]=REINICIAR_ESP8266; siguiente_operacion[ENVIAR_DATOS][ACIERTO]=ENVIAR_SUFIJO_PETICION; siguiente_operacion[ENVIAR_DATOS][LITERAL]=ENVIAR_SUFIJO_PETICION; configuracion[ENVIAR_DATOS]=NO_ESPERAR_RESPUESTA; timeout[ENVIAR_DATOS]=5000; // 11 Enviar el sufijo de la petición operacion[ENVIAR_SUFIJO_PETICION]=” HTTP/1.1\r\nHost: www.servidoriot.com\r\nUser-Agent: ESP8266\r\nConnection: close\r\n\r\n”; mensaje[ENVIAR_SUFIJO_PETICION][FALLO]=sin_enlace; mensaje[ENVIAR_SUFIJO_PETICION][ACIERTO]=“CLOSED\r\n\r\nOK\r\n”; mensaje[ENVIAR_SUFIJO_PETICION][LITERAL]=mensaje_vacio; // “SEND OK” siguiente_operacion[ENVIAR_SUFIJO_PETICION][FALLO]=REINICIAR_ESP8266; siguiente_operacion[ENVIAR_SUFIJO_PETICION][ACIERTO]=VERIFICAR_CONEXION; siguiente_operacion[ENVIAR_SUFIJO_PETICION][LITERAL]=VERIFICAR_CONEXION; configuracion[ENVIAR_SUFIJO_PETICION]=ESPERAR_RESPUESTA|ACIERTO_TERMINA|FALLO_TERMINA; timeout[ENVIAR_SUFIJO_PETICION]=20000; |
Il codice sopra utilizza un vettore (operacion
) per memorizzare il testo delle operazioni successive che compongono l'attività completa. Viene utilizzato un array bidimensionale (mensaje
) con le tre risposte analizzate. Come spiegato sopra, è necessario cercare i messaggi che rappresentano la fine della risposta oltre al messaggio che rappresenta una risposta corretta o errata. Non tutte le operazioni avranno lo stesso numero di risposte possibili; Quando ci sono meno risposte, è possibile utilizzare un messaggio vuoto che consuma il minor numero possibile di cicli nella sua analisi (anche così, non è il modo ottimale). Logicamente sarà necessario che il numero minimo di risposte richieste (tre nell'esempio) comprenda tutte le possibilità operative, anche se non tutte possibili.
Parlando delle possibili risposte si può già vedere che questo esempio non è molto utile per ricevere dati con un formato arbitrario da un Modulo Wi-Fi ESP8266, ma il fatto è che, nel contesto dell'uso con microcontrollori non è normale; La cosa più comune è inviare i dati raccolti dai sensori che hanno collegato e/o ricevere informazioni su cosa fare con gli attuatori che controlla. Informazioni molto preziose, che possono essere previste molto bene.
Nella precedente struttura dati, così come si fa per esprimere le possibili risposte che si analizzano, viene utilizzata anche una matrice bidimensionale per determinare l'operazione che deve essere eseguita in ciascun caso (siguiente_operacion
). Nello specifico, abbiamo scelto di rispondere a tre tipologie di messaggi: ① un testo arbitrario (LITERAL
) per verificare se è presente una connessione al punto di accesso Wi-Fi e al server, ② un testo per rilevare errori nel processo (FALLO
) e ③ un testo che indica che l'operazione è stata completata con successo (ACIERTO
).
Infine, ci sono altri due vettori per impostare il tempo massimo di attesa prima di arrendersi (timeout
) e specificare (configuracion
) se l'operazione termina senza attendere una risposta (ESPERAR_RESPUESTA
) e messaggi che indicano la fine della comunicazione. Quest'ultimo vettore, per illustrare un esempio di come si potrebbe risparmiare memoria, funziona con i bit di un byte di configurazione per indicare i diversi stati.
Il primo Comandi AT ESP8266 della struttura dati si aspettano sempre una risposta, che può essere il messaggio di successo o di errore. Quando si verifica un errore, il modulo viene riavviato e riparte e se il messaggio indica che l'operazione è corretta, passa a quella successiva.
Una volta connesso al server, lo schema cambia. In questo caso è necessario ① inviare la lunghezza del pacchetto dati da trasmettere e ② comporre la richiesta HTTP con un testo fisso più il valore (della temperatura) che viene inviato per essere memorizzato sul server. La preparazione di questi dati viene effettuata in ogni spedizione ed è necessario dividerli in due (comunicare la durata) o in tre (inviare la richiesta HTTP) per ESP8266 AT ordine. Solo l'ultima delle parti in cui è suddivisa l'operazione attenderà una risposta.
In questo caso funzionerà senza problemi (magari avvisando che il modulo è occupato) ma quando la lunghezza dei dati sarà maggiore sarà necessario dividere i blocchi di dati in pezzi più piccoli e potrebbe anche essere necessario implementare un'attesa, poiché viene effettuata la lettura della temperatura, per dare tempo al modulo di inviare i dati senza riempirli bufferizzare.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | #if defined(ARDUINO_AVR_LEONARDO)||defined(ARDUINO_AVR_MEGA2560) /* ¿Es una placa Arduino Mega 2560 o Arduino Leonardo? */ #define SERIE Serial1 /* Si es una placa Arduino Mega 2560 o Arduino Leonardo usar Serial1 */ #else /* En este proyecto solamente uso Placas Leonardo, Mega 2560 y Uno, así que tiene que ser un Arduino Uno si llega hasta aquí */ #define SERIE Serial /* Si es una placa Arduino Uno usar Serial */ #endif #define VELOCIDAD 115200 // Velocidad (en baudios) al que está configurado el módulo wifi ESP8266 (Cuidado con la placa utilizada, no todas o siempre son capaces de trabajar a una velocidad tan alta) #define REINICIAR_ESP8266 0 // Índice del vector de operaciones que representa la orden de reinicio del módulo wifi ESP8266 #define DESCONECTAR_WIFI 1 #define MODO_ESTACION 2 #define MODO_SIMPLE 3 #define CONECTAR_WIFI 4 #define VERIFICAR_CONEXION 5 #define CONECTAR_SERVIDOR 6 #define INFORMAR_CANTIDAD 7 #define ENVIAR_CANTIDAD 8 #define ENVIAR_PREFIJO_PETICION 9 #define ENVIAR_DATOS 10 #define ENVIAR_SUFIJO_PETICION 11 #define CANTIDAD_OPERACIONES 12 // Cantidad de operaciones que forma el cuerpo de la aplicación #define FALLO 0 // Índice del vector de respuestas que representa el mensaje de error #define FALLO_TERMINA 0B00000001 // 1<<FALLO #define ACIERTO 1 #define ACIERTO_TERMINA 0B00000010 // 1<<ACIERTO #define LITERAL 2 #define LITERAL_TERMINA 0B00000100 // 1<<LITERAL #define CANTIDAD_RESPUESTAS 3 // Cantidad de posibles respuestas que se buscan en el texto recibido desde el ESP8266 #define ESPERAR_RESPUESTA 0B00001000 // 1<<CANTIDAD_RESPUESTAS #define NO_ESPERAR_RESPUESTA 0B00000000 #define ENVIAR_OPERACION esperando_respuesta=true;SERIE.print(operacion[operacion_actual]);if(configuracion[operacion_actual]&ESPERAR_RESPUESTA){SERIE.print(“\r\n”);}for(unsigned char numero_respuesta=0;numero_respuesta<CANTIDAD_RESPUESTAS;numero_respuesta++){numero_caracter[numero_respuesta]=0;}cronometro_esp8266=millis(); |
Insieme ad altre macro già spiegate in precedenza, il codice di esempio qui sopra mostra come vengono definiti i diversi stati con cui specificare se attendere una risposta ed eventualmente quale messaggio indica che è terminata.
Poiché in diversi punti del codice verrà inviata un'operazione (quando è il momento di inviare la temperatura media, se viene superato il tempo di attesa di un'operazione, quando l'operazione corrente viene completata con successo...) ma come farlo è affermato a livello globale, è stato definito una macro ENVIAR_OPERACION
che raggruppa le fasi coinvolte nella spedizione.
1 2 3 4 5 6 7 8 9 10 11 12 | // La macro ENVIAR_OPERACION corresponde con las operaciones: esperando_respuesta=true; SERIE.print(operacion[operacion_actual]); if(configuracion[operacion_actual]&ESPERAR_RESPUESTA) { SERIE.print(“\r\n”); } for(unsigned char numero_respuesta=0;numero_respuesta<CANTIDAD_RESPUESTAS;numero_respuesta++) { numero_caracter[numero_respuesta]=0; } cronometro_esp8266=millis(); |
Quello che segue è il codice del programma principale dell'esempio. Il compito più esterno è quello preposto a campionare la temperatura per calcolarne la media e, ogni certo periodo di tempo, la invia al server tramite il comando Modulo Wi-Fi ESP8266. Una volta inviata ciascuna operazione, la risposta viene analizzata per determinare quale sarà la successiva o se l'attività di invio delle informazioni è stata completata.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | #include “ESP8266_operacion_compleja_varias_ordenes_AT.h” #define PIN_TEMPERATURA A0 // Pin analógico al que se conecta la salida del sensor de temperatura LM35 #define INTERVALO_LECTURA_TEMPERATURA 30000 // Leer la temperatura cada 30 segundos (30*1000) #define INTERVALO_GRABACION_TEMPERATURA 300000 // Grabar la media de la temperatura cada 5 minutos (5*60*1000) unsigned long muestras=0; // Número de veces que se ha medido la temperatura (para calcular la media) float temperatura; // de -55.0 °C a +150 °C | de 0 V a 2.050 V | de -550 mV a +1500 mV | analogRead*5.0/1023.0*100-55.0 analogRead/2.46-55.0 float media_temperaturas=0.0; unsigned long cronometro_lectura_temperatura; unsigned long cronometro_grabacion_temperatura; char *mensaje_fallo=“ERROR\r\n”; char *mensaje_acierto=“OK\r\n”; char *sin_enlace=“link is not\r\n”; char *mensaje_vacio=“\f”; char *operacion[CANTIDAD_OPERACIONES]; // Matriz de punteros a constantes de caracteres con las operaciones que se envían al ESP8266 (no solo órdenes AT, aunque seguro que algunas son órdenes AT) char *mensaje[CANTIDAD_OPERACIONES][CANTIDAD_RESPUESTAS]; // Mensajes de respuesta unsigned char siguiente_operacion[CANTIDAD_OPERACIONES][CANTIDAD_RESPUESTAS]; unsigned char configuracion[CANTIDAD_OPERACIONES]; unsigned int timeout[CANTIDAD_OPERACIONES]; unsigned char numero_caracter[CANTIDAD_RESPUESTAS]; unsigned int longitud_peticion; char texto_longitud_peticion[4]; // 3 caracteres para almacenar la longitud de la petición en formato texto char valor_enviado[9]; // signo + 4 enteros + punto + 2 decimales + \0 = 9 unsigned long cronometro_esp8266; unsigned char operacion_actual; // Número de operación que se está procesando unsigned char proxima_operacion; // Siguiente operación que se procesará cuando termine la actual char lectura_serie; boolean grabando_datos=false; boolean esperando_respuesta; void setup() { #include “inicializar_operaciones.h” longitud_peticion=strlen(operacion[ENVIAR_PREFIJO_PETICION])+strlen(operacion[ENVIAR_SUFIJO_PETICION]); SERIE.begin(VELOCIDAD); // Configurar el puerto serie de Arduino a la velocidad del ESP8266 //delay(8000); // En fase de pruebas se puede introducir un tiempo de espera para conectar una consola/sniffer cronometro_lectura_temperatura=millis(); cronometro_grabacion_temperatura=millis(); } void loop() { if((unsigned long)(millis()–cronometro_lectura_temperatura)>INTERVALO_LECTURA_TEMPERATURA) { cronometro_lectura_temperatura=millis(); temperatura=analogRead(PIN_TEMPERATURA)/2.46–55.0; muestras++; media_temperaturas=(float)temperatura/(float)muestras+media_temperaturas*(float)(muestras–1)/(float)(muestras); } if(grabando_datos) { if((unsigned long)(millis()–cronometro_esp8266)>timeout[operacion_actual]) // Si se ha superado el tiempo de espera máximo { operacion_actual=siguiente_operacion[operacion_actual][FALLO]; // Pasar a la operación correspondiente al error ENVIAR_OPERACION } else // Si no se ha superado el tiempo de espera máximo { if(configuracion[operacion_actual]&ESPERAR_RESPUESTA) // Si la siguiente operación depende de la respuesta a la actual desde el ESP8266 hay que leer la información que llegue desde el puerto serie { while(SERIE.available()) { lectura_serie=SERIE.read(); for(unsigned char numero_respuesta=0;numero_respuesta<CANTIDAD_RESPUESTAS;numero_respuesta++) // Comparar la letra cargada desde el puerto serie con la correspondiente de los mensajes disponibles { if(lectura_serie==mensaje[operacion_actual][numero_respuesta][numero_caracter[numero_respuesta]]) // Si el dato que ha llegado es igual al que correspondería del mensaje buscado… { numero_caracter[numero_respuesta]++; // Como el carácter coincide, se puede comparar con el siguiente lo próximo que llegue por el puerto serie if(mensaje[operacion_actual][numero_respuesta][numero_caracter[numero_respuesta]]==0) // Si el carácter que toca es \0 es que se ha terminado de analizar el mensaje { if(esperando_respuesta) // Todavía no se ha encontrado un mensaje que determine la siguiente operación { proxima_operacion=siguiente_operacion[operacion_actual][numero_respuesta]; // La próxima operación que habrá que procesar será la que indique el mensaje encontrado para la operación actual esperando_respuesta=false; // Ya se ha encontrado un mensaje que determina la siguiente operación } if(configuracion[operacion_actual]&(1<<numero_respuesta)) // Si el mensaje encontrado es uno de los que terminan la operación… { if(operacion_actual+1==CANTIDAD_OPERACIONES) // Se ha completado la última operación de la tarea compleja { grabando_datos=false; // Se ha terminado la tarea compleja (grabar datos en el servidor) } else // No es la última operación de la tarea compleja, hay que seguir realizando otras operaciones { operacion_actual=proxima_operacion; // Ejecutar la siguiente operación ENVIAR_OPERACION } } } } else // Si la letra recibida no es igual que la correspondiente del mensaje { numero_caracter[numero_respuesta]=0; // Empezar a comparar desde la primera letra del mensaje } } } } else // Si no hay que esperar datos desde el puerto serie { operacion_actual=siguiente_operacion[operacion_actual][ACIERTO]; ENVIAR_OPERACION } } } else { if((unsigned long)(millis()–cronometro_grabacion_temperatura)>INTERVALO_GRABACION_TEMPERATURA) { cronometro_grabacion_temperatura=millis(); dtostrf(media_temperaturas,4,2,valor_enviado); // snprintf(valor_enviado,9,”%+3.2f”,media_temperaturas); // printf y derivadas no funcionan en AVR snprintf(texto_longitud_peticion,4,“%d”,longitud_peticion+strlen(valor_enviado)); grabando_datos=true; operacion_actual=VERIFICAR_CONEXION; operacion[ENVIAR_DATOS]=valor_enviado; operacion[ENVIAR_CANTIDAD]=texto_longitud_peticion; ENVIAR_OPERACION } } } |
Logicamente si possono effettuare diverse azioni di ottimizzazione sul codice precedente ma, trattandosi di un esempio per capire come funziona il ESP8266 In maniera generica è opportuno soffermarsi solo su alcuni aspetti, primo fra tutti la struttura dei dati. Sembra che la cosa logica sia utilizzare una struttura dati del linguaggio di programmazione (struct
) per rappresentare le informazioni in corso di elaborazione: il Comandi AT ESP8266 e i messaggi che vengono analizzati.
Utilizzare una struttura (struct
) memorizzare i dati invece degli array di esempio (basati su di essi) è banale e, sebbene possa risultare in un codice più elegante, non implica alcun miglioramento nel risultato. La vera alternativa posta dall'uso di struct
è quello di attuare, come spiegato di seguito, lunghezze variabili in strutture che contengono dati “interni”. a cui fanno riferimento. In questo modo, ad esempio, non sarebbe necessario che un'operazione avesse un numero fisso di risposte da analizzare.
Questo approccio suggerisce che sia il modo migliore per implementare la soluzione, ma lo svantaggio è che sarebbe necessario utilizzare l'allocazione dinamica della memoria, una pratica rischiosa lavorando con a microcontrollore che richiede un'attenta misurazione della quantità di memoria che verrà utilizzata in fase di esecuzione, poiché difficilmente il compilatore sarà in grado di avvisarci e c'è una certa possibilità di esaurire la memoria (o lo stack) con conseguenze fatali per l'esecuzione del programma.
Nell'ottica dell'ottimizzazione del codice, è interessante ricordare che, in un programma di questo tipo, che utilizza una grande quantità di testo, può risparmiare spazio di memoria SRAM memorizzazione di stringhe di testo nella memoria del programma (flash) con la macro F()
. Nelle schermate seguenti puoi vedere il diverso programma e la distribuzione dinamica della memoria con l'uso normale del testo e usando la macro F()
.
Rispetto alle azioni che vengono eseguite in base alle informazioni che arrivano dal Modulo Wi-Fi ESP8266, in alternativa alla verifica del messaggio dal codice ed effettuando l'uno o l'altro a seconda di quanto ricevuto, possono essere memorizzati in questa struttura dati puntatori alle funzioni che eseguono ciascuna attività invece degli indicatori di stato (flag) che avvisano di un certo stato che l'applicazione è incaricata di gestire, ad esempio, all'interno del loop principale.
Di seguito è riportato un esempio di strutture per memorizzare i dati delle richieste al ESP8266 (il tipo di dati operacion_esp8266
) e le relative risposte (il tipo di dati respuesta_esp8266
).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | typedef struct estructura_operacion_esp8266 operacion_esp8266; // Se define el tipo de datos operacion_esp8266 que corresponde con la estructura (struct) llamada estructura_operacion_esp8266 que se define más adelante typedef struct estructura_respuesta_esp8266 respuesta_esp8266; // Se define el tipo de datos respuesta_esp8266 que corresponde con la estructura (struct) llamada struct estructura_respuesta_esp8266 que se define más adelante struct estructura_operacion_esp8266 { char *peticion; // Datos que se envían al ESP8266 para iniciar una operación (como una orden AT, pero también algo como la petición a un servidor…) unsigned char cantidad_respuestas; // Número de posibles respuestas del ESP8266 que se van a analizar, que puede ser variable en cada petición (no solo OK y ERROR) unsigned char timeout; // Tiempo (segundos) que se espera la respuesta del ESP8266 antes de desistir respuesta_esp8266 *respuesta; // Respuestas (estructura) que se esperan de esta operación }; struct estructura_respuesta_esp8266 { char *mensaje; // Mensaje que se espera recibir desde el ESP8266 unsigned char posicion_mensaje; // Posición (letra) que se está comparando con la recibida desde el ESP8266 //boolean estado; // El estado se representa por un valor booleano (por ejemplo ¿se ha encontrado ya esta respuesta? para no seguir buscándola) //unsigned char *estado; // El estado se representa con un texto (de longitud variable) unsigned char estado; // El estado se establece con 8 banderas, una por bit operacion_esp8266 *operacion; // Puntero a operación que se ejecutará si se encuentra esta respuesta en el mensaje devuelto por el ESP8266 }; operacion_esp8266 comprobar_conexion; respuesta_esp8266 respuesta_OK; respuesta_esp8266 respuesta_ERROR; |
Poiché la struttura che rappresenta l'operazione (i dati inviati al file Modulo Wi-Fi ESP8266) si riferisce alla struttura con cui vengono definite le risposte, e la struttura delle risposte alla struttura delle operazioni, è necessario dichiararli prima entrambi, definendo il nuovo tipo di dati e quindi definendone il contenuto.
L'esempio precedente considera che il programma che lo include abbia scelto di utilizzare a indicatore di stato, che deve corrispondere ad una variabile accessibile dal codice che ha il compito di eseguire l'una o l'altra operazione come indicato da detto valore. Se nella risposta di ESP8266 Quando si analizza un certo testo, lo stato assume il valore che indica la struttura della risposta corrispondente.
Come detto prima, un’altra alternativa, per sostituire o integrare un indicatore di stato, sarebbe memorizzare una funzione nella struttura di riferimento (un puntatore) che verrebbe richiesto incontrando un determinato testo nella risposta del Modulo Wi-Fi ESP8266.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | typedef struct estructura_operacion_esp8266 operacion_esp8266; // Se define el tipo de datos operacion_esp8266 que corresponde con la estructura (struct) llamada estructura_operacion_esp8266 que se define más adelante typedef struct estructura_respuesta_esp8266 respuesta_esp8266; // Se define el tipo de datos respuesta_esp8266 que corresponde con la estructura (struct) llamada struct estructura_respuesta_esp8266 que se define más adelante struct estructura_operacion_esp8266 { char *peticion; // Datos que se envían al ESP8266 para iniciar una operación (como una orden AT, pero también algo como la petición a un servidor…) unsigned char cantidad_respuestas; // Número de posibles respuestas del ESP8266 que se van a analizar, que puede ser variable en cada petición (no solo OK y ERROR) unsigned char timeout; // Tiempo (segundos) que se espera la respuesta del ESP8266 antes de desistir respuesta_esp8266 *respuesta; // Respuestas (estructura) que se esperan de esta operación }; struct estructura_respuesta_esp8266 { char *mensaje; // Mensaje que se espera recibir desde el ESP8266 unsigned char posicion_mensaje; // Posición (letra) que se está comparando con la recibida desde el ESP8266 //boolean estado; // El estado se representa por un valor booleano (por ejemplo ¿se ha encontrado ya esta respuesta? para no seguir buscándola) //unsigned char *estado; // El estado se representa con un texto (de longitud variable) unsigned char estado; // El estado se establece con 8 banderas, una por bit float (*accion)(unsigned char,unsigned char); // Puntero a la función que se llama si se encuentra la respuesta operacion_esp8266 *operacion; // Puntero a operación que se ejecutará si se encuentra esta respuesta en el mensaje devuelto por el ESP8266 }; operacion_esp8266 comprobar_conexion; respuesta_esp8266 respuesta_OK; respuesta_esp8266 respuesta_ERROR; |
Nell'esempio precedente è stato aggiunto alla struttura dati utilizzata per elaborare la risposta dal Modulo Wi-Fi ESP8266 un puntatore a una (presunta) funzione che restituisce un dato di tipo float
(potrebbe essere il valore ponderato di una lettura analogica) e al quale vengono forniti due byte come argomenti (two unsigned char
che potrebbe essere il pin da cui viene letto l'ingresso analogico e quello che attiva l'ENABLE di un ipotetico integrato).
In sviluppo per MCU, contrariamente a quanto avviene nello stile di sviluppo per sistemi più grandi, non è così raro utilizzare variabili globali quando si definisce il comportamento (globale) dell'applicazione che controlla un assembly, quindi non sarà particolarmente raro trovare questo tipo di definizioni come funzioni senza parametri e che non restituiscono valori, qualcosa del genere void (*accion)();
Se lavori con questo modo di rappresentare i dati, utilizzando struct
di dati a lunghezza variabile, sarà necessario allocare dinamicamente la memoria malloc()
(o new()
, se vengono utilizzati oggetti), che utilizzerà la quantità di memoria allocata come parametro e restituirà un puntatore all'inizio dell'area di memoria riservata. Con sizeof()
Dalla tipologia memorizzata, moltiplicata per il numero di elementi utilizzati, si ottiene la quantità di memoria necessaria. Un esempio con e senza utilizzarlo può essere visto negli screenshot qui sotto. malloc()
; Fare attenzione alla memoria utilizzata dal programma: nel primo caso è necessario caricare la libreria che contiene questa funzione.
Se le operazioni su Modulo Wi-Fi ESP8266 varierà durante l'esecuzione del programma, sarà necessario liberare la memoria non utilizzata free()
(o delete()
, nel caso di oggetti). Sebbene sia ragionevole aspettarsi che il compilatore (GCC) ottimizzerà il programma per evitare il partizionamento della memoria, sicuramente le prestazioni non saranno ottimali come quando si lavora con la memoria allocata staticamente.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | error_operacion.mensaje=“ERROR\r\n”; error_operacion.termina_operacion=true; error_operacion.operacion=&reiniciar_esp8266; error_enlace.mensaje=“link is not\r\n”; error_enlace.termina_operacion=true; error_enlace.operacion=&reiniciar_esp8266; reiniciar_esp8266_correcto.mensaje=“ready\r\n”; reiniciar_esp8266_correcto.termina_operacion=true; reiniciar_esp8266_correcto.operacion=&desconectar_wifi; desconectar_wifi_correcto.mensaje=mensaje_acierto; desconectar_wifi_correcto.termina_operacion=true; desconectar_wifi_correcto.operacion=&establecer_modo_estacion; establecer_modo_estacion_correcto.mensaje=mensaje_acierto; establecer_modo_estacion_correcto.termina_operacion=true; establecer_modo_estacion_correcto.operacion=&establecer_modo_simple; establecer_modo_simple_correcto.mensaje=mensaje_acierto; establecer_modo_simple_correcto.termina_operacion=true; establecer_modo_simple_correcto.operacion=&conectar_wifi; conectar_wifi_correcto.mensaje=mensaje_acierto; conectar_wifi_correcto.termina_operacion=true; conectar_wifi_correcto.operacion=&verificar_conexion; verificar_conexion_correcto.mensaje=“STATUS:5”; verificar_conexion_correcto.termina_operacion=false; verificar_conexion_correcto.operacion=&conectar_servidor; verificar_conexion_terminado.mensaje=mensaje_acierto; verificar_conexion_terminado.termina_operacion=true; verificar_conexion_terminado.operacion=&reiniciar_esp8266; conectar_servidor_correcto.mensaje=“CONNECT”; conectar_servidor_correcto.termina_operacion=false; conectar_servidor_correcto.operacion=&informar_cantidad; conectar_servidor_terminado.mensaje=mensaje_acierto; conectar_servidor_terminado.termina_operacion=true; conectar_servidor_terminado.operacion=&reiniciar_esp8266; informar_cantidad_correcto.mensaje=mensaje_acierto; informar_cantidad_correcto.termina_operacion=true; informar_cantidad_correcto.operacion=&enviar_cantidad; enviar_cantidad_correcto.mensaje=“>”; enviar_cantidad_correcto.termina_operacion=true; enviar_cantidad_correcto.operacion=&enviar_prefijo; enviar_prefijo_correcto.operacion=&enviar_datos; enviar_datos_correcto.operacion=&enviar_sufijo; enviar_sufijo_correcto.mensaje=“CLOSED\r\n\r\nOK\r\n”; enviar_sufijo_correcto.termina_operacion=true; enviar_sufijo_correcto.operacion=&verificar_conexion; reiniciar_esp8266.peticion=“AT+RST”; reiniciar_esp8266.timeout=15000; reiniciar_esp8266.cantidad_respuestas=2; reiniciar_esp8266.respuesta=malloc(sizeof(respuesta_esp8266*)*reiniciar_esp8266.cantidad_respuestas); reiniciar_esp8266.respuesta[FALLO]=&error_operacion; reiniciar_esp8266.respuesta[ACIERTO]=&reiniciar_esp8266_correcto; desconectar_wifi.peticion=“AT+CWQAP”; desconectar_wifi.timeout=2500; desconectar_wifi.cantidad_respuestas=2; desconectar_wifi.respuesta=malloc(sizeof(respuesta_esp8266*)*desconectar_wifi.cantidad_respuestas); desconectar_wifi.respuesta[FALLO]=&error_operacion; desconectar_wifi.respuesta[ACIERTO]=&desconectar_wifi_correcto; establecer_modo_estacion.peticion=“AT+CWMODE=1”; establecer_modo_estacion.timeout=2500; establecer_modo_estacion.cantidad_respuestas=2; establecer_modo_estacion.respuesta=malloc(sizeof(respuesta_esp8266*)*establecer_modo_estacion.cantidad_respuestas); establecer_modo_estacion.respuesta[FALLO]=&error_operacion; establecer_modo_estacion.respuesta[ACIERTO]=&establecer_modo_estacion_correcto; establecer_modo_simple.peticion=“AT+CIPMUX=0”; establecer_modo_simple.timeout=2500; establecer_modo_simple.cantidad_respuestas=2; establecer_modo_simple.respuesta=malloc(sizeof(respuesta_esp8266*)*establecer_modo_simple.cantidad_respuestas); establecer_modo_simple.respuesta[FALLO]=&error_operacion; establecer_modo_simple.respuesta[ACIERTO]=&establecer_modo_simple_correcto; conectar_wifi.peticion=“AT+CWJAP=\”polaridad.es\”,\”54lLij1RiTn3MEd3v41C\””;; conectar_wifi.timeout=20000; conectar_wifi.cantidad_respuestas=2; conectar_wifi.respuesta=malloc(sizeof(respuesta_esp8266*)*conectar_wifi.cantidad_respuestas); conectar_wifi.respuesta[FALLO]=&error_operacion; conectar_wifi.respuesta[ACIERTO]=&conectar_wifi_correcto; verificar_conexion.peticion=“AT+CIPSTATUS”; verificar_conexion.timeout=5000; verificar_conexion.cantidad_respuestas=3; verificar_conexion.respuesta=malloc(sizeof(respuesta_esp8266*)*verificar_conexion.cantidad_respuestas); verificar_conexion.respuesta[FALLO]=&error_operacion; verificar_conexion.respuesta[ACIERTO]=&verificar_conexion_correcto; verificar_conexion.respuesta[OTRO_MENSAJE]=&verificar_conexion_terminado; conectar_servidor.peticion=“AT+CIPSTART=\”TCP\”,\”servidoriot.com\”,80″; conectar_servidor.timeout=15000; conectar_servidor.cantidad_respuestas=3; conectar_servidor.respuesta=malloc(sizeof(respuesta_esp8266*)*conectar_servidor.cantidad_respuestas); conectar_servidor.respuesta[FALLO]=&error_operacion; conectar_servidor.respuesta[ACIERTO]=&conectar_servidor_correcto; conectar_servidor.respuesta[OTRO_MENSAJE]=&conectar_servidor_terminado; // OK, no significa que haya conexión pero sí termina la operación informar_cantidad.peticion=“AT+CIPSEND=”; informar_cantidad.timeout=1000; informar_cantidad.cantidad_respuestas=1; informar_cantidad.respuesta=malloc(sizeof(respuesta_esp8266*)*informar_cantidad.cantidad_respuestas); informar_cantidad.respuesta[0]=&informar_cantidad_correcto; //enviar_cantidad.peticion=””; // Se asigna cuando se conoce el valor que se va a enviar y se puede calcular la longitud que ocupa (número de caracteres) enviar_cantidad.timeout=5000; enviar_cantidad.cantidad_respuestas=2; enviar_cantidad.respuesta=malloc(sizeof(respuesta_esp8266*)*enviar_cantidad.cantidad_respuestas); enviar_cantidad.respuesta[FALLO]=&error_enlace; enviar_cantidad.respuesta[ACIERTO]=&enviar_cantidad_correcto; enviar_prefijo.peticion=“GET /frigo03/almacenar_temperatura.php?temperatura=”; enviar_prefijo.timeout=10000; enviar_prefijo.cantidad_respuestas=1; enviar_prefijo.respuesta=malloc(sizeof(respuesta_esp8266*)*enviar_prefijo.cantidad_respuestas); enviar_prefijo.respuesta[0]=&enviar_prefijo_correcto; //enviar_datos.peticion=””; // Se asigna en cuando se conoce el valor que se va a enviar enviar_datos.timeout=5000; enviar_datos.cantidad_respuestas=1; enviar_datos.respuesta=malloc(sizeof(respuesta_esp8266*)*enviar_datos.cantidad_respuestas); enviar_datos.respuesta[0]=&enviar_datos_correcto; enviar_sufijo.peticion=” HTTP/1.1\r\nHost: www.servidoriot.com\r\nUser-Agent: ESP8266\r\nConnection: close\r\n\r\n”; enviar_sufijo.timeout=20000; enviar_sufijo.cantidad_respuestas=2; enviar_sufijo.respuesta=malloc(sizeof(respuesta_esp8266*)*enviar_sufijo.cantidad_respuestas); enviar_sufijo.respuesta[FALLO]=&error_enlace; enviar_sufijo.respuesta[ACIERTO]=&enviar_sufijo_correcto; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 | #if defined(ARDUINO_AVR_LEONARDO)||defined(ARDUINO_AVR_MEGA2560) /* ¿Es una placa Arduino Mega 2560 o Arduino Leonardo? */ #define SERIE Serial1 /* Si es una placa Arduino Mega 2560 o Arduino Leonardo usar Serial1 */ #else /* En este proyecto solamente uso Placas Leonardo, Mega 2560 y Uno, así que tiene que ser un Arduino Uno si llega hasta aquí */ #define SERIE Serial /* Si es una placa Arduino Uno usar Serial */ #endif #define VELOCIDAD 115200 // Velocidad (en baudios) al que está configurado el módulo wifi ESP8266 (Cuidado con la placa utilizada, no todas o siempre son capaces de trabajar a una velocidad tan alta) #define FALLO 0 // Índice del vector de respuestas que representa el mensaje de error #define FALLO_TERMINA 0B00000001 // 1<<FALLO #define ACIERTO 1 #define ACIERTO_TERMINA 0B00000010 // 1<<ACIERTO #define OTRO_MENSAJE 2 #define OTRO_MENSAJE_TERMINA 0B00000100 // 1<<OTRO_MENSAJE #define CANTIDAD_RESPUESTAS 3 // Cantidad de posibles respuestas que se buscan en el texto recibido desde el ESP8266 #define ESPERAR_RESPUESTA 0B00001000 // 1<<CANTIDAD_RESPUESTAS #define NO_ESPERAR_RESPUESTA 0B00000000 #define ENVIAR_OPERACION esperando_respuesta=true;SERIE.print((*operacion_actual).peticion);if((*operacion_actual).cantidad_respuestas>1){SERIE.print(“\r\n”);for(unsigned char numero_respuesta=0;numero_respuesta<(*operacion_actual).cantidad_respuestas;numero_respuesta++){numero_caracter[numero_respuesta]=0;}}cronometro_esp8266=millis(); #define PIN_TEMPERATURA A0 // Pin analógico al que se conecta la salida del sensor de temperatura LM35 #define INTERVALO_LECTURA_TEMPERATURA 30000 // Leer la temperatura cada 30 segundos (30*1000) #define INTERVALO_GRABACION_TEMPERATURA 300000 // Grabar la media de la temperatura cada 5 minutos (5*60*1000) unsigned long muestras=0; // Número de veces que se ha medido la temperatura (para calcular la media) float temperatura; // de -55.0 °C a +150 °C | de 0 V a 2.050 V | de -550 mV a +1500 mV | analogRead*5.0/1023.0*100-55.0 analogRead/2.46-55.0 float media_temperaturas=0.0; unsigned long cronometro_lectura_temperatura; unsigned long cronometro_grabacion_temperatura; char *mensaje_acierto=“OK\r\n”; typedef struct estructura_operacion_esp8266 operacion_esp8266; // Se define el tipo de datos operacion_esp8266 que corresponde con la estructura (struct) llamada estructura_operacion_esp8266 que se define más adelante typedef struct estructura_respuesta_esp8266 respuesta_esp8266; // Se define el tipo de datos respuesta_esp8266 que corresponde con la estructura (struct) llamada struct estructura_respuesta_esp8266 que se define más adelante struct estructura_operacion_esp8266 { char *peticion; // Datos que se envían al ESP8266 para iniciar una operación (como una orden AT, pero también algo como la petición a un servidor…) unsigned int timeout; // Tiempo (segundos) que se espera la respuesta del ESP8266 antes de desistir bool espera_respuesta; // Si no espera respuesta en cuanto se termine de enviar la orden se puede pasar a la siguiente unsigned char cantidad_respuestas; // Número de posibles respuestas del ESP8266 que se van a analizar, que puede ser variable en cada petición (no solo OK y ERROR) respuesta_esp8266 **respuesta; // Respuestas (estructura) que se esperan de esta operación }; struct estructura_respuesta_esp8266 { char *mensaje; // Mensaje que se espera recibir desde el ESP8266 bool termina_operacion; // Cuando se termina de leer el mensaje ha terminado la operación operacion_esp8266 *operacion; // Puntero a operación que se ejecutará si se encuentra esta respuesta en el mensaje devuelto por el ESP8266 }; operacion_esp8266 *operacion_actual; // Operación sobre el ESP8266 que se está ejecutando actualmente operacion_esp8266 *proxima_operacion; // Siguiente operación que se procesará cuando termine la actual unsigned int longitud_peticion; // Número de caracteres que ocupa la petición HTTP char texto_longitud_peticion[4]; // 3 caracteres para almacenar la longitud de la petición en formato texto char valor_enviado[9]; // signo + 4 enteros + punto + 2 decimales + \0 = 9 unsigned long cronometro_esp8266; // Cronómetro para controlar el tiempo máximo de respuesta del ESP8266 antes de desistir char lectura_serie; // buffer con el carácter leído desde el ESP8266 boolean grabando_datos=false; // Verdadero cuando han terminado todas las operaciones necesarias para grabar los datos boolean esperando_respuesta; // Verdadero si aún no se ha encontrado una de los mensajes que indica que ha terminado la respuesta unsigned char numero_caracter[CANTIDAD_RESPUESTAS]; // Número de orden de la letra del mensaje-respuesta que se está almacenando (una matriz de, como máximo, el mayor número de respuestas posible) operacion_esp8266 reiniciar_esp8266; // Reiniciar el módulo wifi ESP8266 operacion_esp8266 desconectar_wifi; // Desconectar del punto de acceso por defecto (si fuera el caso) operacion_esp8266 establecer_modo_estacion; // Establecer el modo de estación (no punto de acceso) operacion_esp8266 establecer_modo_simple; // Establecer el modo de conexión simple operacion_esp8266 conectar_wifi; // Conectar al punto de acceso operacion_esp8266 verificar_conexion; // Verificar si hay conexión operacion_esp8266 conectar_servidor; // Conectar al servidor operacion_esp8266 informar_cantidad; // Avisar de la cantidad de datos que se envían operacion_esp8266 enviar_cantidad; // Enviar cantidad operacion_esp8266 enviar_prefijo; // Enviar el prefijo de la petición operacion_esp8266 enviar_datos; // Enviar la temperatura operacion_esp8266 enviar_sufijo; // Enviar el sufijo de la petición respuesta_esp8266 error_operacion; // Todas las respuestas “ERROR” reinician el módulo wifi ESP8266 respuesta_esp8266 error_enlace; // Las respuestas “link is not” también reinician el módulo wifi ESP8266 respuesta_esp8266 reiniciar_esp8266_correcto; respuesta_esp8266 desconectar_wifi_correcto; respuesta_esp8266 establecer_modo_estacion_correcto; respuesta_esp8266 establecer_modo_simple_correcto; respuesta_esp8266 conectar_wifi_correcto; respuesta_esp8266 verificar_conexion_correcto; respuesta_esp8266 verificar_conexion_terminado; respuesta_esp8266 conectar_servidor_correcto; respuesta_esp8266 conectar_servidor_terminado; respuesta_esp8266 informar_cantidad_correcto; respuesta_esp8266 enviar_cantidad_correcto; respuesta_esp8266 enviar_prefijo_correcto; respuesta_esp8266 enviar_datos_correcto; respuesta_esp8266 enviar_sufijo_correcto; void setup() { #include “inicializar_operaciones.h” longitud_peticion=strlen(enviar_prefijo.peticion)+strlen(enviar_sufijo.peticion); SERIE.begin(VELOCIDAD); // Configurar el puerto serie de Arduino a la velocidad del ESP8266 //delay(8000); // En fase de pruebas se puede introducir un tiempo de espera para conectar una consola/sniffer cronometro_lectura_temperatura=millis(); cronometro_grabacion_temperatura=millis(); } void loop() { if((unsigned long)(millis()–cronometro_lectura_temperatura)>INTERVALO_LECTURA_TEMPERATURA) { cronometro_lectura_temperatura=millis(); temperatura=analogRead(PIN_TEMPERATURA)/2.46–55.0; muestras++; media_temperaturas=(float)temperatura/(float)muestras+media_temperaturas*(float)(muestras–1)/(float)(muestras); } if(grabando_datos) { if((unsigned long)(millis()–cronometro_esp8266)>(unsigned long)(*operacion_actual).timeout) // Si se ha superado el tiempo de espera máximo { operacion_actual=(*(*operacion_actual).respuesta[FALLO]).operacion; // Pasar a la operación correspondiente al error ENVIAR_OPERACION } else // Si no se ha superado el tiempo de espera máximo { if((*operacion_actual).cantidad_respuestas>1) // Si la siguiente operación depende de la respuesta a la actual desde el ESP8266 hay que leer la información que llegue desde el puerto serie { while(SERIE.available()) { lectura_serie=SERIE.read(); for(unsigned char numero_respuesta=0;numero_respuesta<(*operacion_actual).cantidad_respuestas;numero_respuesta++) // Comparar la letra cargada desde el puerto serie con la correspondiente de los mensajes disponibles { if(lectura_serie==(*(*operacion_actual).respuesta[numero_respuesta]).mensaje[numero_caracter[numero_respuesta]]) // Si el dato que ha llegado es igual al que correspondería del mensaje buscado… { numero_caracter[numero_respuesta]++; // Como el carácter coincide, se puede comparar con el siguiente lo próximo que llegue por el puerto serie if((*(*operacion_actual).respuesta[numero_respuesta]).mensaje[numero_caracter[numero_respuesta]]==0) // Si el carácter que toca es \0 es que se ha terminado de analizar el mensaje { if(esperando_respuesta) // Todavía no se ha encontrado un mensaje que determine la siguiente operación { proxima_operacion=(*(*operacion_actual).respuesta[numero_respuesta]).operacion; // La próxima operación que habrá que procesar será la que indique el mensaje encontrado para la operación actual esperando_respuesta=false; // Ya se ha encontrado un mensaje que determina la siguiente operación } if((*(*operacion_actual).respuesta[numero_respuesta]).termina_operacion) // Si el mensaje encontrado es uno de los que terminan la operación… { if(operacion_actual==&enviar_sufijo) // Se ha completado la última operación de la tarea compleja { grabando_datos=false; // Se ha terminado la tarea compleja (grabar datos en el servidor) } else // No es la última operación de la tarea compleja, hay que seguir realizando otras operaciones { operacion_actual=proxima_operacion; // Ejecutar la siguiente operación ENVIAR_OPERACION } } } } else // Si la letra recibida no es igual que la correspondiente del mensaje { numero_caracter[numero_respuesta]=0; // Empezar a comparar desde la primera letra del mensaje } } } } else // Si no hay que esperar datos desde el puerto serie { operacion_actual=(*(*operacion_actual).respuesta[0]).operacion; ENVIAR_OPERACION } } } else { if((unsigned long)(millis()–cronometro_grabacion_temperatura)>INTERVALO_GRABACION_TEMPERATURA) { cronometro_grabacion_temperatura=millis(); dtostrf(media_temperaturas,4,2,valor_enviado); // snprintf(valor_enviado,9,”%+3.2f”,media_temperaturas); // printf y derivadas no funcionan en AVR snprintf(texto_longitud_peticion,4,“%d”,longitud_peticion+strlen(valor_enviado)); grabando_datos=true; operacion_actual=&verificar_conexion; enviar_datos.peticion=valor_enviado; enviar_cantidad.peticion=texto_longitud_peticion; ENVIAR_OPERACION } } } |
Sebbene in questo esempio (in entrambe le implementazioni) non abbia molto senso, per generalizzare l'operazione per poterla applicare ad altri casi è bene notare che L'invio dei dati ripete sempre lo stesso protocollo: notificare il numero di byte che verranno inviati, attendere l'indicatore (>) e inviare i dati.
Dato che in questo esempio viene utilizzato una sola volta (l'intera richiesta viene fatta in un unico pacchetto), non sembra molto utile ma, in generale, può essere necessario effettuare più invii nella stessa operazione, compresi i casi in cui devono essere Vengono trasmesse quantità significative di dati che devono essere frammentate per evitare di sovraccaricare la memoria del ESP8266.
Per implementare questo comportamento si possono utilizzare gli ultimi due elementi della connessione in modo che ogni volta che i dati vengono inviati, i dati vengano riempiti con i valori corrispondenti: nel primo caso il numero di byte inviati e nel secondo il ( parte della) richiesta.da trasmettere.
Per ripetere l'assegnazione e l'invio dei diversi elementi che devono essere trasmessi è possibile memorizzarli in un vettore. Questo nuovo vettore sarà quello che determinerà la fine dell'operazione complessa e non l'ultima operazione come fino ad ora.
1 commento