Tablice


Tematy pokrewne   Podrozdziały
(w budowie)
  Regulacja świecenia diod LED
Tablica
Podsumowanie

 

 

Regulacja świecenia diod LED

 
   
Jasnością świecenia diody LED da się sterować za pomocą ograniczania prądy, który przez diodę przepływa. Sposób ten jednak jest dla nas mało przydatny, ponieważ mikrokontroler ATTiny13 posiada jedynie wyjścia cyfrowe. Na pierwszy rzut oka wydaje się zatem, że nie będzie można regulować jasności świecenia diod LED. Nic bardziej mylnego. Wykorzystamy bezwładność ludzkiego oka i sterowanie impulsowe.

Wyobraź sobie, że do diody LED wysyłamy szybko zmienny sygnał cyfrowy:

Gdy sygnał osiąga wartość 1, dioda LED świeci. Gdy sygnał ma wartość 0, dioda LED jest zgaszona. Jeśli sygnał ten zmienia się odpowiednio szybko (np. 30 razy w ciągu sekundy), to oko nie zauważy mrugania diody, co wiąże się ze skończoną szybkością spostrzegania zmian w odbieranym obrazie. Na tej zasadzie działa kino ruchome. Na ekranie są wyświetlane nieruchome obrazy, jednak dzieje się to 25 razy na sekundę. Oko odbiera tę serię obrazów jako obraz ruchomy. To samo dzieje się tutaj - dla naszego oka dioda LED będzie świeciła światłem ciągłym.

Regulując wypełnienie sygnału (czas trwania impulsu o wartości 1 w stosunku do czasu trwania stanu 0) wpływamy na czas świecenia diody. Im dużej dioda świeci, tym więcej wyśle energii, co nasze oko rozpozna jako wzrost jasności. W ten sposób możemy sterować jasnością świecenia diody LED.

Regulacji wypełnienia dokonamy w pętli, która będzie cyklicznie wykonywana w programie. Przed pętlą diody będą gaszone. Gdy licznik pętli osiągnie określoną wartość, diody LED zostaną zapalone i w tym stanie dotrwają do zakończenia pętli.

 

Program mrugający diodami LED - wersja 1

Do płytki bazowej APP000 podłącz programator oraz płytkę aplikacyjną APP001. Na płytce APP001 ustaw obie zworki J0 i J1 w położenie górne. Uruchom aplikację Eclipse, utwórz w niej nowy projekt dla ATTINY13, po czym załaduj do edytora poniższy program, skompiluj go i prześlij przez programator do mikrokontrolera na płytce APP000.

/*
 * main.c
 *
 *  Created on: 01 paź 2015
 *      Author: Geo
 */

#include <avr/io.h>

int main(void)
{
    uint8_t i,w=0;
    DDRB  = 0b011111; // PB0...PB4 jako wyjścia
    PORTB = 0;        // Gasimy wszystkie diody
    while(1)
    {
        for(i = 0; i < 255; i++)
            if(i < w) PORTB = 0b11111; // Zapalamy wszystkie diody
            else      PORTB = 0;       // Wyłączamy diody
    	w++; // Modyfikujemy wypełnienie
    }
}

 

W programie pojawia się nowy rodzaj pętli. Jest to tzw. pętla iteracyjna. Iteracją nazywamy numerowany obieg pętli. W pętli iteracyjnej każdy obieg posiada swój numer. Do tego celu używamy dodatkowej zmiennej, którą nazywamy zmienną sterującą pętli lub po prostu licznikiem pętli (ang. loop counter). Pętla for pozwala w prosty sposób tworzyć pętle iteracyjne. Składnia jest następująca:

 

for(początek; kontynuacja; koniec) instrukcja;
for(początek; kontynuacja; koniec)
{
   instrukcje; // dowolna liczba instrukcji
}
początek  –  operacja wykonywana przed rozpoczęciem pętli. Tutaj najczęściej nadajemy wartość początkową licznikowi pętli.
kontynuacja  –  warunek, który musi być prawdziwy (różny od zera), aby pętla wykonała obieg. Zwykle warunek ten sprawdza, czy licznik pętli jest w pożądanym zakresie.
koniec  –  operacja wykonywana po każdym obiegu pętli. Tutaj najczęściej modyfikujemy licznik pętli, aby w następnym obiegu miał inną wartość.
instrukcja  –  powtarzana w pętli instrukcja

Pętla for działa w sposób następujący:

 

Najpierw zostaje wykonana instrukcja początkowa. Instrukcja ta inicjuje licznik pętli, czyli określa numer pierwszego obiegu:

for(i = 0;...

Następnie wyliczony zostaje warunek kontynuacji. Jeśli jest on różny od zera (czyli jest prawdziwy), to jest wykonywana instrukcja, czyli pętla wykonuje obieg. Numer obiegu jest przechowywany przez zmienną i. Jeśli warunek jest równy zero (czyli jest fałszywy), to pętla zostaje przerwana i mikrokontroler wykonuje dalszą część programu. Warunek zwykle sprawdza stan licznika pętli.

for(i = 0; i < 10;...

Gdy instrukcja zostanie już wykonana, czyli gdy obieg pętli dobiegł do końca, zostaje wykonana instrukcja końca obiegu. Najczęściej instrukcja ta zwiększa licznik pętli (gdy obiegi są numerowane w górę: 0, 1, 2...) lub zmniejsza go (gdy obiegi są numerowane w dół: 5, 4, 3, ...).

for(i = 0; i < 10; i++)...

Po modyfikacji licznika pętli następuje powrót do sprawdzania warunku kontynuacji i cykl się powtarza.

 

Program działa w sposób następujący:

 

Przed pętlą for zerujemy rejestr PORTB, czyli ustawiamy wszystkie jego bity na 0. Spowoduje to zgaszenie wszystkich diod D0...D4 na płytce APP001. Teraz uruchamiamy pętlę, która wykona kolejno obiegi o numerach 0, 1, ... 254. Numer obiegu jest przechowywany w zmiennej i. W każdym obiegu sprawdzamy, czy i jest mniejsze od współczynnika wypełnienia w. Załóżmy, że w jest równe 100. Zatem dla obiegów od 0 do 99 warunek będzie prawdziwy i diody będą zapalone. Od następnego obiegu 100 do 254 warunek stanie się fałszywy i diody zgasną. Im większe w tym dłużej będą się świeciły diody, a więc ich światło wyda się jaśniejsze. Po zakończeniu cyklu wypełnienie w jest zwiększane o 1 (co w następnym cyklu pętli for spowoduje wydłużenie czasu świecenia diod i w efekcie wzmocnienie ich światła). Ponieważ w jest zmienną 8-bitową, to po osiągnięciu wartości 255 kolejną wartością będzie 0 (to analogiczna sytuacja do przewinięcia się licznika, np. licznik trzycyfrowy po osiągnięciu swojego maksimum 999 przewija się na 000, tutaj mamy to samo). Po tej operacji cały cykl się powtarza. W efekcie diody emitują mrugające płynnie światło.

 

Program mrugający diodami LED - wersja 2

Drugi program jest małą modyfikacją pierwszego. Tutaj współczynnik wypełnienia rośnie od 0 do 255, po czym znów maleje do 0 itd. W efekcie dostajemy ładną pulsację światła LED.

/*
 * main.c
 *
 *  Created on: 01 paź 2015
 *      Author: Geo
 */

#include <avr/io.h>

int main(void)
{
    uint8_t i,w=0;
    int8_t d=1;
    DDRB = 0b011111; // PB0...PB4 jako wyjścia
    PORTB = 0;       // Gasimy wszystkie diody
    while(1)
    {
        for(i = 0; i < 255; i++)
            if(i < w) PORTB = 0b11111; // Zapalamy wszystkie diody
            else      PORTB = 0;       // Gasimy wszystkie diody
        w += d; // Modyfikujemy wypełnienie
        if((w == 255) || !w) d = -d;
    }
}

Poruszaj szybko obiema płytkami tam i z powrotem – zobaczysz wtedy efekt przerywanego światła diod. W dalszej części kursu pokażemy, jak podobne efekty uzyskiwać w sposób sprzętowy za pomocą wbudowanych w mikrokontroler urządzeń.

Program sterujący jasnością świecenia diod LED

Na płytce APP001 ustaw zworki J0 i J1 w położenie dolne (do linii PB0 i PB1 zostaną dołączone przyciski W0 i W1). Napiszemy program, który reguluje jasność świecenia diod za pomocą przycisków. Naciśnięcie W0 będzie powodowało zmniejszenie jasności, a naciśnięcie W1 zwiększy jasność.

/*
 * main.c
 *
 *  Created on: 03 paź 2015
 *      Author: Geo
 */

#include <avr/io.h>

int main(void)
{
    DDRB  = 0b011100; // Określamy kierunek linii PB0...PB4
    PORTB = 0b000011; // Oporniki podciągające na PB0 i PB1
    uint8_t w = 127;  // Wypełnienie 1/2
    uint8_t i,j;
    while(1)
    {
        for(j = 0; j < 5; j++)
            for(i = 0; i <= 254; i++)       // Pętla cyklu
                if(i < w) PORTB = 0b11111;  // Włączamy diody
                else      PORTB = 0b00011;  // Wyłączamy diody
      if(!(PINB & 0b01) && (w)) w--;        // Obsługa przycisków
      if(!(PINB & 0b10) && (w < 255)) w++;
    }
}

 

 

 

Tablica

 
   
Tablica (ang. array) jest złożoną strukturą danych, która składa się z ciągu elementów tego samego typu. Tego typu obiekt przydaje się wtedy, gdy w programie musimy w podobny sposób przetwarzać wiele danych. Ponieważ tablica również jest zmienną, to przed pierwszym użyciem musi zostać zdefiniowana:

 

typ nazwa[liczba_elementów];
typ  –  określa typ elementów tablicy, np. int8_t.
nazwa  –  jest nazwą zmiennej i tworzymy ją wg poznanych wcześniej zasad.
liczba_elementów  –  określa, ile elementów tablica będzie przechowywała. W ATTINY 13 mamy do dyspozycji jedynie 64 komórki pamięci RAM, zatem tablica nie może być zbytnio duża.

 

Przykład:

uint8_t a[10] // Tworzy tablicę a o 10 elementach

 

W programie można zdefiniować tyle tablic, ile zmieści się nam w pamięci. Tablicę możemy również zdefiniować podając w klamerkach wartości dla kolejnych jej komórek oddzielone przecinkami. W takim przypadku nie musimy podawać liczby elementów, gdyż kompilator sam ją ustali na podstawie liczby wartości.

 

Przykład:

int8_x[] = {1,7,-15,2}; // Powstanie tablica 4 elementowa o podanej zawartości

 

Gdy już mamy zdefiniowaną tablicę, to dostęp do poszczególnych komórek uzyskujemy poprzez nazwę tablicy oraz numer komórki, który nazywamy indeksem. Na przykład poniższy fragment programu umieszcza w dwóch komórkach tablicy a[ ] dane:

 

a[5] = 4;
a[7] = 12;

 

Komórka tablicy jest normalną zmienną i dlatego stosują się do niej wszystkie operacje, które wykonujemy na zmiennych.

 

Przykłady:

a[5]++;          // zawartość komórki a[5] jest zwiększana o 1
c = a[1] + a[5]; // do zmiennej c trafi suma komórek a[1] i a[5]
a[2] <<= 2;      // bity komórki a[2] zostaną przesunięte w lewo o 2 pozycje

 

W języku C indeksy rozpoczynają się od wartości 0. Zatem pierwsza komórka tablicy posiada indeks zero, np. a[0]. Jeśli tablica ma n komórek, to ostatnia z nich ma indeks n-1, np. dla tablicy 10-cio elementowej a ostatnią komórką jest a[9]. Musisz o tym pamiętać, ponieważ kompilator nie zgłosi błędu, jeśli w programie użyjesz komórki np. a[10], której w tablicy już nie ma:

 

int8_t b[5]; // W tablicy mamy: b[0] b[1] b[2] b[3] i b[4]
b[5] = 15;   // To jest błędna operacja dla tablicy b, gdyż b[5] nie ma!

 

Zaletą tablic jest to, że mogą być przetwarzane w pętlach.

 

Program tworzący efekt przesuwającego się światła

Program wykorzystuje tablice 5 elementowe do sterowania poszczególnymi diodami. Sama zasada regulacji oświetlenia zostaje taka sama. Różnica polega na tym, iż współczynnik wypełnienia w dla każdej z diod będzie przechowywany w osobnej komórce tablicy. Wartości tych współczynników są tak dobrane, aby światło emitowane przez diody nabierało lub traciło intensywność w różnym czasie. W efekcie otrzymamy płynnie przesuwający się "wąż" świetlny.

/*
 * main.c
 *
 *  Created on: 02 paź 2015
 *      Author: Geo
 */

#include <avr/io.h>

int main(void)
{
    uint8_t i,j;
    uint8_t w[] = {0,32,64,96,128}; // Wypełnienia
    int8_t  d[] = {4,4,4,4,4};      // Kierunki zmian
    DDRB = 0b011111;                // PB0...PB4 jako wyjścia
    while(1)
    {
        PORTB = 0;                  // Gasimy wszystkie diody
        for(i = 0; i < 96; i++)
            for(j = 0; j < 5; j++)  // Przeglądamy tablicę w[]
                if(i > w[j]) PORTB |= 1<<j; // Zapalamy odpowiednią diodę

        for(i = 0; i < 5; i++)      // Modyfikujemy wypełnienia
        {
            w[i] += d[i];
            if((w[i] == 200) || !w[i]) d[i] = -d[i];
    	}
    }
}

Poeksperymentuj z wartościami w programie. Zmniejszając zakres pętli i, powodujesz przyspieszenie ruchu węża. Zmniejszając wartości w tablicy d spowalniasz węża. jednak uważaj, aby pierwszy warunek w ostatniej instrukcji if był osiągalny, inaczej efekt przestanie działać poprawnie.

Drobna modyfikacja programu daje ciekawy efekt świetlny:

/*
 * main.c
 *
 *  Created on: 02 paź 2015
 *      Author: Geo
 */

#include <avr/io.h>

int main(void)
{
    uint8_t i,j;
    uint8_t w[] = {0,0,0,0,0};      // Wypełnienia
    int8_t  d[] = {1,5,3,2,7};      // Kierunki zmian
    DDRB = 0b011111;                // PB0...PB4 jako wyjścia
    while(1)
    {
        PORTB = 0;                  // Gasimy wszystkie diody
        for(i = 0; i < 96; i++)
            for(j = 0; j < 5; j++)  // Przeglądamy tablicę w[]
                if(i > w[j]) PORTB |= 1<<j; // Zapalamy odpowiednią diodę

        for(i = 0; i < 5; i++)      // Modyfikujemy wypełnienia
        {
            w[i] += d[i];
            if((w[i] > 200) || !w[i]) d[i] = -d[i];
    	}
    }
}

Czy potrafisz wyjaśnić działanie tego programu?

 

Program tworzący efekt typu "Cylon"

Kilka lat temu popularnością cieszył się serial "Battle Star Galactica", który również transmitowała nasza telewizja.  Cyloni byli inteligentnymi robotami walczącymi z ludźmi. W miejscu oczy roboty posiadały przesuwające się tam i z powrotem światło. Efekt ten możesz zaobserwować na filmie:

 

 

Poniższy program tworzy taki efekt na płytce APP001. Program wykorzystuje tablicę w do przechowywania współczynników wypełnień.

/*
 * main.c
 *
 *  Created on: 03 paź 2015
 *      Author: Geo
 */

#include <avr/io.h>

int main(void)
{
    DDRB  = 0b011111; // Określamy kierunek linii PB0...PB4
    PORTB = 0;        // Gasimy wszystkie diody LED
    uint8_t w[5];     // Tablica wypełnień
    int8_t p=0,d=-1,i,j,k;
    while(1)
    {
        if((p >= 0) && (p <= 4)) w[p] = 255;
        for(i = 0; i < 5; i++) if(i != p) w[i] >>= 1;
        for(k = 0; k < 2; k++)     // Pętla spowalniająca
            for(i = 0; i < 127; i++)
                for(j = 0; j < 5; j++)
                    if(i < w[j]) PORTB |= 1 << j;    // Ustawiamy bit
                    else         PORTB &= ~(1 << j); // Zerujemy bit
        p += d;
        if((p == 6) || (p == -2)) d = -d;
    }
}

Program działa w sposób następujący:

 

W zmiennej p przechowujemy pozycję punktu świetlnego. Pozycja ta cyklicznie zmienia się od wartości -2 do 6. Tablica w[ ] przechowuje wartości współczynników wypełnienia dla diod D0...D4. Jeśli p jest w zakresie od 0 do 4, to w tablicy w na tej pozycji ustawiamy maksymalną wartość wypełnienia równą 255. Pozostałe komórki tablicy w[ ] przesuwamy bitowo w prawo o 1 pozycję. Spowoduje to zmniejszenie się jasności diod na pozycjach różnych od p. Dioda na pozycji p będzie miała największą jasność świecenia. Następnie w pętli generujemy odpowiednie wypełnienia dla poszczególnych diod D0...D4. Po zakończeniu tej fazy pozycję p przesuwamy w lewo lub w prawo w zależności od wartości zmiennej d. Gdy pozycja p osiągnie skrajne położenie, d zmieniamy na wartość przeciwną, co spowoduje przesuw pozycji p w drugą stronę w kolejnych obiegach pętli głównej while.

 

Zamek szyfrowy

Na płytce APP001 ustaw zworki J0 i J1 w położeniu dolnym.

Zamek szyfrowy działa następująco:

Klawiszem W0 wybierasz kod cyfry od 000 (diody zgaszone) do 111 (diody zapalone). Każde naciśnięcie W0 wybiera kolejny kod. Wybrany kod wprowadzasz klawiszem W1. Diody wtedy krótko mrugną. Gdy wprowadzisz 4 cyfry kodu, następuje sprawdzenie ich poprawności. Jeśli wprowadzony kod będzie poprawny, to diody będą się kolejno zmieniać. Jeśli wprowadzony kod będzie błędny, to wszystkie trzy diody zaświecą się. W obu przypadkach należy zresetować mikrokontroler, aby powtórzyć sekwencję wprowadzania kodu.

Poprawny kod to 111  101  001  110 (7 5 1 6)

/*
 * main.c
 *
 *  Created on: 03 paź 2015
 *      Author: Geo
 */

#include <avr/io.h>
#include <util/delay.h>


int main(void)
{
    uint8_t kod[] = {0b111,0b101,0b001,0b110}; // 7,5,1,6 - kod zamka
    uint8_t dat[4];     // Tutaj gromadzimy kod od użytkownika
    uint8_t i,c=0,k;    // Zmienne robocze

    DDRB  = 0b011100;   // Określamy kierunek linii PB0...PB4
    PORTB = 0b000011;   // Oporniki podciągające na PB0 i PB1

    while(1)
    {
        while(c < 4)    // Powtarzamy do wprowadzenie wszystkich cyfr
        {
            while((k = PINB & 0b11) == 0b11); // Czekamy na klawisz
            _delay_ms(10);      // Opóźnienie na wygaśnięcie drgań styków

            while((PINB & 0b11) != 0b11); // Czekamy na zwolnienie klawisza
            _delay_ms(10);      // Opóźnienie na wygaśnięcie drgań styków
            
            if(k & 1)
            {
        	dat[c++] = (PORTB >> 2) & 0b111; // Wprowadzamy kod do tablicy
        	for(i = 0; i < 5; i++)
        	{
        	    PINB = 0b11100;  // Mrugamy diodami
        	    _delay_ms(50);
        	}
        	PORTB = 0b00011;     // Zerujemy kod
            }
            else
        	PORTB += 0b100;      // Zwiększamy kod
        }

        // Teraz sprawdzamy poprawność kodu

        for(i = 0; i < 4; i++) if(dat[i] != kod[i]) while(1) PORTB=0b111111; // Blokada
        while(1) // Efekt zliczania binarnego
        {
    	    PORTB += 0b100;
            _delay_ms(100);
        }
    }
}

 

 

 

Podsumowanie

 
   
 

Pętla iteracyjna:

for(początek; kontynuacja; koniec) instrukcja;
for(początek; kontynuacja; koniec)
{
   instrukcje; // dowolna liczba instrukcji
}

Definicja tablicy:

typ nazwa[liczba_elementów];

Indeksy tablicy mają wartości od 0 do n - 1, gdzie n jest liczbą elementów.

Element tablicy jest normalną zmienną i stosują się do niego wszystkie operacje dozwolone dla zmiennych:

a[5] = 12;  // Przypisanie
a[2]++;     // Modyfikacja
c = a[1] - a[3]; // Wyrażenia

Przesunięcie elementów w górę w tablicy n-elementowej. Na pierwszą pozycję trafia wartość v. Ostatni element jest tracony

for(i = n - 1; i > 0; i--) a[i] = a[i - 1];
a[0] = v;

Przesunięcie elementów w dół w tablicy n-elementowej. Na ostatnią pozycję trafia wartość v. Pierwszy element jest tracony

for(i = 0; i < n - 1; i++) a[i] = a[i + 1];
a[n - 1] = v;

 

 



List do administratora Serwisu Edukacyjnego Nauczycieli I LO

Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:
Uwaga: ← tutaj wpisz wyraz  ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048

 

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień szeroko opisywanych w podręcznikach.



   I Liceum Ogólnokształcące   
im. Kazimierza Brodzińskiego
w Tarnowie

©2017 mgr Jerzy Wałaszek

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji
GNU Free Documentation License.