ublo
bogdan's (micro)blog

bogdan » robot wi-fi

08:10 pm on Jan 12, 2020 | read the article | tags:

sărbătorile astea de iarnă au venit la pachet cu o retrospectivă a anului care a trecut, niște rezoluții de care probabil nu o să mă țin și puțin timp liber pe care să-l dedic pasiunilor. în rest, pentru că am neglijat blog-ul ăsta am zis că ar prinde bine să-ți arăt cum să construiești un robot wi-fi. mai exact unul destul de rezistent, ieftin, de preferat pornind de la un set, cu o baterie decentă, pe care-l poți extinde în viitor.

cât m-am zgârcit

bucata asta e ultima adăugată. inițial voiam să o las pentru finalul articolului. dar știu că și mie mi-ar plăcea să văd în primul rând cât m-ar costa proiectul ăsta și de ce am nevoie ca să-l fac. așa că, în lista de achiziții au intrat:

  • un set robot rotund, transparent, să se vadă prin el, 50 de lei (cumpără)
  • un mini-breadboard alb, culoare de cocalar, cum îmi place, 2,40 de lei (cumpără)
  • două seturi de fire mamă-tată de 15 cm, urmează și glumele de șantier, 8,90 de lei (cumpără)
  • un modul ESP8266 WeMos D1 Mini, pentru că m-am zgârcit, 30 de lei (cumpără)
  • o punte H, cu MX1515, pentru că m-am zgârcit mai mult, 3,50 de lei (cumpără)
  • o baretă cu 40 de pini, de care mai aveam, 1 leu (cumpără)
  • o sursă cu încărcător Li-Ion, să fac totul de la zero, 15 lei (cumpără)
  • un suport de acumulator 18650, pentru că mi-e frică de acumulatorii cu litiu, 4 lei (cumpără)
  • un acumulator cu Li-Ion 18650, că n-am chef să cumpăr baterii tot timpul, 38 de lei (cumpără)

pentru un total de 152,80 de lei. link-uri către produse găsești mai jos, cu tot cu alternative. câteva dintre componente poate le ai acasă, iar dacă înlocuiești încărcătorul și acumulatorul cu litiu cu un suport simplu cu 6 baterii AA, scazi 53 de lei, totalul fiind cu puțin sub 100 de lei. mai ai nevoie de un letcon, niște fludor, o șurubelniță în cruce, un calculator cu Arduino și ceva răbdare. hai că încep!

despre roboți la set

cu ce să pornesc? prefer roboții la set pentru că nu trebuie să te chinui cu printare 3D și tăieturi laser. ah! să nu mă înțelegi greșit: îmi place partea asta. doar că uneori durează atât de mult, încât și acum, după aproape doi ani, proiectul meu de aspirator robot e la stadiul de desene pe whiteboard. așa că, pornind de la recomandările pe care mi le face facebook pe baza profilului meu de marketing, am căutat cele mai viabile opțiuni pe saiturile de profil.

robotul pe care-l vreau trebuie să fie suficient de mic, să nu ocupe un spațiu prea mare în apartamentul și-așa înghesuit luat prin prima casă la marginea orașului, să fie ieftin, să conțină aproape toate componentele mecanice necesare, să fie simplu de construit, să se găsească peste tot (îmi pare rău, Pololu, deși Zumo e printre cei mai bine proiectați roboți la set, se găsește rar și e scump). variantele care au rămas nu sunt multe, ci două:

seturile sunt chinezești. motoarele nu sunt foarte puternice, dar își fac treaba minunat. roțile sunt mari, raportul de transmisie e bun și robotul care iese e suficient de rapid. iar ca bonus, motoarele se alimentează cu tensiuni între 3 și 6V, făcând rezonabil numărul de baterii pe care-l poți folosi. personal, m-am oprit la robotul rotund, așa că în continuare pozele și sugestiile sunt pentru el. dacă îți place mai mult mașina inteligentă, n-aș zice că e complicat să adaptezi ceea ce descriu în continuare.

setul conține aproape toate componentele: un suport de baterii complet nefolositor – de ce naiba ai pune un suport pentru două dintre cele mai slabe baterii dintre câte există, AAA?, două motoare, fiecare împreună cu șuruburi, suport de prindere, roată și aproape la fel de nefolositor, un encoder optic de poziție, pe care eu l-am montat ca să nu-l pierd, două plăci din acrilic împreună cu distanțiere pentru a construi corpul robotului și două roți care se rotesc libere, care funcționează ca puncte de sprijin, cu tot cu distanțierele necesare.

foarte puțină teorie: cu două motoare poți deplasa robotul în orice direcție. când cele două motoare se rotesc cu aceeași viteză în aceeași direcție, robotul merge înainte. cum apare o diferență de viteză, robotul va vira către motorul care se rotește mai repede. când motoarele se rotesc cu aceeași viteză în direcții opuse, robotul se va roti pe loc. logic, nu? însă pentru că două roți construiesc o bază de susținere unidimensională – cuvânt mare, dar nu închide pagina încă, e printre ultimele – mai ai nevoie de cel puțin un punct de sprijin în așa fel încât echilibrul să se transforme din precar în stabil. fizică de clasa – de fapt, am uitat. e ca atunci când vrei să așezi o carte în picioare. gata, trec peste.

mai ai nevoie de două capacitoare de 100nF sau p-acolo. valoarea capacității, oricât ar spune florin salam, nu e critică și nici dușmanii nu-ți vor purta pică. capacitoarele astea le vei conecta – un cuvânt ales ca să mă feresc de «a lipi», degeaba, pentru că nu scapi de el – în paralel cu terminalele de alimentare ale motoarelor. tot pentru motoare mai ai nevoie și de niște fire. eu am folosit fire colorate cu conectori dupont mamă – adică cu gaură, hehehe, glume de șantier – la unul dintre capete, cu lungimea de aproximativ 15cm și cu celălalt capăt tăiat și lipit la câte unul dintre terminalele motoarelor. ai nevoie de 4 fire. poți să cumperi fire mai lungi, pe care să le tai la jumătate – de altfel lucrul pe care l-am făcut și eu, mai mult de lene, pentru că aveam unele rămase de la un alt proiect.

după ce ai lipit capacitoarele și firele, e timpul să montezi motoarele pe placa din acrilic aia cu doar două fante perpendiculare pe restul. în fantele astea introduci în același sens, două dintre elementele din plastic în formă de «T» mai anemic, apoi prin celelalte două «T»-uri treci șuruburile lungi, pe care le treci prin motor și apoi prin «T»-ul introdus în placa de acrilic. sună complicat în text, așa că-ți las aici niște poze.

urmează distanțierele pentru roți – mă refer la cele micuțe, care sunt 8 – pe care le fixezi de aceeași parte a plăcii de acrilic cu motoarele prin piulițe. și distanțierele – da, alea mai lungi – pentru placa de sus, pe care le fixezi în partea opusă motoarelor prin piulițe. aici am făcut o prostie. cred că ar fi fost mai bine să pun distanțierele astea mai mari primele și apoi motoarele, dar până la urmă n-a fost așa de complicat să inserez piulița între spațiul dintre motor și placă. ba chiar mi s-a părut că fixează motorul mai bine. e după gust. și nu uita de placa de sus. doar că pentru asta mai bine mai aștepți puțin. e timp și mai sunt componente care vin în interior.

exod de creiere, wi-fi

aici e-aici. probabil ești curios de ce spun asta. ai atât de multe opțiuni, că aproape nu știu cum să încep. fie Raspberry Pi Zero W, care-mi place la nebunie: ai conexiune prin wi-fi, terminal să-l programezi direct în python, o mulțime de software cu tot felul de opțiuni de securitate. da, dacă aș face unul pentru armată, asta ar fi prima opțiune. dar e doar un robot wi-fi. iar Raspberry Pi Zero W consumă mult curent, cât unul dintre motoare la putere normală și e puțin mai complicat de integrat. așa că nu.

varianta doi e Arduino WiFi Rev.2. grozav! e un Arduino. la fel de ușor de programat și de versatil ca celelalte, dar cu wi-fi. foarte utili sunt terminalele care funcționează direct la 5V, lucru la care voi reveni ceva mai târziu, de altfel și singura variantă care are acest avantaj. dezavantajul? imens de scump. wtf? la 40€ e al naibii de scump, așa că next.

RedBearLab WiFi CC3200 în ambele variante e greu de programat. nu folosește Arduino, ci Energia, care e asemănător, dar știu că ultima dată m-am chinuit cu el destul de mult să-l fac să meargă. la preț, deși în varianta originală e cumva scump – oare se mai produce?, la Watterott e în promoția de jumătate din preț. dar cum e greu de programat, next.

și-am ajuns la fix ce nu voiam să ajung, dar care în ultimele luni m-a cucerit definitiv, ESP8266. ieftin, se programează cu Arduino fără niciun stres. doar instalezi suportul pentru ESP din BoardManager și gata. poți să folosești aproape orice bibliotecă software cu el cu care te-ai obișnuit de la Arduino. ce mai! soluția perfectă, cu rezerva că terminalele suportă doar 3,3V. maximum. cu toate astea, asta am ales. variantele care se găsesc la noi sunt:

îmi place mult 12F Witty, pentru că programatorul e detașabil. însă are mici particularități: un senzor și led-uri deja conectate la terminale și aș vrea puțină libertate. ca motiv pentru a-l folosi, nu trebuie să lipești nimic. eu am ales să lipesc. și am mers pe varianta D1 Mini WeMos. ieftin și bun cu 9 GPIO, TX, RX, o intrare analogică, acces facil la 3,3V și programator integrat. după ce am lipit terminalele, am luat un breadboard, am pus doi jumperi ca să multiplic conexiunile la GND și +5V – de fapt, aici sunt 5V doar atunci când se alimentează prin USB, în rest, terminalul funcționează ca alimentare și suportă maximum 24V ceea ce e perfect pentru că nu-mi trebuie o sursă stabilizată de 5V – și apoi peste jumperi, am pus plăcuța cu ESP8266. breadboard-ul l-am lipit pe placa superioară a robotului, ca în poze.

rulez puntea hash

oricât te-ai chinui, motoarele alea nu pot fi controlate direct cu ESP8266. de fapt, cu niciuna dintre variantele de mai sus. dacă încerci, în cel mai bun caz n-o să meargă. în cel mai rău caz, o să cauți pe net «i bricked my board. what can i do?» și răspunsul o să fie aproape întotdeauna: îți iei alta. motoarele electrice de felul ăsta, deși simple – sunt sigur că ai mai văzut și te-ai jucat măcar o dată cu unul pe care-l conectai în diferite feluri la o baterie. wait! cum adică nu ai făcut niciodată asta? well .. – folosind componente electronice nu se controlează atât de ușor. în primul rând pentru că prin componente electronice mă refer la tranzistori. și ăia sunt de fapt întrerupătoare. adică poți cel mult să întrerupi curentul pentru motor – adică să-l oprești – nu să-l faci să se rotească și invers. iar invers, chiar mi-aș dori să se poată roti.

problema are o soluție dacă gândești puțin lateral: dacă aș avea mai multe întrerupătoare – ca să nu te chinui prea mult – 4. acum pot? normal. înseriezi câte două între terminalele de alimentare și conexiunile dintre întrerupătoare le conectezi la motor. în mod evident, nu le conectezi când sunt închise, pentru că scurtcircuitezi alimentarea. și s-ar putea să nu fie foarte plăcut. dar dacă închizi unul dintre întrerupătoarele dinspre terminalul negativ al sursei de alimentare și din celălalt șir de întrerupătoare pe cel dinspre terminalul pozitiv, motorul se va roti într-un sens. când inversezi poziția tuturor întrerupătoarelor, motorul se va roti în sens invers. uhuu! configurația asta se numește punte H.

problema vitezei se rezolvă foarte simplu. pentru unul dintre întrerupătoare folosești un semnal PWM în așa fel încât tensiunea medie va fi proporțională cu raportul între timpul în care terminalul se află la 5V și perioada semnalului PWM. ca atunci când erai mic și aprindeai și stingeai foarte repede becul din cameră, asta dacă apucai să vezi ceva și nu se ardea sau îți luai bătaie de la părinți – dap, nu sunt de acord cu violența fizică, doar că milenial fiind, am crescut în comunism și becurile se ardeau repede, că erau proaste.

nu-ți zic să construiești de la zero un astfel de circuit. departe de mine gândul. iese mult mai scump decât dacă l-ai cumpăra gata făcut și e și destul de complex. sunt multe variante. dar cum sunt ghidat de un buget minim – e frig, gazele s-au scumpit, curentul la fel, inflația a crescut, pe vremea mea .. – variantele care-mi rămân sunt:

îmi place mult DRV8835. se găsește și în Zumo. e integrat cu multe opțiuni de Pololu. curentul maxim suportat e cel mai mic dintre cele trei, însă are tot felul de protecții și se poate controla extrem de simplu cu ESP8266. mai ales că are alimentare separată pentru partea logică și nivelul logic 1 al ESP8266 va fi mai mult decât suficient pentru a obține maximum de performanțe. dar nu l-am ales. e prea scump.

următorul pe listă e L298N. l-am folosit în tot felul de configurații. îmi place la nebunie că îl găsești sub formă de modul din ăla roșu cu tot cu radiator la un preț mai mic decât circuitul separat. dintre toate trei, suportă tensiuni pentru motoare de până la 46V – cine naiba îl folosește cu motoare din astea? mă rog. ce e puțin aiurea e că nivelul logic pentru 1 este stabilit din fabrică la tipic 2,3V, iar cu o variație de 10%, normală de altfel, e posibil că ESP8266 pentru care 1 logic este în jur de 2,7V să nu deschidă suficient logica circuitului. pe lângă asta, fiind conceput pentru versatilitate – în special pentru motoare pas-cu-pas, de unde și tensiunea mare de alimentare – sunt necesari fie pini în plus față de celelalte două variante – 6 în loc de 4 – sau componente externe. să nu mai vorbim de căderea de tensiune de minimum 3,2V pe tranzistoarele de ieșire, care mă obligă să alimentez motoarele cu cel puțin 8,2V pentru a obține pe motor 5V. deci nu.

ultimul – special l-am lăsat la urmă – este un circuit chinezesc – ESP8266 are aceleași origini – conceput pentru jucării. conține tot ce e mai bun dintre cele două de mai sus: logică compatibilă cu 3,3V – 1 logic e de la 2V în sus, etaj de ieșire MOS-FET fără căderi semnificative de tensiune și curent generos. totul cu singura problemă legată de tensiunea maximă de alimentare care e cu 1V mai mică decât în cazul DRV8835, adică 10V în loc de 11V. the horror! not! când includ și prețul infim la care l-am găsit, e circuitul perfect.

față de plăcuța cumpărată am mai avut nevoie de 4 perechi de câte două terminale și două terminale separate – 10 din totalul de 40 cât are o baretă standard, pe care le-am lipit pe plăcuță. plăcuța am fixat-o cu banda dublu-adezivă rămasă de la un dulap pe placa de cu motoarele a robotului, ca în poze. ce-am făcut greșit prima dată a fost că am lipit plăcuța prea aproape de centru, firele incomodând plasarea bateriei. imediat și despre ea.

firele de la motoare le-am introdus peste terminalele «motor A» și respectiv «motor B», cele 4 intrări IN1, IN2, IN3 și IN4 la respectiv D1, D2, D3 și D4 din ESP8266, folosind fire dupont mamă-tată – fără glume de șantier, da? – de 15cm. + și – de pe plăcuță le-am conectat la GND și 5V pe plăcuța ESP8266. cum? ca în poze, bineînțeles.

robotul capătă formă. singurul lucru care lipsește pe partea hardware este sursa de alimentare.

vin cu bateria

din experiență, o bateria trebuie să fie bună. adică măcar să scoată – preferai furnizeze? – măcar cu 50% peste tensiunea de alimentare a creierului. în cazul de față, 5V ar fi minimum. să-ți explic de ce: atunci când motoarele funcționează, acestea consumă destul de mult curent încât să conteze rezistența internă a bateriei și în felul ăsta tensiunea la bornele ei va scădea suficient cât să producă neplăceri de genul resetarea robotului. de-asta n-o să-ți recomand niciodată să alimentezi un robot bazat pe Arduino la 6V. am văzut-o întâmplându-se. simt cum vrei să-mi spui că da, dar Zumo se alimentează la 6V, la el cum de merge? păi merge pentru că are în interior un convertor care indiferent de tensiunea bateriei, produce 7,5V, care e de fapt tensiunea de alimentare a motoarelor și a întregului ansamblu.

nu e exclus să poți să folosești suportul pentru cele două baterii AAA. doar că pentru ca robotul să meargă cât de cât, convertorul folosit trebuie să genereze măcar 5V/1A. adică 5W. cu un randament de 90% – adică bun, în practică e de obicei mai mic, din baterie va consuma 5,6W pe unitatea de timp. bateriile AAA au capacitatea de 1,9Wh și sunt două, deci 3,8Wh, făcând robotul să gâfâie după 20 de minute. dacă vrei să mergi pe varianta asta, îți recomand măcar un suport cu 4 baterii AA, care fiecare înmagazinează 4,2Wh de energie chimică, adică un generos 16,8Wh. împreună cu suportul de baterii – deși poți să-l folosești bine-mersi așa cum e – îți recomand și o sursă coborâtoare/ridicătoare de tensiune de la Pololu, S9V11F5 – 30 de lei, optimus digital, 30 de lei, robofun – ieftină și bună, pe care am folosit-o cu succes pentru a adăuga Raspberry Pi Zero W peste Zumo.

varianta cea mai ieftină e să folosești un suport pentru 6 baterii AA. ai la dispoziție astfel 9V cu 25,2Wh la dispoziție. toate componentele sunt compatibile cu tensiunea asta și poți folosi în suport orice tip de baterii, normale, de 1,5V, producând 9V când sunt noi, baterii alcaline, de 1.5V, care produc aprox. 9,6V când sunt noi, sau acumulatori NiMH care produc 8,4V când sunt noi. deci, suport de 6 plus baterii egal love și costuri mici. mai ai nevoie de două fire de 15cm care de data asta au la un capăt conectori dupont tată, celelalte capete conectându-le la terminalele suportului. doar inserezi alimentarea în breadboard, plasezi suportul în interiorul robotului și-i pui capacul și gata.

varianta pe care am mers eu a fost să folosesc un modul cu care poți să-ți construiești propria baterie externă de 5V. cei 2A de la ieșire sunt mai mult decât suficienți pentru orice alte accesorii aș vrea să adaug pe robot. se încarcă prin USB și am control asupra bateriei cu litiu din interior. am mai folosit un suport de baterie cu Li-Ion, în format 18650 împreună cu o baterie Sony de 2600mAh, adică aproximativ 9,6Wh. fără a conecta bateria, am lipit terminalele suportului la terminalele B+ și B- ale modulului. tot fără a conecta bateria – nu știu dacă știi, dar bateriile cu litiu sunt destul de periculoase, așa că mai bine safe than sorry; oricum, un extinctor cu pulbere nu strică să-ți fie la îndemână – am lipit două terminale pe placa modulului, pe contactele GND și OUT. am folosit mult fludor și un clește mic. între terminalele astea și GND respetiv 5V ale ESP8266 am folosit două fire dupont mamă-tată de 15cm.

pentru a prinde modulul de încărcare pe placa superioară am folosit două distanțiere de plastic, M3, cu piuliță și șurub. deși puteam să folosesc atât șoricei – zip-ties – sau bandă dublu-adezivă, mi-a plăcut mai mult varianta asta, mai ales că trebuie să încarc bateria prin micro-USB. modulul de încărcare are 4 leduri care indică cât de încărcată e bateria și un mic buton pe care-l pot acționa printr-un șoricel. ah. da. și mai are un led aiurea, pe care l-am scurtat. în timpul functionării, led-ul se aprinde și e alb. cred – deși n-am încercat – poți să-l înlocuiești cu orice led de 5mm în culoarea preferată.

zi de soft

cum spuneam înainte, îmi place ESP8266. are fix aceleași funcții ca Arduino, cu niște particularități. prima dintre ele e că include o conexiune wi-fi. ca o placă wireless dintr-un laptop. care pe deasupra e foarte ușor de folosit – incarci două biblioteci, definești rețeaua și parola pentru conexiunea wi-fi, inițializezi conexiunea și gata. ca în continuare:

#include <ESP8266WiFi.h> /** biblioteca prin care ESP8266 acceseaza conexiunea wi-fi */
#include <WiFiClient.h> /** biblioteca prin care ESP8266 folosește wi-fi și își ia adresa de IP */

#ifndef LOCAL_SSID /** protecție să nu definesc rețeaua de două ori */
#define LOCAL_SSID "nume-retea-wireless" /** numele rețelei, așa cum apare el în telefon sau pe calculator */
#define LOCAL_PASS  "parola-de-la-wireless" /** parola de la wireless */
#endif

const char* ssid     = LOCAL_SSID; /** pun în constante de tip char* valorile definite mai sus */
const char* password = LOCAL_PASS;

void setup() { /** functia de setup, specifica Arduino, ruleaza după reset sau la pornire */
  Serial.begin(115200); /** initializez conexiunea serială, să văd ce se întâmplă, viteza 115200 bauds */
  WiFi.begin(ssid, password); /** încerc să mă conectez la wireless */
  Serial.println(""); /** trimit o linie nouă către terminalul serial */
  
  while (WiFi.status() != WL_CONNECTED) { /** atât timp cât încă nu m-am conectat la wireless */
    delay(500); /** aștept jumătate de secundă */
    Serial.print("#"); /** și trimit un # către terminalul serial, după care mai încerc o dată */
  }
  
  Serial.println(""); /** aici înseamnă că m-am conectat, așa că trec la linia următoare în terminal */
  Serial.println(WiFi.localIP()); /** și trimit adresa de IP ca să știu unde mă conectez cu calculatorul */
}

void loop() { /** deocamdată nu pun nimic în loop, vreau doar să văd că se conectează */
}

știu că ți-e lene să scrii, așa că ai aici fișierul întreg: 8266-robot-round-wifi-v1.ino

în mediul Arduino, alegi tipul plăcii cu ESP8266, alegi portul serial care apare atunci când conectezi ESP8266 prin cablu USB și apeși upload. dacă totul merge bine și se uploadează conținutul programului, ar trebui ca în monitorul serial să obții adresa de IP. totul funcționează corect dacă poți să scrii în terminalul de pe calculator «ping IP», unde IP e adresa primită prin serial și să obții reply.

dacă da, hai mai departe. ping de înainte doar îmi arată că ESP8266 s-a conectat la net. mi-ar plăcea în schimb să-i pot trimite comenzi. aici am mai multe variante, dar cea mai simplă se bazează pe un browser, ca ăla prin care accesezi pornhub. cum, tu nu? revenind, mi-ar plăcea să pot să scriu în browser adresa de IP și să îmi apară ceva în care să trimit comenzi către robot.

ESP8266 are o bibliotecă excelentă pentru asta prin care implementează un mic server web. adică, fix chestia care ajută pornhub să poți să le accesezi paginile web. și e un mic server web chiar rapid. dar despre asta într-o altă postare. ca orice server web, va trebui să-i spun cum să interpreteze diferitele căi care vin după adresă. spre exemplu, dacă te uiți în bara de adrese acum ce vine după ublo.ro, începând cu primul slash /. e suficient să îi spun ce să facă atunci când după adresă e doar un slash – pagina asta se cheamă index și e prima pagină a unui sait, pe care o vezi când scrii direct adresa – și ce să facă atunci când nu găsește pagina. pentru asta adaug următoarele lucruri,

imediat după bibliotecile deja incluse, adică sus de tot:

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h> /** asta e biblioteca nouă, restul le-am pus pentru context */

fix înainte de function setup, definesc obiectul care va reprezenta serverul – îl voi numi creativ, server – și două funcții, una care să se ocupe de pagina index a noului meu server web și una care să se ocupe de paginile care nu există:

ESP8266WebServer server(80); /** aici definesc serverul web, folosind portul 80, adică normal */

void handle_root() { /** functia asta spune ce se intampla cand accesez adresa direct in browser */
  server.send(200, "text/html", "heei, asta e prima pagina!"); /** pur si simplu afisez in browser mesajul */
  /** ce mi se pare interesant, e ca in loc de mesaj poti sa pui codul HTML al oricărei pagini */
  /** primul parametru al server.send, 200, este statusul răspunsului HTML; 200 = OK */
  /** al doilea parametru, "text/html", este tipul raspunsului, în cazul ăsta o pagină web */
  /** al treilea parametru, "heei, asta e prima pagina!", este conținutul răspunsului */
}

void handle_404() { /** functia asta spune ce se intampla cand accesez o pagină care nu există */
  server.send(404, "application/json", "{\"error\":1,\"message\":\"not found\"}");
  /** trimit către browser un mesaj de eroare: */
  /** primul parametru, statusul răspunsului, înseamnă 404 = Page Not Found */
  /** al doilea parametru spune că întorc un obiect de tip json, cu error=1 și message=not found */
  /** al treilea parametru conține obiectul json */
}

void setup() { /** pus aici pentru context */

și în interiorul funcției setup, chiar înainte de acolada de final, leg funcțiile pentru pagini de server și pornesc serverul:

void setup() { /** pus aici pentru context */
  Serial.begin(115200);
  WiFi.begin(ssid, password);
  Serial.println("");
  
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print("#");
  }
  
  Serial.println("");
  Serial.println(WiFi.localIP());
  /** urmează bucata nouă: */
  server.on("/", handle_root); /** conectez funcția pentru index la serverul web */
  server.onNotFound(handle_404); /** conectez funcția pentru pagini care nu există la serverul web */
  server.begin(); /** pornesc serverul web */
  Serial.println("S"); /** trimit S către terminal ca să știu că a pornit și serverul web */
}

doar că spre deoserbire de programul anterior, de data asta mai trebuie să îi spun în loop să aștepte conexiuni de la browsere:

void loop() { /** pus aici pentru context, urmează instrucțiunile noi */
  server.handleClient(); /** asteapta conexiuni de la browsere și dacă sunt, interpretează-le */
  /** ca limitare, serverul web poate interpreta câte o conexiune pe rând */
}

da, n-am uitat. fișierul versiunii de acum e aici: 8266-robot-round-wifi-v2.ino

după ce încarci programul vei putea să accesezi robotul în browser, prin adresa de IP care apare în monitorul serial. dacă totul e ok vei primi mesajul pe care l-ai scris în funcția handle_root. dacă încerci să adaugi ceva la adresă, un slash ceva – adică chiar să scrii /ceva – ar trebui să-ți apară obiectul JSON definit.

cred că din momentul ăsta te-ai prins ce trebuie să faci. serios? nu? păi trebuie să definești diferite adrese prin care să trimiți comenzi la motoare. hai să mă ocup de motoare în primul rând. ca să controlez motoarele, voi defini două variabile globale care vor ține minte viteza motorului pe 8 biți: dacă primul bit e 1, atunci motorul merge înainte, dacă primul bit e 0, atunci motorul merge înapoi. următorii 7 biți reprezintă viteza cu care se deplasează. dacă toți sunt 0 – în hexa înseamnă 0x00 – atunci viteza e 0 și motorul stă pe loc, dacă toți sunt 1 – în hexa înseamnă 0x7f – atunci viteza e maximă. așa că imediat după definirea LOCAL_SSID, adaug definiții pentru terminalele la care este conectată puntea hash:

#ifndef LOCAL_SSID /** pus aici pentru context */
#define LOCAL_SSID "nume-retea-wireless"
#define LOCAL_PASS  "parola-de-la-wireless"
#endif
/** urmează definițiile noi: */
#ifndef MOTOR_PINS /** protecție, ca să nu definesc terminalele de două ori */
#define MOTOR_PINS /** definit doar ca să verific dacă nu l-am definit din nou */
#define MOTOR_A_IN1 D1 /** IN1 al punții H e conectat la D1 al ESP8266 */
#define MOTOR_A_IN2 D2 /** IN2 al punții H e conectat la D2 al ESP8266 */
#define MOTOR_B_IN1 D3 /** IN3 al punții H e conectat la D3 al ESP8266 */
#define MOTOR_B_IN2 D4 /** IN4 al punții H e conectat la D4 al ESP8266 */
#endif

după atribuirea constantelor cu numele rețelei și parola, adaug variabilele globale pentru motor. pe lângă cele două viteze, mai definesc o variabilă, de data asta booleană, care îmi spune dacă vitezele s-au modificat sau nu. ca să optimizez puțin timpii de conexiune.

const char* ssid     = LOCAL_SSID; /** pus aici pentru context */
const char* password = LOCAL_PASS;
/** urmează variabilele noi: */
uint8_t motor_A = 0x80; /** viteza motorului A, înainte cu viteză 0 */
uint8_t motor_B = 0x80; /** viteza motorului B, înainte cu viteză 0 */

bool motor_change = false; /** dacă viteza motorului s-a modificat */

imediat sub definitia lui handle_404, introduc o functie care imi spune cum controlez un motor. am grijă că toate terminalele ESP8266 pot fi programate cu PWM, însă spre deosebire de Arduino, plaja de valori este între 0 și 1023 – 10 biți – spre deoserbire de 0 și 255 – 8 biți.

void handle_404() { /** pus aici pentru context */
  server.send(404, "application/json", "{\"error\":1,\"message\":\"not found\"}");
}
/** urmează definiția funcției pentru controlul motorului */
/** parametrii funcției sunt:
 * motor - întreg pe 8 biți, între 0 și 255 = reprezintă viteza motorului
 * in1 - întreg pe 8 biți = terminalul ESP8266 unde e conectat IN1 (IN3, dacă motorul = B)
 * in2 - întreg pe 8 biți = terminalul ESP8266 unde e conectat IN2 (IN4, dacă motorul = B)
 */
void run_motor (uint8_t motor, uint8_t in1, uint8_t in2) {
  /** cu motor & 0x80 extrag doar primul bit; iar rezultatul va fi 0x80, dacă e 1 sau 0x00 dacă e 0 */ 
  if ((motor & 0x80) == 0x80) { /** deci, dacă primul bit e 1, motorul merge înainte */
    /** din foaia de catalog a MX1515, ca motorul să meargă înainte, */
    /** prima intrare trebuie să fie PWM, iar a două LOW */
    /** 0x7f & motor extrage viteza, iar rezultatul va fi între 0x00 și 0x7f */
    /** cum asta înseamnă un număr între 0 și 127, trebuie să-l înmulțesc cu 8 ca să obțin între 0 și 1023 */
    analogWrite (in1, (0x7f & motor) << 3); /** pe in1 pun semnal PWM */
    digitalWrite (in2, LOW); /** pe in2 scriu LOW */
  }
  else { /** dacă nu merge înainte, motorul merge invers */
    /** rationamentul e asemanator, doar că se inversează in1 și in2 */
    /** altă optimizare e să inversez viteza: când rezultatul 0x7f & motor = 0x7f, viteza să fie 0 */
    /** asta deoarece de la 0 la 127 viteza va scădea */
    /** apoi de la 128 la 255, motorul își schimbă sensul și accelerează */
    digitalWrite (in1, LOW); /** pe in1 de data asta scriu LOW */
    analogWrite (in2, (0x7f - (0x7f & motor)) << 3); /** iar pe in2 pun semnal PWM, cu viteza inversata */
  }
}

imediat după funcția definită anterior, voi defini o funcție care stabilește viteza pentru un anumit motor. funcția va primi doi parametrii: viteza motorului care mă interesează și un parametru de tip String care poate fi "up" sau "dw" pentru a accelera sau decelera motorul, sau direct un număr între 0 și 255.

/** funcția pentru stabilirea vitezei unuia dintre motoare */
/** parametrii funcției sunt:
 * motor - referință către variabila corespunzătoare vitezei motorului
 *       - referință înseamnă că variabila va fi modificată de funcție,
 *       - și noua valoare va fi accesibilă în exteriorul funcției
 * value - String, reprezintă valoarea primită prin intermediul conexiunii wi-fi
 *       - value poate fi dw - și atunci viteza scade cu 8 unități din 255
 *       - value poate fi up - și atunci viteza crește cu 8 unități din 255
 *       - value poate fi un număr între 0 și 255
 */ 
void set_motor (uint8_t &motor, String value) {
  long conversion = 0; /** String::toInt() intoarce o valoare de tip long, eu am nevoie de uint8_t */
  motor_change = true; /** daca am apelat funcția asta, automat viteza motorului s-a modificat */
  
  if (value == String ("up")) { /** dacă valoarea e "up" */
    conversion = motor + 0x08; /** cresc viteza motorului cu 8 */
    if (conversion > 0xFF) { /** dacă ce obțin e mai mare de 255 */
      conversion = 0xFF; /** fac viteza 255 */
      /** aici am folosit o smecherie: dacă conversion era uint8_t, atunci când adunam */
      /** și treceam peste 255 se întorcea din nou de la 0 și n-aș fi știu ce să fac */
      /** dar cum conversion e de tip long, pot să depășesc 255 fără probleme */
    }
    motor = conversion; /** știu sigur că conversion e cel mult 255, așa că stabilesc viteza */
    return; /** nu merg mai departe */
  }
  if (value == String ("dw")) { /** dacă valoarea e "dw" */
    conversion = motor - 0x08; /** scad viteza motorului cu 8 */
    if (conversion < 0x00) { /** dacă ce obțin e mai mic ca zero */
      conversion = 0x00; /** fac viteza 0 */
      /** din nou șmecheria: conversion e long și poate să fie și negativ */
    }
    motor = conversion; /** știu sigur că conversion e cel puțin 0, așa că stabilesc viteza */
    return; /** nu merg mai departe */
  }
  conversion = value.toInt(); /** convertesc șirul de caractere la întreg */
  /** dacă conversia eșuează, conversion = 0 */
  if (conversion < 1 || conversion > 0xFF) { /** dacă conversion e mai mic de 1 sau mai mare de 255 */
    conversion = 0x80; /** opresc motorul */
  }
  motor = conversion; /** altfel, motorul va avea viteza stabilită */
}

va trebui în setup să specific cum sunt folosite terminalele pentru motoare, stabilindu-le pe toate ca ieșiri OUTPUT:

void setup() { /** pus aici pentru context */
  /** urmează inițializarea conexiunii cu puntea H */
  pinMode (MOTOR_A_IN1, OUTPUT); /** stabilesc terminalul conectat la IN1 ca ieșire */
  pinMode (MOTOR_A_IN2, OUTPUT); /** stabilesc terminalul conectat la IN2 ca ieșire */
  pinMode (MOTOR_B_IN1, OUTPUT); /** stabilesc terminalul conectat la IN3 ca ieșire */
  pinMode (MOTOR_B_IN2, OUTPUT); /** stabilesc terminalul conectat la IN4 ca ieșire */
  
  Serial.begin(115200); /** pus aici pentru context */
  WiFi.begin(ssid, password);
  Serial.println("");
  
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print("#");
  }
  
  Serial.println("");
  Serial.println(WiFi.localIP());
  server.on("/", handle_root);
  server.onNotFound(handle_404);
  server.begin();
  Serial.println("S");
}

tot în iterația asta de program voi modifica și funcția loop ca să pot controla motorul. am grijă să setez motorul doar dacă funcția set_motor a fost chemată:

void loop() { /** pus aici pentru context */
  server.handleClient();
  /** urmează bucata nouă de cod care se ocupă de controlul motoarelor */
  if (!motor_change) { /** dacă motor_change e false */
    return; /** întoarce-te la început */
  }
  motor_change = false; /** aici motor_change e true, așa că îl resetez */
  run_motor (motor_A, MOTOR_A_IN1, MOTOR_A_IN2); /** stabilesc viteza pentru motorul A */
  run_motor (motor_B, MOTOR_B_IN1, MOTOR_B_IN2); /** stabilesc viteza pentru motorul B */
}

și să nu uit de fișier. îl poți descărca aici: 8266-robot-round-wifi-v3.ino

ultimul pas în ceea ce privește software-ul e să primesc comenzi prin browser. până acum n-am făcut nimic în sensul ăsta. cum spuneam, poți să definești acum câte un «handle» pentru fiecare motor, dar n-aș vrea să fac asta. așa că voi crea o pagină căreia pot să-i trimit mai mulți parametrii. parametrii îi voi trimite folosind HTTP POST, același protocol care-l folosește un browser atunci când scrii ceva în google și apeși enter. dacă la google parametrul trimis prin HTTP POST se numește q - poți să încerci copiind în browser google.com/?q=ceva - în cazul robotului voi avea doi parametrii, motor_a și motor_b. pagina către care se fac cererile va fi /rpc/ de la «remote procedure call» și va răspunde cu un JSON care conține cele două viteze noi. imediat după funcția set_motor vei introduce:

void handle_404() { /** pus aici pentru context */
  server.send(404, "application/json", "{\"error\":1,\"message\":\"not found\"}");
}
/** funcția nouă care se ocupă de interpretarea comenzilor */
void handle_form () {
  if (server.method() != HTTP_POST) { /** verific ca protocolul sa fie HTTP POST */
    server.send(405, "application/json", "{\"error\":1,\"message\":\"method not allowed\"}");
    /** daca nu, trimit un mesaj de eroare cu statusul 405 = Method Not Allowed */
    return;
  }
  for (uint8_t c = 0; c < server.args(); c++) { /** trec printre toti parametrii, n-am cum altfel */
    if (server.argName(c) == String("motor_a")) { /** daca numele parametrului e motor_a */
      set_motor(motor_A, server.arg(c)); /** apelez set_motor pentru motor_A cu valoarea parametrului */
    }
    if (server.argName(c) == String("motor_b")) { /** daca numele parametrului e motor_b */
      set_motor(motor_B, server.arg(c)); /** apelez set_motor pentru motor_B cu valoarea parametrului */
    }
  }
  /** pur si simplu trimit un raspuns cu statusul 200 = OK */
  /** si care contine noile viteze */
  server.send (200, "application/json",
    "{\"motor_A\":" + String(motor_A) + ",\"motor_B\":" + String(motor_B) + "}");
  /** la fel, trimit si prin interfata seriala cele doua viteze noi */
  Serial.print("A");
  Serial.println(motor_A);
  Serial.print("B");
  Serial.println(motor_B);
}

a mai rămas un singur lucru de făcut. să adaugi în funcția setup conexiunea înte server și noua funcție handle_form:

void setup() { /** pus aici pentru context */
  /** dap, și asta exista până acum. va trebui să mai sari câteva rânduri */
  pinMode (MOTOR_A_IN1, OUTPUT);
  pinMode (MOTOR_A_IN2, OUTPUT);
  pinMode (MOTOR_B_IN1, OUTPUT);
  pinMode (MOTOR_B_IN2, OUTPUT);
  
  Serial.begin(115200);
  WiFi.begin(ssid, password);
  Serial.println("");
  
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print("#");
  }
  
  Serial.println("");
  Serial.println(WiFi.localIP());
  server.on("/", handle_root);
  /** așa, aici e bucata nouă de cod care inițializează interpretarea comenzilor */
  server.on("/rpc/", handle_form); /** aici am adaugat conexiunea intre handle_form si server */
  /** restul e la fel, pus aici pentru context */
  server.onNotFound(handle_404);
  server.begin();
  Serial.println("S");
}

ca și până acum, poți descărca fișierul de aici: 8266-robot-round-wifi-v4.ino

după ce încarci programul în robot poți folosi Postman pentru a trimite cereri către robotul tău alegând la tipul conexiunii HTTP POST, la adresă, http://adresa-de-ip-a-robotului-tău/rpc/, iar în corpul - adică body - cererii să treci parametrii motor_a și motor_b cu ce valori vrei. ai aici un mic ghid despre cum se folosește Postman.

ai putea să te oprești aici. prin Postman poți să trimiți comenzi robotului - să nu uit! s-ar putea ca motoarele să nu se rotească în aceeași direcție când le stabilești pe ambele să meargă înainte. nu-i nimic. poți să inversezi firele care leagă motorul de puntea H și problema se rezolvă. doar că parcă nici mie nu-mi place să apăs tot timpul butoane pe tastatură.

control și telefon

după articolul ăsta imens, dacă te întreb «mai ții minte când ziceam?» sunt șanse mici să răspunzi sincer cu «da!». așa că revin. în handler_root poți să incluzi o pagină web care să se încarce atunci când accesezi dintr-un browser adresa robotului tău. așa că am facut o mică pagină în HTML5 pe care am inclus-o în handler_root și care atunci când o încarci nu afișează nimic. suspans creat degeaba, nu? nu chiar. pentru că atunci când faci gesturi cu degetul peste pagina respectivă - swipe în sus, în jos, la stânga, la dreapta sau în diagonală, robotul va merge în direcția indicată de degetul tău și cu viteza dată de cât de lung ai făcut swipe-ul.

am folosit pentru asta biblioteca jquery.finger a lui Nicolas Gryman care îmi permite să citesc cu ușurință gesturile pe care le faci peste o pagină web.

codul e mai jos și dacă-ți place să te joci cu el, îl am și în versiunea pentru download, aici: 8266-robot-html-interface.html

<script>
var motor = { /** definesc un obiect care va controla motorul prin API */
    u:'/rpc/', /** pagina la care e disponibil API-ul */
    a:128, /** = 0x80, viteza implicită a motorului A */
    b:128, /** = 0x80, viteza implicită a motorului B */
    busy:0, /** îmi indică dacă cererea către robot nu s-a încheiat */
    body:$('body'), /** corpul paginii web */
    move:function(){ /** metodă prin care fac robotul să meargă */
    if(this.busy)return; /** dacă robotul e busy, ies */
    this.body.css({'backgroundColor':'blue'}); /** fac fundalul paginii albastru */
    this.busy=1; /** stabilesc că robotul este busy */
    $.ajax({ /** deschid o conexiune către robot */
        type:'POST', /** conexiunea e de tip POST */
        url:this.u, /** adresa e cea definită mai devreme */
        data:{'motor_a':this.a,'motor_b':this.b}, /** aici sunt parametrii pe care îi trimit */
        context:this, /** contextul îmi spune care e obiectul "this" din success și error de mai jos */
        success:function(r){ /** în cazul în care cererea se termină cu succes */
            this.body.css({'backgroundColor':'transparent'}); /** fac fundalul paginii transparent */
            this.busy=0; /** și stabilesc că robotul nu mai e busy */
        },
        error:function(){ /** în cazul unei erori, fac același lucru ca mai devreme. de lene */
            this.body.css({'backgroundColor':'transparent'});
            this.busy=0;
        }
    });
    },
    stop:function(){this.a=this.b=128;this.move()} /** metoda asta oprește robotul. face ambele viteze 0 */
};
var w=$(window); /** o variabilă în care țin fereastra browserului */
/** dacă îmi târăsc degetul, adică fac swipe */
w.on('drag',motor,function(ev){
    /** calculez procentual cât din ecran am făcut swipe pe orizontală = x și pe verticală = y */
    var x=2*ev.dx/w.width(),y=-2*ev.dy/w.height(),
    /** și calculez unghiul față de orizontală */
    t=Math.atan2(y,x),
    /** convertesc coordonatele x și y în coordonate polare, pe care apoi le convertesc în viteze */
    a=Math.sign(y)*Math.sqrt(x*x+y*y)*(1-Math.cos(t)),
    b=Math.sign(y)*Math.sqrt(x*x+y*y)*(1+Math.cos(t));
    /** vitezele obținute sunt între -1 și 1, și vreau să le convertesc între 0 și 255 */
    motor.a=Math.floor(128+127*(a>1?1:(a<-1?-1:a)));
    motor.b=Math.floor(128+127*(b>1?1:(b<-1?-1:b)));
    /** după ce am noile viteze, mișc robotul în direcția pe care o vreau */
    motor.move();
});
/** iar dacă apăs de două ori repede pe ecran - double tap - robotul se oprește */
w.on('doubletap',motor,function(ev){ev.data.stop()});
</script>

bineînțeles, nu am cum să nu închei cu programul întreg pe care-l poți încărca pe robot, cu tot cu interfața asta HTML5: 8266-robot-round-wifi-final.ino.

spor la construit!

referințe:

  1. detalii despre programarea ESP8266 cu Arduino
  2. wikipedia, despre puntea H
  3. foaie de catalog DRV8835
  4. foaie de catalog L298N
  5. foaie de catalog MX1515
  6. energizer watt-hour battery specs

aceast sait folosește cookie-uri pentru a îmbunătăți experiența ta, ca vizitator. în același scop, acest sait utilizează modulul Facebook pentru integrarea cu rețeaua lor socială. poți accesa aici politica mea de confidențialitate.