Pojďme programovat elektroniku | Programování | Počasí

Pojďme programovat elektroniku: Postavíme si větroměr – redakční „tornádoměr“

  • Dnes si vyzkoušíme ten nejlevnější anemometr z Aliexpressu
  • Jedná se o primitivní otáčkoměr s jazýčkovým kontaktem
  • Stačí tedy počítat elektrické pulzy

V dnešním pokračování našeho seriálu o programování elektroniky se vrátíme k amatérské meteorologii, namísto měření teploty a vlhkosti vzduchu se však podíváme na něco mnohem efektnějšího.

Před pár dny mi totiž dorazil laciný modul senzoru rychlosti větru (anemometru) z čínského AliExpressu. Laciný v tomto případě znamená, že se jeho cena pohybuje v řádu několika stokorun (okolo 17 dolarů) a na stránce produktu chyběla jakákoliv dokumentace.


Anemometr měří rychlost větru

Princip podobných plastových hraček je však zpravidla naprosto identický. Uvnitř rotující hlavice, kterou roztočí vítr, se nachází magnet a v základně pak jazýčkový kontakt – v angličtině známý jako reed switch.

Jedná se o maličký průhledný váleček, uvnitř kterého je vakuum a dva feromagnetické drátky velmi blízko sebe. Když se k nim přiblíží magnet rotující hlavice, drátky se díky působení magnetického pole a dle konstrukce buď spojí a uzavřou obvod, anebo naopak rozpojí.

Klepněte pro větší obrázek
Jazýčkový kontakt (Foto: André Karwath, CC BY-SA 2.0)

Ve vodiči v každém případě dojde k náhlé změně napětí – elektrickému pulzu, který už naše elektronika může zachytit a interpretovat. Můj laciný anemometr má pouze dva vodiče, je to tedy ten nejhloupější možný otáčkoměr na trhu. Odstranil jsem pár šroubků a podíval se do jeho nitra.

Na fotografiích je patrné, že se jazýčkový kontakt nachází podélně uprostřed základny, magnet v rotující hlavici se k němu tedy během jedné otočky přiblíží dvakrát: 360° otočení by mělo vyvolat dva elektrické pulzy.

Klepněte pro větší obrázekKlepněte pro větší obrázekKlepněte pro větší obrázekKlepněte pro větší obrázek
Tištěný spoj v základně anemometru s jazýčkovým kontaktem. Nic dalšího se uvnitř nenachází, jedná se tedy o ten nejprimitivnější možný obvod otáčkoměru.

První a neúspěšný pokus o přečtení otáček

Abych si to ověřil, připojil jsem jeden vodič anemometru k 5V pinu desky Arduino Mega, kterou jsem měl zrovna v redakci, a druhý do jednoho z GPIO pinů, kterým budu číst digitální informaci. Do obvodu jsem nakonec ještě připojil rezistor, který omezí protékající proud.

Klepněte pro větší obrázekKlepněte pro větší obrázek
Pokouším se přečíst logický stav v obvodu s anemometrem. Připojím rezistor, abych snížil protékající proud, protože v cestě není žádný spotřebič.

Poté jsem začal ve smyčce loop vypisovat digitální hodnoty z pinu 21, na který byl anemometr připojený, do sériové linky a vší silou roztočil hlavici senzoru. Namísto střídajících se nul a jedniček a obdélníkového signálu v plotru, které by představovaly postupné spínání a rozpínání jazýčkového kontaktu, však Arduino do sériové linky vypisovalo jen samé jedničky. A jejda, ti Číňani už neumějí vyrobit ani takto primitivní zařízení a já přišel o tři stovky!

Klepněte pro větší obrázek
Nehledě na to, jestli anemometrem otáčím, nebo je v klidu, Arduino napětí na jeho pinu interpretuje stále jako logickou jedničku. Něco je špatně.

Ale kdepak, problém je, skoro jako vždy, mezi židlí a počítačem. Jedná se pouze o typickou chybu začátečníků, kteří si neuvědomí, že v okamžiku, když se jazýčkový kontakt rozpojí, může Arduino stále zaznamenávat nějaký šum, neboť GPIO pin s otevřeným a nikterak izolovaným obvodem se prakticky promění v anténu.

Abych si to ověřil, připojil jsem anemometr namísto do digitálního do analogového pinu a začal do plotru vypisovat přesnější hodnoty z A/D převodníku.


Při rozpojeném kontaktu Arduino nevidí logickou nulu, ale šum

Heuréka! Když je obvod uzavřený, Arduino měří téměř plných 5V, ale když jej jazýčkový kontakt rozpojí, neklesne na stabilních 0V, a tedy logickou nulu, ale promění se v silný šum, který nyní jasně vidím v grafickém plotru vývojového prostředí Arduino. Šum je natolik silný, že jej logika řídícího čipu i nadále interpretuje jako logickou jedničku.

Pull-down rezistor pomůže s logickou nulou

Potřebuji tedy nějakým způsobem tento šum vyčistit – vyrobit v rozpojeném stavu stabilní logickou nulu. Pomůže mi odlišně zapojený rezistor. Tentokrát volím o něco silnější a připojím jej do pinu země (GND).

Klepněte pro větší obrázekKlepněte pro větší obrázek
Nové zapojení s tzv. pull-down rezistorem, který se postará o stabilní digitální nulu

Na jedné straně je tedy anemometr nadále připojený k 5V, ale na druhé se vodič rozdvojuje: přes rezistor vede do pinu GND a zároveň opět do jednoho z logických pinů, jehož stav budu následně číst ve svém programu.

Vyrobil jsem tedy základní stabilizační zapojení, se kterým jsme už v našem seriálu pracovali mnohokrát – tzv. pull-down rezistor. Jakmile nyní jazýčkový kontakt rozpojí obvod, připojená země se postará o to, aby k sobě stáhla případný šum a logický pin zaznamená čistou nulu.


S pull-down rezistorem už Arduino získává čistý obdélníkový signál

Potvrzuji si to opět v plotru při detailním čtení jak z A/D převodníku na analogovém pinu, tak na tom digitálním, který už nyní vrací čisté digitální stavy 0 a 1 podle toho, v jakém stavu je zrovna jazýčkový kontakt.

Jen připomenu, že vedle pull-down rezistorů používáme ještě tzv. pull-up rezistory, které zapojujeme naopak k pracovnímu napětí čipu (5V, 3,3V apod.) a docílíme s nimi stabilní logické jedničky. Pull-up rezistory jsou nezbytné například u sběrnice I2C, v jejichž signálních vodičích musí být v klidu stabilní logická jednička, neboť logická nula iniciuje začátek komunikace.

V případě Arduina a mnoha dalších prototypovacích stavebnic se ale o ně nemusíme povětšinou vůbec starat, knihovna Wire pro práci s I2C totiž aktivuje interní pull-upy v řídícím čipu prototypovací destičky. Externí by byly potřeba pouze u nestandardních obvodů (třeba při použití delších/nekvalitních vodičů apod.).

Pulzy anemometru za mě spočítá interrupt

Výborně, takže mám anemometr, který vytváří obdélníkový signál, ale jak ho číst? Namísto neefektivního a programově složitého čtení v nekonečné smyčce loop pomocí funkce digitalRead nebo pulseIn použiji přerušení – interrupt.

Jedná se o techniku, která zpracuje kód ve chvíli, kdy dojde na některém z podporovaných logických pinů ke změně jejich stavu. V případě desky Arduino Mega je jedno z přerušení mapováno právě na digitální pin 21, který už používám (seznam pinů pro všechny oficiální desky Arduino najdete zde).

Samotné sledování stavu pinu poté nastartuji třeba příkazem:

attachInterrupt(digitalPinToInterrupt(21), preruseni, FALLING);

Když jej umístím do hlavní funkce setup, která se zpracovává hned po startu programu, sdělím tím řídícímu čipu, aby neustále sledoval stav na digitálním pinu 21, a pokud začne padat z logické jedničky na logickou nulu, zavolá moji funkci preruseni. Mohl bych také detekovat opačný stav (RISING) nebo třeba jakoukoliv logickou změnu (CHANGE).

Klepněte pro větší obrázek
Program níže při každé detekci pulzu vypíše do sériové linky jejich aktuální počet

Obsah funkce preruseni by měl být co nejstručnější – co nejrychlejší, aby zbytečně neblokoval řídící čip. Já v něm tedy jen nastavím globální proměnnou p na pravdu.

Ve smyčce loop pak mohu stále dokola kontrolovat stav této proměnné, a když bude rovna pravdě, navýším druhou číselnou proměnnou s počítadlem pulzů a vypíšu ji do sériové linky. Zároveň hodnotu proměnné p opět nastavím na nepravdu.

// Globalni promenna indikujici pulz
// Slovo volatile dava prekladaci vedet, ze k promenne bude
// pristupovat jak asynchronni preruseni, tak moje smycka loop,
// takze nesmi dojit ke kolizi pri pokusu o jeji zmenu
volatile bool p = false;

// globalni promenna s poctem pulzu
int pulzy = 0;

void preruseni(){
  // Pokud doslo k preruseni, nastav pulz na true
 p = true;
}

// Hlavni funkce, ktera se spusti po startu
void setup() {
  // Nastartuj seriovou linku co nejvyssi rychlosti,
  // aby seriova linka neblokovala dlouho ridici cip
  Serial.begin(115200);
  // Anemometr je pripojeny na pin 21 (Arduino Mega)
  pinMode(21, INPUT);
  // Nastav interrupt: Pokud se bude menit stav z HIGH na LOW,
  // spust funkci preruseni
  attachInterrupt(digitalPinToInterrupt(21), preruseni, FALLING);
}

// Smycka loop, ktera se opakuje stale dokola
void loop() {
  // Pokud eviduji preruseni
  if(p){
    // Pozastav detekci preruseni
    cli();
    // Navys pocet pulzu
    pulzy++;
    // Resetuj informaci o preruseni
    p = false;
    // Vypis aktualni pocet pulzu do seriove linky
    Serial.print(c);
    Serial.println(" preruseni");
    // Opet spust detekci preruseni
    sei();
  }
}

A teď spočítám RPM

Vytvořil jsem si tedy primitivní počítadlo pulzů z jazýčkového kontaktu a nyní mohu sledovat, jak se bude anemometr chovat. Jedna celá otočka okolo osy by měla vytvořit dva elektrické pulzy, které by se měly vypsat do sériové linky.

Jedno celé otočení hlavice by mělo vytvořit dva pulzy:

Pokud bych podobným způsobem počítal pulzy delší dobu a třeba jednou za pět sekund jejich počet podělil dvěma, zjistím počet celých otoček za pět sekund, a když jej vynásobím 12, odhadnu RPMrevolutions per minute, a tedy počet otáček za celých 60 sekund.

Jejda, ono to ale počítá více pulzů, než by mělo! Zase nějaké rušení?

No dobrá, spustil jsem program výše, začal otáčet lžícemi anemometru, jenže namísto dvou pulzů jich Arduino čas od času zaznamenalo mnohem více. Co je to zase za rušení? Poměrně typický bouncinghopsání, poskakování, kodrcání (jak je libo), které se poměrně často vyskytuje o všech podobných součástek, které mechanicky spojují a rozpojují kontakt (časté je tedy i u tlačítek).

Jednoduše řečeno jde o to, že při pohybu vodičů, které obvod spojí, nebo opět rozpojí, se občas nevytvoří jen jeden pulz, ale i několik dalších, které bychom si mohli představit jako vlny na hladině, když do rybníka hodíme třeba pytel vysloužilých lithiových baterií.

Tento problém lze řešit čistě softwarovou cestou, anebo elektrickou. Po vyvolání funkce prerusit bych tedy mohl několik dalších milisekund ignorovat všechna případná následující přerušení, protože by se mohlo jednat o ono hopsání. Jaký čas zvolit? Na to přijdete vlastním experimentováním, nesmí ale být příliš dlouhý, jinak bych při rychlých otáčkách anemometru ignoroval i zcela legitimní přerušení a naměřil mnohem menší RPM.

Klepněte pro větší obrázekKlepněte pro větší obrázek
Bouncing vyčistím pomocí kondenzátoru, který tyto ruchy pohltí

Druhou a mnohem čistší možností je… Čistící kondenzátor! Vedle rezistoru, který spojuje výstup z anemometru pro stabilní logickou nulu v okamžiku rozpojeného kontaktu, tedy připojím do země ještě vhodný kondenzátor, tedy součástku, která uchovává elektrický náboj.

V našem případě nasaje elektrickou energii případných rušivých pulzů a poté se zase vybije. Jaký kondenzátor zvolit? Opět záleží na konkrétním obvodu a jeho elektrických charakteristikách, já však použil keramický 1uF kondenzátor, který jsem měl zrovna po ruce (v každé elektrodílničce začínajícího bastlíře by neměla chybět krabička s hromadou různých kondenzátorů – z eBaye a Aliexpressu je seženete za pár kaček a levné jsou i v českých obchodech pro kutily).

Kondenzátor signál spolehlivě vyčistil, aniž by ublížil hlavním pulzům a technika přerušení je nyní už bez jediné chybičky spolehlivě počítá.

Známe bezchybné RPM, takže už jen stačí najít datasheet a přepočítat otáčky na rychlost, nebo použít pro kalibraci komerční anemometr

Na úplný závěr na řadu přichází samotný výpočet rychlosti. Jelikož můj neznačkový anemometr žádnou specifikaci nemá, je třeba se porozhlédnout po internetu na ostatní příklady a podobné senzory rychlosti větru, jejichž parametry by mohly být identické.

Klepněte pro větší obrázek
Program níže vypíše každé tři sekundy do sériové linky aktuální RPM

Pokud už nějaký datasheet najdete, zpravidla bude udávat přepočet ve stylu RPM/RPH na rychlost v konkrétních jednotkách. Takže třeba tento kombinovaný poloprofesionální anemometr za 150 dolarů od Davis Instruments ve své specifikaci (PDF) udává přepočet: 1 600 otáček za hodinu (RPH) = 1 míle za hodinu.

// Popularni knihovna casovace pro cipy ATmega
// Lze doinstalovat primo z Arduina
// Z GitHubu treba: https://github.com/PaulStoffregen/TimerOne
#include <TimerOne.h>

// Promenna s poctem pulzu
volatile long pulzy = 0;

// Funkce preruseni pocita pulzy anemometru
void preruseni() {
  pulzy++;
}

// Funkce casoveho preruseni nam rekne, ze je cas spocitat RPM
void preruseniCasovac() {
  cli();
  // Vypocitej pocet celych otocek za minutu
  // Jedna otocka ma dva pulzy * 20, protoze
  // tato funkce se vola kazde tri sekundy
  int rpm = (pulzy * 20) / 2;
  pulzy = 0;
  Serial.print("Otacky: ");
  Serial.print(rpm);
  Serial.println(" RPM");
  sei();
}

// Hlavni funkce setup se zpracuje po startu programu
void setup() {
  // Nastaveni pinu anemometru a seriove linky
  pinMode(21, INPUT);
  Serial.begin(115200);
  // Nastartovani detekce preruseni na pinu anemometru
  attachInterrupt(digitalPinToInterrupt(21), preruseni, FALLING);
  // Nastaveni casovace
  // Kazdych 300 000 000 us (3 sekundy) zavola funkci preruseniCasovac
  Timer1.initialize(3e6);
  Timer1.attachInterrupt(preruseniCasovac);
}

// Smycka loop je prazdna
void loop() {
  ;
}

Postupnými experimenty, porovnáváním a třeba i divokou kalibrací, kdy anemometr umístíte na střechu automobilu, nebo na bicykl a rozjedete se za bezvětří známou rychlostí podle GPS, pak zkalibrujete svůj anemometr tak, aby program RPM přepočítával na rychlost co nejpřesněji. Zcela exaktní to pochopitelně nebude nikdy, ale i mnohem dražší anemometry dosahují odchylky až několika mil za hodinu.

Na závěr nesmí chybět „tornádoměr“

Na závěr se pojďme podívat na krátké video s demonstrací redakčního tornádoměru. Když nějaké dorazí k nám na Živě.cz, budeme o tom vědět. Arduino Mega tentokrát posílá skrze sériovou linku do počítače jen číselnou hodnotu RPM, kde ji zpracovává skript v prostředí Processing a zobrazuje grafické okno s údajem aktuálních otáček a varováním, pokud překročí určitou úroveň.

Redakční Tornádoměr v akci:

Mimochodem, Processing je takové Arduino pro PC. Jeho výchozí programovací jazyk kombinuje Arduino a Javu (lze použít i jiný) a můžete v něm na pár řádcích kódu napsat třeba zrovna podobnou testovací aplikaci, která bude poslouchat na sériové lince příchozí textové zprávy z Arduina a zpracovávat je a třeba kreslit nějaký graf do okna.

Klepněte pro větší obrázekKlepněte pro větší obrázekKlepněte pro větší obrázek
Vývojové prostředí Processing je skvělým doplňkem pro Arduino na straně PC

Jednoduchý program z videa výše v Processingu vypadá takto:

import processing.serial.*;

PFont f;
Serial port;
Integer otacky = 0;

// Funkce setup plni stejnou roli jako na Arduinu 
void setup() {
  // Velikost okna
  size(1280, 720);
  // Titulek okna
  surface.setTitle("Detektor hurikánů v redakci");

  // Pouziji TTF font
  f = createFont("Verdana", 24);
  textFont(f);

  /*
   Nastartovani seriove linky a otevreni
   prvniho zarizeni v poradi rychlosti 115200 b/s
   */
  port = new Serial(this, Serial.list()[0], 115200);
  port.clear();
}

// Funkce pro prevod otacek na textovy popisek
String otackyNaText(int otacky) {
  if (otacky > 120) {
    return "Tornádo Áááááááá!";
  }
  if (otacky > 90) {
    return "Držte si klobouky!";
  }
  if (otacky > 60) {
    return "Vítr";
  }
  if (otacky > 30) {
    return "Vánek";
  }
  if (otacky > 0) {
    return "Lehký vánek";
  }
  if (otacky == 0) {
    return "Bezvětří";
  }
  return "";
}

// Funkce draw je analogii funkce loop z Arduina
// V tomto pripade Processing stale dokola prekresluje obsah okna
void draw() {
  // Cerne pozadi okna
  background(0);
  // Veliksot pisma
  textSize(100);
  //Centrovani pisma
  textAlign(CENTER);
  // Napis textovy popisek na pozici X, Y
  String titulek = otackyNaText(otacky);
  text(titulek, width/2, 150);

  // Pokud jsou otacky pod 90 RPM, nastav barvu pisma na zelenou
  if (otacky < 90) {
    fill(0, 255, 0);
  }
  // Pokud RPM prekorci 90, zmen barvu pisma na cervenou
  else {
    fill(255, 0, 0);
  }

  // Zmen velikost pisma a na pozici X, Z napis aktualni otacky
  textSize(250);
  text(str(otacky) + " RPM", width/2, 500);

  // Pokud jsou nejaka data na seriove lince z Arduina
  while (port.available() > 0) {
    // Precti text az ke konci radku (z Arduina posilam hodnoty oddelene koncem radku, tedy funkci println)
    String radek = trim(port.readStringUntil('\n'));
    // Pokud se mi podarilo precist radek
    if (radek != null) {
      // Preved obsah radku na cele cislo a uloz do globalni promenne otacky
      otacky = Integer.parseInt(radek);
    }
  }
}
Diskuze (8) Další článek: Sci-fi v praxi: První kluzák s iontovým motorem ulétl několik desítek metrů

Témata článku: Pojďme programovat elektroniku, Programování, Arduino, Počasí, C++, Logická nula, Logická jednička, Digitální pin


Určitě si přečtěte



Aktuální číslo časopisu Computer

Nejlepší programy pro úpravu fotek zdarma

Externí disky pro zálohu dat

Velký test: herní notebooky

Srovnání 12 batohů