Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Info

Cvičenie je zamerané na prácu s vývojovou doskou Arduino. Cvičenie je orientované na základné programovanie mikroprocesora. V rámci cvičenia budú demonštrované základné spôsoby obsluhy I2C zbernice a LCD displeja pripojeného na túto zbernicu. Tiež bude  vysvetlená filozofia pulzne šírkovej modulácie PWM, jej využitia a implementácie na doske Arduino. Po osvojení si učiva, študent dokáže realizovať jednoduchú komunikáciu pro zbernici I2C, obslúžiť LCD displej a implementovať PWM moduláciu. Študent rozumie problematike generovania jednosmerného napätia za pomoci PWM a filtračného RC obvodu.    

  • Pulzne šírková modulácia (PWM)
  • I2C zbernica
  • LCD displej 
Info

Odporúčaná literatúra a dôležité odkazy

[1] KPI-FEI-TUKE: Arduino UNO

[2] STUBA: Mikroprocesorová technika-Sériové komunikačné rozhranie I2Citnetwork.sk: 15. diel - Arduino a I2C zbernice https://www.itnetwork.sk/hardver-pc/arduino/arduino-a-i2c-zbernice

[3] Arduino.cc:  Wire Library

[4] arduino.cc:  LiquidCrystal Library

[5] Bakker, B.: How to control a character I2C LCD with Arduino  

[6] LastminuteEngineers: Interface an I2C LCD with Arduino 


Pulzne šírková modulácia (PWM)

V elektronike a hlavne v dnešnej digitálnej dobe sa častokrát stretávame s potrebou generovania spojite meniaceho sa napätia. Doteraz sme ľubovoľnú hodnotu napätia menili pomocou potenciometrov. No je jasné, že klasický potenciometer asi len ťažko budeme ovládať programovo. V mikroprocesorovej technike, ale aj všade inde sa k tomuto účelu využíva PWM. Poďme si teda ozrejmiť, čo to tá PWM je. Začneme trochou matematiky, ktorou si musíme pomôcť pre správne porozumenie problematiky.  Oveľa podrobnejšie sa týmto problémom budete zaoberať na predmete Signály a sústavy v 2. ročníku.

Majme periodický pravouhlý signál, ktorý má hodnotu +1V v prvej polovici periódy T a hodnotu 0V v druhej polovici periódy. Priebeh signálu sa periodicky opakuje. Toto je zobrazené na obr. vpravo. Pri periodických signáloch vieme určiť ich výkon,  ktorý tento signál nesie.  Keďže ide o spojitý signál, výkon vyrátame integrovaním. Pre ilustráciu ukážeme riešenie integrálu pre daný príklad, ale nezľaknite sa! V rámci predmetu Programovanie vám toto netreba. Na programku to budeme robiť intuitívne pomocou kódu a vhodného zapojenia kondenzátora a záťaže. Prečo hovoríme o výkone? Výkon nesený v PWM pravouhlom signáli je to čo vieme považovať za ekvivalent výkonu aký by dodal jednosmerný zdroj do záťaže 1 Ohmu. Z elektrotechniky by ste mali vedieť, že výkon je daný ako druhá mocnina napätia podelená odporom. My uvažujeme, že odpor je rovný 1 Ohm. Výkon je zároveň dodaný za jednotku času preto je vo vzťahu 1/T.  

Na matematike sa budete učiť, že integrál je matematická operácia, ktorou je možné vypočítať plochu pod krivkou.  V podstate toto sme aj spravili. Vypočítali sme plochu, kde má signál hodnotu 1. Teda je to plocha obdĺžnika so stranami a = 1, b = T/2. Z toho je zrejmé že a*b = T/2 a to sa ešte delí periódou T.  Výhodou pri tomto signáli je to, že jeho hodnota je 1 a teda umocňovanie nehralo žiadnu rolu. Teda signál f(t) za jednotku času má rovnaký výkon ako by mal  jednosmerný zdroj s hodnotu 0.5V. 

Ukážme si ešte jeden príklad. Teraz bude mať signál hodnotu 1V iba 1/4 periódy T. Dá sa ukázať, že teraz by bolo ekvivalentných 0.25V. Vyššie uvedené úvahy platia všeobecne, ale pre pravouhlý signál máme ale jednoduchší prístup. Pomer medzi trvaním nenulovej hodnoty signálu a periódy nazývame pracovný cyklus ale skôr sa stretávame s anglickým termínom Duty cycle označme ho ako D. Pre prvý príklad môžeme písať: 

Pre druhý príklad bude D = 25%.

Na záver môžeme uviesť, že ekvivalentná hodnota jednosmerného napätia je daná ako súčin D a nenulovej hodnoty napätia. Ak by hodnota namiesto 1V bola hodnota 5V, tak ako tomu je v Arduine, bude hodnota napätia pre prvý príklad A = D*5V = 0.5*5 = 2.5V. Toto si ukážeme na príklade priamo na Arduine. 

Poznámka: Pomocou PWM je možné generovať aj zložitejšie signály ako len jednosmerné napätie.

PWM v Arduine

Arduino Uno má 6 výstupov (D3,D5, D6, D9,D10 a D11), ktoré môžu fungovať ako PWM výstup.  Na ich výstupe sa teda generuje pravouhlý periodický signál. Vlastnosti tohto signálu je možné meniť. Je možné meniť D v rozsahu  0(0%)  až 255(100%). PWM na pine spustíme príkazom analogWrite(pin, D) [3].  Piny majú prednastavenú frekvenciu  490.20 Hz (D3, D9, D10 a D11) a 976.56 Hz (D5 a D6). Frekvenciu je možné meniť nastavovaním časovača - timer0. Toto je ale trochu zložitejšie nakoľko sa musia nastavovať priamo registre procesora preto si na predmete Programovanie vystačíme so základným nastavením frekvencie. 

Nasledujúce zapojenie  demonštruje použitie PWM pre vytvorenie jednosmerného napätia. Na osciloskope vpravo vidíme PWM signál s D=20%. Vidíme, že signál má hodnotu +5V v trvaní jeden dielik a hodnotu 0V v trvaní ďalšie 4 dieliky.  Jeden dielik trvá približne 0.4ms (pretože ich tam je 10 a celý priebeh zobrazený na osciloskope trvá 4 ms). Z toho je možné určiť frekvenciu signálu f = 1/T (Hz). Perióda T je 5 dielikov, teda T = 5*0.4*10-3s. Potom f = 1/T = 500Hz čo sa približuje hodnote 490,20Hz nakoľko sme počet dielikov určili len približne...

Na osciloskope vľavo vidíme hodnotu napätia na kondenzátore. Kondenzátor sa počas trvania impulzu nabíja. Je dôležité poznamenať, že hodnota kapacity kondenzátora ako aj pripojená záťaž by mali byť vhodne zvolené(napr. 10 uF a 12 kOhm). Toto zapojenie je uvedené hlavne preto, aby sme ukázali rozdiel medzi jednosmerným napätím a PWM signálom. Ak sa na PWM výstup pripojí takýto RC obvod hovoríme, že na výstup PWM je zaradený dolnopriepustný filter. Takto je možné na Arduine vytvoriť Digitálno-Analógový prevodník. Napätie na tomto výstupe, ale nemôže byť používané pre napájanie záťaže (LED a podobne). Je však vhodné na postupné otváranie tranzistora, ktorý následné môže lineárne ovládať prúd tečúci záťažou a podobne.

Vo vašich projektoch sa skôr budete stretávať s tým, že budete riadiť jas diódy. Vtedy na výstup nemusíme pripojiť kondenzátor. Zapojenie je zobrazené vpravo. Dióda bude meniť intenzitu jasu podľa toho aká úroveň napätia bude meraná na analógovom pine A0. Kód k príkladu je nasledovný:

Code Block
languagecpp
linenumberstrue
float V=0;
int D = 0;
void setup()
{
  pinMode(3, OUTPUT);
  pinMode(10, OUTPUT);
}
void loop()
{
  V = (float)255*analogRead(A0)/1023; 
  delay(100); // To je tu hlavne kvôly Tinkercadu, aby stíhal
  D=(int)V;
  analogWrite( 3, D);
  analogWrite(10, D);
}


I2C zbernica

I2C alebo tiež IIC zbernica je dvojlinková, teda obsahuje dva vodiče, obojsmerná zbernica vyvinutá firmou Philips začiatkom 90tych rokov. Jedná sa o pomerne pomalú zbernicu, ale jej rýchlosť je postačujúca práve pre IoT riešenia [2]. Veľmi výhodné je to, že zbernica umožňuje pripojiť až 127 zariadení na iba tieto dva vodiče. Tento typ komunikácie pracuje v režime jeden Master a mnoho poddaných (Slave). Každé zariadenie pripojené na túto zbernicu má vlastnú adresu. Táto adresa je 8 bitová. Ako už bolo uvedené, zbernica používa dva vodiče – Serial Data (SDA) a Serial Clock (SCL).  SCL - určuje hodinový takt, t.j. rýchlosť prenosu dát a SDA slúži pre prenos samotných dát a celkovú komunikáciu.  Master je zariadenie, ktoré inicializuje a ukončuje prenos dát na zbernici, generuje hodinový takt (SCL). Slave je zariadenie adresované Masterom. Pokiaľ na zbernici neprebieha žiadna komunikácia na oboch linkách je logická 1, teda +5V. Toto je zabezpečené pomocou interných pullup rezistorov mikroprocesora. Na tejto zbernici je prenos simplexný, teda v daný moment môže komunikovať len jedno zariadenie. Správu na linke môžu prečítať v princípe všetky zariadenia ale táto správa je adresovaná len jednému konkrétnemu zariadeniu. Detaily o tejto zbernici sa budete učiť vo vyššom ročníku. Na predmete Programovanie by sme ale mali mať aspoň trochu poňatia s čím pracujeme. 

Pre komunikáciu cezI2C zbernicu je potrebné použiť knižničný súbor Wire.h [3]. Je potrebné si uvedomiť, že so zbernicou sa pracuje v režime prerušení, ktoré budeme preberať na nasledujúcej hodine. So zbernicou v drvivej väčšine prípadov budete pracovať tak, že Arduino bude Master a k nemu budete pripájať rôzne senzory a displeje. 

Príklad: Na nasledujúcom príklade si ukážeme jednoduchú komunikáciu medzi dvoma Arduinami. Jedno bude Master, ktorý bude posielať príkazy druhému pracujúcemu v režime Slave. Príklad je jednoduchý, Slave bude rozsvecovať LED diódy na svojich 5 digitálnych portoch podľa toho, akú LED mu Master prikáže rozsvietiť. Ak bude komunikácia úspešná pošle sa Mastrovi správa o úspechu.  

Master  posiela údaj o porte a hodnote - pritom rozsvieti oranžovú diódu, ak Slave potvrdí prijatie a vykonanie príkazu, Master zopne aj zelenú LED. Poruchu na linke, resp. nepotvrdenie úspešného príjmu simuluje prepínač medzi portami A4. Zapojenie si vyhotovte buď v Tinkerkade alebo ak máte 2 Arduina ,tak môžete aj na nich. Nižšie je kód pre obidve zariadenia. Môžete experimentovať s ďalšími stavmi či zariadeniami. 

Code Block
languagecpp
linenumberstrue
// MASTER
#include <Wire.h> 
#define ORANGE 9
#define GREEN 8
  int stav = 1;
  int msg = 0;
void setup() 
{
  pinMode(GREEN,OUTPUT);
  pinMode(ORANGE,OUTPUT);
  Wire.begin();  // Inicializácia I2C - Master nemusí mať adresu
}
void loop()
{
  for(int pin = 3; pin<8; pin++) 
  {
    Wire.beginTransmission(55); // Začiatok komunikácie Slave je na adrese 55
    digitalWrite(ORANGE,HIGH);	   
    Wire.write(pin);			// Zaslanie čísla pinu	
    Wire.write(stav); 			// Zaslanie stavu pinu
    Wire.endTransmission();   	// Koniec komunikácie
    Wire.requestFrom(55, 4); 	// Požiadavka na odpoveď
    msg = Wire.read(); 			// Prečítanie odpovede
   
    if(msg == 1)
      digitalWrite(GREEN,HIGH);
    else
      digitalWrite(GREEN,LOW);            
    delay(1000);				//Delay používame preto aby sme to stihli zbadať
    digitalWrite(GREEN LOW);
    digitalWrite(ORANGE,LOW);
    delay(1000);
  }     
  stav = !stav;  // Do ďalšej iterácie bude stav negovaný
}
Code Block
languagecpp
linenumberstrue
// SLAVE
#include <Wire.h>
int Stav;
int Port;
bool OK=0;
void setup()
{ 
  for(int i = 3; i<8; i++)
  	pinMode(i, OUTPUT);
  Wire.begin(55);  // Inicializácia I2C - nech adresa zariadenia je 55               
  Wire.onRequest(requestEvent); // requestEvent-Funkcia, ktorá sa vykoná ako prerušenie keď 
								// je Mastrom požadovaná odpoveď
  Wire.onReceive(receiveEvent); // receiveEvent-Funkcia, ktorá sa vykoná ako prerušenie
  								// keď po zbernici niečo príde pre toto zariadenie           
}
void loop() 
{
  if(Port>2 && Port<8 && (Stav == 1 || Stav == 0) )
  {
    digitalWrite(Port, Stav); // V loope len zapisujeme stav na piny
	OK = 1;
  }else
    OK = 0;
}
void receiveEvent(int bytes) // Ak prišla správa adresovaná tomuto zariadeniu
{
  Port = Wire.read();	 // Prečíta sa jeden bajt	
  Stav = Wire.read();    // Prečíta sa jeden bajt
}

void requestEvent() // Ak prišla požiadavka od Mastera 
{
  Wire.write(OK);  // Zašle sa hodnota v OK 0 alebo 1	
}

LCD displej 

Pomerne často je potrebné vyvinúť hardvér, ktorý s používateľom komunikuje, ale je nežiadúce, aby bol prostredníctvom sériovej linky neustále pripojený k PC. Pre tieto prípady je LCD displej vynikajúcou voľbou. Stretávame sa viacerými prevedeniami LCD displejov. Najčastejšie sa stretávame s dvojriadkovým LCD. Tento zvyčajne v jednom riadku zobrazuje 16 znakov. Stretávame sa s označením LCD 16x2. Bežné sú tiež LCD 16x4.  Displej má 16 vývodov. Pre samotnú komunikáciu je potrebné k Arduinu pripojiť minimálne 6 výstupov.  Bežne sa zapája tak, ako je zobrazené na schéme vpravo. K práci s LCD je potrebné použiť knižnicu LiquidCrystal.h [4].

Takto zapojený displej je potom možné použiť s využitím nasledovného kódu:

Code Block
languagecpp
linenumberstrue
#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2); // inicializácia v tomto poradí (RS, EN, DB4, DB5, DB6, DB7)
void setup()
{
	lcd.begin(16, 2); // Inicializácia 16 znakov a 2 riadky 
}
void loop()
{
	lcd.setCursor(0,0); // Kurzor sa nastaví na prvú pozíciu v 1. riadku
	lcd.print("Text na 1. riad."); // Výpis testu do 1. riadku
	lcd.setCursor(0,1); // Kurzor sa nastaví na prvú pozíciu v 2. riadku
	lcd.print("Text na 2. riad.");// Výpis testu do 2. riadku
	// V prípade, že je potrebné premazať celý displej pred vypísaním nového obsahu je možné použiť
	delay(1000);
	lcd.clear();
}

LCD displej s I2C modulom

Nevýhodou vyššie popísaného displeja je to, že je potrebné použiť až 6 pinov Arduina, ktoré by sa dali využiť aj lepšie. Tiež takto zapojený displej je náchylný na chybné zobrazenie údajov. Nastavenie jasu je potrebné vykonať "hardvérovo", cez potenciometre a podobne. Tieto nedostatky je možné odstrániť pripojením displeja cez I2C zbernicu. V programovaní je len minimálny rozdiel a to ten, že je potrebné inicializovať I2C zbernicu a tiež je potrebné doinštalovať knižnicu LiquidCrystal_I2C.h. Manuál a omnoho detailnejší popis možnosti I2C displeju je možné nájsť tu [5, 6]: 

Code Block
languagecpp
linenumberstrue
#include <Wire.h> 
#include <LiquidCrystal_I2C.h> // Knižnica pre LCD
LiquidCrystal_I2C lcd(0x27, 16, 2); // Inicializácia LCD 0x27 je I2C adresa displeja 
									// (vo veľkej väčšine prípadov je to táto adresa)
									// 16 a 2 - veľkosť displeja 16x2
void setup() 
{
  lcd.init();       // Spustenie komunikácie 
  lcd.backlight();  // Zapnutie podsvietenia
  // lcd.noBacklight(); // Vypnutie podsvietenia v prípade potreby
}
void loop()
{
	lcd.setCursor(0,0); // Kurzor sa nastaví na prvú pozíciu v 1. riadku
	lcd.print("Text na 1. riad."); // Výpis testu do 1. riadku
	lcd.setCursor(0,1); // Kurzor sa nastaví na prvú pozíciu v 2. riadku
	lcd.print("Text na 2. riad.");// Výpis testu do 2. riadku
}


Niekedy sa môže stať, že I2C modul LCD displeja má inú adresu ako 0x27. V takom prípade je možné túto adresu získať skenovaním pripojených zariadení na I2C zbernicu. Kód pre skenovanie a následné zobrazenie adresy I2C zariadenia je uvedený nižšie. Viac informácii získate na [6].

Code Block
languagecpp
linenumberstrue
#include <Wire.h>
void setup() 
{
  Serial.begin (9600);
  Serial.println ();
  Serial.println ("I2C scanner. Scanning ...");
  byte count = 0;
  Wire.begin();
  for (byte i = 8; i < 120; i++)
  {
    Wire.beginTransmission (i);
    if (Wire.endTransmission () == 0)
      {
      Serial.print ("Found address: ");
      Serial.print (i, DEC);
      Serial.print (" (0x");
      Serial.print (i, HEX);
      Serial.println (")");
      count++;
      delay (1);  // maybe unneeded?
      } // end of good response
  } 
  Serial.println ("Done.");
  Serial.print ("Found ");
  Serial.print (count, DEC);
  Serial.println (" device(s).");
}  
void loop() {}