ublo
bogdan's (micro)blog

bogdan » classroom of the elite

12:19 am on Nov 29, 2023 | read the article | tags:

as a self-proclaimed relic of the past, venturing into the realm of new anime often feels like a gamble. i’m ancient in the eyes of my peers, but once in a while, a series breaks through the age barrier. enter Classroom of the Elite – an exception that’s managed to retain my attention amidst the generational shift.

one gripe i’ve developed with contemporary anime is the excessive use of CGI. the hand-drawn essence, synonymous with anime for me, seems lost. however, Classroom of the Elite sidesteps this issue with finesse. the animation remains crisp, seamlessly integrating CGI elements. the characters’ nuanced expressions, complemented by unique eye designs, draw you in. and let’s not forget the fan service, a context provider for some of the most intense exchanges since the era of Neon Genesis Evangelion.

i’ll confess – i’m not a fan of chess. to me, it’s not a “true” game. enter the realm of poker, a game where predicting opponents’ moves is the key to victory. watching Classroom of the Elite feels like observing a high-stakes poker match. you know who holds the power, but the unpredictability of each move keeps you on edge. it’s a high school hierarchy game where misfits are expected to rise, packed with plot twists that make the journey thrillingly unpredictable.

the series hints at inspirations from ’90s TV like The Pretender. a child prodigy manipulating situations despite an unassuming facade is a trope i’ve seen before. there are echoes of Code Geass: Lelouch of the Rebellion too. however, Classroom of the Elite delves deeper into characters’ psyches, blurring the lines between good and bad. The dialogues could easily fit a psychological drama set in a mental hospital, adding layers to the already multifaceted characters.

what caught my attention was its compactness. despite longer episodes, there’s no filler content. it’s so dense with detail that revisiting certain scenes becomes tempting.

but the cherry on top? naming episodes with well-curated quotes from works exploring the human psyche is a stroke of brilliance. it’s a subtle nod, indicating this anime’s true genre – a psychological thriller that keeps viewers hooked episode after episode.

bogdan » metropolis (2001)

11:44 pm on Oct 9, 2023 | read the article | tags:

sometimes, my train of thoughts wanders in the most random directions. last time, this happened as i was listening to one of biskuwi’s tracks. from there, i somehow ended up watching a colorized sequence of 1927’s metropolis synced to techno music, and i was utterly mesmerized by its aesthetics. while i’m not typically a fan of silent films, i decided to check if there was a recent adaptation, and that’s when i discovered the 2001 anime version.

the trailer, unfortunately, did not do justice to the film. it presented a misleadingly cartoonish look, which in hindsight, was a result of poorly chosen shots. however, if you manage to push past this initial barrier (i used microsoft movies and tv for that), you’ll find yourself immersed in a beautifully drawn, organically animated 50s style cartoon. it reminds me of both astroboy for its visuals and fantasia for the fluidity of motion. the colors are vibrant, and surprisingly, a prevalent theme emerges: robots taking mankind’s jobs.

the story unfolds at a deliberate pace, initially appearing somewhat dull, with an almost post-purchase dissonance. soon, though, it plunges into action, following a japanese private investigator’s nephew who stumbles upon the de facto leader of metropolis commissioning an android surrogate daughter. this daughter, it turns out, is instrumental in a terminator-style uprising of the machines. while the uprising is ultimately prevented through the boy’s humanity, this is not your typical hollywood movie. there are several subplots left unresolved or concluded hastily, and a multitude of characters with interesting, lightly touched-upon themes.

a permeating christian overtone stems from the tower of babel biblical story, with a few visually stunning scenes inspired by christianity that left their vivid afterimages lingering in my mind for a long time.

metropolis 2001 is, above all, a visual experience. personally, i was expecting more of the skyscraper aesthetics from the original film’s imagery. while there are a few, none quite match the magnificence of brutalist art deco. what struck me most was the meticulously planned and lengthy choreography for every character’s motion, even the most basic ones. it’s mesmerizing to watch, surprising every single time through intricate, fluid, ballet-like movements, making the metropolis animation a worthwhile investment of time.

bogdan » the problem with AI

01:42 pm on Jul 20, 2023 | read the article | tags:

generative machine learning models such as chatgpt and midjourney have demonstrated that our creativity, once thought to be a unique human essence, is in fact one of the simplest aspects of our core that machines can successfully replicate.

bogdan » red sonja – she-devil with a sword

11:01 pm on Jun 16, 2023 | read the article | tags:

comic books are one of my favorite things. i appreciate the storytelling power of this medium, with its small panels that serve as captivating pieces of art, weaving fantastic romantic tales filled with heroes and villains. seeking something different, i took a leap of faith and ordered the first volume of red sonja – she-devil with a sword from amazon, and boy, was i in for a treat.

i was immediately impressed by the artwork. the graphics are clean and rich in detail, with an overall hue that expertly sets the cinematic framing. red sonja herself is a likable, flawed character, guided by a strong moral compass. the first story arc of the book proved to be truly captivating, filled with intriguing plot twists that kept me sleepless after long days at work, when my eyes are weary.

now, delving into some technical aspects, the story takes place in a universe shared by conan the barbarian. it is a world of intense fighting and bloodshed, depicted with graphic images that are tasteful and restrained. the primary theme explored in the first arc revolves around religious manipulation and cultism, featuring a powerful depiction of red sonja as a character who exudes sexiness without veering into vulgarity. impressed by the initial volume, i promptly ordered the next two volumes as well.

bogdan » star trek: strange new worlds

07:30 pm on Jun 10, 2023 | read the article | tags:

i must admit that i added skyshowtime to my long list of subscriptions for tulsa king and star trek. i was afraid to watch the new strange new worlds as my experience with voyager was mixed: had nice ideas, mixed feelings casting and that arc format which i hate, as it makes you wanna binge it.

as thursday evening i was pretty tired, i decided to skip any after-work work and check it out. i was really pleased. no obvious arc, stand-alone episodes, very nice casting (i dislike uhura’s casting as she’s not the sex-symbol i was expecting after michelle nichols and zoe saldana, and also dislike the character “know it all”, “shut up, wesley!” attitude) and amazing ideas – like the touching first episode in which they break the general order one to save an earth-like culture from self-destruction and pike’s facing his own mortality (something that i resonate profoundly with). anyways, good show! will keep you posted.

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 » 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 » de ce nu faci mai multă cercetare?

08:36 am on Nov 4, 2019 | read the article | tags:

recent, asta e întrebarea pe care o aud destul de des. abilitățile mele în domeniu se încadrează în domeniul “physical computing”: ştiu binişor cum funcționează diferite sisteme fizice încât să le conectez la un calculator şi apoi să procesez datele şi să obțin ceva util. am şi 3 brevete în domeniul ăsta. adica “i did my share”. doar că m-am plictisit. nu de satisfacția unei descoperiri – departe de mine gândul, până la urmă asta e pasiunea mea, ci de cum cercetarea “organizată” vede asta.

prin cercetare organizată înțeleg contractarea unui grup de cercetători de către o instituție, publică sau privată să rezolve o problemă. e nevoie de un grup pentru că nimeni nu are cunoştințe care să acopere tot. la nivel superficial, e bine să ai noțiuni despre părțile întregului proiect, însă atunci când ajungi la detalii, e nevoie de o anume experiență care nu poți să o ai decât dacă ai aprofundat un domeniu. şi nu poți fizic să aprofundezi toate domeniile.

prima problemă pe care o am e cu noțiunea asta de grup: pe scurt, nu toți sunt la fel de competenți. în majoritatea cazurilor nu poți să alegi cu cine lucrezi şi te trezeşti în situația că trebuie să faci compromisuri pentru că cineva nu şi-a făcut treaba, iar în opinia mea, asta diluează extrem de mult rezultatul obținut. pentru că în loc să atingi “state of the art” te opreşti la un românesc “merge şi aşa”.

a doua problemă, mai importantă, e legată de partea financiară, dar nu aşa cum ți-ai imagina: intru într-un proiect de cercetare ca să am acces la o infrastructură pe care altfel nu mi-o permit. eh, pentru că suntem în România şi pentru salarii mai mari tăiem din bugetul de achiziții, mă trezesc că pot să cumpăr aproape tot ce am nevoie doar lucrând puțin mai mult la birou sau cumpărându-mi mai puține lucruri de la Zara. şi e trist. poate şi domeniul e de vină, pentru că tot ce-mi trebuie se găseşte pe AliExpress la prețuri derizorii, de altfel şi sursa originală a majorității achizițiilor pentru un proiect. mai mult, pentru lucruri mai complexe (cum am făcut de altfel, mă refer la tranzistori personalizați), nu ies o lună în club şi contractez un serviciu online, pentru că sunt o mulțime.

în al treilea rând e birocrația unui proiect. rapoarte. achiziții. referate de necesitate. discuții cu finanțatori şi investitori şi managementul aşteptărilor lor nerealiste – ah, o mică paranteză aici, dacă nu e niciun risc implicat, n-ar mai fi cercetare, nu? e la fel ca în prima problemă, din lipsă de competență la nivel de grup, m-am trezit plimbat în întâlniri pe post de maimuță, doar pentru a susține credibilitatea proiectului, lucru fără de care pot să trăiesc bine-mersi.

aşa că una peste alta, dacă vreau să cercetez, mai bine muncesc puțin mai mult, îmi iau fără stres tot ce îmi trebuie din munca mea, stau fără stres birocratic, nu trebuie să fac şi munca “colegilor” mei şi beneficiez doar eu de rezultatele muncii mele. cu un singur compromis, că nu pot să adresez o problemă interdisciplinar, că logic, n-am competențe. ah, da, iar probleme găsesc la tot pasul. mai nou şi centralizat, cum sunt pe kaggle.

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 » 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.

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.