Vyhledávání

#7 - Uživatelsky definované funkce

05.02.2014 23:14

Uživatelsky definované funkce

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 = 0;
		//pro záporné hodnoty není faktoriál definován
	}
	else if(n == 0){
		vysledek = 1;
		//pro 0 je faktoriál roven 1
	}
	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. sinusoida 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: 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: 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
  2. Piezo reproduktor
  3. Nepájivé kontaktní pole s vodiči
  4. 3x tlačítko
  5. 3x 10 kohm resistor
  6. 1x 100 kohm resistor (doporučený k Piezo)

zapojeni-priklad1 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. Sedmisegmentovy-display V dokumentaci od výrobce nalezneme vnitřní zapojení displeje. sedmisegmentovy-display-schema 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. sedmisegmentovy-display-schema 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.


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
  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 (nepočítáme-li vrchní tón, který připadá i další oktávě) . Každé klávese bude odpovídat jedno tlačítko.


  Klavir 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]
[sinusoida]
[Squarewave]
Sedmisegmentový display
[+-1]
[šestnáctisegmentový display]

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

 

Kam pokračovat?


<--#6 - Užitečné funkce || #8 - Arduino jako klávesnice a myš -->

Zpět

Diskusní téma: #7 - Uživatelsky definované funkce

Datum
Vložil
Titulek

převod série ASCII do proměnné

Zdravím,
poradí někdo jak převést sérii ASCII kódů na hodnotu s destinými místy do proměnné. Z váhy vyčtu přes RS232 a Serial.read např. 51 46 51 48 53, což představuje (51=3, 46=., 51=3, 48=0, 53=5) hmotnost 3.305 a tuto hodnotu potřebuji vložit do proměnné. Dík za případnou pomoc. Hugo

Datum
Vložil
Titulek

tone - PWM

Je normální že funkce tone () ovlivňuje úroveň na PWM výstupu? I bez připojeného zvukového měniče. Př.: výstup na pinu ~3 dává 50% a příkaz tone(10,1000,1000) mimo to že pošle na pin 10 na 1000mS frekvenci 1kHz, v tu samou dobu na 1000mS stáhne výstup ~3 na 0%.

Datum
Vložil
Titulek

Hi there. Simply just planned to request a simple dilemma. feebekeededg

Datum
Vložil
Titulek

orvnrwu@gmail.com

Datum
Vložil
Titulek

gftvsfbpw@gmail.com

Datum
Vložil
Titulek

pmpzhntyyp@gmail.com

Datum
Vložil
Titulek

Faktoriál

Zdravím,

ta první definice faktoriálu je chybná, vrací 0 pro 0! Jinak ta druhá použitá při výpočtu Eulerova čísla je ok.

Tomáš

Datum
Vložil
Titulek

Re: Faktoriál

Děkuji za upozornění. Už jsem chybu opravil.

© 2015 Všechna práva vyhrazena.

www.hwkitchen.com