ublo
bogdan's (micro)blog

bogdan

bogdan » muzică și lumină

08:51 pm on Jan 26, 2020 | read the article | tags:

nostalgic încă după sărbătorile de iarnă – și aici mă refer la timpul liber, nu la mâncare și obiceiuri – după robotul wi-fi, de fapt, puțin înaintea lui, inspirat de un instastory, m-am gândit că n-ar strica să mai pierd ceva timp lipind niște circuite de roboți cu bucăți de software să am și eu luminițe în casă, că p-alea de pe Magheru nu le-am văzut. dar cu o ș’pârlă: mi-ar plăcea să fie sincronizate pe muzică.

e greu? pare greu

depinde. dacă mi-ai fi pus întrebarea asta acum douăzeci de ani ți-aș fi spus că da. de fapt nici nu m-aș fi încumetat. dar acum? neah. am nevoie doar de ceva care să transforme sunetele în semnale electrice, pe care apoi le procesez cu un fel de Arduino pentru a controla o bandă de leduri adresabile cu neopixel. simplu ca bună ziua!

pe rând, în fraza de dinainte, sunet în semnal electric: orice microfon face asta. cum mă zgârcesc, să fie microfon condensator – ieftin, bun și de fapt tipul de microfon care se găsea în aproape orice acum ceva timp. e cilindric, în capsulă de metal cu o bucată de pânză pe una dintre margini și doi electrozi pe cealaltă. ce e rău e că semnalul lui e prea mic să pot să-l procesez direct cu Arduino. așa că, fie aleg un modul care are amplificator – dar asta e pentru începători, fie îmi construiesc propriul amplificator de microfon.

lăsând modestia deoparte, prefer varianta asta mai hardcore pentru că pot să controlez exact ce se întâmplă cu semnalul electric de la microfon. am ales pentru amplificare un amplificator operațional. pentru că e la același preț, m-am aruncat direct la unul dual – LM358B. ți-l recomand. de obicei aș fi venit cu alternative, dar circuitul ăsta de la Texas Instruments e printre puținele care funcționează bine cu o singură sursă de alimentare și tensiuni mici – 3-5V. prima jumătate amplifică și cum mai aveam una disponibilă, pe ce-a de-a doua am configurat-o ca superdiodă să pot să integrez semnalul microfonului – dada, e integrarea aia de la matematică, Riemann-Stieltjes.

ai aici o diagramă electronică a părții analogice din proiect, la rezoluție ceva mai bună și gata de print, ca să o ai în față în timp ce-l construiești.

trecând la Arduino, am folosit de fapt un ESP8266 Witty. are wi-fi să pot să configurez luminile din browser – deși, de lene, în practică am folosit Postman, e destul de mic și mult mai rapid decât un Arduino la procesat date – iar, nu că aș avea mare chestie de făcut, dar mai multă viteză nu strică. pentru că ieșirile lui ating maximum 3,3V, nu e direct compatibil cu ledurile neopixel. așa că la mijloc apare un tranzistor care schimbă nivelul discuției digitale.

nu uita, ți-am pregătit aici diagrama electronică a conexiunilor digitale în format pdf, gata pentru print.

leduri neopixel. îmi plac la nebunie. cu un singur fir poți să controlezi o mulțime de leduri înseriate. multicolore! și arată bine și se controlează ușor și poți să faci tot felul de chestii cu ele – așa că obișnuiește-te, vor mai apărea pe aici. problema cu ele e că fiecare led consumă 60mA. banda pe care am cumpărat-o eu acum ceva timp are 150 de leduri = 5m × 30 leduri/m care adunate dau 9A. nouă amperi! am repetat ca să nu crezi că am tastat greșit. așa că ai nevoie de o sursă de alimentare bună, pentru că, deși pentru teste poți să folosești portul USB3 al calculatorului sau un încărcător pentru telefon, în varianta finală vei avea nevoie de ea.

lista de cumpărături

totul pornește de la o bandă cu leduri neopixel. adică cu leduri WS2812 sau variante ale acestora. ledurile de acest fel sunt ceva mai scumpe decât cele obișnuite, chiar și RGB. asta deoarece fiecare led include propriul circuit de control, făcând posibilă aprinderea individuală a fiecărui led din bandă. îți recomand modelul WS2812, deși WS2811 e și el o opțiune, pentru simplul fapt că sunt mai rapide. ce mi se pare interesant e că odată stabilită culoare și intensitatea pentru un led, procesorul poate să se ocupe de orice altceva, lucru important pentru dispozitive fără prea multă putere de calcul.

microfonul și circuitul integrat LM358B vin la pachet cu un buchet de rezistori și o mână de capacitoare. strecurate mai sunt două diode – superdioda aia nu se face singură, da?, un led – te ajută să vezi că merge ce-ai făcut – și un tranzistor – pentru led, săracul, să nu fie singur.

ESP8266 Witty vine singur. mă rog, cu tranzistorul translator, dar ăla nu se pune, chiar dacă e însoțit de două rezistoare. am ales modulul ăsta cu ESP8266 că e ieftin. merge orice altă variantă. de fapt, dacă știam eu că la același preț găsesc ESP8266 WeMos D1 Mini, n-aș fi mers pe Witty. dar, asta e.

chiar dacă am folosit un breadboard pentru definitivarea circuitului audio – versiunea din articolul ăsta e a treia, asta după ce am început măreț cu o versiune care includea nu unul, ci două LM358B și care n-a mers deloc – în final, am ales o variantă mai permanentă care m-a făcut să șterg de praf abilitățile de a folosi Autodesk Eagle.

alte lucruri pe care le-am avut la îndemână și pe care ți le recomand, dar care pot fi refolosite după asta sunt: un clește pentru tăiat fire – al meu e de la hornbach, 86 de lei, dar au și la conexelectronic unul decent, 15 lei, o stație de lipit, conexelectronic, 300 de lei sau un ciocan de lipit, conexelectronic, 50 de lei – ți-l recomand pentru că găsești ușor vârfuri de schimb, pentru că vârfurile sunt consumabile – și aliaj de lipit, un breadboard cu puțin peste 400 de puncte, fire de conexiune – un set de 10 fire tată-tată e perfect, o ramă de fotografie, niște hârtie de calc, o lampă uv pentru unghii, emag, 45 de lei, mr. proper – granule sau lichid, cloură ferică 1l, conexelectronic, 16 lei și diluant de unghii. să nu uiți de multimetru – am testat cu studenții două modele ieftine, DVM832, conexelectronic, 50 de lei și AX100, conexelectronic, 60 de lei – versiunea europeană a multimetrului descris aici.

sunet electric

da. microfonul condensator transformă vibrațiile – nu alea eterice, cele muzicale, da? – în variații ale capacității unui capacitor și pentru că producătorii s-au gândit la noi, au inclus un mic convertor în interior care transformă variația capacității în variații de curent. atenție! microfonul e polarizat. terminalul negativ e cel conectat la carcasa metalică și poți să-l găsești ușor cu un multimetru testând continuitatea. cel de-al doilea terminal e mixt, folosit pentru alimentare, dar și pentru semnal. alimentarea se face printr-un rezistor cu valoarea între 1 și 10KΩ. aici am ales să fac o șmecherie: am împărțit rezistorul în două bucăți, una de 4,7KΩ conectată între polul pozitiv al sursei de alimentare, înseriată cu una de 1KΩ conectată la microfon. punctul median l-am conectat la un capacitor polarizat de 100μF/10V.

de ce m-am chinuit? îți spuneam mai sus că ledurile consumă foarte mult curent. asta înseamnă că tensiunea de alimentare va fi afectată destul de mult – mă rog, e relativ termenul, dar crede-mă pe cuvânt – atunci când ledurile se aprind. păi, ledurile se aprind când primesc semnal audio. dar dacă variația asta de tensiune de alimentare se suprapune peste semnalul audio? îți amintești țiuitul ăla enervant de la karaoke când, de la curajul lichid consumat anterior, ajungi mai aproape de boxe decât ți-ai dori? același lucru se întâmplă și aici: ledurile modulează intrarea de microfon care la rândul ei comandă ledurile care modulează care comandă care … și în loc să facă ce trebuie, circuitul va oscila, cel mai probabil neplăcut.

capacitorul ăla de 100μF netezește extrem de mult aceste variații. ordinea rezistoarelor e irelevantă din ce-am văzut practic. prefer varianta cu rezistorul mai mare către sursa de alimentare și cel mai mic către microfon. semnalul audio – și doar el, că nu mă interesează potențialul ăla continuu obținut prin rezistor – îl extrag printr-un capacitor de 2,2μF conectat la terminalul de alimentare / semnal al microfonului.

tensiunea pe terminalul liber al capacitorului are vârfuri de 40mV în cazul în care sunetul e puternic și cam 12mV pentru sunete normale. ESP8266 are o sensibilitate la intrare de 3mV, cam mică pentru semnalul microfonului.

totuși sunt departe de probleme, am un LM358B la dispoziție. îl pun pe breadboard și îl alimentez, cu plus la pinul 8 și minus la pinul 4.

conectez jumătate din LM358B în configurație de amplificator inversor: leg capacitorul printr-un rezistor de 1KΩ la intrarea inversoare a amplificatorului operațional (pin 2) pe care la rândul ei o conectez la ieșirea amplificatorului operațional (pin 1) printr-un rezistor de 220KΩ. mai am nevoie de o rețea de polarizare a intrării neinversoare, foarte simplă, realizată din doi rezistori de 100KΩ înseriați și un capacitor de 220nF în paralel cu unul dintre ei. amplificarea obținută e în teorie de 220 = 220KΩ/1KΩ.

în practică obțin cam 2V, normal că nu 8,8V, că n-aș avea de unde. mai mult decât suficient. totuși, nu sunt încă mulțumit. dacă mă uit la semnalul pe care l-am obținut conectând osciloscopul la ieșire, nu arată prea «curat». adică nu văd bassul și nici toba mare în el.

trec la cealaltă jumătate din LM358B. nu uit să înseriez un capacitor de 2,2μF nepolarizat la ieșirea primului amplificator, să fiu sigur că nu trece niciun fel de potențial continuu.

cu două diode 1N4148 – de fapt, aici poți să folosești orice diode, dar să fie la fel – și două rezistoare, construiesc o superdiodă folosind amplificatorul operațional disponibil prin pinii 5,6 și 7 al circuitului integrat. construcția e asemănătoare cu a amplificatorului inversor, doar că inserez niște diode la ieșire. pentru intrarea neinversoare nu mă mai interesează să o polarizez la jumătatea tensiunii de alimentare. semnalul nu mai am chef să fie simetric. pentru că fix asta face superdioda: ce e peste zero, trece, ce e sub zero, nu trece. de fapt, pe mine mă interesează alt comportament al diodei: să nu conducă în sens invers, adică de la ieșire spre intrare. e superdiodă pentru că se comportă ca o diodă ideală, fără vrăjeli – ăăă, căderi vreau să zic – de tensiune.

pentru că la ieșirea superdiodei, am conectat un rezistor de 47KΩ în serie cu un capacitor de 10nF către polul pozitiv al sursei de alimentare. circuitul ăsta, diodă în serie cu rezistor în serie cu capacitor se cheamă integrator. e cu rimă Barbiliană. pentru că integrarea aia înseamnă de fapt integrarea semnalului la intrarea superdiodei în raport cu timpul. adică face un fel de medie pe unitatea de timp – încă puțin și îți explic și care e unitatea asta de timp. deci, când un semnal apare, trece prin diodă și prin rezistor și încarcă capacitorul. nu-l și descarcă, chiar dacă scade către 0V, pentru că dioda nu mai conduce.

am pus osciloscopul să-ți arăt cum arată semnalul pe capcitorul din circuitul integrator: frumos nu? se vede clar acum când bate toba. să nu uit, cea mai bună melodie pentru teste e Shed a lui Shebbe. semnalul are acum peste 3V – mai mult nu merge, că și superdioda amplifică de 100 de ori = 1MΩ/10KΩ. 3V pentru că exită căderi de tensiune în etajul de ieșire al amplificatorului operațional. dar deja nu mă mai interesează.

ca să văd dacă sunetul e convertit bine, folosesc un tranzistor 2N3904 în configurația cu colector-comun, numită și repetor-pe-emitor. în link e o simulare aproximativă a modului în care se comportă – apropo, îți recomand Multisim de la National Instruments, te ajută să vezi foarte rapid ce se întâmplă într-un circuit. configurația e de fapt un convertor de impedanță. adică, mai simplu, ce vede capacitorul în paralel e rezistorul de 100Ω conectat în serie cu ledul legat la emitorul tranzistorului, înmulțit ca valoare cu factorul de amplificare în curent al tranzistorului – se cheamă β sau hFE și se găsește în foaia de catalog a tranzistorului. în cazul ăsta, cam 200 în medie. adică 20KΩ. adică intervalul ăla de timp pentru integrare 20ms = 20KΩ × 10nF. vezi? n-am uitat.

semnalul pe led este cam mare pentru limita aia de 3,3V pentru intrarea analogică a ESP8266, așa că în paralel cu ledul și rezistorul – nu doar cu ledul, pentru că dacă-l conectezi acolo, tensiunea va fi constantă, egală cu căderea de tensiune pe led – conectez un divizor rezistiv care împarte la potențialul. doi rezistori de 4,7KΩ fac treaba asta. și gata prima parte. muzica aia ar trebui să facă ledul să lumineze pe ritm. dacă nu, verifică toate conexiunile. ai poze.

esp8266 witty, serios?

dacă mă gândesc acum, nu sunt chiar sigur că am făcut alegerea bună. de fapt, cred că a fost chiar proastă. pentru că prima chestie pe care trebuie să o faci cu Witty e să dezlipești de pe spatele plăcuței superioare un rezistor – vezi imaginea, e suficient să încălzești unul dintre terminale și să miști puțin cu cleștele de el – și să extragi și fotorezistorul de pe față – poți să-l și tai, dar dacă-l dezlipești poți să-l folosești în alt proiect.

înainte de a monta ESP8266 pe breadboard va trebui să pregătești conexiunile pentru VCC și GND prin jumperi și către ADC (2) și GPIO14/D5 (5), deoarece va fi puțin mai greu – ca să nu spun imposibil, că nu-mi place cuvântul – să conectezi terminalele respective acolo unde trebuie după ce modulul e plasat pe breadboard. mare atenție când conectezi jumperii, deoarece eu am avut neinspirația să inversez terminalele VCC și GND și să mă trezesc cu un miros suspect, indicator al faptului că pot liniștit să arunc modulul la gunoi.

pentru programarea în circuit a modului ESP8266 a trebuit să conectez terminalele RST (1), VCC (8), GND (9), D3 (12), RXD (15) și TXD (16) la plăcuța de bază, care conține convertorul USB/serial și circuitul care resetează modulul pentru programare.

la terminalul GPIO14/D5 (5) nu uita să conectezi printr-un rezistor de 4,7KΩ tranzistorul 2N3906. cred că ai observat că e o diferență între cele două tranzistoare. cel cu șase la final, adică ăsta, ultimul, este PNP. configurația în care sunt conectate cele două este aceeași, de «repetor-pe-emitor», în care potențialul electric pe emitor urmărește potențialul bazei, dar spre deosebire de configurația cu tranzistor NPN, potențialul crește cu o valoare constantă, suficientă cât să transforme nivelul HIGH specific 3,3V în nivel HIGH specific 5V. ai aici link cu simularea configurației cu colector-comun PNP.

zi de soft

bibliotecile software pe care le-am utilizat sunt cele clasice, pentru ESP8266 – pentru conexiunea la wi-fi și pentru pornirea unui server web, la care se adaugă biblioteca NeoPixel de la Adafruit. sunt mai multe care fac același lucru, dar cea de la Adafruit mi se pare ușor de folosit. așa că sketch-ul începe simplu cu:

#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 */
#include <ESP8266WebServer.h> /** biblioteca prin care ESP8266 pornește serverul web, nimic nou */
#include <Adafruit_NeoPixel.h> /** dar asta da, se ocupă de ledurile NeoPixel */

urmează să definesc constantele. pe lângă numele rețelei și parola de wireless, mai am nevoie de o constantă care să-mi spună câte leduri se găsesc în banda mea cu leduri. a mea are 150. pentru teste am folosit banda cu 8 leduri, așa că am modificat constanta asta corespunzător. mai jos o las 150, dar nu uita să o modifici. sincer, n-am testat să văd ce se întâmplă dacă inițializarea se face cu un număr diferit de leduri decât cele disponibile.

#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;
const uint16_t all_pixels = 150; /** aici e numărul de leduri din banda pe care o am conectată */

poate ți-ai dori ca numărul de leduri să fie configurabil, însă fără restartarea esp8266, nu cred că e posibil, mai ales că inițializarea ledurilor se face imediat după alimentare, prin funcția setup. probabil are merge o variantă cu salvarea setărilor în memoria EEPROM și restartarea circuitului, dar o las pentru un articol viitor. deci, variabile:

ESP8266WebServer server(80); /** aici definesc serverul web, folosind portul 80, adică normal */
Adafruit_NeoPixel pixels(all_pixels, D5, NEO_GRB + NEO_KHZ800); /** aici definesc banda cu leduri */
/**
 * parametrii sunt:
 * all_pixels, constantă de tip unsigned int, numărul de leduri din bandă
 * D5, este terminalul esp8266 witty la care e conectată banda cu leduri
 * NEO_GRB + NEO_KHZ800, sunt două constante care activează tipul dispunerii ledurilor în neopixel,
 *     și poate fi NEO_GRB sau NEO_RGB, în timp ce a doua constantă îmi spune viteza de comunicare
 *     NEO_KHZ800 pentru WS2812 sau NEO_KHZ400 pentru WS2811
 */

uint16_t _min = 1023; /** minimum înregistrat de convertorul ADC, 1023 petru că va scădea cu fiecare sunet */
uint16_t _max = 0; /** maximum înregistrată de convertorul ADC, 0 pentru că va crește cu fiecare sunet */
uint16_t _length = 32; /** pentru că sursa nu are suficient curent, voi aprinde doar 32 de leduri deodată */
uint16_t _hue = 40000; /** _hue poate fi între 0 și 65535 și reprezintă culoarea în modul de funcționare 1 */
uint8_t _mode = 0; /** reprezintă modul curent de funcționare */
uint16_t pixel = 0; /** o variabilă care indică pixelul curent */
uint16_t last_pixel = 0; /** o variabilă care indică precedentul pixel */

las pentru început doar două dintre funcțiile care se ocupă de clienții serverului web. adică cele implicite, care nu fac nimic important, ci doar îmi permit să testez conexiunea.

void handle_root() { /** functia asta spune ce se intampla cand accesez adresa direct in browser */
  /** adică nimic, doar îi spun clientului că nu sunt erori */
  server.send(200, "text/html", "");
}

void handle_404() { /** functia asta spune ce se intampla cand accesez o pagină care nu există */
  /** doar trimit către client un mesaj de eroare */
  server.send(404, "application/json", "{\"error\":1,\"message\":\"not found\"}");
}

și ajung la setup. inițializarea serverului web e standard. mă conectez la rețeaua wi-fi, aștept să fiu conectat, trimit adresa de IP către portul serial, după care inițializez serverul web. tot aici, inițializez și pixelii și îi stabilesc pe toți ca fiind stinși inițial. vei vedea că mare parte din cod seamănă cu cel de la robotul wi-fi. am totuși grijă ca ledul RGB instalat pe placa ESP8266 Witty să fie stins, făcând terminalele D5, D6 și D7 să aibă potențialul LOW (GND).

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 */
  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 */

  /** până aici, codul e foarte asemănător cu cel de la robotul wi-fi */  

  pixels.begin(); /** inițializez banda cu leduri */
  Serial.println("P"); /** trimit P către termina să știu că banda e inițializată */
  pixels.clear(); /** sting toți pixelii din bandă */
  Serial.println("C"); /** și trimit C prin serial să știu că am făcut asta */

  pinMode (D6, OUTPUT); /** stabilesc terminalele D6, D7, D8 ca ieșiri */
  pinMode (D7, OUTPUT); /** la terminalele astea sunt conectate leduri direct pe placuta witty */
  pinMode (D8, OUTPUT); /** asa ca am grija sa le sting */
  digitalWrite (D6, LOW); /** sting ledul conectat la D6 */
  digitalWrite (D7, LOW); /** sting ledul conectat la D7 */
  digitalWrite (D8, LOW); /** sting ledul conectat la D8 */
}

în funcția loop se întâmplă magia. semnalul analog este procesat de convertorul analog-digital (ADC), i se stabilește intervalul de variație apoi acest interval este transformat într-unul corespunzător fie nuanței reproduse de led – hue, în formatul de reprezentare al culorii HSV – fie intensității culorii – value, din reprezentarea HSV. în fiecare iterație a lui loop, aprind pixelul următor de pe bandă, avâng grijă să sting cel mai vechi pixel – să string un pixel e simplu, pun value = 0 în formatul HSV și gata. nu e chiar cel mai bun algoritm de afișare al sunetului și cu siguranță merge îmbunătățit. proiectul îl găsești și pe github, așa că poți oricând face un fork.

void loop() { /** functia asta se repetă la nesfârșit, după setup */
  /** am nevoie de câteva variabile locale */
  uint8_t
    b_map; /** variabilă pentru intensitatea pixelului când _mode = 1 */
  uint16_t
    a_val, /** variabilă pentru valoarea măsurată de ADC */
    a_map; /** variabilă pentru culoarea pixelului când _mode = 0 */
  uint32_t
    color; /** variabilă pentru culoarea pixelului, în format NeoPixel */
  server.handleClient(); /** aici văd dacă serverul web a primit ceva date */
  
  a_val = analogRead(A0); /** citesc în a_val valoarea din ADC; aici e nivelul sunetului */
  if (_min > a_val) { /** dacă nivelul sunetului e mai mic decât cel minim */
    _min = a_val; /** redefinesc valoarea minimă */
  }
  if (_max < a_val) { /** dacă nivelul sunetului e mai mare decât cel maxim */
    _max = a_val; /** redefinesc valoarea maximă */
  }

  switch (_mode) { /** în funcție de modul de operare */
    case 0: /** dacă modul e 0, atunci: */
      /**
       * găsesc punctul a_map în intervalul [0,65535] care împarte intervalul
       * în aceleași proporții în care a_val împarte intervalul [_min, _max]
       * în felul ăsta, culoarea pixelului e proporțională cu intensitatea sunetului
       */
      a_map = _max > _min ? (uint16_t) floor (65535.0 * (float)(a_val - _min) / (float) (_max - _min)) : 0; 
      color = pixels.gamma32(pixels.ColorHSV(a_map)); /** transform numărul în culoare */
      break;
    case 1:
      /**
       * găsesc punctul b_map în intervalul [0,255] care împarte intervalul
       * în aceleași proporții în care a_val împarte intervalul [_min, _max]
       * în felul ăsta, intensitatea luminoasă a pixelului e proporțională cu sunetul
       */
      b_map = _max > _min ? (uint8_t) floor (255.0 * (float)(a_val - _min) / (float) (_max - _min)) : 0; 
      /** pornind de la culoarea _hue, stabilesc culoarea pixelului având intensitatea b_map */
      color = pixels.gamma32(pixels.ColorHSV(_hue, 255, b_map));
      break;
  }
  
  pixels.setPixelColor(pixel, color); /** fac pixelul curent pixel de culoarea color */
  /**
   * consider că toți pixelii sunt așezați în cerc. pe cerc, calculez pixelul pe care
   * trebuie să-l șterg -> păstez ultimii _length pixel și îl șterg pe _length+1
   */
  last_pixel = (all_pixels + pixel - _length) % all_pixels;
  pixel = (pixel + 1) % all_pixels; /** trec la pixelul următor, în cerc */
  pixels.setPixelColor(last_pixel, pixels.Color(0,0,0)); /** sting ultimul pixel */
  pixels.show(); /** trimit datele către banda cu leduri */
}

versiunea intermediară a sketch-ului pentru IDE-ul Arduino o găsești pe github: 8266-neopixel-wifi-v1.ino.

încărcând programul pe ESP8266, în acest moment ledurile conectate la circuit se vor aprinde în ritmul dictat de sunet. totuși, mă interesează să pot să configurez modul în care ledurile se aprind – adică să aleg între _mode = 0 și _mode = 1, să pot să stabilesc culoarea atunci când _mode = 1, să stabilesc câți pixeli afișez de-o dată sau să resetez valorile _min și _max ale sunetului, pentru a potrivi circuitul cu mediul înconjurător.

comenzile le voi trimite prin HTTP POST către serverul web pornit de ESP8266, cu următorii parametri: mode, care poate fi 0 sau 1 și scrie valoarea în _mode, hue care poate fi orice număr între 0 și 65535 și stabilește _hue, length care poate fi orice număr între 0 și numărul maxim de pixeli minus unu și va modifica _length și reset care atunci când e on va reseta valorile pentru _min și _max.

am nevoie doar de o funcție care să proceseze parametrii și pe care să o apelez când se conectează un client. funcția o voi adăuga fix înainte de setup:

void handle_form () {
  long convert; /** variabilă temporară în care convertesc șiruri în numere */

  if (server.method() != HTTP_POST) { /** dacă cererea nu e HTTP POST */
    server.send(405, "application/json", "{\"error\":1,\"message\":\"method not allowed\"}");
    /** trimit către client codul de eroare asociat cu method not allowed, 405 */
    return;
  }
  for (uint8_t c = 0; c < server.args(); c++) { /** scanez toți parametrii */
    if (server.argName(c) == String("mode")) { /** dacă am găsit mode */
      convert = server.arg(c).toInt(); /** în convert pun valoarea parametrului */
      /** dar dacă cumva valoarea e mai mare de 1, pun în _mode 1 */
      _mode = convert < 0 ? 0 : (convert > 1 ? 1 : convert);
    }
    if (server.argName(c) == String("hue")) { /** dacă am găsit hue */
      convert = server.arg(c).toInt(); /** convertesc valoarea lui în număr */
      /** iar dacă numărul e mai mare de 65535, pun în _hue 65535 */
      _hue = convert < 0 ? 0 : (convert > 65535 ? 65535 : convert);
    }
    if (server.argName(c) == String("length")) { /** dacă am găsit length */
      convert = server.arg(c).toInt(); /** convertesc valoarea lui în număr */
      /** dacă numărul e mai mare decât all_pixels - 1, pun în _length all_pixels - 1 */
      _length = convert <  0 ? 0 : (convert > all_pixels - 1 ? all_pixels - 1 : convert);
    }
    /** dacă găsesc reset cu valoarea on, reset valorile pentru _min și _max */
    if (server.argName(c) == String("reset") && server.arg(c) == String("on")) {
      _min = 1023;
      _max = 0;
    }
  }
  /** trimit ca răspuns valorile nou stabilite */
  server.send (200, "application/json",
    "{\"mode\":" + String(_mode) +
    ",\"length\":" + String(_length) +
    ",\"hue\":" + String(_hue) +
    ",\"min\":" + String(_min) +
    ",\"max\":" + String(_max) +
    "}");
}

mai am doar de asociat funcția care procesează parametrii trimiși către serverul web, modificând setup și inserând legătura imediat după cea pentru procesarea paginii principale – handle_root.

void setup() {
  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);
  server.on("/rpc/", handle_form); /** aici asociez handle_form cu serverul web */
  server.onNotFound(handle_404);
  server.begin();
  Serial.println("S");
  
  pixels.begin();
  Serial.println("P");
  pixels.clear();
  Serial.println("C");

  pinMode (D6, OUTPUT);
  pinMode (D7, OUTPUT);
  pinMode (D8, OUTPUT);
  digitalWrite (D6, LOW);
  digitalWrite (D7, LOW);
  digitalWrite (D8, LOW);
}

versiunea finală a sketch-ului pentru IDE-ul Arduino – sau dacă îți place mai mult, pentru Visual Studio Code o poți descărca de pe github: 8266-neopixel-wifi-final.ino. am pus-o aici pentru că și mie mi-ar fi lene să o scriu.

lampa pentru unghii

în momentul ăsta, circuitul de pe breadboard funcționează. dacă ai răbdare, vei observa că numărul de leduri influențează negativ semnalul de intrare: ledul indicator de nivel audio va lumina aproape constant, indiferent dacă aude ceva sau nu. asta se întâmplă din cauza contactelor imperfecte și a curenților relativ mari. e momentul pentru o îmbunătățire mai permanentă: cablajul imprimat.

cablajul ăsta e bucata aia de plastic – nu e chiar plastic, dar are aspect – verde – bine, nu doar verde, albastră, neagră, roșie, mov – cu desene, din interiorul oricărui aparat electric. e de fapt o bucată de fibră din sticlă – adică o țesătură din fibră de sticlă acoperită cu un fel de poxypol – peste care se așează o foiță subțire din cupru, care apoi e decupată pentru a desena conexiuni între componente. poate pare complicat de realizat, dar e cât se poate de simplu.

folosind Autodesk Eagle poți să alegi toate componentele de care ai nevoie, să le așezi virtual pe plăcuța ta și apoi să le conectezi între ele cu trasee din cupru. când ești gata, pur și simplu printezi rezultatul pe o hârtie de calc. desenele le-am făcut eu și le găsești aici.

într-un articol viitor îți explic cum se face, cu toate că nivelul la care sunt e cel mult de amator-începător – ai nevoie doar de puțină răbdare și să-ți explic puțin regulile de bază. când deschizi fișierul esp8266-lights-v3.brd descărcat – ai grijă să ai descărcate în același director și fișierul esp8266-lights-v3.sch – apeși prima dată pe butonul Ratsnet, apoi din meniul View, Layer Settings ascunzi toate straturile cu excepția Bottom (albastru), Pads (verde) și Dimension (galben). apeși CTRL și P în același timp și bifezi la Options Solid și Black – doar astea două, da?, Scale Factor ai grijă să fie 1 și ambele Border Left și Border Top le stabilești la 1 inch. nu te descurci, sau n-ai chef de Eagle? nicio problemă. ai desenul traseelor aici, formatat, în format A4. ai nevoie doar de un cititor de PDF-uri care să știe să nu scaleze pagina atunci când o printezi.

decupezi ce-ai printat la dimensiunea ramei foto, în așa fel încât desenul să fie centrat. apoi, din rama foto vei folosi doar geamul și bucata de carton din spate – va trebui să rupi piciorul de suport din carton. stingi lumina – poți să lași doar o lumină slabă, care să nu bată direct către zona ta de lucru. sau dacă chiar ai nevoie de lumină, o lampă roșie e perfectă. așezi plăcuța de circuit imprimat foto cu partea albastră în sus pe cartonul ramei. dezlipești plasticul albastru protector de pe plăcuță. plasezi peste partea metalică desenul cu fața printată în jos. acoperi cu geamul și prinzi totul cu două agrafe de hârtie, simetric. vezi să fie prinse bine și desenul să acopere corect bucata metalică. dimensiunea e fix pentru o plăcuță standard de circuit imprimat de 100×50mm.

introduci rama pregătită în felul ăsta, cu geamul în sus, în lampa de unghii. în funcție de dimensiunea geamului, s-ar putea să fie nevoie să îndepărtezi capacul de jos al lămpii. prima dată când am făcut asta am testat mai multe intervale de timp, dar am văzut că timerul lămpii e perfect. de obicei inserez rama și apăs butonul, iar după ce se termină, întorc rama foto cu capătul celălalt către interiorul lămpii și mai expun o dată, de data asta pentru numai 10-15 secunde. radiația UV polimerizează stratul fotosensibil și imprimă desenul pe plăcuță. să nu arunci foaia de calc, o poți refolosi de câte ori vrei.

în timpul expunerii, găsești o tăviță din plastic – fie iei una dedicată pentru proiecte de tipul ăsta, fie una de unică folosință. eu folosesc o cutie pentru înghețată. neapărat mai ai nevoie de o pereche de mănuși de cauciuc. substanțele pe care ți le indic în continuare sunt toxice, așa că te rog să ai grijă.

pune-ți mănușile! în cutia de plastic măsori fie 50g de mr. proper lichid – eu folosesc un cântar de bucătărie pe care așez direct cutia, fie 5g de mr. proper granule. mr. proper pentru desfundat chiuvete, da? n-ai uitat de mănuși, nu? completezi conținutul cutiei cu 500ml de apă rece de la robinet. nu uita să amesteci continuu. mr. proper e practic făcut din sodă caustică. din aia care se folosește la săpun. atenție la mâini! sper că ai mănuși!

odată expunerea terminată, iei plăcuța și o scufunzi, folosind mănuși!, în soluția de mr. proper. în câteva secunde vei vedea desenul printat prinzând contur. agită plăcuța în tot acest timp și ai grijă să nu atingi suprafața cu desenul. și nici să nu o ții prea mult în soluție. maximum un minut. când e gata, pune plăcuța sub jet de apă de la robinet. dacă nu faci mai multe plăcuțe chiar acum, poți să arunci – folosind mănuși! – soluția în chiuvetă. până la urmă pentru asta a fost făcută, nu?

cu desenul pregătit – folosind mănuși! – pune într-o altă tăviță clorură ferică încât să treacă cu jumătate de centimetru peste plăcuța cu desenul. ai grijă! clorura ferică e foarte corozivă și pătează aproape orice! scoate-ți mănușile și uită-te la un serial timp de o oră. când te întorci, pune-ți din nou mănușile și verifică plăcuța. desenul ar trebui să fie numai din cupru, arămiu, iar între traseele desenată să nu mai fie nimic. dacă mai sunt urme de material între trasee, lasă plăcuța în soluție pentru încă 10-15 minute. ajută mult dacă încălzești soluția în baie de apă. ai mare grijă pentru că lichidul pătează aproape orice, inclusiv metalul! sper că ai mănuși!

pentru a putea arunca lichidul în siguranță, neutralizează-l înainte cu bicarbonat de sodiu. vei știi că e neutralizat, atunci când bicarbonatul nu se mai dizolvă în lichid și nu mai apar bule. durează mult, poate și câteva zile. partea bună e că soluția o poți folosi pentru destul de multe plăcuțe și nu expiră așa repede. probabil o tăviță cu capac e cea mai bună. eu din nou, folosesc o altă cutie pentru înghețată. ai grijă unde o depozitezi! e toxică!

așa. acum plăcuța se numește corodată. desenele sunt vizibile, nu sunt urme de cupru între trasee și ai clătit-o cu apă din abundență. pentru a înlătura stratul rezistent la coroziune, șterge-o cu diluant pentru unghii. când mă gândesc că jumătate din proces a inclus cuvântul «unghii», îmi vin în minte numai saloanele de apartament și condițiile în care-și desfășoară activitatea. dar ce știu eu?

ultimul pas în pregătirea plăcuței este găurirea ei. cu o minibormașină – poate fi de orice fel, eu am una de la Dremmel, cu stand – perforezi locurile special marcate pe plăcuță. majoritatea cu un burghiu de 0.8mm. am observat că deși mai mic e mai bine, unele componente au terminale prea groase. găurile pentru baretele cu pini le poți lărgi cu un burghiu de 1.2mm. perforarea o fac întotdeauna dinspre partea cu traseele de cupru – spațiul pentru găuri te ajută în ghidarea burghiului, iar după ce termin, prefer să spăl plăcuța cu detergent de vase și burete abraziv pentru a înlătura eventualele așchii. o șterg cu un șervețel de bucătărie și gata.

e timpul pentru lipeli

cum plăcuța e pregătită, încep să transfer componentele de pe breadboard pe ea. uite și diagrama de amplasare a componentelor, care să te ghideze, în format PDF, pentru print. încep cu rezistorii. în poze, i-am pus pe toți deodată. nu te sfătuiesc să faci asta. transferă câte unul de-o dată. imediat ce l-ai transferat, îl lipești cu aliaj și ciocanul de lipit și tai cu cleștele sfic terminalele cât mai aproape de plăcuță. dacă vrei, poți să le păstrezi pe post de jumperi, însă eu nu-mi bat capul cu ele.

după rezistori, urmează capacitorii ceramici. nu știu dacă observi, dar ordinea e dictată de înălțimea componentelor. urmează soclul pentru circuitul integrat, tranzistoarele, condensatorul electrolitic – aici, dacă vrei să păstrezi dimensiunea poți folosi unul cu tantal, deși e cam de 10 ori mai scump – baretele cu pini mamă și pinii pentru alimentare.

vei observa că pe diagramă – și pe plăcuță – e spațiu pentru un rezistor de 0Ω. aici e de fapt locul pentru un jumper, pe care-l poți folosi dacă vrei să alimentezi ledurile separat de restul circuitului. în cele mai multe situații, poți să-l înlocuiești cu un simplu fir și scapi de o problemă. eu am ales să pun două terminale pe care să le conectez între ele cu un fir cu conectoare dupont mamă-mamă.

fără a conecta alimentarea, montezi circuitul integrat LM358B în soclu și ESP8266 Witty în baretele lui, cu antena wi-fi îndreptată în partea opusă conectorului pentru leduri. nu uita de leduri. polaritatea e 5V, date și GND, dacă ții plăcuța cu GND către tine.

mai e ceva. microfonul nu l-am lipit pe placă. am preferat să pun alte două terminale pentru a conecta microfonul prin două fire dupont mamă-mamă. nu-ți recomand. semnalul fiind mic și amplificarea mare, fie folosești cablu pentru microfon, fie fire foarte scurte, răsucite, fie lipești microfonul direct pe placă. orice fir mai lung, funcționează ca antenă și ajungi ca ledurile să nu fie controlate de lumină, ci de undele radio din casă.

pentru alimentare, te sfătuiesc să folosești o sursă potentă în comutație. rezultate bune am obținut cu una de 5V 4.3A. dar depinde mult de banda cu leduri pe care o folosești, câte are pe metru și cât de lungă e. pentru cele mai scurte, un încârcător de 5V/2.4A pentru telefon ar trebui să fie suficient și are avantajul că poți alimenta tot circuitul prin conectorul micro USB al modului ESP8266 Witty.

(alte) referințe:

  1. LM358B – foaie de catalog
  2. 2N3904 – foaie de catalog
  3. ESP8266 Witty – diagrama conexiunilor

conflict de interese:

în articole apar des conexelectronic – principala sursă de componente discrete, foarte profesioniști, optimusdigital și robofun – magazine cu module electronice pentru că sunt din bucurești. câteodată – dar mai rar – cumpăr chestii de la clește – galați și ardushop – sibiu. prefer ce e în bucurești pentru că în caz de urgență, când fac un proiect pentru facultate, pot să primesc repede ce am nevoie. componente electronice mai cumpăr uneori de la adelaida – craiova – aproape cel mai bun stoc, doar că serviciile sunt puțin cam lente, mouser – polonia, foarte bine aprovizionați, dar cu timp de livrare de câteva zile, olimex – bulgaria – care au module interesante, unice și watterott – germania – cu prețuri excelente și servicii premium, doar că și-au schimbat site-ul și stocul a rămas în urmă. acest articol nu este susținut de niciunul dintre ele.

bogdan

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
bogdan

bogdan » arduino: senzor de culoare

10:50 pm on Apr 29, 2019 | read the article | tags:

în urma rugăminții profesorului Stamatin, de câțiva ani țin un curs la Facultatea de Fizică din Măgurele. dacă inițial numele «modelare și simulare» ascundea ecuații matematice și metode numerice, recent am înlocuit diferențialele cu Arduino, în ovațiile celor câtorva studenți care frecventează cursul. dotările limitate m-au făcut să devin creativ cu materialele de curs, iar rezultatul mi s-a părut suficient de interesant pentru a-l reproduce aici. așa că:

cum funcționează? (puțină teorie)

pe lângă simbolurile folclorice și religioase, un curcubeu poartă și dovada că lumina albă are în componență o multitudine de culori, pornind cu roșu și sfârșindu-se cu violet. când privești un obiect, o parte dintre aceste culori sunt reflectate către fotoreceptorii din retină, în timp ce restul sunt absorbiți. în felul ăsta, un obiect pare alb, dacă reflectă toate culorile înapoi către privitor, pare negru dacă nu reflectă (aproape) nimic și pare verde dacă absoarbe toate celelalte culori cu excepția culorii verzi, pe care o reflectă.

un mic experiment pe care-l poți face pentru a observa mai bine acest fenomen este să privești un obiect verde sau albastru iluminat doar de un led roșu. culoarea obiectului va fi foarte apropiată de negru, semn că obiectele verzi sau albastre absorb mare parte din lumina roșie incidentă.

lumina este radiație electromagnetică, foarte asemănătoare cu ce încălzește mâncarea în cuptorul cu microunde sau ce poartă ultimul snapchat către prietenii tăi. diferența este dată de frecvența cu care acestea oscilează: 2.450.000.000 oscilații pe secundă pentru cuptorul cu microunde, între 700.000.000 și 2.600.000.000 oscilații pe secundă pentru telefoane mobile și între 394.460.000.000.000 și 768.700.000.000.000 oscilații pe secundă pentru lumină. numerele sunt mult prea mari pentru a fi utilizate în practică, așa că în locul numărului de oscilații pe secundă se folosește lungimea de undă, mai exact distanța parcursă de undă într-un interval de timp cât durează o singură oscilație:
$$\lambda = \frac{c}{f}$$
,unde:
λ – e lungimea de undă, exprimată în metri;
c – e viteza cu care se deplasează undele, în acest caz, viteza luminii, ~3×108 metri pe secundă;
f – reprezintă numărul de oscilații pe secundă și se măsoară în herzi sau secunde-1;

fără a intra în detaliile care mă depășesc legate de nuanțe, intervalele de lungime de undă asociate fiecărei culori sunt 415 ± 35nm, 475 ± 25nm, 535 ± 35nm, 585 ± 15nm, 615 ± 15nm, 695 ± 65nm.

o ultimă formulă fizică vine din suprapunerea a două culori. atunci când oscilațiile cu frecvențe diferite se suprapun, apare fenomenul numit bătăi: pentru un observator extern va deveni dominantă o oscilație cu frecvența media aritmetică a frecvențelor oscilațiilor componente, cu amplitudinea oscilând cu frecvența cât jumătate din diferența absolută a frecvențelor oscilațiilor componente. pentru lungimi de undă, formula implică media armonică:
$$\lambda_{rezultat} = \frac {2 \lambda_{1} \lambda_{2}}{\left|\lambda_{1} \pm \lambda_{2}\right|}$$
,unde:
λrezultat – reprezintă lungimea de undă rezultată;
λ1,2 – reprezintă lungimile de undă componente;

pornind de la formula asta și de la particularitățile ochiului uman, combinând fascicule cu lumină cu lungimea de undă corespunzătoare culorilor roșu, verde și albastru poți obține majoritatea culorilor din spectrul vizibil.

cum construiesc un senzorul de culoare?

trecând peste teoria oarecum plictisitoare de mai nainte, drept senzor de culoare voi folosi un simplu fotorezistor (2 lei, 1,95 lei) – un dispozitiv care își modifică conductivitatea în funcție de cantitatea de lumină incidentă. conductivitatea fotorezistorului variază însă și cu lungimea de undă, astfel, dacă sensibilitatea maximă este în jurul culorii verzi, pentru a obține același efect, roșu și indigo au nevoie de fascicule de câteva ori mai puternice, ca în figura care urmează. un alt aspect pe care îl voi reține din foaia de catalog a fotorezistorului este timpul de răspuns maxim de 30ms.

CdS Photorezistor Sensitivity

principiul de funcționare al senzorului va fi următorul: iluminăm obiectul a cărui culoare o voi determina cu un fascicul de lumină de culoare cunoscută, măsor cât din ea se reflectă către senzor și compar cu valoarea dată de iluminarea ambientală. în funcție de rezultat, voi stabili cu exactitatea culoarea.

în lumina discuției de mai sus, voi alege cele trei culori standard: roșu, verde și albastru pe care le folosesc pe rând pentru a ilumina obiectul, măsurând pentru fiecare intensitatea luminii reflectate. dacă dintre cele trei culori, roșu, verde și albastru, obiectul va reflecta mai mult verde, atunci va fi verde, dacă reflectă și roșu și verde în același timp, cel mai probabil va fi galben.

pentru sursele de lumină cu culoare stabilită, voi folosi LED-uri cu lentile colorate în culorile roșu, verde și albastru (50 de lei / 500 bucăți). bineînțeles că poți utiliza mai multe culori de LED-uri. cu puțină răbdare și costuri ceva mai mari, poți găsi cel puțin 16 nuanțe diferite de LED, inclusiv infraroșu (~900nm) și ultraviolet (~280nm), însă va trebui să folosești un senzor mai bun de intensitate luminoasă (cine știe, poate un viitor articol despre asta, până atunci ai aici unul ceva mai avansat).

circuitul este simplu: conectez fiecare dintre LED-uri la unul dintre terminalele digitale 3, 4 și respectiv 5 ale Arduino, în timp ce fotorezistorul formează un divizor rezistiv împreună cu un rezistor de 47KΩ conectat către Vcc (+5V), al cărui punct intermediar e conectat la terminalul analogic A0.

Color Senzor Schematics

notă
modul de aranjare al componentelor pe placa de experimente încearcă să reproducă cât mai fidel realitatea. fotorezistorul e înconjurat de LED-uri pentru a reflecta lumina sub aproximativ același unghi, făcând ca poziția obiectului din fața senzorului să nu influențeze major rezultatul măsurătorii. orice altă poziționare e posibilă, doar că drumul optic între LED-uri și fotorezistor va avea dimensiuni și forme diferite, influențând intensitatea luminoasă recepționată de senzor.
Color Sensor

cum măsor intensitatea culorilor?

pentru început, definind niște constante și un tip de date pentru valoarea măsurată de senzor. constantele sunt:

#define RED_LED 3 // <-- terminalul la care e conectat LED-ul rosu
#define GREEN_LED 4 // <-- terminalul la care e conectat LED-ul verde
#define BLUE_LED 5 // <-- terminalul la care e conectat LED-ul albastru
#define SENSOR_PIN A0 // <-- terminalul analog care masoara tensiunea electrica pe senzor
#define SAMPLES 20 // <-- numarul de masuratori analogice; explicatia urmeaza

tipul de date de care vorbeam este o structură în care voi stoca valoarea măsurată cu LED-urile stinse, pe care o voi numi base (de la bază) și valorile pentru fiecare dintre leduri red, green, blue pentru roșu, verde și respectiv albastru. typedef e cuvântul cheie care mă ajută să definesc tipul color (de la sfârșit). tipul e definit ca o structură definită cu struct, al cărei identificator este _color și ale cărei componente sunt toate de tip întreg. definesc tipul ăsta de date pentru că devine foarte ușor de folosit când e întors sau folosit ca parametru în funcții.

typedef struct _color {
  int base;
  int red;
  int green;
  int blue;
} color;

în bucata de cod de mai sus am introdus constanta SAMPLES ca fiind 20. citirea datelor folosind convertorul analog-digital din Arduino nu e întotdeauna stabilă, așa că voi citi SAMPLES mostre, pe care le voi media pentru a obține o valoarea mai apropiată de valoarea reală a tensiunii măsurate. mai trebuie să țin cont că rezistența electrică a fotorezistorului scade odată cu creșterea intensității luminoase. cum fotorezistorul este conectat către GND, tensiunea pe acesta va fi direct proporțională cu rezistența electrică, adică va scădea la rândul ei cu creșterea intensității fluxului luminos incident. acest lucru e o mică problema în interpretarea datelor, preferând o funcție de conversie crescătoare. ca orice problemă legată de Arduino, am două variante: prima, fizică, în care inversez locul rezistorului și fotorezistorului în divizor, conectând fotorezistorul către Vcc (+5V); a doua, în cod, în care scad din 1023 (valoarea maximă produsă de convertorul analog-digital), valoarea măsurată. cum pregătirea mea de bază e de matematician, am ales-o pe a doua.

int sensor_read () {
  long output = 0; // <-- s-ar putea sa depasesc valoarea maxima pentru intregi simpli
  delay (30); // <-- mai tii minte? senzorul reactioneaza cu 30ms intarziere
  for (byte c = 0; c < SAMPLES; c++) { // <-- citesc 20 de mostre
    output += analogRead (SENSOR_PIN); // <-- adun valoarea masurata pentru calculul sumei
  }
  return 1023 - (int) (output / SAMPLES); // <-- din 1023 scad media masuratorilor
}

cum analogRead produce la iesire un întreg între 0 și 1023, dacă numărul de mostre culese e mai mare de 30, valoarea maximă pentru tipul de date int (întreg stocat pe 2 octeți, având valoarea maximă aprox. 32 de mii) este depășită, rezultatul fiind trunchiat. din acest motiv am utilizat pentru sumă tipul de date long. in cadrul liniei care contine return, am folosit (int) pentru a converti output / SAMPLES din tipul de date long (întreg stocat pe 4 octeți, având valoarea maximă aprox. 2 miliarde) în tipul de date int. în mod obișnuit operația nu e necesară, conversia făcându-se automat. prefer totuși aceast mod de lucru pentru a putea urmări cu atenție tipurile de date folosite și pentru a fi sigur că memoria folosită e suficientă pentru valorile folosite.

până acum am funcția necesară măsurării unei singure valori. pentru citirea tuturor valorilor necesare voi începe cu citirea valorii de bază, urmată pe rând de aprinderea LED-ului corespunzător culorii, efectuarea unei măsurători și apoi stingerea lui, pentru fiecare culoare.

color read_color () {
  color c; // <-- color e noul tip de date definit

  c.base = sensor_read (); // <-- citesc valoarea de baza (lumina ambientala)
                           // fiecare componenta a lui color o accesez cu .nume_componenta
                           // c.base face referire la componenta base din tipul de date color
  digitalWrite (RED_LED, HIGH); // <-- aprind LED-ul rosu
  c.red = sensor_read (); // <-- citesc intensitatea fasciculului rosu reflectat
  digitalWrite (RED_LED, LOW); // <-- sting LED-ul rosu
  digitalWrite (GREEN_LED, HIGH); // <-- repet procesul pentru LED-ul verde
  c.green = sensor_read ();
  digitalWrite (GREEN_LED, LOW);
  digitalWrite (BLUE_LED, HIGH); // <-- repet procesul pentru LED-ul albastru
  c.blue = sensor_read ();
  digitalWrite (BLUE_LED, LOW);

  return c;
}

după ce citesc valorile, mi-ar plăcea să le trimit către interfața serială, pentru procesarea ulterioară și pentru că mi-e mai comod, definesc o funcție care ia ca parametru o structura din noul tip definit:

void print_color (color c) {
  Serial.print (c.base); // <-- trimit catre portul serial valorile
  Serial.print (" "); // <-- separate de cate un spatiu
  Serial.print (c.red);
  Serial.print (" ");
  Serial.print (c.green);
  Serial.print (" ");
  Serial.println (c.blue); // <-- la final, trec pe randul urmator cu "ln" din "println"
}

urmează inițializarea terminalelor Arduino și a portului serial, pentru culegerea datelor.

void setup(){
  pinMode (RED_LED, OUTPUT); // <-- configurarea terminalului RED_LED (3) ca iesire
  pinMode (GREEN_LED, OUTPUT);
  pinMode (BLUE_LED, OUTPUT);

  digitalWrite (RED_LED, LOW); // <-- stabilirea potentialului terminalului RED_LED (3) la 0V
  digitalWrite (GREEN_LED, LOW);
  digitalWrite (BLUE_LED, LOW);
  Serial.begin (9600); // <-- pornesc interfata seriala
}

și ultima bucată de cod, care citește culoarea obiectului din fața senzorului, o dată pe secundă.

void loop() {
  color c; // <-- folosesc noul tip de date definit
  c = read_color (); // <-- citesc culoarea
  print_color (c); // <-- afisez rezultatele
  delay (1000); // <-- astept o secunda (1000ms)
}

notă
rezultatele obținute nu se traduc direct în culori. o metodă simplă pentru detectarea culorii o reprezintă plasarea unei coli de hârtie în fața senzorului și determinarea valorilor pentru cele trei canale. culoarea hârtiei fiind albă, intensitatea fluxului reflectat este maximă pentru fiecare canal. folosind valorile maxime măsurate pentru fiecare canal, din care scad intensitatea de bază, pot să determin pentru fiecare culoare ce procent din fluxul inițial se reflectă. în felul ăsta pot folosi principiul descris la începutul articolului: valori egale pentru roșu și verde și o valoare mult mai mică pentru albastru, înseamnă culoarea galbenă.
$$I_{relativa, culoare} = \frac{I_{culoare} - I_{baza}}{I_{alb, culoare} - I_{alb, baza}}$$
,unde:
culoare - e una dintre culorile roșu, verde sau albastru;
Irelativa, culoare - e intensitatea masurata relativa pentru culoarea culoare; va fi un numar real intre 0 și 1;
Iculoare - e intensitatea masurata pentru pentru culoarea culoare, pentru obiectul a cărui culoare vreau să o determin; un număr întreg între 0 și 1023;
Ibaza - e intesitatea de bază măsurată pentru obiectul a căreui culoare vreau să o determin; un număr întreg între 0 și 1023;
Ialb, culoare - e intensitatea măsurată pentru culoarea culoare, având coala de hârtie în fața senzorului; un număr întreg între 0 și 1023;
Ialb, baza - e intensitatea măsurată de bază, având coala de hârtie în fața senzorului; un număr întreg între 0 și 1023.

bogdan

bogdan » arduino: un fel de theremin (senzor capacitiv de poziție)

06:46 pm on Dec 30, 2018 | read the article | tags:

în urma rugăminții profesorului Stamatin, de câțiva ani țin un curs la Facultatea de Fizică din Măgurele. dacă inițial numele «modelare și simulare» ascundea ecuații matematice și metode numerice, recent am înlocuit diferențialele cu Arduino, în ovațiile celor câtorva studenți care frecventează cursul. dotările limitate m-au făcut să devin creativ cu materialele de curs, iar rezultatul mi s-a părut suficient de interesant pentru a-l reproduce aici. așa că:

ce e un theremin?

thereminul e un instrument muzical derivat din primele cercetări cu privire la senzorii de proximitate: două antene controlează tonul și intensitatea sunetelor prin modificarea distanței între acestea și mâinile cânterețului. principiul de funcționare este realtiv simplu: două conductoare separate de un izolator alcătuiesc un capacitor, lucru valabil pentru orice materiale conductoare, cum ar fi o antenă din metal sau corpul uman. la fel ca în cazul detectorului de metale discutat anterior, conectând antena în paralel cu circuitul oscilant al unui oscilator, frecvența acestuia se va modifica prin mișcarea mâinii în raport cu antena prin modificarea capacității.

variația tonului se obține prin mixarea semnalului oscilatorului cu frecvența variabilă cu cel al unui oscilator cu frecvența stabilă, dând naștere fenomenului de bătăi – generarea unui semnal cu frecvența cât jumătate din diferența celor două frecvențe inițiale. pentru variația intensității, e suficientă integrarea semnalului obținut din cel de-al doilea oscilator, folosind valoarea acestuia pentru controlul intensității.

cum detectez poziția mâinii cu arduino?

ideea e simplă și vine din descrierea anterioară: măsor capacitatea condensatorului alcătuit dintr-o antenă și mâna mea. formula fizică de la care pornesc este simplă – un capacitor alcătuit din două plăci paralele are capacitatea invers proporțională cu distanța dintre plăci, urmărind formula:

$$ C = \epsilon \frac {A} {d} $$

, unde:
C este capacitatea electrică exprimată în Farazi;
ε este permitivatea mediului care se găsește între cele două plăci paralele (Farazi / metru);
A este suprafața comună a celor două plăci, măsurată în metri pătrați;
d este distanța între cele două plăci, măsurată în metri;

într-un articol precendent, am arătat cum poate fi măsurată capacitatea. pe scurt, dacă aplic o diferență de potențial pe plăcile capacitorului, acesta va păstra diferența de potențial atât timp cât nu va curge curent electric între cele două plăci. conectând un rezistor în paralel cu terminalele capacitorului, circuitul se închide, iar tensiunea electrică dintre plăcile condesatorului va scădea exponențial, respectând următoarea formulă:

$$ U_{capacitor}(t) = U_{initial} e^{- \frac {t} {RC}} $$

, unde:

Ucapacitor(t) este tensiunea electrică dintre plăcile capacitorului, la momentul t de la închiderea circuitului, în Volți;
Uinitial este tensiunea electrică dintre plăcile capacitorului, imediat înainte de a închide circuitul, în Volți;
t este timplul scurs de la momentul închiderii circuitului, măsurat în secunde;
R este rezistența electrică a rezistorului folosit pentru a închide circuitul, exprimată în Ohmi;
C este capacitatea electrică a capacitorului, exprimată în Farazi;

urmărind ultima formulă, timpul în care tensiunea electrică între plăcile capacitorului atinge o anumită valoare depinde de produsul între capacitate și rezistența electrică, denumit datorită unităților de măsură implicate, constanta de timp a circuitului. luând spre exemplu o capacitate de 10pF, un rezistor de 4,7MΩ, o tensiune inițială de 5V și una finală de 2,1V, timpul în care tensiunea între plăcile condensatorului atinge acest prag este:

$$ t = RC \times log \left(\frac {U_{initial}} {U_{capacitor}}\right),\\ t = 10^{-11} (F) \times 4,7 \times 10^6 (\Omega) \times log \left(\frac {5(V)}{2,1(V)}\right) = 1,77 \times 10^{-5} (s)$$

valorile nu sunt alese întâmplător. fiecare instrucțiune Arduino se execută în aproximativ 62,5ns, asta înseamnă că pot executa 283 instrucțiuni în timpul necesar descărcării capacitorului, mai mult, 5V e tensiunea de alimentare a Arduino, disponibilă la VCC sau pe oricare dintre terminale când sunt configurate ca ieșiri și stabilite drept HIGH, 2,1V e tensiunea sub care un terminal configurat ca intrare digitală este considerat ca fiind LOW, iar 10pF e capacitatea minimă a unei mâini față de un electrod metalic cu suprafața cel puțin egală, la distanța de aproximativ 10cm.

deja în acest moment cred că e destul de clar ce urmează să fac: la unul dintre terminalele digitale ale Arduino voi conecta o antenă, care va fi legată printr-un rezistor cu valoarea de 4,7MΩ către GND. în acest fel obțin circuitul alcătuit din capacitor și rezistorul prin care se descarcă. pentru a încărca inițial capacitorul, voi stabili potențialul terminalului digital la 5V pentru 2ms, de aproximativ 100 de ori constanta de timp a circuitului. imediat, voi transforma terminalul în intrare digitală și voi număra câte operații pot efectua până când tensiunea electrică între antenă și GND scade sub 2,1V, intervalul fiind direct proporțional cu capacitatea și invers proporțional cu distanța de la mână la antenă.

void setup() {
  Serial.begin (9600); // in primul rand, pornesc interfata seriala pentru a citi valorile masurate
}

void loop() {
  uint16_t t = 0; // voi folosi un contor cu dimensiunea de 16 biti
  pinMode (2, OUTPUT); // stabilesc terminalul 2 al Arduino ca fiind iesire digitala
  digitalWrite (2, HIGH); // stabilesc potentialul terminalului 2 la 5V
  delay (2); // astept 2ms pentru a incarca capacitorul
  pinMode (2, INPUT); // comut terminalul 2 al Arduino ca intrare digitala
  while (digitalRead (2) == HIGH && t < 0xFFFF) { // atat timp cat terminalul 2 are tensiunea peste 2,1V,
                                                  // dar in acelasi timp contorul nu a ajuns la capat,
                                                  // capatul fiind FFFF(baza 16) = 65535(baza 10)
    t++; // incrementez contorul de timp cu o unitate
  }
  // in momentul asta, t contine timpul in care s-a descarcat capacitorul format de antena si corp
  pinMode (2, OUTPUT); // configurez terminalul 2 ca iesire digitala
  digitalWrite (2, LOW); // stabilesc potentialul antenei ca fiind 0V
  delay (498); // astept 498 de ms, in mare pentru ca vreau sa pot citi cu usurinta datele
               // si ca in perioada asta sa anulez orice sarcina stocata in antena
  Serial.println (t); // trimit prin interfata seriala, valoarea obtinuta
}

valorile pe care le-am obținut folosind ca antenă o bucată de 25×25cm din folie alimentară din aluminiu au fost următoarele:

Capacitive Sensor Results

notă:

din datele obținute se vede o dependență clară a numărului obținut în funcție de distanța mâinii. cu toate acestea, valorile obținute prezintă zgomot, care influențează negativ modul în care poate fi folosită informația. o metodă relativ simplă de atenuare a zgomotului este aceea de a colecta mai multe valori decât este necesar și în locul unei singure măsurători să utilizez media valorilor obținute. în acest fel, zgomotul este atenuat. din nefericire, îmbunătățirea repetabilității afectează precizia măsurătorilor.

cum folosesc datele obținute?

având în vedere zgomotul observat și faptul că sunt mai mulți factori care influențează valorile componentelor din circuit, cum ar fi valoarea tensiunii de alimentare, valoarea pragului minim pentru ca un terminal să fie considerat LOW, dar și lungimea firelor sau umiditatea aerului, primul pas constă în calibrarea senzorului. în acest sens, în momentul în care Arduino pornește, știind că în acel moment nu am mână aproape de senzor, voi culege suficient de multe date, pe care le voi media, valoarea obținută urmând s-o folosesc ca nivel de referință.

ca să îmi ușurez munca, citirea timpului o voi include într-o funcție, definită astfel:

uint16_t read_one_capacity (byte pin) { // functia se numeste read_one_capacity,
                                        // ia ca parametru numarul terminalului Arduino 
                                        // si intoarce valoarea timpului, ca un numar intre 0 si 65535
  uint16_t t = 0; // in locul de memorie specificat cu t voi tine minte timpul
  pinMode (pin, OUTPUT); // configurez terminalul pin ca iesire
  digitalWrite (pin, HIGH); // stabilesc potentialul terminalului pin la VCC (+5V)
  delay (2); // astept 2ms
  pinMode (pin, INPUT); // transform terminalul pin in intrare digitala
  while (digitalRead (pin) == HIGH && t < 0xFFFF) { // atat timp cat pin este considerat ca fiind HIGH
                                                    // si contorul e mai mic de 65535
    t++; // incrementez contorul cu o unitate
  }
  pinMode (pin, OUTPUT); // configurez terminalul pin ca iesire
  digitalWrite (pin, LOW); // conectez terminalul pin la GND
  delay (2); // astept 2ms, in felul asta durata unei masuratori va fi de aprox. 5ms
  return t; // ies din functie si intorc rezultatul obtinut
}

pentru calibrarea masuratorilor, in blocul setup voi efectua 100 de masuratori pe care le voi media pentru a obține valoarea de referință, corespunzătoare distanței infinit.

uint16_t reference; // definesc locul unde tin minte referinta

void setup() {
  reference = 0; // initial, stabilesc referinta cu valoarea 0
  byte c = 0; // c e un contor care numara de cate ori am cules date
  while (c++ < 100) { // atat timp cat c e strict mai mic ca 100, incrementeaza c cu o unitate
    reference += read_one_capacity (2); // si aduna la valoarea referinte rezultatul masuratorii
  }
  reference = reference / 100; // in referinta am suma masuratorilor si e suficient sa impart cu
                               // numarul de masuratori pentru a obtine media
  Serial.begin (9600); // cum voi citi datele prin consola, pornesc interfata seriala
}

folosind acelați principiul al medierii, voi defini o funcție care să întoarcă valoarea medie pentru 32 de măsurători. prefer puteri ale lui 2 pentru numărul de mostre deoarece diviziunea va fi mai rapidă, putând fi transpusă de compilator într-o simplă translatare a biților către dreapta.

uint16_t read_capacity (byte pin) { // definesc o functie numita read_capacity,
                                    // care ia ca parametru terminalul arduino la care e conectata antena
                                    // si intoarce timpul de descarcare
  uint16_t average = 0; // in locul din memorie average voi avea rezultatul masuratorii
  byte c = 0; // c e un contor care imi spune cate masuratori am efectuat
  while (c++ < 32) { // atat timp cat contorul nu depaseste 32,
    average += read_one_capacity (pin); // citesc valoarea timpului si il adun la average
  }
  average = average / 32; // pentru ca am citit 32 de mostre, calculez media
  return average > reference ? average - reference : 0; // daca media e mai mare decat referinta,
                                                        // intorc diferenta lor (care va fi pozitiva)
                                                        // daca nu, intorc 0
}

notă:

poate părea puțin ciudat că nu folosesc bucle de tip for. țin minte că la curs am fost întrebat asta. am pornit de la ideea din teorema Böhm-Jacopini care spune că orice program de calculator poate fi scris utilizând atribuiri, condiții și bucle. altfel spus, pentru a scrie absolut orice aplicație trebuie să știi cum să atribui o variabilă, cum să folosești if/the/else și cum să folosești while. din acest motiv, prefer ca orice program scris pentru curs să folosească întotdeauna numai cele trei tipuri de construcții.

având în vedere cele scrise mai sus, loop devine foarte simplu:

void loop() {
  uint16_t t = read_capacity (2); // citesc timpul necesar descarcarii capacitorului
  Serial.println (t); // il trimit prin interfata seriala
  delay (500); // astept 500 de ms
}

Capacitive Sensor Improved

cum îl fac să cânte?

Arduino Theremin

pentru a scoate sunete am nevoie de un traductor piezoelectric, care nu e altceva decât micul difuzor care se găsea în toate jucăriile chinezești, sub forma unui mic disc de metal. în mod normal, traductorul este polarizat, însă în practică n-am observat diferențe între conectarea lui în ambele sensuri. așa că, unul dintre firele traductorului se conectează la unul dintre terminalele libere ale Arduino, să spunem 9, în timp ce celălalt se va conecta la GND. dacă traductorul nu are fire, va trebui să conectezi un fir la discul de metal și unul la electrodul aflat în centrul discului.

Arduino dispune de o funcție care permite generarea de tonuri folosind un traductor piezoelectric. funcția se numește tone și are trei parametrii: terminalul Arduino care este conectat la traductor, frecvența notei în Herzi și durata în milisecunde. pentru a determina frecvența notelor poți folosi un tabel ca cel din wikipedia sau poți rezolva problema matematic: LA-ul diapazonului aparține octavei 4, normală și are 440Hz. LA-ul din octava 5 are 2×440 = 880Hz, iar cel din octava 3 440/2 = 220Hz. restul notelor urmează o progresie geometrică, iar o octavă are 12 note, incluzând ambele clape, albe și negre de la pian.

orice variantă aș alege, notele din octava 4 sunt 262 (do), 294 (re), 330 (mi), 350 (fa), 392 (sol), 440 (la) și 494 (si) Hz. voi alege încă o notă cu frecvența 0 care are semnificația că instrumentul construit nu scoate niciun sunet. din observațiile făcute mai sus, valoarea măsurată de senzor se încadrează între 0 și 32, iar cum am 7 note și un non-sunet, înseamnă că pot să transform valoarea obținută în notă prin împărțirea la 4. cum discutam la realizarea detectorului de metale, împărțirea la 4 înseamnă translatarea numărului cu 2 biți spre dreapta folosind operatorul >>.

acum, observațiile pe care le-am făcut s-ar putea să nu fi fost chiar bune, iar valoarea obținută, chiar divizată cu 4, poate depăși ca valoare 7. pentru a elimina o condiție suplimentară, am folosit un mic artificiu: numărul obținut, divizat cu 4 dacă i se aplică operația și pe biți cu reprezentarea binară a lui 7 (111) vor fi păstrați doar ultimii 3 biți, adică 8 combinații, reprezentând cifre între 0 și 7.

int notes[8] = { 0, 262, 294, 330, 350, 392, 440, 494 }; // definesc frecvențele notelor
void loop() {
  uint16_t cap = read_capacity (2); // citesc distanta
  int note = notes[(byte) 0x07 & (cap >> 2)]; // gasesc frecventa notei
                                              // cap are o valoare intre 0 și 32
                                              // cap >> 2 va fi între 0 si 8
                                              // 0x07 & (cap >> 2) va fi intre 0 și 7 întotdeauna
                                              // deoarece 0x07 e 111 in binar si aplicând si la nivel
                                              // de biti, singurii care nu vor fi 0, vor fi ultimii 3
                                              // biti ai cap >> 2
  if (note > 0) { // daca frecventa notei e mai mare ca 0
    tone (9, note, 200); // reproduct nota cu o durata de 200ms prin traductorul conectat la terminalul 9
  }
  else { // daca frecventa e 0
    delay (200); // fac o pauza de 200ms
  }
}

notă:

n-aș spune că thereminul realizat astfel e foarte bun. e mai mult un exemplu de ce poate fi făcut cu un minimum de componente, așa că să nu ai așteptări prea mari. poate fi îmbunătățit cu un algoritm mai bun pentru detectarea zgomotului de măsură, cu un capacitor de 10pF pus în paralel cu rezistorul de 4,7MΩ pentru o mai bună stabilitate, cu o diodă zenner în paralel cu rezistorul pentru protecția la supratensiuni și acumulări de sarcină și tot așa. în schimb, pentru a construi un simplu senzor de atingere capacitiv cu un minimum de componente e mai mult decât suficient - vezi Makey Makey.

bogdan

bogdan » arduino: detector de metale

04:08 pm on Dec 27, 2018 | read the article | tags:

în urma rugăminții profesorului Stamatin, de câțiva ani țin un curs la Facultatea de Fizică din Măgurele. dacă inițial numele «modelare și simulare» ascundea ecuații matematice și metode numerice, recent am înlocuit diferențialele cu Arduino, în ovațiile celor câtorva studenți care frecventează cursul. dotările limitate m-au făcut să devin creativ cu materialele de curs, iar rezultatul mi s-a părut suficient de interesant pentru a-l reproduce aici. așa că:

pe scurt, sărind puțin peste partea de fizică, metalele influențează o mărime fizică numită permeabilitate magnetică în preajma lor. această mărifme fizică definește capacitatea unui mediu de a susține formarea câmpuri magnetice. în preajma metalelor feroase, permeabilitatea magnetică crește în timp ce în jurul nemetalelor aceasta scade. detectorul de metale pe care îl prezint în continuare ia în calcul acestă mărime.

permeabilitatea magnetică influențează inductanța unei bobime (inductor) direct proporțional. o formulă utilă pentru inductori cilindrici este:

$$ L \approx \mu N^2 \frac {D} {2} (ln(\frac {8D}{d}) – 2) $$

, unde:
L este inductanța electrică a bobinei (măsurată în Henry);
D este diametrul mediu al bobinei (metri);
d este diametrul conductorului din care este realizată bobina (metri);
N este numărul de înfășurări ale bobinei (spire);
μ este permeabilitatea magnetică a bobinei (Henry / metru);

cum convertesc permeabilitate magnetică în frecvență?

permeabilitatea magnetică influențează direct inductanța electrică. spre deosebire de cazul unui capacitor, ecuația care descrie comportamentul unui inductor nu e de mare ajutor, în special din cauza timpilor implicați prea mici pentru a putea măsura mărimile implicate direct cu un Arduino. dacă ești curios, asta e ecuația de care vorbeam:

$$ U(t) = L \frac {dI(t)} {dt} $$

, unde
U(t) este tensiunea la bornele inductorului (volți);
L este inductanța electrică a bobinei (Henry);
I(t) este curentul care circulă prin inductor (Amperi);

cu toate astea, întotdeauna există o soluție. un capacitor în paralel cu un inductor formează un circuit oscilant a cărui perioadă este dependentă doar de mărimile caracteristice ale celor două componente.

încărcând capacitorul și lăsându-l să se descarce în inductor obții oscilații a căror perioadă poate fi măsurată direct cu Arduino. în mod normal ai conecta circuitul oscilant la unul dintre terminalele digitale ale Arduino, ai trimite un scurt impuls pozitiv urmat de transformarea terminalului în intrare și măsurarea numărului de impulsuri într-un interval de timp. problema cu scenariul anterior e că lumea reală e puțin diferită de modelele matematice, iar circuitul oscilant este atenuat în așa fel încât după încărcarea capacitorului, numărul de oscilații care pot fi folosite este mult prea mic. cu toate astea, pentru că are valoare didactică, poți recrea experimentele mele cu următorul mic program:

void setup() {
  pinMode (7, OUTPUT); // <- vom conecta circuitul la terminalul 7
}

void loop() {
  digitalWrite (7, HIGH); // <- stabilim potentialul la 5V
  delayMicroseconds (10); // <- pentru 10us
  digitalWrite (7, LOW); // <- restabilim potentialul la 0V
  delayMicroseconds (990); // <- pentru 990us
}

forma tensiunii în funcție de timp prezentă la terminalul 7 este dreptunghiulară, stând 1% din timp la valoarea de 5V și 99% la valoarea 0V, totul cu o perioadă de 1ms. pentru tipul ăsta de undă, raportul între timpul în care valoarea e 5V și lungimea perioadei se numește factor de umplere.

Arduino: pin 7 waveform

pentru a proteja terminalul 7 al Arduino este necesară o diodă înseriată cu acesta. circuitul oscilant variază intensitatea tensiunii la borne simetric față de 0V, în intervalul -5V – 5V, putând deteriora circuitul de intrare al terminalului 7. dioda mai folosește și ca întrerupător, deconectând circuitul oscilant de la terminalul 7 imediat după ce acesta este stabilit la 0V (LOW) și lăsându-l să oscileze liber.

Arduino: Metal Detector 1st Try

inductorul este realizat dintr-un cablu de cupru cu miez solid (monofilar), cu diametrul de 0,25mm, folosit la sonerii, care se găsește gata înfășurat la Hornbach. capacitorul folosit este ceramic, de 100nF în timp ce dioda poate fi de orice fel, influența acesteia fiind mică. în cadrul experimentului am folosit totuși o diodă foarte rapidă, 1N5189, care poate fi înlocuită la fel de bine cu 1N4148 sau 1N4001.

pierderile din conductoarele electrice, dielectricul capacitorului și miezul inductanței fac oscilațiile să fie atenuate exponențial, lucru care face detecția variației de frecvență indusă de modificarea permeabilității miezului inductanței aproape imposibilă fără amplificare.

LC Circuit Wave Form

varianta a doua și cea pe care ți-o recomand e construirea unui mic oscilator, conectând circuitul oscilant într-o buclă cu reacție pozitivă, care să compenseze atenuarea. o buclă cu reacție pozitivă va culege o fracțiune din tensiunea oscilantă, o va amplifica și o va suprapune peste cea din circuitul oscilant, generând interferențe constructive care mențin nivelul oscilațiilor. condiția pentru ca oscilațiile să se mențină este ca amplificarea să compenseze cel puțin atenuarea.

în trecut cel mai probabil ți-aș fi recomandat un tranzistor pe post de amplificator. însă lucrurile sunt mult mai simple în prezent, mai ales că în ceea ce privește amplificarea poți folosi la același preț un amplificator operațional. un astfel de dispozitiv este un mic circuit care în afară de alimentare are 3 terminale: o intrare negativă, o intrare pozitivă și o ieșire. ecuația care guvernează acest circuit este:

$$ U_{iesire} = \gamma \times (U_{intrare{}+{}} – U_{intrare{}-{}}) $$

, unde:
Uieșire e diferența de potențial între ieșire și punctul considerat 0V;
Uintrare+ e diferența de potențial între intrarea pozitivă și punctul considerat 0V;
Uintrare- e diferența de potențial între intrarea negativă și punctul considerat 0V;
γ e factorul de amplificare în buclă deschisă al amplificatorului operațional, o proprietate ce depinde de tipul de circuit folosit, care în cazul LM358N folosit la curs este aproximativ 32000, fără unitate de măsură;

în mod obișnuit, un amplificat operațional se conectează în configurația de amplificator inversor, în care intrarea pozitivă e conectată la 0V în timp ce pe intrarea negativă este aplicat semnalul care trebuie amplificat. o mică observație o reprezintă inversarea polarității semnalului de ieșire față de cel de intrare, conform formulei:

$$ U_{iesire} = {} – \gamma \times U_{intrare{}-{}} $$

pentru ca interferența să fie constructivă, semnalul preluat la intrarea amplificatorului trebuie să aibă semn schimbat față de semnalul de ieșire, altfel interferența este distructivă și oscilațiile încetează.

revin puțin la faptul că la intrarea amplificatorului e necesară o fracțiune din tensiunea la bornele circuitului oscilant. în mod obișnuit prima idee care îmi vine este un divizor rezistiv. problema cu el e că absoarbe energie din circuit, atenuând și mai mult oscilațiile. o variantă mult mai elegantă e să creez un divizor capacitiv, introducând un electrod suplimentar în capacitor care are un potențial proporțional cu distanța electrodului față de terminalul conectat la potențialul 0V. în practică e imposibil să modific un capacitor ceramic, însă pot înseria două capacitoare și folosi terminalul lor comun pentru a fracționa semnalul. tensiunea între acest terminal și potențialul 0V este raportul dintre capacitatea condensatorului situat către 0V și suma celor două capacități. cu toate acestea, cele două tensiuni au același semn.

un artificiu îl reprezintă considerarea punctului intermediar al capacitoarelor ca având un potențial fix, tensiunile la capatele inductorului fiind acum în opoziție. un divizor rezistiv va stabili potențialul “0V” al intrării pozitive a amplificatorului operațional la 2,5V față de GND (0V). tot la intrarea pozitivă va fi conectat un capacitor către GND (0V) pentru filtrarea posibilelor perturbații.

tot ce mai trebuie făcut este limitarea curentului prin circuit folosind două rezistoare, unul pentru intrare și unul pentru ieșire – amplificatorul operațional este o componentă mult mai simplă decât Arduino, neavând limitări implicite.

cum construiesc oscilatorul?

Arduino: Metal Detector

pentru amplificatorul operațional poți folosi orice amplificator operațional. am preferat LM358N (”N” reprezintă varianta normală, care poate fi insearată într-o plăcuță de prototipare) deoarece e ieftin și funcționează bine cu tensiuni mici. privindu-l de deasupra, unul dintre piciorușe este marcat cu un punct (gaură), fiind primul picioruș (terminalul 1) al circuitului. celelalte terminale sunt numerotate crescător în sens invers acelor de ceasornic, atunci când circuitul e privit de deasupra. terminalele care ne interesează sunt 4 (GND), 8 (+5V), 5 (intrarea pozitivă, IN+), 6 (intrarea negativă, IN-) și 7 (ieșirea).

pentru început vrei ca circuitul să fie alimentat, așa că vei conecta terminalul 4 la GND și 8 la +5V. pentru a-l transforma într-un amplificator, este necesară conectarea terminalului 5 (IN+) la un potențial intermediar care permite inductorului să oscileze în jurul lui fără a depăși limitele tensiunii de alimentare. poți face acest lucru conectând doi rezistori sub forma unui divizor rezistiv, ambii de aceeași valoare 10kΩ, stabilind potențialul terminalului +5V la 2,5V. pentru mai multă stabilitate, un mic capacitor de 100nF poate fi conectat în paralel cu unul dintre rezistori (din experimentele inițiale am observat că nu e obligatoriu).

următorul pas îl constituie realizarea circuitului oscilant format dintr-un inductor în paralel cu două capacitoare înseriate. punctul comun al celor două capacitoare va fi conectat la GND (0V) în timp ce fiecare dintre terminalele inductorului vor fi conectate prin rezistori de 1kΩ la terminalele 6 și 7 ale circuitului integrat. terminalul 7 se va conecta la terminalul 5 al Arduino pentru a măsura frecvența.

în imaginea de mai jos se vede forma de undă generată de oscilator și modul în care apropierea unei doze din aluminiu de bobină influențează (crește) frecvența oscilatorului.

LC Colpitts Oscillator

notă:

din nefericire, terminalul 5 al Arduino UNO este singurul care poate fi utilizat pentru măsurarea frecvenței semnalului cules. acesta e conectat la ceasul intern Timer 1 pentru microcontrollerul în jurul căruia este construit Arduino UNO. un ceas intern nu e neapărat un ceas în sensul naiv al cuvântului. mai degrabă un dispozitiv care poate număra impulsuri interne sau externe și genera diferite semnale interne în cazul în care un prag bine stabilit (alarmă) sau numărul maxim de impulsuri este depășit. impulsurile interne sunt sincronizate cu ceasul de bază al microcontrollerului, putând seta frecvența cu care acestea sunt generate ca fracțiuni ale frecvenței de bază (16MHz în cazul Arduino UNO). impulsurile externe pot fi culese prin intermediul terminalului 5. avantajul folosirii ceasului Timer 1 îl reprezintă numărarea hardware a semnalelor, adică în timp real, fără întârzieri, complet predictibil din punct de vedere fizic.

cum măsor frecvența?

Arduino poate face destul de multe lucruri utilizând software, așa cum am arătat în cursurile precendente. însă atunci când este vorba de evenimente care se petrec în timp real, acest lucru nu ajută foarte mult deoarece momentul execuției instrucțiunilor nu mai este predictibil, făcând imposibilă măsurarea frecvenței cu exactitate. cu toate acestea întotdeauna există o soluție, mai ales atunci când componenta care trebuie programată este un microcontroller. ATMega328P, circuitul principal al Arduino UNO conține trei ceasuri interne independente și metode prin care acestea pot fi accesate în timp real, numite întreruperi. întreruperile sunt evenimente generate de componentele hardware ale microcontrollerului care suspendă execuția programului curent în favoarea rezolvării unor mici sarcini cu prioritate. un exemplu, de altfel și cel utilizat în detectorul de metale, îl reprezintă întreruperea generată de depășirea alarmei unuia dintre ceasuri, cu ajutorul căruia pot stabili cu precizie fereastra de timp în care sunt numărate impulsurile generate de oscilator.

astfel, algoritmul de măsurare al frecvenței poate fi schițat în următorii pași: stabilirea ceasului intern Timer 1 ca fiind controlat extern de semnalul oscilatorului, stabilirea ceasului intern Timer 2 ca fiind controlat de un semnal derivat din ceasul general al Arduino, iar cu o întrerupere care se execută în momentul depășirii pragului pentru Timer 2 voi salva valoarea numărată. deși pot să măsor cu precizie frecvența, nu mă interesează în mod implicit valoarea ei absolută, ci variația cauzată de metalele așezate în preajma bobinei.

majoritatea codului care va rula pe Arduino va fi conținut în setup. în bucla loop doar voi verifica dacă am reușit să termin o numărătoare de impulsuri și să o trimit prin interfața serială pentru a o citi.

notă:

în bucățelele de cod chemate prin intermediul întreruperilor, care poartă numele de Interrupt Service Routines (ISRs), pot modifica locuri din memorie accesibile întregului program. sigurul lucru de care trebuie ținut cont este ca în declararea acestor locuri din memorie să fie specificat faptul că acestea pot fi modificate în timpul întreruperilor folosind cuvântul cheie special ”volatile”.

volatile uint16_t count_prev = 0; // valoarea numărată de Timer 1 anterior
volatile uint16_t count_output = 0; // valoarea frecvenței măsurate de Timer 1
                                    // volatile e un cuvânt cheie special care definește tipul
                                    // variabilei count_output ca fiind invariabil la întreruperi
volatile uint8_t count_ready = 0; // indicator că am reușit să măsurăm un ciclu complet
uint16_t gate_index = 0; // numărul de perioade ale Timer 2 în care Timer 1 va număra impulsurile

// pentru că voi modifica valorile unor regiștrii speciali, va trebui să-i salvez undeva
uint8_t saveTCCR1A; // variabila în care voi salva valoarea registrului special TCCR1A
uint8_t saveTCCR2A; // variabila în care voi salva valoarea registrului special TCCR2A
uint8_t saveTCCR1B; // variabila în care voi salva valoarea registrului special TCCR1B
uint8_t saveTCCR2B; // variabila în care voi salva valoarea registrului special TCCR2B

poate pare destul de complicat, însă e destul de simplu. TCCR1A, TCCR1B, TCCR2A, TCCR2B sunt locuri din memorie predefinite care controlează modul în care ATMega328 funcționează, numite regiștrii. explicația pentru comportamentul fiecărui registru se găsește în foaia de catalog ATMega328.
TCCR1A, TCCR2A – sunt regiștrii care stabilesc comportamentul terminalelor conectate la Timer 1, respectiv Timer 2;
TCCR1B, TCCR2B – sunt regiștrii care stabilesc modul în care cele două ceasuri sunt controlate: oprit, controlate de ceasul general al Arduino sau de ceas extern; Timer 1 va fi controlat extern, la tranziția între 0V și 5V, în timp ce Timer 2 va fi controlat intern, de o fracțiune a frecvenței ceasului general; fracțiunile posibile sunt 1, 8, 64, 256 sau 1024;
SREG – e un registru general de stare care printre altele controlează modul în care funcționează întreruperile; nu-l vom modifica direct, ci vom folosi funcția predefinită cli; cu toate astea, va trebui să-i restabilim valoarea după modificările făcute, lucru destul de simplu;
TCNT1, TCNT2 – sunt regiștrii care conțin valoarea numărată de fiecare dintre ceasuri, Timer 1, respectiv Timer 2; TCNT1 e un registru cu lungimea de 16 biți în timp ce TCNT2 are doar 8 biți;
OCR1A, OCR1B, OCR2A, OCR2B – sunt regiștrii în care se pot stabili alarmele pentru fiecare dintre Timer 1 și Timer 2; fiecare ceas are două alarme configurabile;
TIFR1, TIFR2 – sunt regiștrii în care pot fi citite evenimentele care au generat întreruperile pentru fiecare dintre Timer 1 și Timer 2;
TIMSK1, TIMSK2 – sunt regiștrii în care pot fi stabilite ce întreruperi sunt generate de către fiecare dintre Timer 1 și Timer 2;
GTCCR – este un registru în care controlează modul în care ceasurile interne pot fi sincronizate;

notă:

vei observa că de multe ori, în codul următor, apare expresia 1<<CONSTANTĂ. constantele sunt predefinite și se găsesc în foaia de catalog ATMega328P, reprezentând poziția într-un octet (grup de 8 biți) a bitului de interes. spre exemplu, TOV1 este 0 fiind primul bit din registrul special TIFR1. WGM21 are valoarea 1 fiind al doilea bit din registrul special TCCR2A. operația << reprezintă translatarea numărului din stânga catre stânga cu numărul din dreapta cifre, în reprezentarea binară. astfel, 1<<TOV1 va fi 1 deoarece TOV1 este 0 și nu se va translata nimic. 1<<WGM21 va fi 2 (10 în binar), deoarece 1 va fi translatat către stânga cu o poziție. echivalentul matematic al operației este:
$$ {a}\ll{b} = a \times 2^{b} $$
în cazul numerelor care sunt stocate pe 8 biți, operația anterioară se va face modulo 256; în cazul numerelor stocate pe 16 biți, operația se va face modulo 65536.
alte operații întâlnite mai jos sunt:
a|b va avea ca rezultat un număr care în reprezentarea binară va avea drept componente operația binară sau aplicată biților corespunzători din a și b. un exemplu simplu 1|2 = 3 deoarece 01|10 = 11.
a&b va avea ca rezultat un număr care în reprezentarea binară va avea drept componente operația binară și aplicată biților corespunzători din a și b. un exemplu simplu 1&2 = 0 deoarece 01&10 = 00.

void setup() {
  uint8_t status; // variabila în care voi salva valoare registrului special de stare SREG
  Serial.begin (9600); // urmează să citesc frecvența folosind conexiunea serială

  // pentru că vom modifica niște regiștrii speciali, le vom salva starea în primă fază
  saveTCCR1A = TCCR1A; // TCCR1A este un registru special de control al Timerului 1,
                       // care specifică modul în care funcționează Timerul 1
  saveTCCR1B = TCCR1B; // TCCR1B este al registru special de control al Timerului 1,
                       // care specifică sursa și frecvența impulsurilor

  TCCR1B = 0; // resetez valoarea registrului TCCR1B
  TCCR1A = 0; // resetez valoarea registrului TCCR1A
  TCNT1 = 0; // resetez numărătorul Timerului 1 (îl aduc la valoarea 0)
  TIFR1 = (1 << TOV1); // resetez indicatorul de depășire a valorii maxime pentru Timer 1
  TIMSK1 = 0; // dezactivez întreruperile generate de Timer 1
  
  // pentru că vom modifica alți regiștrii speciali, le vom salva starea în acest moment
  saveTCCR2A = TCCR2A; // TCCR2A este registrul de control al Timerului 2,
                       // are un rol similar cu TCCR1A
  saveTCCR2B = TCCR2B; // TCCR2B este registrul de control al Timerului 2,
                       // are un rol similar cu TCCR2B

  TCCR2B = 0; // resetez valoarea registrului TCCR2B
  TCCR2A = (1 << WGM21); // stabilesc modul de funcționare al Timerului 2, ca fiind generator
                         // de evenimente atunci când se depășește valoarea din OCR2A 
  OCR2A = 124; // stabilesc valoarea lui OCR2A astfel încât fereastra pentru măsurarea frecvenței
               // este (124 + 1) / (16000000 / 1024 (Hz)) = 8ms
  TIFR2 = (1 << OCF2A); // resetăm și pregătim întreruperea generată de depășirea valorii
                        // stabilite în OCR2A
  TCNT2 = 0; // resetez numărătorul pentru Timer 2
  
  status = SREG; // salvez valoarea registrului special de stare SREG
  cli(); // dezactivez toate întreruperile

  GTCCR = (1 << PSRASY); // pregătesc Timerul 2 pentru a fi comandat de ceasul intern
  TCCR2B = (1 << CS22) | (1 << CS21) | (1 << ); // cu frecvența de 1024 de ori mai mică decât cea a ceasului
  TIMSK2 = (1 << OCIE2A); // stabilesc ca microcontrollerul să genereze o întrerupere la depășirea lui OCR2A

  TCCR1B = (1 << CS12) | (1 << CS11) | (1 << CS10); // stabilesc ceasul extern pentru Timer 1
                                                    // ca numărând impulsurile de trecere de la 0V la 5V
  SREG = status; // restabilesc valoarea registrului special de stare SREG 
}

în interiorul buclei Arduino singurul lucru pe care îl voi verifica este dacă am o numărătoare completă (count_ready = 1), moment în care resetez și pregătesc ceasurile pentru o nouă măsurătoare. aah, da. și bineînțeles, trimit prin interfața serială valoarea măsurată.

void loop() {
  if (count_ready) { // dacă numărarea impulsurilor s-a terminat
    uint8_t status = SREG; // salvez valoarea registrului special de stare
    cli(); // dezactivez toate întreruperile
    count_ready = 0; // resetez starea numărătorului de impulsuri
    SREG = status; // reactivez întreruperile
    Serial.println (count_output); // trimit frecvența prin portul serial
  }
}

ISR este un macro valabil pentru toate procesoarele AVR prin care se pot stabili mici rutine care sunt activate de întreruperi. parametrul macro-ului este numele vectorului generat de întrerupere, în cazul de față depășirea OCR2A de către Timer 2, TIMER2_COMPA_vect. rutina doar verifică valoarea contorului Timer 1, pe care îl stochează în cazul în care a trecut 1 secundă sub eticheta count_output, specificând programului principal că numărătoarea a fost cu succes sub eticheta count_ready.

ISR(TIMER2_COMPA_vect){ // această rutină pentru întreruperi e activată de întreruperile generate
                        // atunci când Timer 2 depășește valoarea din OCR2A, adică la fiecare
                        // 1024 x (124 + 1) / 16000 = 8 ms
  uint16_t count; // locul din memorie în care salvez valoarea curentă a contorului Timer 1

  count = TCNT1; // salvez valoarea contorului Timer 1
  if (TIFR1 & (1<<TOV1)) { // dacă cumva depășește valoarea 65536
    TIFR1 = (1<<TOV1); // resetez indicatorul de depășire
  }

  gate_index++; // pentru a măsura mai exact timpul și pentru a avea suficient timp pentru
                // a sesiza micile schimbări în frecvență, voi aduna toate numerele găsite
                // pe parcursul mai multor măsurători; gate_index ține minte câte măsurători
                // am făcut până în prezent
  if (gate_index >= 125) { // dacă au fost 125 de măsurători, adică după aproximativ 1s = 125 * 8ms
                              // atenție! e o coincidență că 125 de aici e același cu 125 din setup
    gate_index = 0; // resetez contorul de măsurători,
    count_output = count - count_prev; // calculez numărul de impulsuri ca diferența între valoarea
                                       // numărată și valoarea anterioară
    count_prev = count; // stabilesc valoarea anterioară
    count_ready = 1; // spun programului că am găsit frecvența
  }
}

ultimul pas, care depășește puțin scopul acestui articol, este calibrarea detectorului de metale așezând diferite obiecte din metal în apropierea inductorului și citind frecvența. valorile pot fi furnizate unui clasificator care să construiască un model pentru detecția tipului de metal.

bogdan

bogdan » arduino: multimetru digital

07:14 pm on Nov 25, 2018 | read the article | tags:

în urma rugăminții profesorului Stamatin, de câțiva ani țin un curs la Facultatea de Fizică din Măgurele. dacă inițial numele «modelare și simulare» ascundea ecuații matematice și metode numerice, recent am înlocuit diferențialele cu Arduino, în ovațiile celor câtorva studenți care frecventează cursul. dotările limitate m-au făcut să devin creativ cu materialele de curs, iar rezultatul mi s-a părut suficient de interesant pentru a-l reproduce aici. așa că:

lucrul care m-a fascinat prima data când am descoperit Arduino a fost capacitatea lui de a conecta lumea reală prin software, în primul rând folosind informații culese prin senzori. în majoritatea lor, senzorii transformă o mărime fizică oarecare într-una electrică: tensiune, curent, rezistență, capacitate sau inductanță, de unde și ideea transformării unui Arduino într-un dispozitiv pentru măsurărea acestor mărimi – într-un cuvânt, multimetru.

cum măsor tensiune electrică?

toate versiunile de Arduino pot măsura tensiuni continue implicit folosind ca terminale de măsură una dintre conexiunile analogice, notate cu A0, A1 etc. și conexiunea GND (de la ”ground”, adică ”împământare”) – în realitate potențialul de referință pentru 0V al plăcuței. intervalul în care un Arduino poate măsura direct tensiuni electrice este cuprins între 0 și 5V pentru versiunile cele mai comune de Arduino – Duemillanove, Uno R3, Leonardo, Nano, LilyPad, LilyTiny, Pro Mini/Micro 5V sau Mega, sau 0 și 3,3V pentru versiunile Pro Mini 3V3, Zero și Due.

Arduino - Direct Measure Voltage

la curs am folosit de cele mai multe ori Arduino UNO și Leonardo, așa că în continuare, dacă nu specific explicit, mă refer la versiunile care pot măsura între 0 și 5V implicit.

un Arduino poate măsura tensiuni cu o rezoluție de 10 biți. mai exact, va împărți intervalul 0 – 5V în 210 = 1024 de sub-intervale egale și va verifica în care dintre cele 1024 de sub-intervale se regăsește tensiunea aplicată la intrare. lungimea unui sub-interval este de 5V / 1024 = 0,004882V, primul sub-interval fiind 0 – 0,004882V, următorul 0,004883V – 0,009765V, urmat de 0,009766V – 0,014648V ș.a.m.d. pentru a respecta convenția utilizată în majoritatea limbajelor de programare, sub-intervalele sunt numerotate începând cu 0.

void setup() {
}

void loop() {
  // functia analogRead primeste ca parametru conexiunea
  // pentru vei masura tensiunea, intorcand sub-intervalul in care
  // se gaseste aceasta
  int interval = analogRead (A0);
  // pentru conversia in tensiune,
  // poti lua capatul din stanga al sub-intervalului:
  float voltage_left = 0.004882 * interval;
  // sau mijlocul sub-intervalului:
  float voltage_center = 0.002441 + 0.004882 * interval;
  // sau capatul din dreapta al sub-intervalului:
  float voltage_right = 0.004882 * (interval + 1);
}

în micul program de mai sus, analogRead este o funcție care are ca parametru identificatorul terminalului Arduino pentru care vrei să măsori tensiunea, întorcând un număr întreg – specificat de cuvântul cheie int – care corespunde numărului de ordine al sub-intervalului în intervalul de măsură. pentru conversia intr-un numar real e suficient să-l incluzi în operații aritmetice cu numere reale, iar rezultatul să-l atribui unui loc în memorie declarat ca fiind real prin cuvântul cheie float.

o eroare pe care am întâlnit-o destul de des o reprezintă folosirea numerelor întregi în operațiile aritmetice care ar trebui să producă numere reale. Arduino va face automat conversia în ultimul moment posibil, astfel că următoarea secvență va produce ca rezultat numere întregi intre 0 si 4:

void loop() {
  // analogRead intoarce un numar intreg
  int interval = analogRead (A0);
  // operatiile se efectueaza in ordine, pastrand tipul cel mai convenabil
  // 5 * interval va fi un numar intreg intre 0 si 5115, care ulterior va fi
  // impartit ca numar intreg (cu rest) la 1024, rezultand un numar intreg
  // intre 0 si 4, care abia apoi va fi convertit intr-un numar real
  float voltage_left = 5 * interval / 1024;
}

notă
numărul de zecimale care pot fi folosite din tensiunea obținută este dat de numărul de zerouri după virgulă din lungimea unui sub-interval de măsură. în cazul anterior, pot folosi 2 zecimale deoarece lungimea sub-intervalului este 0,004882V

așa cum unul dintre studenți mei a aflat experimental, aplicând tensiuni mai mari decât tensiunea de alimentare la intrarea Arduino de cele mai multe ori va strica ireversibil conexiunea unde a fost aplicată. pentru a evita problemele de acest tip și pentru a putea măsura totuși tensiuni electrice mai mari, metoda cea mai la îndemână este ”divizorul rezistiv”.

Arduino - Voltage Divisor

un divizor rezistiv este alcătuit din doi rezistori, R1 și R2 conectați în serie, ale căror terminale libere devin terminalele de măsură ale multimetrului, în timp ce Arduino va măsura căderea de tensiune doar pe unul dintre rezistorii divizorului, R2.

$$U_{arduino} = U_{necunoscuta} \times \frac{R_2}{R_1 + R_2} \\ U_{necunoscuta} = U_{arduino} \times \frac{R_1 + R_2}{R_2}$$

pentru R2 o valoarea bună este 10kΩ, bazată pe caracteristica circuitului de intrare a Arduino. în funcție de intervalul în care estimezi că se va afla tensiunea necunoscută, poți calcula valoarea rezistorului R1. să presupunem că vrei să măsori tensiunea unui acumulator LiPo 3S – adică, cu trei celule. tensiunea unei celule variază în intervalul 2,8 – 4,3V, făcând tensiunea acumulatorului să varieze în intervalul 8,4 – 12,9V. considerând o marjă de siguranță, capătul superior al intervalului devine 15V. în situația asta, valoarea lui R1 va fi:

$$ R_1 = (\frac{U_{max}}{U_{arduino_max}} – 1) \times R_2 \\ R_1 = (\frac{15V}{5V} – 1) \times 10k\Omega = 20k\Omega $$

notă
știi sigur că o formulă fizică e corectă atunci când unitățile de măsură din stânga egalului sunt aceleași cu cele din dreapta, aplicând regulile aritmetice. în situația de mai sus, R1 se măsoară în ohmi, prima paranteză după egal nu are unitate de măsură (volt se ”simplifică” cu volt), iar factorul rămas se măsoară în ohmi, rezumând formula la ohmi = ohmi.

în același timp, lungimea unui sub-interval de măsură va deveni 0,014648V

void loop() {
  int interval = analogRead (A0);
  float voltage_left = 0.014648 * interval;
  float voltage_center = 0.007324 + 0.014648 * interval;
  float voltage_right = 0.014648 * (interval + 1);
}

din nefericire, 20kΩ nu e o valoare standard pentru un rezistor, însă poate fi ușor replicată cu două rezistoare înseriate de câte 10kΩ. în cazul în care vrei să alegi o valoare standard, cea mai apropiată e 22kΩ. în această situație, tensiunea maximă devine:

$$ U_{max} = U_{arduino_max} \times \frac{R_1 + R_2}{R_2} \\ U_{max} = 5V \times \frac{22k\Omega + 10k\Omega}{10k\Omega} = 16V $$

iar formulele pentru calculul tensiunii electrice folosind Arduino sunt:

void loop() {
  int interval = analogRead (A0);
  float voltage_left = 0.015625 * interval;
  float voltage_center = 0.007812 + 0.015625 * interval;
  float voltage_right = 0.015625 * (interval + 1);
}

notă
recomand măsurarea tensiunilor mari cu extrem de mare atenție. tot ce depășește 32V poate fi extrem de periculos. în nicio situație nu încerca să măsori tensiunea rețelei de curent electric!

o nouă problemă în măsurarea unei tensiuni electrice o reprezintă polaritatea. aparatele de măsură comerciale o detectează automat, afișând semnul corespunzător pe ecran. în cazul Arduino, aplicarea unei tensiuni electrice negative – potențialul conexiunii analogice mai jos decât potențialul de referință 0V (GND) – va distruge circuitul de intrare al terminalului respectiv.

o metodă pentru evitarea acestei situații este simularea unui potențial de referință virtual mai sus, în așa fel încât aplicarea unei tensiuni negative să nu coboare potențialul terminalului de intrare analog sub nivelul 0V (GND). poți stabili un astfel de potențial folosind un divizor rezistiv, care să înjumătățească tensiunea de alimentare de 5V pentru a crea un punct de potențial 2,5V față de 0V, care va fi folosit ca terminal negativ al multimetrului pe care-l vei construi, în timp ce terminalul pozitiv va fi una dintre intrările analogice ale Arduino.

Arduino - Floating Voltage

notă
dezavantajele acestui artificiu sunt consumul suplimentar de energie: pentru a obține un potențial stabil, curentul care trece prin divizor trebuie să fie relativ mare; o variantă posibilă îl reprezintă utilizarea unui divizor alcătuit din doi rezistori de 220 de Ω, asigurând un curent de aproximativ 10mA.
un al doilea dezavantaj de care trebuie ținut cont este că intervalul de măsură al multimetrului a fost translatat cu 2,5V. mai exact, a devenit -2,5V – +2,5V. în aceste condiții formulele trebuie ajustate corespunzător – introducând termenul de translație de -2,5V, iar tensiumea maximă care poate fi aplicată la intrare nu poate depăși 2,5V. în cazul în care o tensiune mai mare este necesară, se poate aplica metoda divizorului rezistiv de mai sus.

ultima situație pe care vrei să o ai acoperită este măsurarea tensiunilor mult mai mici. Arduino pune la dispoziție două metode pentru rezolvarea problemei, una software și una hardware. varianta software nu implică componente suplimentare. folosind funcția analogReference poți seta capătul superior al intervalului în care se poate afla tensiunea necunoscută.

void setup() {
  analogReference (INTERNAL); // <- capatul intervalului devine 1,1V
}

lungimea unui sub-interval de măsură devine în această situație 0,001074V transformând formulele pentru derivarea tensiunii electrice în:

void loop() {
  int interval = analogRead (A0);
  float voltage_left = 0.001074 * interval;
  float voltage_center = 0.000537 + 0.001074 * interval;
  float voltage_right = 0.001074 * (interval + 1);
}

dacă ai nevoie să măsori tensiuni mult mai mici, poți apela la varianta unei referințe fizice. folosind un divizor rezistiv cu ieșirea conectată la terminalul AREF poți stabili valoarea tensiunii capăt de interval de măsură. activarea acestei referințe externe se face prin apelarea funcției analogReference cu parametrul EXTERNAL.

Arduino - External Referrence

în exemplul din imagine, valorile pentru divizorul rezistiv sunt R1 = 1,8kΩ și R2 = 220Ω și stabilesc capătul intervalului tensiunilor măsurate la 0,544544V în timp ce lungimea sub-intervalul de măsură este de 0,000531V.

void setup() {
  // deoarece am conectat AREF la valoare 5V * 220 ohm / (1800 ohm + 220 ohm)
  analogReference (EXTERNAL); // <- capatul intervalului devine 0,5445V
}
void loop() {
  int interval = analogRead (A0);
  float voltage_left = 0.000531 * interval;
  float voltage_center = 0.000265 + 0.000531 * interval;
  float voltage_right = 0.000531 * (interval + 1);
}

cum măsor curent electric?

deși sunt puțini senzori care convertesc mărimi fizice în curent electric, măsurarea acestuia este importantă, cel puțin prin prisma monitorizării energiei consumate într-un circuit. voi porni de la un exemplu simplu: cât curent consumă un LED?

notă
LED-urile sunt dispozitive semiconductoare care conduc curentul electric cu precădere într-o singură direcție și sunt caracterizate de doi parametrii importanți: căderea de tensiune în conducție directă și curentul direct maxim. căderea de tensiune în conducție directă poate fi înțeleasă simplu ca tensiunea necesară aprinderii LED-ului, orice tensiune mai mică păstrând LED-ul stins, în timp ce orice tensiune mai mare îl va face să lumineze, cu observația că LED-ul va încerca să mențină tensiunea aplicată constantă. acest lucru e imposibil, fiind principala cauză pentru ”arderea” LED-urilor. tensiunea în conducție directă variază în funcție de culoarea LED-ului, de la 1,9V pentru cele roșii, la peste 3V pentru cele albastre și albe.
curentul direct maxim îl reprezintă curentul maxim care poate trece prin LED, atunci când acesta este aprins în așa fel încât durata de viață a LED-ului să fie maximă. spre deosebire de alte dispozitive, LED-urile își variază intensitatea luminoasă direct proporțional cu curentul care le străbate.
din aceste motive, LED-urile vor avea nevoie întotdeauna de un circuit care să limiteze curentul maxim prin circuit. un exemplu este un simplu rezistor înseriat cu acesta.

am în vedere cel mai simplu circuit cu LED: un rezistor R în serie cu un LED, conectate între terminalele GND (0V) și 5V (+5V) ale Arduino.

Arduino - Current Measuring

în mod normal vei conecta un rezistor cu o valoare întâlnită des în astfel de circuite, 220 de Ω. curentul prin circuit va depinde de căderea de tensiune în conducție directă pe LED și va respecta legea lui Ohm:

$$U_{arduino,5V} = U_{LED,direct} + R \times I_{circuit} \\ I_{circuit} = \frac{U_{arduino,5V} - U_{LED,direct}}{R} \\ I_{circuit} = \frac{U_{rezistor}}{R}, U_{rezistor} = U_{arduino,5V} - U_{LED,direct}$$

ultima formulă este cea mai convenabilă, mai ales dacă rezistorul are un terminal conectat la GND (0V), situație în care poți măsura direct căderea de tensiune pe acesta folosind experiența câștigată anterior în măsurarea tensiunii. dacă celălalt terminal al rezistorului este conectat la A0, curentul prin circuit poate fi măsurat direct astfel:

void setup() {
}

void loop() {
  // găsim sub-intervalul în care se află tensiunea pe rezistorul R
  int interval = analogRead (A0);
  // estimăm tensiunea pe rezistor ca fiind centrul sub-intervalului de măsură
  float voltage_rezistor = 0.002441 + 0.004882 * interval;
  // aplicăm legea lui Ohm pentru a afla curentul, direct în A
  float current = voltage_rezistor / 220.0;
}

notă
cu un Arduino nu poți măsura direct curentul electric într-un circuit, având în vedere că intrările analogice ale acestuia funcționează ca niște mici aparate care măsoară tensiunea electrică. în această situație, soluția cea mai la îndemână este conversia curentului în tensiune folosind un simplu rezistor cu valoare cunoscută. variante alternative, care se pot adapta în funcție de situație, sunt utilizarea unor alte fenomene fizice care convertesc curentul electric într-o tensiune sau variația acesteia: efectul Hall, folosit în special pentru izolarea circuitului care va fi măsurat de cel care îl măsoară sau variația curbei de încărcare a unui capacitor cu valoare cunoscută.

cum măsor rezistență electrică?

asemănător cazului anterior al curentului electric, nu poți măsura direct rezistența electrică și va trebui să te bazezi pe formula pe care-am menționat-o des până acum, legea lui Ohm. în cazul în care prin rezistorul necunoscut trece un curent cunoscut, diferența de potențial între terminale va fi direct proporțională cu rezistența electrică. soluția se complică atunci când îți dorești o sursă de curent constant. circuitul electronic care se comportă ca o astfel de sursă e complex și costisitor, metoda fiind folosită în unele aparate din laboratoarele cu bugete serioase. cum nu e cazul, următoarea soluție este o sursă de curent predictibil, pentru care poți aplica legea lui Ohm. vei folosi în acest sens sursa de tensiune constantă a Arduino disponibilă între terminalul 5V și GND (0V) și un rezistor cu valoare cunoscută în serie cu cel necunoscut, acesta din urmă având unul dintre terminale conectat la GND (0V), în timp ce Arduino v-a fi folosit pe post de voltmetru, măsurând prin A0 potențialul conexiunii comune a celor doi rezistori.

Arduino - Rezistence Measuring

$$\begin{cases}U_{constant,5V} = (R_{cunoscuta} + R_{necunoscuta}) \times I_{circuit} \\ U_{comun} = R_{necunoscuta} \times I_{circuit}\end{cases} \Rightarrow \\ R_{necunoscuta} = R_{cunoscuta} \times \frac {U_{comun}}{U_{constant,5V} - U_{comun}} $$

tensiunile electrice din formule pot fi convertite direct în valori numerice obținute prin intermediul Arduino astfel: 5V va fi 1023 în timp ce valoarea lui Ucomun va fi rezultatul aplicării funcției analogRead pentru terminalul A0.

$$R_{necunoscuta} = R_{cunoscuta} \times \frac {N_{analogRead(A0)}}{1023 - N_{analogRead(A0)}} (\Omega)$$

// valoare rezistorului cunoscut, in Ohmi
float R = 100000.0;
void setup() {
}

void loop() {
  // in n_comun citim numarul sub-intervalului de masura
  int n_comun = analogRead (A0);
  // in unele situatii, mai exact atunci cand nu este conectat un
  // rezistor, valoarea lui n_comun va fi 1023, generand o eroare
  // rezultata in urma diviziunii cu zero; utilizand operatorul
  // ternar (denumire pompoasa pentru conditionarea atribuirii)
  // poti verifica daca n_comun e mai mic decat 1023 (mai mare
  // e imposibil sa fie) si in situatia asta vei aplica formula,
  // iar in cazul in care n_comun este 1023, valoarea rezistorului
  // va fi -1 (valoare imposibila fizic, dar reprezentand infinit)
  float rezistenta = n_comun < 1023 ?
    R * (float) n / (1023.0 - n) :
    -1;
}

in micul program anterior, (float) urmat de un identificator de variabilă forțează variabila respectivă să fie interpretată ca fiind de tipul specificat între paranteze. parantezele sunt obligatorii. în ceea ce privește secvența

, aceasta va fi evaluată de Arduino ca fiind valoare1 atunci când condiția este îndeplinită și ca valoare2 în caz contrar.

altfel, vei mai observa declararea unui loc în memorie ca număr real - float, în afara celor două blocuri, setup și loop. declarat în acest fel, R poate fi accesat fără nicio problemă din orice parte a programului, în limbaj tehnic numindu-se variabilă globală.

cum măsor capacitatea electrică?

în cazul multimetrelor comerciale, măsurarea capacității electrice se găsește la modelele mai costisitoare. cu ajutorul dispozitivelor hardware pe care un Arduino le conține, capacitatea poate fi măsurată relativ facil.

puțină teorie. formula care vine în ajutor este chiar definiția capacității electrice, ca fiind raportul între sarcina stocată într-un capacitor și diferența de potențial între terminalele acestuia, o formulă ușor de reținut fonetic:

$$Q = C \times U \\ \frac{dQ}{dt} = I$$

unde:
Q este sarcina electrică exprimată în Coulombi; poate puțin abstractă, însă cu o proprietate extrem de interesantă: viteza de variație a sarcinii electrice este chiar curentul electric;
C este capacitatea electrică a capacitorului și se măsoară în Farazi (Farad, la singular); în practică, subunitățile sunt frecvent întâlnite, în timp ce valori de ordinul Farazilor sunt specifice supercapacitorilor;
U este diferența de potențial între terminalele capacitorului, exprimată în volți;
t este timpul, măsurat în secunde, iar;
I este curentul, măsurat în amperi; așa cum îmi atrăgea atenția o studentă (profesoară de fizică), în mod obișnuit, în limbajul de lemn al manualelor școlare, se folosește pentru această mărime litera i (mic). nu-mi plac convențiile, așa că peste tot, curentul este notat cu I (mare, de la intensitate imporantă).

dacă în paralel cu un capacitor necunoscut încărcat la o diferență de potențial inițială U0 conectezi un rezistor R, cu valoare cunoscută, capacitorul se va descărca prin intermediul rezistorului, transformând energia stocată în căldură.

Arduino - Capacitance Measuring

ecuațiile implicate sunt următoarele:

$$ \begin{cases} Q(t) = C \times U(t) \\ U(t) = R \times - \frac{dQ(t)}{dt} \\ U(t=0) = U_{0} \end{cases} \Rightarrow \begin{cases} U(t) = -RC \times \frac{dU(t)}{dt} \\ U(t=0) = U_{0} \end{cases} $$

ultima formulă este o ecuație diferențială ordinară, pentru care e suficient să găsim o soluție particulară. dacă am reușit asta, soluția va fi unică, mulțumită teoremei Cauchy-Lipschitz. semnul minus din a doua ecuație îmi spune că nu am nicio sursă de tensiune în circuit și că rezistorul consumă sarcina din capacitor. interpretarea asta naivă, când e formalizată are denumirea de lege a lui Kirchhoff. având în vedere că în stânga egalului avem funcția, iar în dreapta derivata ei multiplicată cu o constantă, o soluție posibilă este o funcție exponențială, de forma eαt + β.

$$ e^{\alpha t + \beta} = -RC \frac{d}{dt} (e^{\alpha t + \beta}) \Rightarrow \\ e^{\alpha t + \beta} = -RC \alpha e^{\alpha t + \beta} \Rightarrow \alpha = -\frac{1}{RC} $$

parametrul β poate fi dedus din condiția inițială a diferenței de potențial

$$ U(t=0) = U_{0} \Rightarrow e^{-\frac{1}{RC}(t=0) + \beta} = U_{0} \Rightarrow \beta = ln (U_{0}) \\ U(t) = U_{0} e^{-\frac{1}{RC}t} $$

Capacitor Discharge

ultima formulă vine și cu ideea măsurării capacității necunoscute: încărcarea capacitorul până la o tensiune cunoscută, apoi descărcarea acestuia pentru un timp determinat t1, urmată de măsurarea diferenței de potențial între terminalele capacitorului, U1.

$$ U_{1} = U_{0} e^{-\frac{1}{RC}t_1} \Rightarrow C = \frac{t_1}{R}\frac{1}{ln(\frac{U_{0}}{U_{1}})} $$

sau folosind identificatorul sub-intervalului de măsură obținut folosind analogRead și luând în considerare că U0 va fi 5V, formula devine:

$$ C = \frac{t_1}{R}\frac{1}{ln(\frac{1023}{N_{analogRead(A0)}})} $$

terminalele Arduino sunt flexibile și pot fi configurate în funcție de nevoie, chiar în timpul rulării programului. mai exact, poți configura terminalul A0 ca ieșire și să-i stabilești un potențial de 5V față de GND, urmat de configurarea acestuia ca intrare și citirea tensiunii după descărcare.

// timpul de asteptare pentru descarcarea capacitorului, in microsecunde
long t_1 = 100000;
// valoarea rezistorului de descarcare, in ohmi
float rezistenta = 100000.0;

void setup() {
}

void loop() {
  // stabilesc terminalul A0 ca terminal de iesire
  pinMode (A0, OUTPUT);
  // conectam terminalul A0 la +5V pentru a incarca capacitorul
  digitalWrite (A0, HIGH);
  // astept ~1s pentru a fi siguri de incarcarea acestuia
  delay (1000);
  // stabilesc terminalul A0 ca terminal de intrare
  pinMode (A0, INPUT);
  // astept t_1 microsecunde pentru a descarca capacitorul prin rezistorul cunoscut
  // am ales microsecunde, deoarece in formula, capacitatea este direct proportionala
  // cu timpul, iar capacitatile obisnuite sunt de ordinul micro-farazilor
  delayMicroseconds (t_1);
  // citesc identificatorul sub-intervalului de masura
  int n = analogRead (A0);
  // in cazul in care n este 0, adica in acest timp capacitorul s-a descarcat de tot,
  // capacitatea este 0, altfel folosesc formula
  float capacitate = n > 0 ?
    ((float) t_1 / rezistenta) * log (1023.0 / n) :
    0;
}

notă
scala de măsură a capacității nu mai este liniară de această dată, majoritatea valorilor aflându-se în primii 20% din lățimea intervalului de măsură. parametrul pe care-l poți controla atunci când măsori capacități este raportul între timpul de descărcare și valoarea rezistorului de descărcare. cu ajutorul acestui raport poți stabili o scală potrivită valorilor pe care urmează să le măsori.

pentru a vedea cum sunt distribuite valorile măsurate în funcție de rezultatul aplicării analogRead am folosit GNU Octave.

Capacity vs AnalogRead

n = 5:10:1000; # creez un vector cu valori consecutive intre 5 si 1000, din 10 in 10.
plot(n,1./log(1023./n),'+r') # desenez graficul formulei in raport cu n, folosind + si culoarea rosie (r)
xlabel('analogRead(A0)') # pun legenda pentru axa ordonatelor
ylabel('capacitate(uF)') # pun legenda pentru axa absciselor
grid on # afisez un caroiaj pentru observarea mai simpla a valorilor
text (50,31,'max(capacitate) = 31.4660 uF') # afisez un text la pozitia 50 (ordonata) si 31 (abscisa)
text (50,32,'min(capacitate) = 0.1443 uF')

și totuși, cum citesc datele?

deja apreciez că ai citit până aici. a fost lung, dar cred că a meritat. tot ce-am făcut a fost să-ți arăt cum poți măsura, însă fără a putea citi undeva valoarea măsurată. pentru a putea face acest lucru, Arduino pune la dispoziție o conexiune care poate fi accesată prin intermediul cablului USB, interfața serială. așa cum îi spune și numele, prin intermediul ei poți trimite și recepționa secvențial biți. circuitele au de obicei nevoie de 3 fire pentru a putea face acest schimb de informații: unul comun (GND), unul prin care trimit date (TX) și unul prin care recepționează date (RX). pe plăcuța Arduino există un mic circuit care convertește semnalul USB în cele două de care are nevoie Arduino, RX și TX. semnalele sunt disponibile la terminalele digitale 0 (RX) și 1 (TX). viteza de transmitere a informațiilor poate varia între 1200 și 115200 de caractere pe secundă. în cele mai multe cazuri, recomand 9600 ca viteză de comunicație. și calculatorul și Arduino trebuie configurate cu aceeași viteză pentru a putea comunica.

în cele ce urmează vei folosi doar partea prin care Arduino poate trimite date către calculator. bucata de cod care trimite datele din oricare dintre programele de mai sus către calculator este următoarea:

void setup() {
  Serial.begin (9600);
}

void loop() {
  // aici vine codul programului de masura
  // ...
  // urmat de:
  // trimiterea catre interfata seriala a valorii, urmata de un sfarsit de rand
  Serial.println (valoare);
  // si o mica pauza de 1s sa poti citi informatiile mai usor
  delay (1000);
}

instrucțiunile folosite mai sus sunt Serial.begin care inițializează conexiunea serială și stabilește viteza de comunicare care fiind 9600 de caractere pe secundă și care în majoritatea situațiilor trebuie să fie prezentă în blocul setup. instrucțiunea Serial.println care trimite prin interfața serială parametrul dat urmat de un sfârșit de linie, pentru creșterea lizibilității.

ultimul pas e citirea valorii recepționate prin apăsarea butonului care deschide programul de monitorizare al interfeței seriale, în care se pot citi direct valorile:

Arduino - Serial Monitor

bogdan

bogdan » arduino: termometru

09:21 am on Nov 7, 2018 | read the article | tags:

în urma rugăminții profesorului Stamatin, de câțiva ani țin un curs la Facultatea de Fizică din Măgurele. dacă inițial numele «modelare și simulare» ascundea ecuații matematice și metode numerice, recent am înlocuit diferențialele cu Arduino, în ovațiile celor câtorva studenți care frecventează cursul. dotările limitate m-au făcut să devin creativ cu materialele de curs, iar rezultatul mi s-a părut suficient de interesant pentru a-l reproduce aici. așa că:

temperatura este una dintre mărimile fizice pe care o conștientizezi imediat: e prea frig afară, e prea fierbinte supa, oare ai febră? pe lângă conștientizarea acestei mărimi, este foarte simplu să achiziționezi un termometru cu care să poți cuantifica aceste senzații. dar unde e farmecul dacă nu e făcut de tine și nu poți înregistra periodic datele pentru a urmări procesele în timp real? în cele ce urmează îți voi prezenta o variantă de termometru pe care o poți realiza cu Arduino.

termistor

un termistor este un dispozitiv electronic în care temperatura modifică modul în care conduce curentul electric: dacă temperatura crește, rezistența pe care acesta o opune trecerii curentului electric se micșorează și invers. un Arduino nu poate măsura rezistența direct. dispozitivele cele mai potrivite din interiorul acestuia pentru o astfel de operație sunt convertoarele analog-digitale, care transformă o tensiune într-un număr. mai exact, pentru fiecare 0,004882V aplicați la intrare, Arduino va înregistra câte o unitate, în așa fel încât pentru 0V va înregistra 0, iar pentru 5V numărul 1023, variația fiind liniară (ex. 1,5V → 307, 3,3V → 676). acest lucru nu te ajută prea mult direct, mărimea care variază cu temperatura fiind rezistența dispozitivului. ajutorul vine din partea legii lui Ohm:

$$U = R_{termistor} I$$

unde:
U este tensiunea electrică la bornele termistorului, în volți (V),
Rtermistor este rezistența acestuia, în ohmi (Ω), iar
I este curentul care trece prin circuit, în amperi (A).

Arduino pune prin conectorii săi mai multe tensiuni pe care le poți utiliza ca referință: 3,3V (3V3), 5V sau Vin. cum Arduino poate măsura tensiuni între 0 și 5V, 3,3V e o tensiune prea mică față de capătul intervalului, pierzând astfel din precizie, Vin poate fi cu mult peste cei 5V și are și dezavantajul că nu este stabilă, rămânând astfel opțiunea 5V.

notă
3,3V poate fi folosit destul de ușor, conectând un fir între terminalul AREF și 3,3V, stabilind în acest fel capătul intervalului la valoarea 3,3V. acest lucru poate fi făcut cu orice sursă de tensiune, cuprinsă între 0 și 5V. trebuie să ții cont însă, că valoarea unei unități se modifică corespunzător. în cazul 3,3V, o unitate va însemna 0,003222V (ex. 1,5V → 465, 3,3V → 1023).

alegând U = 5V, în legea lui Ohm rămân totuși două necunoscute, rezistența termistorului și curentul prin circuit. pentru curent însă putem să aplicăm încă o dată legea lui Ohm, introducând în circuit un rezistor cu valoare cunoscută, în serie cu termistorul. tensiunea la bornele acestui rezistor de referință este direct proporțională cu curentul prin circuit, tensiune pe care o poți măsura direct folosind Arduino.

Simple Thermometer Animation

fie Rreferință valoarea în ohmi a rezistorului de referință și cu Ureferință valoarea măsurată cu ajutorul Arduino:

$$\begin{cases}5V = (R_{termistor} + R_{referinta}) I \\ U_{referinta} = R_{referinta} I\end{cases} \Rightarrow \\ R_{termistor} = R_{referinta} (\frac{5V}{U_{referinta}} – 1)$$

dependența de temperatură a rezistenței termistorului este dată de ecuația Steinhart-Hart, puțin prea complexă pentru nevoi obișnuite. drept urmare îți recomand să folosești ecuația beta, o formă simplificată a ecuației anterioare, dar utilizată de producători în caracterizarea termistorilor comercializați:

$$\beta = \frac{ln(R_{termistor}) – ln(R_{25})}{\frac{1}{T} – \frac{1}{T_{25}}}$$

ecuația anterioară descrie dependența de temperatura absolută T, în Kelvin, a rezistenței termistorului Rtermistor, în ohmi, folosind trei constante: R25, în ohmi, care este rezistența termistorului la 25°C – de altfel și valoarea sub care termistorul se comercializează, T25, în Kelvin, fiind temperatura absolută care corespunde 25°C, adică 298,15K și β care este o constantă proprie fiecărui termistor și care se măsoară tot în Kelvin.

ecuația anterioară poate fi aranjată într-o formă mai prietenoasă pentru calculul direct al temperaturii, cu mențiunea că temperatura obținută se măsoară în Kelvin, astfel:

$$T = \frac{1}{\frac{1}{\beta}ln(\frac{R_{termistor}}{R_{25}}) + \frac{1}{T_{25}}}$$

următorul pas în rezolvarea problemei este identificarea constantelor lipsă. pentru a determina facil aceste constante, îți recomand să achiziționezi termistorul dintr-o sursă care pune la dispoziție și datele de catalog. la curs am ales un termistor cu valoarea R25 de 100KΩ. raționamentul e simplu: 100KΩ deoarece puterea disipată prin trecerea curentului electric prin termistor nu-l va încălzi suficient pentru a afecta măsurătorile, iar în foaia de catalog vei găsi direct valoarea lui β. pentru termistorul pe care l-am ales, β=4600K.

Termistor Beta Characteristic

notă
pentru rezistorul de referință am ales o valoare egală cu R25. raționamentul e simplu, atunci când rezistenețele sunt egale, tensiunea în punctul median este jumătate din tensiunea de alimentare, plasând astfel temperatura de 25°C în centrul scalei. din cauza neliniarității funcției de conversie, îmi convine să am această valoare situată central deoarece este o valoare de interes pentru activitatea umană și măsurătorile din vecinătatea ei vor avea o precizie mai mare. în general, e bine să aleg rezistorul de referință în așa fel încât temperatura medie a intervalului de interes să corespundă jumătății tensiunii de alimentare.

programare

în acest moment ai toate informațiile necesare pentru a putea scrie un mic program Arduino care să citească temperatura cu ajutorul unui termistor conectat în configurația descrisă mai sus. pentru început, orice program Arduino conține o funcție setup și o funcție loop.

conținutul funcției setup este rulat de Arduino atunci când acesta este inițializat, fie după reset, fie atunci când este alimentat, o singură dată. în această funcție se trec de obicei instrucțiuni pentru a inițializa perifericele Arduino. în cazul de față, convertorul analog-digital care poate măsura tensiuni este inițializat automat.

funcția loop este cea care ne interesează aproape întotdeauna. proprietatea care o face interesantă este că atunci când lista de instrucțiuni pe care le conține a fost epuizată, loop se va reseta și o va lua de la început. Arduino va face acest lucru până când este apăsat butonul reset, se încarcă un program nou sau alimentarea plăcuței este întreruptă.

în fragmentul de cod de mai jos apare și funcția analogRead cu parametrul A0. această funcție primește ca parametru eticheta unuia dintre pinii analogici ai Arduino, de la A0 la A5 și întoarce un număr întreg cuprins între 0 și 1023, număr care este proporțional cu tensiunea aplicată intrării A0. având în vedere că numărul întors este întreg, pentru a forța zecimale în calculele realizate de Arduino, am ales să reprezint toate numerele în format zecimal (”cu virgulă”).

cuvântul cheie float din fața etichetei U_referinta anunță Arduino să rezerve memorie în care să stocheze un număr zecimal care poate fi apelat întotdeauna folosind eticheta U_referinta. același lucru se aplică și pentru R_termistor, T și t. vei observa că eticheta acestor fragmente de memorie ține cont de tipul literelor (mici sau mari), astfel încât T și t vor face referire la fragmente separate, deci valori diferite.

am mai utilizat încă două funcții, log, care corespunde logaritmului natural, luând ca parametru un număr zecimal și întorcând tot un număr zecimal și delay, care suspendă execuția listei de instrucțiuni pentru numărul de milisecunde furnizate drept parametru. dacă nu folosești această funcție, operațiile vor fi efectuate extrem de rapid, furnizând o cantitate mare de date, oarecum irelevantă, mai ales datorită vitezei relativ mici de variație a rezistenței termistorului cu temperatura.

void setup() {
}

void loop() {
  // măsor tensiunea în volți înmulțind valoarea citită prin
  // conectorul A0 cu factorul 0.004882V (= 5V / 1024)
  float U_referinta = 0.004882 * analogRead (A0);
  // din U_referință și valoarea lui R_referință = 100k ohmi
  // calculez R_termistor
  float R_termistor = 100000.0 * (5.0 / U_referinta - 1.0);
  // folosim ecuația temperaturii, în care beta = 4600K și
  // R_25 = 100k ohmi, T_25 = 298.15K și obținem temperatura în
  // Kelvin
  float T = 1.0 /
  (1.0 / 4600.0 * log (R_termistor / 100000.0) + 1.0 / 298.15);
  // pentru a afla temperatura în grade Celsius, scădem din
  // temperatura în Kelvin 273.15
  float t = T - 273.15;
  // fac o pauză de 1 secundă, după care repet instrucțiunile
  delay (1000);
}

micul program de mai sus poate fi compilat și încărcat pe plăcuța ta Arduino folosind Arduino IDE. conectezi Arduino prin cablul USB la calculator și aștepți ca sistemul de operare să instaleze automat uneltele pentru conectare. apoi deschizi aplicația Arduino și vei alege utilizând meniul Tools → Boards → modelul de Arduino pe care îl ai și folosind meniul Tools → Port → portul la care ai conectat plăcuța. te prinzi ușor despre ce port e vorba. dacă nu, le poți încerca fără probleme pe toate, până reușești.

în fereastra care s-a deschis, copiezi codul de mai sus, sau mai bine, îți recomand să-l scrii de mână. poți sări peste liniile care încep cu // și sunt colorate cu albastru. ele reprezintă comentarii și nu influențează programul. după ce ai terminat, vei apăsa butonul Verify, așteptând ca în bara de stare a ferestrei să apară cuvintele Done compiling.

Arduino - Verify - Upload

în situația în care au apărut erori, verifică în ordine următoarele lucruri:

  • ai pus ; (punct și virgulă) după fiecare instrucțiune? din experiența cu studenții, e de departe cea mai frecvent întâlnită eroare;
  • fiecare paranteză deschisă, fie că este paranteză rotundă sau acoladă, trebuie să se și închidă la un moment dat, fix ca la matematică, în ordinea inversă deschiderii lor. adică {…(…)…} este corect, în timp ce sunt greșite: {…(…}…), {…(…)…, {…)…}
  • felul literelor contează mult, așa că verifică dacă ai scris cu litere mari și mici ca în exemplul de mai sus; etichetele, cum sunt U_referinta, R_termistor, T și t le poți modifica după plac, cu singura mențiune să le modifici pe peste tot;
  • cu excepția liniilor cu comentarii, în rest nu ai voie să folosești diacritice; știu, nu e așa comun, dar am întâlnit și eroarea asta;

Arduino Done Compiling

ultimul pas este cel de încărcare al programului pe plăcuța Arduino, apăsând butonul Upload. în caz de erori, cele mai întâlnite sunt:

  • nu ai selectat corect portul la care este conectat Arduino;
  • nu ai selectat corect tipul de plăcuță Arduino; pentru a te asigura, tipul plăcuței este scris pe ea, nu ai cum să greșești;
  • portul USB la care ai conectat plăcuța nu funcționează corect și va trebui să alegi altul;
  • cablul USB nu este de calitate și va trebui să-l schimbi sau să folosești unul mai scurt;

note

  • în jargonul programatorilor, loop și setup poartă denumirea de proceduri, pentru că nu returnează nimic. cuvântul cheie void care alcătuiește definiția unei funcții specifică fix acest lucru: că respectiva bucată de cod întoarce ”vid” (nimic). prefer să folosesc denumirea de funcție, care provine din matematică, pentru a simplifica asimilarea noțiunilor.
  • în teorie funcția log are nevoie de o bibliotecă de funcții să funcționeze. din fericire, Arduino știe să o adauge implicit, așa că micul program va funcționa fără probleme așa cum este scris.
  • în ceea ce privește tipul de variabile pe care lucrează implicit log, care se numește double, pentru majoritatea plăcuțelor Arduino, acesta este doar un sinonim pentru float. chiar dacă nu ar fi așa, Arduino se ocupă de conversia implicită între cele două formate.
  • spațiile nu sunt relevante, însă ele asigură lizibilitate programului scris, așa că dacă ți-e lene, le poți omite.

lcd

până acum, Arduino știe ce temperatură măsoară, însă o ține pentru el. pentru a o putea afișa, are nevoie de un dispozitiv extern, cum ar fi un ecran LCD cu 2 rânduri a câte 16 caractere, foarte des întâlnit (și ieftin). în plus, mai ai nevoie de un mic potențiometru semiregrabil necesar în ajustarea contrastului, cu valoarea de 10KΩ, o rezistență de 220 de Ω, câteva fire de conexiune mai lungi, colorate și o plăcuță pentru experimente (breadboard). tot ce ai de făcut în continuare pe partea fizică este să replici construcția din desenul următor, după ce ai deconectat Arduino de la portul USB:

Arduino Thermometer with LCD

sfatul meu este să începi cu firele roșii și negre (alimentarea), plasând apoi rezistența de 100KΩ, cea de 220 de Ω, termistorul și potențiometrul semiregrabil pe plăcuța pentru experimente, urmate de firele mov, galben, portocaliu și roz, iar într-un final de cele verzi. verifici de două ori așezarea firelor și conexiunile, ții degetele încrucișate și conectezi din nou Arduino la portul USB. nu-ți face griji, portul USB este protejat în caz de probleme, așa că nu ai ce strica la calculator. un semn bun, că totul merge, îl reprezintă luminarea ecranului LCD. ajustând potențiometrul semiregrabil, ar trebui să vezi la unul dintre capete 2 rânduri cu câte 16 pătrățele negre alcătuite din pixeli, pe ecran. dacă nu funcționează, verifică din nou conexiunile.

vei modifica acum micul program de mai sus pentru a include ecranul LCD folosind biblioteca inclusă în aplicația Arduino, LiquidCrystal.h. aceasta definește un tip de obiect LiquidCrystal care poate fi folosit pentru a controla ecranul LCD. pentru început, vei ințializa obiectul dându-i o denumire și specificând conexiunile către Arduino. pentru a inițializa ecranul, vei adăuga instrucțiunea de inițializare a acestuia în funcția setup: lcd.begin. inițializarea se face specificând dimensiunile ecranului. Arduino știe prin intermediul LiquidCrystal.h să comunice cu ecrane de mai multe dimensiuni.

// am nevoie de o bibliotecă care știe să comunice cu ecranul LCD
#include <LiquidCrystal.h>
// inițializez un obiect de tip LiquidCrystal pe care îl denumesc
// lcd, căruia îi spun la ce conectori Arduino am conectat firele
// 12 = galben, 11 = portocaliu, 5, 4, 3, 2 = cele 4 fire verzi
LiquidCrystal lcd (12, 11, 5, 4, 3, 2);

void setup() {
  // inițializez ecranul și îi spun că are 2 rânduri cu 16 caractere
  lcd.begin (2, 16);
}

după ce ai calculat temperatura, vei curăța ecranul pentru a te asigura că nu e nimic pe el folosind lcd.clear. vei muta cursorul pe coloana 0, linia 0 (colțul stânga-sus) folosind lcd.setCursor, cu parametri coloană (de la 0 la 15) urmat de linie (de la 0 la 1), după care vei tipări diferite lucruri, în ordine, folosind lcd.print. lcd.print poate primi ca parametri șiruri de caractere delimitate de ghilimele, numere zecimale sau întregi, dar și caractere speciale, prin codul lor, cum este simbolul °, dar pe care trebuie să-l diferențiezi de un număr întreg obișnuit, folosind sintaxa (char). această operație se numește conversia tipului (cast în engleză) și îi spune Arduino să trateze numărul 223 ca un caracter (char).

notă
lcd este o etichetă pe care am ales-o eu în vederea accesării obiectului LiquidCrystal. dacă ai altă preferință, cum ar fi ecran, o poți folosi fără probleme. trebuie să ții cont că funcțiile apelate se vor modifica corespunzător: lcd.begin devine ecran.begin, lcd.clear devine ecran.clear, lcd.setCursor devine ecran.setCursor și lcd.print devine ecran.print. acest tip de funcții, care depind de obiectul folosit, în jargonul programatorilor se numesc metode și se folosesc de starea internă a obiectului de care sunt anexate pentru a-și îndeplini rolul. fără inițializarea unui astfel de obiect, ele nu pot fi folosite, așa că să nu uiți bucata anterioară funcției setup în care alegem o etichetă pentru un obiect de tip LiquidCrystal.

void loop() {
  // ...
  // bucata de până aici rămâne neschimbată
  float t = T - 273.15;
  // dacă e ceva scris pe ecranul LCD, îl șterg
  lcd.clear ();
  // îi spun ecranului LCD că vreau să scrie începând cu colțul stânga-sus
  // primul 0 îl reprezintă coloana (0-15), al doilea rândul (0-1)
  lcd.setCursor (0,0);
  // pornind de la cursor, afiseaza T = 
  lcd.print ("T = ");
  // apoi temperatura în grade Celsius
  lcd.print (t);
  // urmată de simbolul grad
  lcd.print ((char) 223);
  // și apoi de litera C
  lcd.print ("C");
  // fac o pauză de 1 secundă, după care repet instrucțiunile
  delay (1000);
}

după încărcarea programului pe plăcuța Arduino, singurul lucru pe care îl mai ai de făcut este să ajustezi contrastul în așa fel încât literele și cifrele să fie lizibile.

bogdan

bogdan » arduino: lucruri necesare

05:24 pm on Oct 31, 2018 | read the article | tags:

am descoperit Arduino în 2009, într-o reclamă Google. ce m-a uimit e că pe o mică plăcuță am regăsit ceva asemănător primului calculator pe care l-am văzut în viața mea, CIP-03, o clonă de ZX-Spectrum. un calculator care mi-a gravat în minte așteptările pe care trebuie să le am de la o mașină de felul ăsta: să pot să o programez și să interacționeze cu lumea din exterior. Arduino le face pe-amândouă, lucru pentru care l-am inclus în cursul meu de modelare și simulare pe care-l țin periodic la Facultatea de Fizică. recent, am primit la curs întrebarea: ce-ar însemna un set minim cu care să pot experimenta cu Arduino? așa că răspunsul vine în continuare.

arduino

Arduino UNO R3 – prefer varianta cu USB-type-B (conector USB pentru imprimantă) și AtMega328-PU (cip cu piciorușe într-un soclu); raționamentul e simplu: așa cum am văzut în propriul buzunar, când faci experimente cu Arduino poți să-l strici; un cip separat e numai o fracțiune din preț și e majoritar componenta care se strică cel mai ușor; alt motiv este că în timp, vrei să incluzi Arduino în proiecte mai mici și mai statice, iar un Arduino cu soclu îți permite să programezi mai multe cipuri fără bătăi de cap. când ai avansat, poți trece la variantele cu cip miniatură lipit, în diferite forme, dar primul Arduino ar trebui să fie UNO R3 clasic; (29 de lei, 30 de lei, 146 de lei)
LED-uri – pentru că primul lucru pe care-l vei face cu el e să aprinzi luminițe; e simplu și oferă satisfacții imediate; recomand LED-uri de 5mm, cât mai multe, cât mai colorate (49 de lei); câteva IR nu strică (3,5 lei / 5 bucăți);
fire de conexiune – Arduino e făcut să folosească fire de conexiune; dacă LED-urile se pot pur și simplu ”înfige” în sloturile Arduino, celelalte componente sunt mult mai greu de conectat așa că două seturi de fire de conexiune (un set are 10 fire), unul cu pini la ambele capete (”tată-tată”), celălalt cu pini și socluri (”mamă-tată”) sunt valoroase (3,5 lei / 10 bucăți / 15 cm / tată-tată și 4,5 lei / 10 bucăți / 15 cm / tată-mamă);
placă de conexiune (breadboard) – pentru a putea crea mici circuite ai nevoie de o placă de conexiune; recomand una medie (half-size), cu linii pentru alimentare – acestea sunt linii perpendiculare pe rândurile conectate ale plăcii, care pot fi folosite pentru alimentarea circuitului (5 lei, 5 lei, 33 de lei (calitate mai bună));
ecran LCD – după ce ai aprins LED-uri și te-ai jucat puțin cu interfața serială, vei fi uimit că poți scrie mesaje pe un mic ecran LCD; îl recomand pe cel ieftin, cu conexiune paralelă; necesită în plus un potențiometru de 10KΩ și destul de multe fire, dar are avantajul că e ieftin (11 lei, 19 lei, 35 de lei); o variantă mai bună e versiunea I2C a acestuia, dar ceva mai scumpă (18 lei, 29 de lei, 76 de lei);
butoane – știi să afișezi lucruri, deci să obții informație din Arduino; e momentul să și introduci și ce e cel mai simplu decât să conectezi butoane cu contact momentan la el (push-buttons); un simplu meniu are nevoie de minimum 3 butoane: sus/jos pentru a naviga în el și un buton pentru a selecta opțiunea (4,5 lei / 3 bucăți, 6 lei / 3 bucăți);
potențiometre – când butoanele nu te mulțumesc, vei vrea să folosești un potențiometru; la un Arduino poți conecta între 1 și 6 (9 lei / 6 bucăți, 15 lei / 3 bucăți);
rezistori – Arduino limitează curentul prin terminalele sale, însă de multe ori ai nevoie fie să limitezi curentul mai mult, fie să le folosești pentru a forța anumite intrări într-o poziție (pull-up, pull-down) fie pentru a limita tensiunile analogice; valorile pe care le recomand sunt 470Ω (0,6 lei), 1KΩ (0,6 lei), 4.7KΩ (0,6 lei), 10KΩ (0,6 lei), 100KΩ (0,6 lei) și 1MΩ (0,6 lei). câte 10 din fiecare, pentru că oricum sunt ieftine;
punte H – una dintre aplicațiile cele mai spectaculoase ale Arduino este aceea de a face lucrurile să se miște; iar pentru a putea comanda motoare folosind Arduino, ai nevoie de o punte H; de preferat dublă pentru că e foarte ușor să controlezi mișcarea unui robot utilizând două motoare (10 lei);
tranzistori MOS-FET – de la LED-uri mici la LED-uri mai mari e un singur pas, așa că MOS-FET-urile sunt extrem de utile pentru controlul consumatorilor mari de curent; fie ca vrei să creezi o mini-seră încălzită sau să controlezi un element Peltier pentru a-ți răci băutura preferată, un MOS-FET e ideal; numărul minim recomandat este 2 (5 lei / 2 bucăți);
relee – când vezi că poți controla atâtea lucruri în jurul tău, te gândești cum poți trece la nivelul următor: poate să aprinzi o veioză? un releu te ajută să separi Arduino de tensiunile periculoase (priză, de cele mai multe ori). 2 sunt suficiente, de preferat cu tranzistorii și diodele necesare pentru control; dacă nu, se mai trec în listă câte un tranzistor și o diodă de curent mic pentru fiecare releu (7 lei / 2 bucăți, 19 lei / 2 bucăți);
conector baterie – la un moment dat proiectele vor deveni independente de interfața USB; în această situație, un conector de la o baterie de 9V la borna de alimentare a Arduino devine esențial (6 lei); o variantă mai bună, este obținearea unei cutii pentru baterii AA minimum 4 (3,5 lei), deși recomand 6 (5 lei) cu conector pentru alimentare – deoarece Arduino consumă destul de mult curent, iar o baterie de 9V are o capacitate relativ mică;

câteva tipuri de senzori

fotorezistori – așa cum îi spune numele, un fotorezistor își variază rezistența cu lumina incidentă: cu cât e mai multă lumină, cu atât rezistența e mai mică; poate fi extrem de util pe post de ”ochi” pentru un mic proiect Arduino; doi sunt utili (3,9 lei / 2 bucăți, 4 lei / 2 bucăți);
termistor – pe lângă lumină, temperatura e o altă mărime fizică fundamentală care poate fi convertită ușor în rezistență; un termistor este o rezistență care crește atunci când e frig și scade când e cald; recomand un termistor cu valoarea de 100KΩ; unul e suficient (3 lei);
microfon – poate nu la fel de facil de utilizat, însă cu siguranță spectaculos, un senzor util este cel pentru sunet; recomand un microfon cu electret și amplificator integrat, pentru a putea trimite semnalul acestuia direct, către un Arduino; util în proiectele în care vrei ca Arduino să ”simtă” muzica (36 de lei);
senzor de distanță – aici sunt mai multe opțiuni: optic sau cu ultrasunete; îl recomand pe cel din urmă, având o rază mai mare de acțiune, cele optice putând fi ușor replicate cu simple leduri (20 de lei, 25 de lei, 29 de lei);

câteva tipuri de elemente de control

difuzor / buzzer – după semnale optice, a doua metodă de a obťine feedback de la Arduino sunt sunetele; conectat direct la unul dintre piciorușele Arduino, un mic difuzor poate fi elementul care poate face proiectul tău mai interesant (2 lei, 6 lei, 10 lei);
servomotoare – sunt mici motoare care au inclus un circuit de feedback și cu ajutorul cărora poți interacționa prin mișcări precise cu lumea reală; recomand două, în special utile pentru a poziționa un senzor (ultrasonic sau optic) cu două grade de libertate (pe suprafața unei sfere) pentru, spre exemplu, un radar 3D (40 de lei / 2 bucăți, 46 de lei / 2 bucăți, 58 de lei / 2 bucăți);
motoare – sunt elementele care asigură autonomie unui robot controlat de Arduino; recomand două, cu reductor și roată inclusă (24 de lei / 2 bucăți);

câteva elemente esențiale, dar care pot fi achiziționate ulterior

fire de conexiune pentru breadboard (jumperi) – deși nu sunt necesare, sunt extrem de utile atunci când dezvolți circuite complexe folosind o placă pentru conexiuni; permit evitarea interferențelor parazite cauzate de firele prea lungi (15 lei, 25 de lei, 32 de lei);
un programator ICSP – am luat unul atunci când am stricat primul Arduino; e util pentru reprogramarea fără bătăi de cap a bootloader-ului pe cip și nu numai (60 de lei, 20 USD – include convertor USB-serial); poate fi simulat cu un alt Arduino;
un multimetru – nu neapărat necesar, este util în depanarea circuitelor și identificarea mai ușoară a problemelor: fie e un fir întrerupt, fie o tensiune nu e suficientă, fie un curent e prea mare; multimetrul identifică cu ușurință aceste probleme; ca să nu mai vorbim de rezistorii cu peliculă metalică pe care eu nu disting culorile (55 de lei, 61 de lei); poate fi emulat cu puțină îndemânare cu un Arduino;
convertor USB-serial – nu e esențial, dar incredibil de util pentru reprogramarea proiectelor care folosesc variante miniaturizate de Arduino (15 lei, 39 de lei, 75 de lei – include ICSP); poate fi emulată cu un Arduino;
o sursă coborâtoare/ridicătoare de tensiune – pentru proiectele mobile e o necesitate; Arduino conține un stabilizator de tensiune liniar, ieftin și robust, dar lipsit de eficiență; un astfel de circuit permite alimentarea de la acumulatori; recomand o variantă care să poată susține 5V la cel puțin 1.5A (30 de lei, 30 de lei);

raspberry pi

Raspberry PI 3 – e un mic calculator pe o plăcuță de mărimea unui card bancar; dacă Arduino e echivalentul unui ZX-Spectrum, Raspberry PI 3 e un fel de Pentium 3; adică destul de puternic pentru a vedea filme și a naviga pe internet; mai mult, are 4 porturi USB pentru periferice normale, o mulțime de spațiu de stocare și interfețe de rețea: bluetooth, wi-fi și ethernet; altfel, face cam ce face Arduino, dar cu un sistem de operare obișnuit, care suportă compilator C, limbaje de scripting (PHP, Ruby, Perl, JS) și servere http și ssh (209 lei, 245 de lei, 259 de lei);
RPI-UNO-HAT – produs de echipa din spatele watterott.com, RPI-UNO-HAT este o extensie a Raspberry PI care conține un Arduino împreună cu circuitele necesare interfațării; deși poate fi emulat cu un Arduino și câteva componente discrete, recomand varianta asta pentru simpliatatea în utilizare; poți astfel să construiești lucruri complexe, conectate la internet; RPI-UNO-HAT necesită un mic circuit pentru alimentarea Raspberry PI prin conectorul comun, care în această situație trece în lista de componente obligatorii (16 EUR);

software

Arduino – așa cum îi spune numele e complementul software al plăcuței; cu ajutorul acestuia poți programa o mulțime de plăcuțe compatibile cu Arduino (download: windows, linux, mac os x);
GNU Octave – dacă ești familiarizat cu MatLab, GNU Octave e o variantă open-source a acestuia; altfel, imaginează-ți-l ca pe un calculator foarte avansat care face în primul rând operații cu matrice (download: windows, linux, mac os x); sunt utile pachetele suplimentare io (download) și instrument-control (download);
native script – este o extensie de node.js care permite dezvoltarea de aplicații mobile; foarte util atunci când vrei să conectezi proiectul tăubla telefon (ghid instalare);
Microsoft Visual Studio Code – de departe cel mai bun editor text pentru programatori; chiar dacă e făcut de Microsoft, e open source, rulează cam pe orice platformă și e bun! (download);
Git/GitHub – atât clientul desktop cât și cel de consolă, împreună cu un cont gratuit, pentru a salva și a împărtăși cu toată lumea experimentele tale (download);

bogdan

bogdan » arduino: conectarea la octave

10:29 pm on Apr 9, 2018 | read the article | tags:

în urma rugăminții profesorului Stamatin, de câțiva ani țin un curs la Facultatea de Fizică din Măgurele. dacă inițial numele «modelare și simulare» ascundea ecuații matematice și metode numerice, recent am înlocuit diferențialele cu Arduino, în ovațiile celor câtorva studenți care frecventează cursul. dotările limitate m-au făcut să devin creativ cu materialele de curs, iar rezultatul mi s-a părut suficient de interesant pentru a-l reproduce aici. așa că:

GNU Octave:
GNU Octave este o alternativa gratuită a MathWorks MatLab. ambele fac în mare același lucru pentru utilizatorul de rând: efectuează extrem de eficient operații matematice cu matrice. mai mult, în cele mai multe cazuri, codul scris pentru unul funcționează fără probleme în celălalt. GNU Octave poate fi descărcat de aici.

recomand varianta zip, pentru arhitectura potrivită calculatorului tău cu mențiunea că în cazul în care nu știi ce înseamnă arhitectură, poți alege cu încredere varianta care are în numele fișierului w32. fișierul descărcat este o arhivă de tip zip și necesită dezarhivare. dacă utilizezi Microsoft Windows 10, poți folosi cu încredere utilitarul integrat în Windows Explorer. vei obține un director care conține octave.bat, fișierul pe care trebuie să dai dublu-clic pentru a porni aplicația.

instrument control:
GNU Octave poate comunica cu Arduino prin intermediul interfeței seriale, dar nu o poate face în starea inițială. la fel ca în cazul MatLab, e necesară o bibliotecă externă cu instrucțiuni care să-i permită citirea datelor seriale. cea pe care am utilizat-o în curs se numește instrument control și e disponibilă aici.

biblioteca se instalează ușor, descărcând fișierul și mutându-l în directorul src (de la source) al GNU Octave. în cazul în care directorul nu există, îl poți crea pur și simplu. urmează să pornești GNU Octave și cu ajutorul browserului de fișiere din stânga ferestrei principale, găsești și intrii în directorul src. ultimul pas constă în introducerea următoarei comenzi în consola GNU Octave:

>> pkg install instrument-control-0.3.0.tar.gz

instalarea instrument control durează destul de mult așa că poți să iei o pauză de câteva minute.

read_arduino.m:
pentru citirea interfeței seriale a Arduino am scris o mică bucățică de cod în formatul GNU Octave, read_arduino.m care îți pune la dispoziție o funcție cu ajutorul căreia poți citi datele într-o matrice. presupunem că îmi doresc să citesc tensiunea pe fiecare dintre cele 6 intrări analogice și să îi urmăresc evoluția în timp, la intervale de o secundă.

codul pentru Arduino este următorul:

void setup () {
  Serial.begin (9600); // <-- read_arduino.m suportă doar viteza de 9600 bauds
}

void loop () {
  byte port = 0; // <-- am nevoie de o variabila care specifica numarul portului analog
  while (port < 6) { // <-- cel mult voi citi date de la portul 5
    Serial.print (analogRead (port)); // <-- citesc si trimit valoarea citita catre portul serial
    Serial.print ("\t"); // <-- pentru a separa valorile, folosesc caracterul TAB (\t)
    port = port + 1; // <-- trec la portul urmator
  }
  Serial.print ("\n"); // <-- pentru a separa sirurile de date, folosesc caracterul NEW LINE (\n)
  delay (1000); // <-- aștept o secundă pentru a relua ciclul
}

care ar trebui să producă la fiecare secundă câte o linie cu valori cuprinse între 0 și 1023, corespunzătoare diferenței de potențial între pinul GND și pinii de la A0 la A5.

pentru a citi 10 rânduri conținând cele 6 valori, vei folosi:

>> A = read_arduino ('COM5', 10, 6)
A =

   339   339   326   319   312   324
   328   330   319   313   309   319
   327   329   319   314   310   319
   324   327   317   312   308   318
   323   326   316   311   307   317
   324   327   317   312   309   318
   322   324   314   310   306   315
   323   326   317   312   307   317
   324   327   317   312   308   318
   323   326   316   311   307   317

note de final:
COM5 este portul serial pe care se conectează plăcuța mea Arduino. în cazul tău, acesta va fi cel mai probabil diferit. verifică în Device Manager care este portul corect. read_arduino.m funcționează și pe Linux și pe MAC, însă în locul COM5 vei folosi denumirea portului corespunzătoare platformei. portul serial trebuie să fie liber, drept pentru care amintește-ți ca înainte să rulezi read_arduino să închizi monitorul serial al Arduino. ca în cazul MathWorks Matlab, fișierul read_arduino.m în directorul curent pentru ca funcția să devină accesibilă.

bogdan

bogdan » arduino: senzor de lumină cu led

11:20 am on Mar 25, 2018 | read the article | tags:

în urma rugăminții profesorului Stamatin, de câțiva ani țin un curs la Facultatea de Fizică din Măgurele. dacă inițial numele «modelare și simulare» ascundea ecuații matematice și metode numerice, recent am înlocuit diferențialele cu Arduino, în ovațiile celor câtorva studenți care frecventează cursul. dotările limitate m-au făcut să devin creativ cu materialele de curs, iar rezultatul mi s-a părut suficient de interesant pentru a-l reproduce aici. așa că:

senzorii de lumină domină lumea simțurilor electronice. cu mici modificări, aceștia pot răspunde unei multitudini de întrebări, de la banalul ”e lumină afară?”, la ”ce culoare are un obiect?” sau ”la ce distanță am un obstacol în față?”. la prima vedere funcționează complex: un fragment de siliciu reacționează la lumina incidentă, modificând o mărime electrică. Einstein a luat premiul Nobel pentru explicarea principiului în 1921, deci trebuie să fie complicat. cu toate acestea, tehnologia s-a dezvoltat pe parcursul secolului care a trecut suficient de mult ca să putem reface experimentul în bucătărie.

materiale necesare:

  • un Ardunio, de orice fel, în funcție de cât de familiarizat ești cu el; la curs l-am folosit pe ăsta (31 de lei);
  • un led obișnuit, orice culoare și orice mărime, dar să fie un led simplu; știi că e un led simplu (30 de bani), dacă e foarte ieftin;
  • două fire pentru a lega ledul la Arduino; mie îmi plac astea (3 lei), dar pot fi de orice fel;

puțină teorie:
un led este un dispozitiv semiconductor, de obicei din siliciu, al cărui element activ este vizibil. elementul activ poartă numele de joncțiune, adică locul de întâlnire pentru două materiale cu proprietăți diferite. ce e important de reținut pentru construcția de față este că cele două materiale formează un sandviș cu un mic spațiu între ele, asemeni unui condensator de la fizică. în funcționarea normală, trecerea curentului electric prin acel mic spațiu produce lumină. cu siguranță ai observat că polaritatea e importantă, deoarece lumina și circulația curentului se produc doar într-un singur sens.

ce se întâmplă în schimb când polaritatea e inversată? lipsa curentului electric duce la acumularea de sarcini electrice pe fețele sandvișului, încărcând condensatorul. cum naturii îi plac simetriile, orice rază de lumină incidentă generează perechi de sarcini, care se vor deplasa în direcții opuse, datorită atracției electrostatice. ajunse pe suprafețele sandvișului, acestea vor scădea sarcina acumulată pe condensator, scăzând proporțional și tensiunea electrică.

în mod normal, procesele se întâmplă extrem de repede și sunt extrem de mici ca intensitate. dar aici intervine genialitatea oamenilor care au proiectat Arduino: acesta e suficient de rapid și suficient de sensibil pentru a face față experimentului.

ce se va întâmpla:
Arduino va încărca ledul, alimentându-l invers, după care va număra cât îi ia condesatorului format în jurul joncțiunii ledului pentru a se descărca. pentru a repeta experimentul de la curs, vei conecta ledul cu plusul (piciorușul mai lung, anodul) la GND (ground, 0V) și minusul (piciorușul mai scurt, catodul) la unul dintre terminalele Arduino, cu excepția pinilor 0,1 – care sunt responsabili pentru comunicarea serială și pinul 13, care are deja un led conectat intern și care te va încurca. eu am ales pinul 2.

în pregătirea Arduino, am definit un loc în memorie pentru stocarea informațiilor primite de la led. tipul de date folosit va fi întreg (int), numărând câte perioade de timp condensatorul a fost încărcat. pentru a fi accesibilă de oriunde, definiția se va afla în afara și înaintea celor două funcții speciale Arduino, setup și loop.

int value; // <- asa definesc un loc in memorie, int este tipul, iar value este numele
// pentru ca am definit-o în afara setup și loop, voi putea să o accesez de oriunde, prin nume
void setup() {}
void loop() {}

pentru citirea informațiilor, am folosit interfața serială a Arduino, care va fi inițializată prin:

int value;
void setup() {
  Serial.begin(9600); // <- aici initializez conexiunea seriala cu viteza de 9600 caractere / s
}
void loop() {}

periodic, am încărcat ledul pentru un interval de timp. experimental am ales 1ms.

int value;
void setup() {
  Serial.begin(9600);
}
void loop() {
  pinMode(2, OUTPUT); // <- aici definesc pinul 2 ca fiind pin de iesire
  digitalWrite(2, HIGH); // <- setez tensiunea pe pinul 2 la tensiunea de alimentare a Arduino
  delay(1); // <- astept 1ms, sa incarc condensatorul ledului
}

am măsurat în cât timp tensiunea la bornele ledului scade sub un anumit prag. aici aș fi putut să folosesc convertorul analog-digital din Arduino, dar în unele situații este prea lent. așa că am folosit proprietatea unui pin digital configurat ca intrare de a-și schimba starea în jurul jumătății tensiunii de alimentare a Arduino. astfel, dacă tensiunea la intrare scade sub 2.5V față de GND (pentru un Arduino alimentat la 5V), valoarea citită intern va fi LOW, în timp ce dacă tensiunea crește peste 2.5V, valoarea citită va fi HIGH.

pentru a determina în cât timp tensiunea la bornele ledului scade, am verificat la intervale scurte de timp dacă a scăzut. dacă a scăzut, trimit prin conexiunea serială valoarea înregistrată, altfel, mai aștept puțin timp și verific din nou. experimentând, am folosit ca timp de așteptare 40uS.

int value;
void setup() {
  Serial.begin(9600);
}
void loop() {
  pinMode(2, OUTPUT);
  digitalWrite(2, HIGH);
  delay(1);
  pinMode(2, INPUT);
  value = 0; // <- aici resetez ce stochez în memorie
  while(digitalRead(2) == HIGH) { // <- citesc pinul 2 si verific daca tensiunea depasește pragul
    delayMicroseconds(40); // <- daca e peste prag, lumina e prea slabă și aștept 40uS
    value = value + 1; // <- cresc cu o unitate valoarea stocată
  }
  // <- aici tensiunea la bornele ledului a scazut sub prag
  Serial.println(value); // <- așa că trimit valoarea prin conexiunea serială
}

în funcție de led și de condițiile de iluminare, valorile primite de calculator sunt uneori negative. acest lucru se întâmplă deoarece numărul de repetări depășește valoarea maximă care poate fi stocată într-un segment de memorie de tip întreg. pentru a preveni această situație, am introdus o limitare la 255 a numărului de cicluri pentru care verificarea are loc:

int value;
void setup() {
  Serial.begin(9600);
}
void loop() {
  pinMode(2, OUTPUT);
  digitalWrite(2, HIGH);
  delay(1);
  pinMode(2, INPUT);
  value = 0;
  while((digitalRead(2) == HIGH) && (value < 255)) { // <- verific în plus dacă valoarea stocată e sub 255
    delayMicroseconds(40);
    value = value + 1;
  }
  Serial.println(value);
}

note de final:
ledul este sensibil la același tip de lumină pe care o emite. folosind acest principiu, poți foarte ușor să-l transformi într-un senzor de culoare. de asemenea, un led are de obicei o lentilă care dirijează razele de lumină, limitându-i astfel câmpul vizual, la fel ca în cazul emisiei. folosind această informație, poți adapta foarte ușor un led pentru a măsura distanța.

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.