ATmega8 - Programozás C++-ban

2016-03-15

 

Az utóbbi időben adódott egy feladat Arduino Mega panelen, amivel sajnos nem tudtam megküzdeni. Az Arduino egy baromi jó elektronikai LEGO. Leegyszerűsíti a program fejlesztést, a fordítást, a letöltést, nem kell forrasztgatni, nem kell táp (USB), és kész modulok vannak hozzá, szinte minden elektronika kezeléséhez. Mint a LEGO-nak megvannak a maga saját szabályai, ami leegyszerűsíti a munkát, de korlátozza is a lehetőségeket. Például az eszköz drágább, mintha egy csupasz uC-t venne az ember (főleg magyar kereskedőknél). Ez egy darabnál nem gond, a könnyebb kezelhetőség és a gyorsabb fejlesztés sokkal nagyobb előny. Az Arduino Uno mindkét oldalán végigfut egy foglalat sor, amire előre gyártott modulokat, "Shield"-eket lehet rádugni. Az Ardiono Uno meglévő pin-ek kiosztása szabványos, de az egyes változatoknál a többi pin változik. Amíg előre elkészített "Shield"-eket csatlakoztatunk rá, addig rendben van a dolog, sőt nagyon klassz. A láb számozási rendszer elfedi az eredeti AVR port/láb kiosztást. Nem rossz koncepció a hardware-t elfedő egységes felület definiálása, de az eredeti Arduino Uno után megjelent verzióknál kiderül, hogy ez nem sikerült teljesen. Az a benyomásom, hogy a fejlődés jelentősen túlszárnyalta az eredeti koncepciót.

Visszatérve, több érdekes tanulsága volt a project-nek. Az egyik, hogy jobban megismerkedtem az Arduino-hoz szabadon letölthető fejlesztő környezettel, az egyes eszközöket kezelő modulokkal, amit az Arduino-ban "Libraries"-nak neveznek. Az egyes modulok C++-ban megírt objektumok, amelyeknek egyszerű a felhasználásuk. Mindegyikhez melléklenek felhasználási minta programo(ka)t. Mivel GNU rendszer, sokan tesznek közzé hozzá készített objektumokat, amiknek változó a minőségük. A használatuk rendben van, de ha az ember hozzá akar nyúlni, akkor kiderül hogy a kód olvashatóságával baj van. Eddig is figyelmet fordítottam a kódom olvashatóságára, mert előfordul az ember praxisában, hogy egy régebbi programban valamit módosítani kell. És ha mondjuk nem is egy nyúlfaroknyi programról van szó... Igyekezni fogok, hogy a közzé tett program kódjaim mások számára is érthetőek legyenek. Igaz, ehhez részetekről sem ártana egy néha egy kis visszajelzés. Rádöbbentem, hogy az Arduino-ban ugyan az a GCC fordító dolgozik, mint amit eddig is használtam. Sőt a mappái között turkálva megtalálható az Arduino teljes C/C++ forráskódja is. A "Libraries" is forráskódban van. Ez egy nagy pirospont.

A C++ a C továbbfejlesztése (persze vannak akik mást mondanak), jelenleg még nem vagyok penge benne. Különben nincs egyszerű dolga a földi halandó szakinak, ha C++-t akar tanulni, mintha mindegyik Google-val turkált magyar leírást elméleti matematikus írta volna. Azt hiszem a C, meg a C++ is hardware közelében :) tudja megmutatni az erejét. Akkor lesz standard I/O, ha majd megírjuk hozzá :). Megbarátkozva azzal a gondolattal, hogy már C-ben is rosszabb és lassabb kódot írok, mintha mondjuk assembly-ben dolgoznék, azt hiszem érdemes C++-ban dolgozni. Úgy látom az az előnye, hogy egybe lehet kapcsolni, egy objektumba, az adott eszköz kezeléséhez szükséges függvényeket és adatokat. Amúgy ugyanazokat a függvényeket kell megírnunk, mint amivel C-ben oldanánk meg a feladatot. Viszont nem lesz szükség globális függvény és adat definíciókra, mert az objektum függvényei hozzáférnek az objektum adataihoz. Egy nagyobb project-nél ez fontos. Az objektumok megkönnyítik a feladat felosztását modulokra, iletve korábban megírt kód újra használatát.

Az első objetum a "morse" volt, amit az Arduino-val kapcsolatban valaki feltett oktató anyagként. Nézzük ennek hagyományos megvalósítását C-ben. A program egy LED-en a közismert SOS jelzést villogtatja Morse kóddal.


/*******************************************************************************
*   Author       -  Kiraly Tibor
*                   http://www.tkiraaly.hu
*   Date         -  2016.03.15.
*   Chip         -  Atmel AMega8
*   Compiler     -  avr-gcc ( WinAVR)
*
*   morse.c - demo program
*
********************************************************************************
*   PonyProg Configuration and Security Bits (bepipalva):
*
*   CKSEL3, CKSEL2, CKSEL1, CKSEL0
*   P       P       -       -       0011  Calibrated Internal RC Oscillator 4MHz
*
*   Calibrated Internal RC Oscillator
*   SUT1  SUT0
*   -     P         Slowly rising power
*   
*******************************************************************************/
#define F_CPU                4 MHZ


#include "tkiraaly_atmega8.h"
#include <util/delay.h>


#define LED_BE               PB0_0
#define LED_KI               PB0_1
#define LED_KIMENET          PB0_OUTPUT


int main( void);
void pont( void);
void vonal( void);
void szunet( void);


int main( void)
{
   LED_KI;
   LED_KIMENET;
   for(;;)
   {
      pont();
      pont();
      pont();
      szunet();
      vonal();
      vonal();
      vonal();
      szunet();
      pont();
      pont();
      pont();
      szunet();
      szunet();
   }
}


void pont( void)
{
   LED_BE;
   szunet();
   LED_KI;
   szunet();
}


void vonal( void)
{
   LED_BE;
   szunet();
   szunet();
   LED_KI;
   szunet();
}


void szunet( void)
{
  _delay_ms( 300);
}

Így néz ki C++-ban. Olvasgattam a GCC leírását, hogyan kellene módosítani a "fordit.bat"-ot .cpp fordításához, de csak a program nevét kellett átírni benne .c-ről .cpp-re. Korábban a két .HEX között csak 10 byte méret különbség volt, később ez 50 byte-ra (10%) nőtt. Gondolom a fordító sebesség/memória optimalizálását lehetne változtatni.


/*******************************************************************************
*   Author       -  Kiraly Tibor
*                   http://www.tkiraaly.hu
*   Date         -  2016.03.15.
*   Chip         -  Atmel AMega8
*   Compiler     -  avr-gcc ( WinAVR)
*
*   morse - cpp demo objektum
*
********************************************************************************
*   PonyProg Configuration and Security Bits (bepipalva):
*
*   CKSEL3, CKSEL2, CKSEL1, CKSEL0
*   P       P       -       -       0011  Calibrated Internal RC Oscillator 4MHz
*
*   Calibrated Internal RC Oscillator
*   SUT1  SUT0
*   -     P         Slowly rising power
*   
*******************************************************************************/
#define F_CPU                4 MHZ


#include "tkiraaly_atmega8.h"
#include <util/delay.h>


#define LED_BE          PB0_0
#define LED_KI          PB0_1
#define LED_KIMENET     PB0_OUTPUT


class morse
{
  public:
    morse();
    void pont();
    void vonal();
    void szunet();
};


int main( void)                                  // SOS jelzes
{
   morse m= morse();
   for(;;)
   {
      m.pont();
      m.pont();
      m.pont();
      m.szunet();
      m.vonal();
      m.vonal();
      m.vonal();
      m.szunet();
      m.pont();
      m.pont();
      m.pont();
      m.szunet();
      m.szunet();
   }
}


morse::morse()
{
   LED_KI;
   LED_KIMENET;
}


void morse::szunet()
{
   _delay_ms( 300);
}


void morse::pont()
{
   LED_BE;
   szunet();
   LED_KI;
   szunet();
}


void morse::vonal()
{
   LED_BE;
   szunet();
   szunet();
   LED_KI;
   szunet();
}

Az objektum definíciója a C++-ban a "class". Ebben definiáljuk az objektum függvényeit és adatait, ezek lehetnek "privat:" meg "public:". Ebben most nincs "privat:" rész. A "privat:" változókat és függvényeket csak az objektum függvényei érhetik el. Értelem szerűen a "public:" dolgokat pedig a programunkból kívülről is elérhetjük. Attól, hogy van egy "class"-sunk, azzal még nem tud a program dolgozni, létre kell hozni belőle legalább egy példányt. Ez egy egyszerű objektum, nincsenek adatai sem, de az objektum példány létrehozásakor történik meg az adatok számára a memóriában a helyfoglalás. Az objektumnak kötelező, hogy legyen egy "construktor"-a, ez egy függvény aminek a neve megegyezik a "class" nevével, és kizárólag az objektum létrehozásakor fut le. Tehát ide lehet tenni a vátozók kezdeti értékadását... stb. A "class morse"-ban leírt objektumból a "main()"-ban "morse m= morse()" bemondással hozunk létre egy "m" objetumot. Lehet persze megszüntető "destruktor" függvényt is definiálni, AVR-ek esetében most nem látom ennek szükségességét. A "main()" után található a "morse" objektum tagfüggvényeinek definíciói. A C++-ban ha a függvénynek nincs paramétere, nem kell beírni a void kulcsszót. Soxor láttam, hogy egyetlen objektumot hoznak létre csupán, azután a tagfüggvényeit hívogatják. A minta programok alapján ne pipáljuk ki, hogy a C++ is megvolt. Számos érdekes dolgot lehet még művelni benne, sok olyat is, amit még nekem is meg kell fejtenem. Már most elárulhatom, lesz még folytatása a C++ témának :).

Itt a vége, fuss el véle, legytek az én vendégeim, innen letölthetitek a teljes programokat, miegymást, összecsomagolva.