Close

Uživatelsky definované funkce

Zapojení piezzo buzzeru

Jak už název článku napovídá, budeme se dnes zabývat hlavně funkcemi, které si může uživatel sám vytvořit. Také se ale podíváme na způsob převodu jednoho datového typu na jiný, na možnosti Arduina generovat zvuk a na způsob používání jednoduchých znakových displejů založených na LED diodách.

Uživatelsky definované funkce

anglicky user defined functions

Jsou funkce, které může uživatel vytvořit sám. Jak už jsme zjistili v předchozích dílech, funkce je jakýsi soubor instrukcí „zabalený“ v jednom příkazu. Může mít vstupní parametry, se kterými dále pracuje. Obsahuje blok příkazů, které se při volání (spuštění) funkce provedou a také může vracet hodnotu. Zajímavé je, že každá funkce má určitý datový typ. Ten se liší podle typu dat, které vrací. Pokud funkce žádnou hodnotu nevrací, používá se speciální datový typ void. Důležité je nezapomenout na to, že proměnné definované v těle funkce není možné používat mimo tuto funkci. Ve funkci však lze používat proměnné definované na začátku programu. Funkce musí být definována mimo tělo jiných funkcí, nezáleží však, jestli je definovaná před, mezi nebo za funkcemi setup() a loop().

//funkce může být tedy definována:
//tady
void setup() {
	//tady ne
}
//tady
void loop() {
	//tady ne
}
//tady

Definice funkce

Aby funkce pracovala bez problému, potřebuje mít datový typ, název a závorky.

datový-typ název-funkce(prostor-pro-parametry){
	příkazy...
}

U funkcí bez vstupních parametrů se kulaté závorky nechají prázdné (ale musí zde být).

void text(){
	Serial.println("TEXT TEXT");
}

S parametry se pracuje stejně jako s proměnnými. Pokud funkce nějaké má, musíme je nadefinovat. Definice probíhá v kulatých závorkách. Pokud má funkce více parametrů, oddělují se čárkami.

void zprava(char a[], char b[]){
	Serial.print(a);
	Serial.print(' ');
	Serial.println(b);
}

Volání funkce

V této chvíli se ale po spuštění programu nic nestane. Funkce je sice vytvořená, ale ještě jsme ji nikde nezavolali (nepoužili). Následující kód vypíše po sériové lince:

Ahoj Karle
TEXT TEXT
void setup() {
	Serial.begin(9600);
	zprava("Ahoj", "Karle");
	text();
}

void loop(){
}

void text(){
	Serial.println("TEXT TEXT");
}

void zprava(char a[], char b[]){
	Serial.print(a);
	Serial.print(' ');
	Serial.println(b);
}

Funkce, které vrací hodnotu

Pokud má funkce něco vracet, musí mít jiný datový typ než void. Pro vrácení vybrané hodnoty se používá příkaz return. Pokud chceme vrátit řetězec znaků, nepoužívá se pole char[], ale datový typ String. Také není možné jednoduchým způsobem vrátit pole. Ostatní datové typy se používají stejně.

String slovo(){
	return "Ahoj";
}

//volání
Serial.println(slovo()); //vypíše: Ahoj

Jednoduchá funkce pro sečtení dvou čísel a vrácení součtu poté vypadá takto:

void setup() {
	Serial.begin(9600);
	Serial.println(secti(10,11));
}

void loop(){
}

int secti(int a, int b){
	int soucet = a + b;
	return soucet;
}

Pro výpočet faktoriálu čísla si můžeme sestavit vlastní funkci:

void setup() {
	Serial.begin(9600);
	Serial.println(fact(-2));
}
 
void loop(){
}
 
int fact(int n){
	int vysledek;
	if(n <= 0){
		vysledek = 1;  
		//pro n <= se nebude nic provadet 
		//pro zaporne hodnoty neni faktorial definovan
		//a pro nulu neni potreba nic delat
	} 	 	
	else{
		vysledek = n; 		 		
		for(int i = n-1; i > 0; i--){
			vysledek *= i;
		}
	}  
	return vysledek;
}	

Výhodnou vlastností je také možnost volání funkce v těle jiné funkce. Ukažme si to na výpočtu Eulerova čísla. Parametrem této funkce bude požadovaná přesnost.

void setup() {
	Serial.begin(9600);
	Serial.println(euler(10), 10); //Eulerovo číslo s deseti desetinnými místy
}

void loop(){
}

float fact(float n){
	if(n == 0){
		return 1;
	}
	float vysledek = n;
	for(int i = n-1; i > 0; i--){
		vysledek *= i;
	}
	return vysledek;
}

float euler(int presnost){
	float e = 0.0;
	for(int i = 0; i <= presnost; i++){
		e += (1/fact(i));
	}
	return e;
}

Převody datových typů

Možná jste se již při programování dostali do situace, kdy si program dělal s čísly a datovými typy co chtěl. Mohlo to být tím, že s čísly pracoval jako s jiným datovým typem, než bychom zrovna potřebovali. Pokud chceme mít jistotu, jaký datový typ z dané operace vyjde, použijeme funkce pro převod datových typů.

char()

Jak už jsme si řekli před časem, i datový typ char je vlastně číslo, které odpovídá číslu znaku v ASCII tabulce.

Serial.println(char(107)); //vypíše: k

byte()

Převede danou hodnotu na datový typ byte. Pokud je hodnota větší než rozsah tohoto typu, výsledná hodnota se řídí pravidlem:
vysledek = vstup % 256;

int a = 255;
Serial.println(byte(a)); //vypíše: 255

int(), long(), float()

Konverze těchto typů probíhá stejně. Rozdílem je pouze jiný rozsah výchozích hodnot.

float a = 12.345;
Serial.println(int(a)); //vrátí 12
Serial.println(float(a), 3); //vrátí 12.345
Serial.println(long(a)); //vrátí 12

Zvuk a tón

Zvuk si můžeme představit jako mechanické kmity částic vzduchu či jiného materiálu. Slyšíme, protože kmitající částice narážejí do ušního bubínku a rozkmitávají ho. Jednotlivé vlny jsou převáděny na nervové signály, které jsou poté zpracovány mozkem. Jednoduchým příkladem zvukové vlny může být například sinusoida.

Graf funkce sinus

Funkce sinus

Ve skutečnosti ale nejsou zvukové vlny ideální a matematicky přesně popsatelné jako sinusoida. Jejich záznam může vypadat třeba takto:

Grafické znázornění zvukové vlny

Nahrání zvuku

U Arduina je možné generovat zvuk pouze v nejjednodušší podobě. Neumožňuje totiž generovat analogové hodnoty. Rozlišuje tedy pouze 0V a 5V. Výsledná vlna nazývaná squarewave vycházející z Arduina vypadá takto:

Graf čtvercové vlny

Čtvercová vlna

Výška tónu závisí na frekvenci, to je počet opakování „hřebenů“ vlny za jednu sekundu, což vztaženo na Arduino znamená počet změn z 0 na 5V za sekundu. Jednotkou frekvence je hertz (Hz). Lidské ucho je schopné rozlišit přibližně tóny mezi 20 Hz a 20 000 Hz. Rozsah se však liší mezi jedinci.

tone()

Funkce tone slouží ke generování tónu. Má dva povinné a jeden nepovinný parametr. Prvním z nich je pin, na kterém bude připojen reproduktor, druhým je frekvence tónu a nepovinný parametr je délka tónu v milisekundách.

noTone()

Touto funkcí se vypne generovaný tón na daném pinu. Používá se tedy, když není nastavena délka tónu ve funkci tone().

Ukázka

Vytvoříme si příklad, ve kterém budeme vybírat tón pomocí tří tlačítek. Abychom nemuseli zadávat frekvenci tónu stále jen čísly, existuje jakýsi „slovník“, ve kterém je vždy název tónu a patřičná frekvence. Do programu se přidá příkazem #include „pitches.h“ umístěným na začátku programu. Poté ještě musíme soubor fyzicky přidat k programu. Pod ikonou pro spuštění Sériové komunikace naleznete šipku, která rozevře rozbalovací nabídku. Zvolíme možnost New Tab a do pole pro název zadáme pitches.h. Do vzniklé záložky zkopírujeme obsah souboru pitches.h. Poté vše zapojíme podle schématu. Budeme potřebovat:

  1. Arduino UNO
  2. Piezo reproduktor
  3. Nepájivé kontaktní pole s vodiči
  4. 3x spínací tlačítko
  5. 3x 10 kohm resistor
  6. 1x 100 kohm resistor (doporučený k Piezo)
Zapojení piezzo buzzeru

Buzzer s tlačítky

Program poté vypadá následovně (tóny záleží na našem výběru):

#include "pitches.h"

void setup() {
}

void loop() {
	if(digitalRead(5) == 1){
		tone(2, NOTE_A4, 200);
	} 
	if(digitalRead(4) == 1){
		tone(2, NOTE_C5, 200);
	} 
	if(digitalRead(3) == 1){
		tone(2, NOTE_E5, 200);
	} 
}
	

Segmentové displeje

Občas se hodí, když můžeme přímo zobrazit určitý znak, nebo číslo bez použití sériové komunikace. K tomuto účelu slouží různé displeje. Nyní se nebudeme zabývat LCD displeji, ale podíváme se na nejjednodušší způsob zobrazování, kterým jsou segmentové displeje. Jedná se několik LED diod zalitých v jednom pouzdře, které dohromady vytvářejí znaky. Často mají tyto LED diody společnou jednu z nožiček. Podle typu je to buďto anoda, nebo katoda (typ lze vyčíst v dokumentaci daného displeje). Nejčastějším typem je sedmisegmentový displej, který jistě všichni známe.

Sedmisegmentový displej

Tento displej je dobře známá osmička, kterou můžeme najít v pokladnách, různých čítačích a dalších zobrazovacích zařízeních. Většinou má sedm vývodů pro jednotlivé segmenty čísla, vývod pro tečku a společnou anodu/katodu.

Sedmisegmentový displej

Sedmisegmentový displej

V dokumentaci od výrobce nalezneme vnitřní zapojení displeje.

Vnitřní schéma sedmisegmentového displeje

Vnitřní schéma sedmisegmentového displeje

V tomto případě máme displej se společnou anodou. Jednotlivé segmenty se tedy budou zapínat tím, že nastavíme logickou hodnotu jim odpovídajícím pinům na LOW. Společná anoda bude připojena k +5V.

Zapojení sedmisegmentového displeje

Zapojení sedmisegmentového displeje

Program, který na displeji vypíše čísla od 0 do 9, bude vypadat následovně:

byte segmenty[8] = {2,3,4,5,7,8,9,10};
//jaké segmenty se používají při jakém čísle
byte cislice[10][8] = 
{{1,0,1,1,0,1,1,1},{0,0,0,0,0,1,1,0},
{0,1,1,1,0,0,1,1},{0,1,0,1,0,1,1,1},
{1,1,0,0,0,1,1,0},{1,1,0,1,0,1,0,1},
{1,1,1,1,0,1,0,1},{0,0,0,0,0,1,1,1},
{1,1,1,1,0,1,1,1},{1,1,0,0,0,1,1,1}};

void setup() {
	for(int i = 0; i < 8; i++){
		pinMode(segmenty[i], OUTPUT);
		digitalWrite(segmenty[i], LOW);
		delay(500);
		digitalWrite(segmenty[i], HIGH);
	}
	for(int i = 0; i < 10; i++){
		for(int j = 0; j < 8; j++){
		if(cislice[i][j] == 1){
			digitalWrite(segmenty[j], LOW);
		}
		else{
			digitalWrite(segmenty[j], HIGH);
		}
	}
	delay(1000);
	}
	for(int i = 0; i < 8; i++){
		digitalWrite(segmenty[i], HIGH);
	} 
}

void loop() {
}

Vícesegmentové displeje

Segmentových displejů existuje celá řada. Může se jednat o displeje schopné zobrazit pouze znak 1, až po šestnácti a vícesegmentové displeje pro zobrazování písmen a dalších znaků. Jelikož je použití stejné jako u sedmisegmentových, jen s jiným počtem pinů, nebudeme se jimi více zabývat.

LCD displej - jednička

Dvousegmentový displej

Šestnáctisegmentový displej

16 segmentový displej

Příklad

V dnešním příkladu si ukážeme, jak vytvořit jednoduchý klavír s možností volby mezi oktávami pomocí potenciometru. Číslo oktávy si necháme posílat pomocí sériové linky. Budeme potřebovat:

  1. Arduino UNO
  2. Piezo reproduktor
  3. Nepájivé kontaktní pole s vodiči
  4. 12x tlačítko
  5. 12x 10 kohm resistor
  6. 100 kohm resistor
  7. potenciometr

Na nepájivém kontaktním poli poté tlačítka poskládáme jako na klaviatuře. Jedna oktáva má sedm bílých kláves a pět černých. Každé klávese bude odpovídat jedno tlačítko.

Klaviatura

Klaviatura

Zapojení tlačítek klavíru

Klavír

Opět musíme k programu přidat soubor pitches.h, který obsahuje frekvence jednotlivých tónů. Budeme vybírat ze čtyř oktáv.

#include "pitches.h";
byte oktava;
byte piezo = 12;

byte klavesy[12] = {6,7,5,8,4,3,9,2,10,1,11,0};  //piny jednotlivých tlačítek zleva doprava
//tóny v jednotlivých oktávách
int oktavy[4][12] = 
{{NOTE_C3, NOTE_CS3, NOTE_D3, NOTE_DS3, NOTE_E3, NOTE_F3,
  NOTE_FS3, NOTE_G3, NOTE_GS3, NOTE_A3, NOTE_AS3, NOTE_B3},
{NOTE_C4, NOTE_CS4, NOTE_D4, NOTE_DS4, NOTE_E4, NOTE_F4, 
 NOTE_FS4, NOTE_G4, NOTE_GS4, NOTE_A4, NOTE_AS4, NOTE_B4},
{NOTE_C5, NOTE_CS5, NOTE_D5, NOTE_DS5, NOTE_E5, NOTE_F5, 
 NOTE_FS5, NOTE_G5, NOTE_GS5, NOTE_A5, NOTE_AS5, NOTE_B5},
{NOTE_C6, NOTE_CS6, NOTE_D6, NOTE_DS6, NOTE_E6, NOTE_F6, 
 NOTE_FS6, NOTE_G6, NOTE_GS6, NOTE_A6, NOTE_AS6, NOTE_B6}};

void setup() {
	tone(piezo, 440, 500);
}

void loop() {
	oktava = map(analogRead(A0),0,1023,0,3);
	for(int i = 0; i < 12; i++){
		if(digitalRead(klavesy[i]) == HIGH){
			tone(piezo, oktavy[oktava][i], 100);
		}   
	}
}

Poznámka: Pokud se nedaří nahrát program do Arduina, odpojte napájení desky tlačítek. Po uploadu programu je opět připojte.

Zdroje obrázků

[zvuková stopa]

[Squarewave]

Sedmisegmentový display

[šestnáctisegmentový display]

V případě jakýchkoliv dotazů či nejasností se na mě neváhejte obrátit v komentářích.

Zbyšek Voda

2 Comments on “Uživatelsky definované funkce

Karel
2.2.2016 at 8:21

Příklad s faktoriálem máte rozbitý, chybí tam část kódu ve funkci fact(). (Není definováno „i“, je tam pravá závorka } navíc. Nepochybně cyklus.)

Zbyšek Voda
2.2.2016 at 19:30

Napsat komentář