Možno ste narazili na problém pri väčších projektoch s organizáciou kódu. Väčšina programátorov si s týmto nerobí starosti (ale mali by). Určite ste si to sami všimli, keď ste na internete narazili na zaujímavý projekt s Arduinom. Celý program bol napísaný v jednom súbore a bol tak neprehľadný, že vás to hneď odradilo. Je teda dôležité správne formátovanie kódu a jeho rozčlenenie do metód a tried, tak aby bol nie len pre nás jednoducho pochopiteľný a aby sme aj po x dňoch či mesiacoch vedeli o čo ide. Tento článok bude venovaný práve tejto téme.

Prvé úpravy (refactoring) - prehľadnejší kód

V tomto príklade si ukážeme ako upraviť kód, tak aby bol pre nás čo najprehľadnejší. Logicky teda oddelíme určitú časť kódu do samostatnej metódy, ktorú môžeme kedykoľvek použiť v programe.

Pôvodný kód

const int ledPin =  13;

int ledState = LOW;
long previousMillis = 0;
long interval = 1000;

void setup() 
{	
	pinMode(ledPin, OUTPUT);
}

void loop()
{
	unsigned long currentMillis = millis();
	
	if(currentMillis - previousMillis > interval) 
	{	
		previousMillis = currentMillis;

		if (ledState == LOW)
		{
			ledState = HIGH;	
		}		
		else
		{
			ledState = LOW;	
		}		
		
		digitalWrite(ledPin, ledState);
	}
}

Pôvodný kód určite poznáte je to príklad z Arduino IDE, ide o verziu Blink bez funkcie delay (BlinkWithoutDelay). Samozrejme tento kód je plne funkčný, ale ak by sme chceli tento program rozšíriť mohol by byť kód už neprehľadný. Preto je zvykom logické časti nejako oddeliť. Oddelíme teda časť kódu, ktorý vykonáva blikanie LEDky. Túto časť dáme do samostatnej metódy, ktorú nazveme blink (viď upravený kód).

Upravený kód

#define PIN_LED 13
#define BLINK_INTERVAL 1000

int ledState = LOW;
long previousMillis = 0;

void blink();

void setup()
{
	pinMode(PIN_LED, OUTPUT);
}

void loop()
{
	blink();
}

void blink()
{
	unsigned long currentMillis = millis();
	
	if(currentMillis - previousMillis > BLINK_INTERVAL)
	{
		previousMillis = currentMillis;

		if (ledState == LOW)
		{
			ledState = HIGH;
		}
		else
		{
			ledState = LOW;
		}
		
		digitalWrite(PIN_LED, ledState);
	}
}

 

Podrobnejší popis úpravy

Prvú úpravu, ktorú uvidíte je zmena premenných na riadkoch 1 a 5 v pôvodnom kóde na zástupné znaky pomocou direktívy define na riadkoch 1 a 2 v upravenom kóde. Táto úprava má skôr zmysel pri väčších, rozsiahlejších programoch, ale ja osobne to používam skoro vždy. A v čom spočíva to zlepšenie ? V pôvodnom kóde nám premenné zaberajú určité miesto v pamäti, čo môže byť pri už spomínanom rozsiahlejšom kóde nežiadúce (vzhľadom na veľkosť flash pamäti v mikrokontroléri) a prišlo by vhod pár voľných bytov alebo dokonca kilobytov. Preto teda použijeme direktívu define na zadefinovanie zástupných znakov. Tieto zástupné znaky budú nahradené pri kompilácii znakmi pri definícii (PIN_LED bude nahradene 13). Vo výslednom programe nefiguruje žiadna premenná a teda ani nezaberá žiadne miesto v pamäti. Tento spôsob môžeme použiť len pri premenných, ktoré sú určené len na čítanie a nebudú sa počas vykonávania programu meniť. Môžu to byť určité konštanty napríklad čísla pinov, matematické konštanty, alebo iné pevné hodnoty, ktoré súvisia s programom.

Ak používate ako vývojové prostredie Arduino IDE je riadok číslo 7 pre vás nevyhnutný. Je to vlastne deklarácia novej metódy (deklarácia prototypu metódy), ktorú chceme vytvoriť. Takéto deklarácie sa väčšinou nachádzajú na začiatku súboru alebo sa vložia do samostatného hlavičkového súboru. Pre tých, ktorí používate Atmel Studio tento riadok, nie je povinný. Rozšírenie pre Arduino, generuje špeciálny súbor do ktorého sa ukladajú deklarácie metód, funkcii a iných nastavení. Tento súbor nájdete v projekte v záložke Visual Micro, jeho názov končí vsarduino.h.

Na riadkoch 19 až 38 máme definíciu metódy blink. Táto metóda obsahuje kód, ktorý zabezpečuje blikanie LEDky. Jej telo sa nejak výrazne nezmenilo, iba sme nahradili premenné zastupnými znakmi.

Prečo je dobré si vytvoriť triedu ?

Predstavte si že máte program, ktorý sa vám rozrástol na nejakých 100 a viac riadkov. Takýto kód už býva často neprehľadný. Možno pár dní si budete pamätať čo s čím súvisí a podobne, ale po miesiaci alebo roku ak sa pozriete znova, tak asi ťažko sa rozpamätáte. Jednoducho budete mať problémy s v tom vyznať.

V takomto prípade je dobré porozmýšľať, či by nebolo vhodné rozdeliť niektoré časti do samostatných tried a tie potom v projekte použiť. Ukážeme si ako na to. Zoberieme si sketch z predchádzajúceho príkladu a vytvoríme jednoduchú triedu, ktorá sa nám postará o blikanie LED spolu s nastavením vývodov, atď.

Vpravo môžete vidieť už upravený kód sketchu. Čítajte ďalej a zistíte ako som sa k tomu dopracoval.

 

Upravený kód (použitie triedy Blink)

Takto bude vyzerať kód po použití triedy Blink. Kód je takto oveľa prehľadnejší a vcelku rýchlejšie pochopiteľný aj pre ostatných.

#include "Blink.h"

Blink blink0(13, 1000);

void setup()
{
	blink0.init();
}

void loop()
{
	blink0.run();
}

 

Template na vytvorenie triedy

V Atmel Studiu existuje jednoduchá šablóna (template) na vytvorenie špeciálnej triedy pre Arduino aplikácie. Takúto triedu vytvoríme veľmi jednoducho. Klikneme pravým tlačítkom na názov projektu v Solution Exploreri, potom na Add -> Add New Cpp Item, presne tak ako ukazuje obrázok. Následne nato sa nám zobrazí okno, do ktorého zadáme názov triedy "Blink", potvrdíme klávesov enter alebo kliknutím na tlačítko OK.

Atmel Studio nám teraz vygenerovalo dva súbory Blink.h a Blink.cpp. Tieto súbory obsahujú už nejaký základný kód, ktorý môžeme alebo nemusíme použiť. My tentokrát použijeme niektoré časti a ešte doplníme ďalší kód.

Obsah čerstvo vygenerovaného súboru Blink.h.

// Blink.h

#ifndef _BLINK_h
#define _BLINK_h

#if defined(ARDUINO) && ARDUINO >= 100
	#include "Arduino.h"
#else
	#include "WProgram.h"
#endif

class Blink
{
 private:


 public:
	void init();
};

extern Blink BLINK;

#endif

Na riadkoch 12 až 24 máme deklaráciu triedy Blink. Pod návestím private sú zadefinované privatne premenné, ktoré sú prístupné len z triedy Blink. Nie je možné prístúpiť k týmto členom triedy nijak inak. Pod návestím public sú zadefinované verejné metódy init, run a konštruktor Blink s dvomi parametrami pin a interval (riadok 21).

Súbor Blink.h upravený a doplnený.

// Blink.h

#ifndef _BLINK_h
#define _BLINK_h

#if defined(ARDUINO) && ARDUINO >= 100
	#include "Arduino.h"
#else
	#include "WProgram.h"
#endif

class Blink
{
 private:
	uint8_t _pin;
	uint8_t _state;
	long _previousMillis;
	long _interval;

 public:
	Blink(uint8_t pin, long interval);
	void init();
	void run();
};


#endif

 

Obsah čerstvo vygenerovaného súboru Blink.cpp.

// 
// 
// 

#include "Blink.h"

void Blink::init()
{


}


Blink BLINK;

V súbore Blink.cpp máme na začiatku vložení hlavičkový súbor Blink.h. V ďalších riadkoch je implementácia všetkých členských metód, funkcii a konštruktora.

Hodnoty z konštruktora sa nám nastavia do lokálnych premenných _pin a _interval. Ďalej nastavíme počiatočné hodnoty premenných _state a _previousMillis.

Metódu init sme vytvorili na nastavenie vývodu na ktorý máme pripojenú LED diódu. Túto metódu voláme v setup metóde v súbore ino.

Posledná metóda run obsahuje kód, ktorý už poznáte z predchádzajúceho príkladu. Táto metóda je volaná v loop metóde v hlavnom súbore ino.

Súbor Blink.cpp upravený a doplnený.

// Blink.cpp

#include "Blink.h"

Blink::Blink(uint8_t pin, long interval)
{
	_pin = pin;
	_interval = interval;
	_state = LOW;
	_previousMillis = 0;
}

void Blink::init()
{
	pinMode(_pin, OUTPUT);
}

void Blink::run()
{
	unsigned long currentMillis = millis();
	
	if(currentMillis - _previousMillis > _interval)
	{
		_previousMillis = currentMillis;
		
		_state = _state == LOW ? HIGH : LOW;
		
		digitalWrite(_pin, _state);
	}
}

Znovu použiteľný kód alebo knižnica (library)

Možno vás teraz napadne, že ste napísali celkom užitočnú časť kódu, napríklad triedu a chceli by ste ju použiť aj v iných projektoch. Ako jednoduché riešenie sa javí skopírovať triedu do nového projektu a tú potom použiť rovnakým spôsobom ako v tom prvom projekte. To však nemusí byť tá správna cesta. Pokiaľ chceme túto triedu použiť bez zmeny je zvykom si z tejto triedy vytvoriť knižnicu. Knižnica je vlastne užitočný kód, ktorý môžeme použiť v ďalších projektoch bez toho aby sme museli tento kód neustále kopírovať. Použijeme tzv. referenciu na našu knižnicu. V Arduino platforme majú knižnice (libraries) svoju špecifickú štruktúru a preto je nižšie postup ako takúto knižnicu vytvoriť.

Postup ako vytvoriť knižnicu:

  1. V priečinku library, ktorý sa nachádza vo vašom sketch priečinku (priečinok s Arduino projektami), vytvorte nový priečinok s názvom Blink.
  2. Prekopírujte súbory Blink.h a Blink.cpp do tohto priečinka.
  3. Ďalej môžeme vytvoriť príklad ako použiť túto našu knižnicu. Vytvorte ďalší nový priečinok s názvom Examples v priečinku Blink.
  4. V priečinku Examples vytvoríme nový priečinok, ktorého názov musí byť rovnaký ako názov príkladu. Takže dajme tomu, že príklad bude mať názov BlinkDemo.Vytvoríme teda priečinok s názvom BlinkDemo a tiež sketch (ino súbor), ktorý bude mať rovnaký názov.

Obsah súboru BlinkDemo.ino bude nasledovný:

#include <Blink.h>

Blink blink(13, 1000);

void setup()
{
	blink.init();
}

void loop()
{
	blink.run();	
}

Kompletná štruktúra knižnice Blink:

 

 

Knižnicu Blink nájdeme v Atmel Studiu medzi ostatnými užívateľskými knižnicami.

 

Taktiež príklad pre túto knižnicu nájdeme v Micro Explorer / Examples.

 

Ešte predtým je nutné reštartnúť Atmel Studio alebo kliknúť v menu na Project -> Add Arduino Library -> Refresh.

Knižnica Blink na prevzatie.

Záver

Zhrniem teda tie najdôležitejšie časti na ktoré by dobrý programátor nemal zabudnúť. Rozdelenie kódu do samostatných logických celkov (metódy, triedy alebo knižnice). Ďalej je to formátovanie kódu (CTRL+K,CTRL+D v Atmel Studiu)  a taktiež sú dôležité názvy premenných, metód a tried (určite vám hovorí niečo premenná ledState, ale zase premenná ls vám veľa nenapovie :/). Ak vám niečo nie je jasné alebo sa chcete niečo spýtať môžete mi napísať prostredníctvom komentárov dole pod článkom.