C suli 5 - Etűdök hangszóróra és szirénára

2016-10-27

 

Kicsit módosítunk az áramkörünkön, a LED helyére tegyünk be egy kis hangszórót, amit kitermelhetünk akár egy kimustrált PC-ből is. Ennek hangereje várhatóan nem fogja a szomszédaink rosszallását kivívni.

Egy kép az áramkörről.

Emlékeztetőül az ATmega8 bekötése:

Az első példánk szinte megegyezik azzal a programmal, amivel a LED-et villogtattuk, hiszen a hangszoró is úgy működik, akkor ad ki hangot, ha periódikusan áramot kapcsolunk rá. Viszont vegyük észre, hogy a LED-et lasabban villogtattuk, a szemünk másodpercenként 24 felvillanást már nem tud megkülönbeztetni (mozi). A fülönk pedig másodpercenként 40-16000 rezgésű (színuszos) hangokat képes érzékelni, ezért a programunkat nemes egyszerűségel 1000 Hz-re állítottuk be. A LED vezérlésénel a _delay_ms()-t, itt pedig a _delay_us()-t használjuk. A LED-nél ezred másodpercben, itt milliomod másodpercben adjuk meg a késleltetés hosszát.


/*******************************************************************************
*
*   Author       -  Kiraly Tibor
*                   http://www.tkiraaly.hu
*   Date         -  2016.10.22.
*   Chip         -  ATmega8
*   Compiler     -  avr-gcc
*
*   c_suli_5_1.c
*   1 kHz-es hang generalas
*
*******************************************************************************/

#define F_CPU  4 MHZ

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

#define  HANG_KIMENET   PB0_OUTPUT
#define  HANG_BE        PB0_0
#define  HANG_KI        PB0_1


int main( void)
{
   HANG_KIMENET;
   for(;;)
   {
      HANG_BE;
      _delay_us( 500);
      HANG_KI;
      _delay_us( 500);
   }
}

A következő programunk egy morse gyakorló lesz. Addig fog szólni a hang, amíg az A gombot nyomva tartjuk. Úgy működik, hogy ciklikusan vizsgáljuk az A gombot, és csak akkor kapcsoljuk a hangszórót, ha le van nyomva.


/*******************************************************************************
*
*   Author       -  Kiraly Tibor
*                   http://www.tkiraaly.hu
*   Date         -  2016.10.22.
*   Chip         -  ATmega8
*   Compiler     -  avr-gcc
*
*   c_suli_5_2.c
*   Morse gyakorlo, a hang addig szol, amig a gombot nyomva tartjuk
*
*******************************************************************************/

#define F_CPU  4 MHZ

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

#define  HANG_KIMENET   PB0_OUTPUT
#define  HANG_BE        PB0_0
#define  HANG_KI        PB0_1

#define  A_FELHUZAS     PD1_PULLUP
#define  A_GOMB         PD1_RD == 0


int main( void)
{
   HANG_KIMENET;
   HANG_KI;                                      // alapban 0-an all a kimenet
   for(;;)                                       
   {
      if ( A_GOMB)
      {
         HANG_BE;
         _delay_us( 500);
         HANG_KI;
         _delay_us( 500);
      }

   }
}

Most pedig nem a fő programunk fogja a hangot generálni, hanem készítünk erre egy beep() függvényt, egy mini programot.


/*******************************************************************************
*
*   Author       -  Kiraly Tibor
*                   http://www.tkiraaly.hu
*   Date         -  2016.10.22.
*   Chip         -  ATmega8
*   Compiler     -  avr-gcc
*
*   c_suli_5_3.c
*   Fuggveny hasznalat demo
*
*******************************************************************************/

#define F_CPU  4 MHZ

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

#define  HANG_KIMENET   PB0_OUTPUT
#define  HANG_BE        PB0_0
#define  HANG_KI        PB0_1

#define  A_FELHUZAS     PD1_PULLUP
#define  A_GOMB         PD1_RD == 0


void beep( unsigned char);


int main( void)
{
   HANG_KIMENET;
   HANG_KI;                                      // alapban 0-an all a kimenet
   for(;;)                                       
   {
      if ( A_GOMB) beep( 200);
   }
}


void beep( unsigned char hossz)                  // 1000 Hz-es csippantas 
{
    while( hossz--)
    {
       HANG_BE;
       _delay_us( 500);
       HANG_KI;
       _delay_us( 500);
    }
}

Azt látjuk, hogy main() előtt és utána is szerepel a beep(), amit a main() -ben használunk fel.


void beep( unsigned char);

A C fordítónak használat előtt meg kell adjuk, hogy a függvényünk hány és milyen típusú paramétert vár, és mit fog visszadni. Ezért előszőr deklaráljuk, hogy lesz egy void típusú beep() nevü függvényünk, ami tehát nem fog semmit visszaadni, viszont egy előjel nélküli char típusú adatot fog várni. Erre nem azért van szükség, mert a C fordító buta, hanem összetettebb programoknál, az egyes modulokat előre lefordítják úgynevezett library-ba. Ezután a forrás program a fordítónak már nem érhető el. Ezeket a definíciókat viszon ki kell gyűjteni úgynevezett header (.h) file-okba, és szükséges mellékelni a fordításhoz. A C fordító ezekből fogja tudni mivel kell számolnia, amikor egy library-beli függvényt hívunk meg. A GNU C-hez is tartozik több library, például matematikai. Nem kell leprogramoznunk ha egy szinuszt vagy logaritmust kell kiszámolnunk, azok megtalálhatók a library-ben, elegendő meghívnunk őket, és fordító beépíti a szükséges modulokat a programunkba (profik kedvéért - nem a fordító, hanem a linker). Ugyanakkor ezek a függvények nem képezik a C nyelv részét. Ha nem tetszenek nekünk valamiért, mondjuk nem elég pontosak, vagy nem elég gyorsak, megtehetjük, hogy saját függvényt készítünk.


void beep( unsigned char hossz)                  // 1000 Hz-es csippantas 
{
    while( hossz--)
    {
       HANG_BE;
       _delay_us( 500);
       HANG_KI;
       _delay_us( 500);
    }
}

Ezután már bárhol lehet a beep(), a függvények sorrendje nem kötött a forrásban, a fordító program majd meg fogja keresni. A beep() egy paramétert vár, hogy hány periódus hosszú legyen a a hang. Egy periódus 1 ezredmásodperc hosszú, azaz a frekvenciája 1 kHz lesz. A while() ugye megkapja a hossz-t, amit ciklikusan csökkent egggyel, és mindaddig fogja be/ki kapcsolni a hangszórót, amíg a hossz nagyobb mint 0.

Akkor szirénázzunk, ahogy a címben igértem. Ehhez egy olyan függvényre lesz szükségünk amivel a kesleltetés változtatható. Mít kell ezen vacakolni, ott a _delay_us() függvény. Korábban magam is beleestem ebbe a csapdába, a _delay_us() nem függvény, hanem egy speciális makró. A makró kifejezést a C fordító a fordításkor feldolgozza, vagyis a program futás közben nem lehet állítani az értékét. A C fordító az F_CPU és a megadott késlelteési idő alapján _delay_us() helyére beilleszt egy olyan kis gép kódú programot, ami a kívánt ideig fut. Azért megoldjuk, hogy a futási időben a fő programunkból vezéreljük a hang magasságát.


/*******************************************************************************
*
*   Author       -  Kiraly Tibor
*                   http://www.tkiraaly.hu
*   Date         -  2016.10.22.
*   Chip         -  ATmega8
*   Compiler     -  avr-gcc
*
*   c_suli_5_4.c
*   Szirena hang
*
*******************************************************************************/

#define F_CPU  4 MHZ

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

#define  HANG_KIMENET   PB0_OUTPUT
#define  HANG_BE        PB0_0
#define  HANG_KI        PB0_1


void wait_10us( unsigned char);                  // hossz x 10usec varakozas 


int main( void)
{
   unsigned char i;                              // hang magassaga
   unsigned char j;                              // hang periodusok szama
   HANG_KIMENET;
   HANG_KI;                                      // alapban 0-an all a kimenet
   for(;;)                                       
   {
      i= 21;                                     // hang magasodik
      while( --i)                                // azert elol decrementalok, hogy wait_10us() ne kapjon 0-t
      {
         j= 50;                                  // egy hangmagassag periodusszama
         while( j--)
         {
            HANG_BE;
            wait_10us( i);                       // 20..1
            HANG_KI;
            wait_10us( i);
         }
      } 
      i= 20;                                     // hang melyul
      while( i--)
      {
         j= 50;                                  // egy hangmagassag periodusszama
         while( j--)
         {
            HANG_BE;
            wait_10us( 20- i);                   // 1..20
            HANG_KI;
            wait_10us( 20- i);
         }
      } 
   }
}


void wait_10us( unsigned char hossz)             // hossz x 10usec varakozas 
{
    while( hossz--) _delay_us( 10);
}

Csináltam egy wait_10us() függvényt, ami megadott számú 10 mikrosecundum hosszúságú késleltetést csinál. A main()-ban viszont több szinten egymásba ágyazott ciklusokat kellet alkalmaznom. Megkönnyíti a megértésüket, hogy a forráskódban az egyes ciklusok magját mindig egy kicsitt jobbra toltam. Ez nem kötelező, akár egyetlen sorba is írhattam volna az egészet. Csak akkor ki tudná megérteni rajtam és a C fordítón kívül? C-ben sajnos nagyon könnyű olvashatatlan, követhetetlen kódot elkövetni, találni ilyet az Interneten. Én nem fogom azt mondani, hogy figyeljünk oda a munkánkra, írjunk bele megjegyéseket, tagoljuk, hogy mások is meg tudják érteni mit csináltunk. Jó, titkolni fogjuk, de ha csak saját magunknak kell is egy idő után valamit megnézni vagy megváltoztatni benne, akkor majd...