ESProbottino

Da raspibo.
Jump to navigation Jump to search

ESProbottino è l'evoluzione di Ardubottino. L'idea è quella di sfruttare le capacità wi-fi dell'ESP32 per fare un piccolo server per il controllo remoto di un piccolo rover. L'ESP32 usato in questo caso è l'Ai-Thinker ESP32-CAM che ha a bordo una cam per lo streaming. La cam purtroppo impegna gran parte dei pin disponibili, per cui è stato necessario usare un altro integrato, in questo caso un ATmega8, per la gestione dei motori, dei sensori e degli encoder.ESP32BOTTINO.JPG

Componenti

La maggior parte dei componenti è la stessa dell'Ardubottino:

  • Ai-Thinker ESP32-CAM [1]
  • AVR ATmega8 [2]
  • Board per ponte H L298
  • Kit Tamiya per robotica, con chassis e motori
  • Sensore a ultrasuoni SRF05
  • Sensore di vicinanza IR
  • RobotShop coppia di Encoder [3]
  • Batterie e powerbank

ESP32-CAM

Per aggiungere le schede ESP32 alle board programmabili dalla IDE di Arduino occorre inserire questo indirizzo fra le Preferences, alla voce Additional Boards Manager URLs. Se avete già delle voci, separatele con una virgola. https://dl.espressif.com/dl/package_esp32_index.json

Questo aggiungerà le board ESP32 al vostro menu Tools/Board e aggiungerà uno sketch di esempio.

L'ESP32-CAM, con il programma di esempio, fa tre cose:

  • Si collega o crea una wi-fi
  • Tira su un piccolo server web con una pagina con alcuni controlli per la cam
  • Scrive sulla porta seriale dei messaggi di debug (l'indirizzo IP)

Per piegare ESP32-CAM alle nostre esigenze, occorrerà fare delle piccole modifiche.

CameraWebServer - Sketch principale

Per prima cosa modifichiamo lo sketch principale. Negli esempi della board si trova con il nome di CameraWebServer.

Per prima cosa occorre selezionare la nostra board scegliendo la define giusta. Nel nostro caso occorre decommentare la riga:

#define CAMERA_MODEL_AI_THINKER

e commentare le altre.

Nelle due righe successive

const char* ssid = "nostra_rete";
const char* password = "password_rete";

bisogna specificare una rete alla quale il nostro ESP32 dovrà collegarsi.

Non occorre fare altro, ma già che c'ero ho cambiato alcune cose per migliorare alcuni aspetti.

  • Tolto tutte le comunicazioni seriali non necessarie perché avrebbero potuto interferire con l'ATmega8
  • Abbassato il baud da 115200 a 9600, mi sembra che i motori facessero un po' di disturbo sulla seriale e mi è sembrato che abbassare il rate risolvesse gli errori
  • Prima dell'assegnamento dell'indirizzo IP ho messo come comando una richiesta al sensore a ultrasuoni (manda un 0 sulla seriale), in modo che lampeggi durante l'attesa

Per i test avevo aggiunto anche la stampa sulla porta seriale del MAC address e dell'indirizzo IP. Nella versione finale è disabilitata perché interferiva con i comandi di movimento del robot e inoltre, essendo la seriale attaccata all'ATmega8, non era comunque possibili leggere tali messaggi. Le funzioni da chiamare sono:

WiFi.macAddress()

e

WiFi.localIP()

Riguardo il setup della camera non occorre modificare niente. Dal codice si può notare come gran parte dei pin sia occupato dalla CAM. È possibile (credo) anche modificare i parametri di default, tuttavia funzionano perfettamente out-of-the-box.

app_httpd.cpp - Server web

Questa parte del codice lancia un server web con l'interfaccia per cambiare i parametri della CAM e lanciare o arrestare lo streaming, nonché gestire la parte di riconoscimento facciale. Di particolare interesse sono le variabili "variable" e "value" che contengono il nome di una variabile e del suo valore passato tramite GET. I controlli della CAM mandano una richiesta GET che chiama una funzione che ha come parametro il valore. Ad es.

s->set_wb_mode(s,val)

altera il bilanciamento del bianco del valore "val" ("val" è "value" tradotta in integer).

Per aggiungere i controlli del robot avrei dovuto quindi creare dei bottoni che inviassero alla Seriale il valore val. Il tutto è stato abbastanza semplice, è bastato aggiungere queste due righe:

else if(!strcmp(variable, "controls")){
	Serial.print(value); }

alla lista dei vari altri controlli. Se GET legge la variabile "controls", il suo valore "value" viene mandato dalla seriale all'ATmega8.

camera_index.h - La pagina web

Alla pagina web occorre aggiungere i pulsanti per il controllo del nostro rover. Ogni pulsante chiamerà una funzione che passerà tramite GET la variabile "controls" e un carattere che corrisponde all'azione da fare.

Ora però c'è un problema: la pagina è in html ma 1) in esadecimale e 2) compressa in gz. Qui c'è stato bisogno di un aiuto da CyberChef!

https://gchq.github.io/CyberChef/

Questo software traduce da e in esadecimale, g/un/zippa e fa anche moltissime altre cose. Per ora però limitiamoci a chiedergli queste tre cose:

  • Remove whitespace (toglie gli spazi fra le cifre hex, nonché gli a capo)
  • From Hex, 0x with comma (traduce da hex a dec)
  • Gunzip

Ora incolliamo nella finestra input tutto l'array "const uint8_t index_ov2640_html_gz[]". Come per magia comparirà la pagina html! Modifichiamola aggiungendo i comandi per il robot.

Aggiungo un nuovo

con i bottoni sotto il layer dove dovrebbe comparire lo streaming. Già che c'ero ho aggiunto uno stile al css.
<controls>
 <\div class="grid-container" align="center">
 <\div class="item1"> </\div>
 <\div class="item2"><button id="Forward">↑</button></\div>
 <\div class="item3"> </\div>
 <\div class="item4"><button id="Left">←</button></\div>
 <\div class="item5"><button id="NoMov">O</button></\div>
 <\div class="item6"><button id="Right">→</button></\div>
 <\div class="item7"> </\div>
 <\div class="item8"><button id="Back">↓</button></\div>
 ... (eccetera)
 </\div>
</controls>

Ora bisogna associare a ogni tasto una chiamata GET. Per far questo mi appoggio allo script che già viene usato per i controlli della CAM. Andiamo quindi a modificare la funzione document.addEventListener

Per prima cosa si prendono i vari elementi e si mettono nelle costanti:

 const forward = document.getElementById('Forward')
 const left = document.getElementById('Left')
 const right = document.getElementById('Right')
 const back = document.getElementById('Back')
 const nomov = document.getElementById('NoMov')
 ...

Poi si associano i listener:

 forward.onclick = () => { fetch(`${baseHost}/control?var=controls&val=F`) }
 left.onclick = () => { fetch(`${baseHost}/control?var=controls&val=L`) }
 right.onclick = () => { fetch(`${baseHost}/control?var=controls&val=R`) }
 back.onclick = () => { fetch(`${baseHost}/control?var=controls&val=B`) }
 nomov.onclick = () => { fetch(`${baseHost}/control?var=controls&val=N`) }

Al click la pagina control viene richiamata la pagina control che prende una variabile "controls" di valore "val". Questo valore viene letto dal server che lo manda via seriale all'ATmega8.

Una volta fatto questo occorre ri-usare CyberChef e fare l'esatto opposto per codificare la pagina com'era in origine, ovvero:

  • gzip
  • To Hex, 0x with comma

Per quanto riguarda gli a capo e gli spazi, possiamo ignorarlo perché possiamo scrivere tranquillamente tutto su una sola linea.

Invece è importante cambiare la lunghezza dell'array. Nel mio caso sono passato da 4926 a:

#define index_ov2640_html_gz_len 5022

Se non mettiamo la lunghezza giusta la pagina verrà troncata. CyberChef conta i caratteri dell'output in automatico, ovviamente compresso. Ri-traducete la vostra pagina come all'inizio però disabilitando il gunzip e dovrebbe dirvi quanti byte è lunga.

Lato ESP32-CAM abbiamo finito. Occorre adesso flashare.

Flashare l'ESP32-CAM

Per flashare l'ESP32-CAM occorre un adattatore USB a TTL. Collegare il filo rosso a +5v, il nero a un GND, bianco a TX e verde a RX.

Per mettere l'ESP32-CAM in modalità programmazione occorre collegare il pin 0 a GND e resettare. Occorre farlo subito dopo aver lanciato l'uploading quando compare il messaggio "Connecting ......______......", se fallisce (e se state usando la IDE di Arduino) conviene copiare il comando che lancia esptool e abbassare il baud rate.

ATmega8

All'ATmega8 è lasciata tutta la parte di controllo dei pin. Il suo lavoro è molto semplice: legge un comando in entrata sulla seriale ed esegue una delle operazioni. Oltre a questo, controlla anche lo stato dei sensori e degli encoder. L'ATmega8 è molto ridotto e molto economico ma è sufficiente per quel che dobbiamo fare.

Seriale

ESP32-CAM e ATmega8 sono collegati tramite la porta seriale. La funzione Serial.available() è >0 quando ci sono dei caratteri in attesa di essere letti. Serial.read() li legge. Il carattere lo si mette solitamente in una variabile char.

 if (Serial.available() > 0) {
   command = Serial.read(); }

Una volta ricevuto il comando, verrà chiamata una funzione.

switch(command) {
  case 'F':
    forward(4);
  case 'R':
    right(4);
  (etc.)
}

Le funzioni forward(), backward(), right() e left() (rispettivamente comandi 'F', 'B', 'R' e 'L') prendono come parametro un intero. Quel parametro sarà la distanza da coprire in passi dell'encoder. Alcuni comandi non vengono eseguiti ad es. se il sensore di caduta segnala che c'è rischio di caduta. Le funzioni accendono i pin collegati al ponte ad H in modo che i motori girino nella direzione desiderata e li tengono accesi fino a che non è finita l'esecuzione della funzione steps, che calcola i passi.

Il comando 'M' abiliterà l'automazione del movimento. Esprobottino andrà in giro da solo evitando gli ostacoli (per quanto possibile) facendo affidamento sul sensore di distanza.

Ci sono anche quattro comandi ('1','2','3' e '4') per calibrare i due motori e uno per scrivere ('W') la calibrazione nella EEPROM. Questi comandi variano il duty cycle del PWM collegato agli Enable dei motori.

Altri comandi che possono essere interpretati: 'D' abilita il debug via seriale, '0' interroga i sensori e 'C' dovrebbe far partire la calibrazione in automatico (ma è ancora da rivedere).

Encoder

A differenza di quanto pensavo per la prima versione dell'Ardubottino, gli encoder non sono digitali ma analogici. Per questo avevo delle letture non precise. Il problema di questo tipo di sensore è che non potendo usare l'interrupt, occorre fare delle letture continue, il polling, durante la marcia. Questo però comporta che durante il movimento il nostro integrato deve rimanere in ascolto e non può fare nient'altro.

La funzione steps conta quanti passi sono stati fatti.

Sensori di distanza

Ci sono due sensori di distanza sull'ESProbottino.

Sensore a ultrasuoni

Il sensore a ultrasuoni ci restituisce la distanza in cm dall'ostacolo più vicino. Funziona come un sonar: lanciando un ultrasuono e ascoltandone l'eco possiamo capire quanto sia distante l'oggetto che sta davanti al sensore. Qui c'è un link sul suo funzionamento, con anche un programma di esempio: https://create.arduino.cc/projecthub/abdularbi17/ultrasonic-sensor-hc-sr04-with-arduino-tutorial-327ff6

Sensore IR

Il sensore IR è molto più semplice. Tramite una vite si regola quale dev'essere la soglia oltre il quale si accende. Uso questo sensore per capire se davanti a ESProbottino c'è un vuoto. Semplicemente se il pin è acceso, allora c'è un vuoto, altrimenti non c'è rischio di caduta. Se c'è un vuoto, la variabile falling diventa vera e impedisce ad es. di eseguire la funzione forward()

Ponte ad H

Il ponte ad H permette di manovrare i due motori. Per ogni motore ci sono tre pin: uno è un Enable, ovvero accende il motore quando riceve +5v, gli altri due determinano il verso in cui girerà.

EN P1 P2 + - - Motore fermo + + - Motore gira in senso orario + - + Motore gira in senso antiorario - ? ? Motore fermo

Un PWM al pin Enable permette di controllare la velocità del motore.

La board del ponte H dovrebbe essere in grado anche di alimentare a 5v la board ESP32 e l'ATmega8, ma dopo diversi tentativi ho preferito separare le alimentazioni. Spesso la partenza dei motori richiedeva troppa corrente che riavviava o creava rumore nell'ESP32.

Movimento automatico

Il movimento automatico non è molto elaborato per ora. Va avanti, se incontra un ostacolo guarda prima a destra, poi a sinistra e infine decide di prendere la strada dove ha sentito più spazio. Se si accende il sensore di caduta allora fa retromarcia e poi fa circa 180° prima di ripartire.

Flash

Per flashare l'ATmega8 occorre un Arduino che abbia il firmware "Arduino as ISP". Nell'ultima versione della IDE, nel Boards Manager c'è la possibilità di aggiungere le board ATmega8. Nelle Preferences, in "Additional Boards Manager URLs" occorre inserire: https://mcudude.github.io/MiniCore/package_MCUdude_MiniCore_index.json (se avete già degli indirizzi, separateli con una virgola) dovrebbe comparire fra le board un nuovo menu chiamato "MiniCore", fra cui c'è anche l'ATmega8.

Per tutti i problemi relativi al flash dell'ATmega8 vi rimando all'OttoBot, che è basato sullo stesso integrato.


Il codice

TBD