background image

 
Artykuł pochodzi ze strony XYZ HOBBY ROBOT (

xyz.isgreat.org

Kurs AVR-GCC cz.2

30.11.2008 ABXYZ

W pierwszym odcinku kursu 
zainstalowaliśmy pakiet programów 
WinAVR, nauczyliśmy się kompilować kody 
źródłowe programów oraz ładować 
programy do pamięci flash 
mikrokontrolera AVR. W tej części kursu 
zaczniemy pisać proste programy w języku 
C, poznamy w jaki sposób programować 
równoległe porty wejścia/wyjścia układów 
AVR. Zaczniemy od naprawdę prostych 

przykładów :), zupełnie od zera. Jeśli i tak coś w tekście będzie 
niezrozumiałe, to nie należy szybko się zniechęcać, w trakcie 
dalszej lektury kursu powinno się wyjaśnić. 

Równoległe porty wejścia/wyjścia 

Układ atmega8 w obudowie DIP28, na którym będziemy 
uruchamiać przykładowe programy, posiada 28 wyprowadzeń, 
z których 23 mogą służyć jako uniwersalne binarne wejścia/wyjścia. 
Linie we/wy atmega8 podzielone są na trzy grupy, nazwane: 
PORTB, PORTC i PORTD. Mikrokontrolery AVR w obudowach DIP40
(atmega16, atmega32) posiadają cztery porty: A,B,C,D; natomiast 
attiny2313 w obudowie DIP20, posiada: PORTA (tylko PA0..PA2), 
PORTB i PORTD(PD0..PD6). 

PORTB układu atemga8 posiada osiem linii we/wy (PB0..PB7), ale 
jeśli zamierzamy podłączyć do mikrokontrolera rezonator, to linie 
PB6 i PB7 odpadają. Z kolei wyprowadzenia: 17(PB3), 18(PB4) i 19
(PB4) wykorzystywanie są przy programowaniu szeregowym ISP 
pamięci flash mikrokontrolera. PORTC uC atmaga8 posiada 7 linii 
we/wy (PC0..PC6), ale wyprowadzenie 1(PC6) normalnie pełni rolę 
wejścia sygnału reset mikroprocesora. Aby wykorzystywać 
wyprowadzenie 1(PC6) atmeg8 jako kolejną linię portu we/wy, 
potrzeba zaprogramować fuse-bit RSTDISBL (na temat fuse-bitów 
będę pisał), lecz w ten sposób pozbawimy się możliwości 
programowania szeregowego ISP pamięci FLASH mikrokontrolera. 
PORTD atmega8 posiada 8 linii we/wy (PD0..PD7). 

Page 1 of 20

XYZ Hobby Robot-Kurs AVR-GCC, cz.2

17/04/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=2&pv

background image

 

Rozkład wyprowadzeń mikrokontrolera ATmega8 

Każda z wymienionych linii we/wy układu atmega8 może zostać 
indywidualnie skonfigurowana jako binarne wejście lub wyjście. 
Domyślnie wszystkie ustawione są wejściami z wyjątkiem 
wyprowadzenia 1 (RESET|PC6), które jest normalnie w atmega8 
wejściem sygnału RESET mikroprocesora. 

Program kontroluje układy peryferyjne mikrokontrolera AVR 
poprzez "rejestry I/O", 64 8-bitowe rejestry I/O znajdują się 
w przestrzeni adresowej pamięci danych - program może zapisywać 
do "rejestrów I/O" lub odczytywać z nich jakby korzystał z pamięci 
RAM. W zbiorze instrukcji języka maszynowego uC AVR istnieją też 
specjalne rozkazy służące do odczytu(zapisu) zawartości rejestrów 
I/O, także rozkazy do manipulowani poszczególnymi bitami 
rejestrów. W jaki sposób można w AVR-GCC odczytywać i  
zapisywać rejestry I/O pokażę za chwilę, przy objaśnianiu 
przykładowych programów. 

Z każdym z równoległych portów we/wy: A,B,C,D powiązane są po 
trzy rejestry I/O, o nazwach: DDRx, PORTx, PINx, gdzie x to 
oczywiście litery A,B,C,D. Stan poszczególnych bitów rejestrów 
DDRx (Port Data Direction Register) decyduje czy odpowiadające im 
linie są wejściami, czy wyjściami (0-wejście, 1-wyjście). 

Jeśli dana linia we/wy pracuje jako wyjście, wtedy ustawiając na 
wartość 1 odpowiadający tej linii bit w rejestrze PORTx (Port Data 
Register), wymuszamy na wyprowadzeniu stan wysoki napięcia, 
a ustawiając wartość bitu na 0, oczywiście stan niski. 

Jeśli linię we/wy skonfigurowano jako wejście, poziom napięcia na 
wyprowadzeniu, niski czy wysoki, sprawdza się odczytując wartość 
odpowiadającego tej linii bitu w rejestrze PINx (Port Input Pins 
Address), oczywiście wartość 0 oznacza stan niski, 1 stan wysoki. 
Dodatkowo, gdy linia jest wejściem i odpowiadający tej linii bit w 
rejestrze PORTx ma wartość 1, wtedy wyprowadzenie jest 
wewnętrznie podciągnięta do napięcia zasilania. 

DDRx.n PORTx.n

Px.n

0

0

wejście

0

1

wejście z podciągnięciem do VCC

Page 2 of 20

XYZ Hobby Robot-Kurs AVR-GCC, cz.2

17/04/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=2&pv

background image

DDRx.n PORTx.n

Px.n

1

0/1

wyjście

Konfiguracja równoległych portów we/wy mikrokontrolerów AVR

A teraz przykład konfiguracji portu B, patrzymy na ilustrację 
poniżej. 

 

Przykład konfiguracji portu B 

Wyżej, na ilustracji widać, że cztery mniej znaczące bity rejestru 
DDRB mają wartość "0", a pozostałe cztery wartość "1", więc linie 
PB0..PB3 będą wejściami, a linie PB4..PB7 będą wyjściami. Bity 
numer 0 i 1 w rejestrze PORTB ustawione zostały na wartość 1, 
więc linie PB0 i PB1 pracują jako wejścia z wewnętrznym 
podciągnięciem do napięcia zasilania. Stan wejść (PB0..PB3) 
sprawdzamy odczytując bity 0..3 rejestru PINB, natomiast 
zmieniając wartości bitów 4..7 rejestru PORTB można wymusić 
oczekiwany stan napięcia(wysoki lub niski) na wyprowadzeniach 
PB4..PB7. 

Schematy połączeń

Oprócz mikro_kontrolera AVR, dla uruchomienia przykładowych 
programików, potrzebne będą: osiem diod LED, cztery miniaturowe 
przyciski monostabilne oraz buzzer z generatorem. Diody LED 
przyłączone będą do portu D, przyciski do linii PC0..PC3, a buzzer 
do PB1. Każda z ośmiu diod LED, wraz z rezystorem ograniczającym 
prąd, przyłączona jest między wyprowadzenie portu a masę, więc 
będzie się świecić, kiedy do odpowiedniego bitu rejestru wpiszemy 
wartość "1". 

Przyciski przyłączone są do linii we/wy portu C w taki sposób, że 
przy wciśnięciu zwierają dane wyprowadzenie układu z masą, więc 
przy wciśniętym przycisku z rejestru PINC odczytamy wartość 
odpowiedniego bitu "0", a przy zwolnionym przycisku, odczytamy 
"1". Linie we/wy z przyłączonymi w ten sposób przyciskami należy 
skonfigurować jako wejścia z podciągnięciem do VCC. 

Page 3 of 20

XYZ Hobby Robot-Kurs AVR-GCC, cz.2

17/04/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=2&pv

background image

 

Sposób przyłączenia przycisku do portu we/wy uC AVR 

Dla przejrzystości schemat połączeń rozdzieliłem na dwa. Pierwszy 
schemat przedstawia sposób przyłączenia do atmega8 zasilania, 
resetu oraz programatora ISP, na drugim schemacie widać sposób 
podłączenie do portów we/wy mikro_kontrolera diod LED, 
przycisków i buzzera. 

 

Schemat przedstawia sposób przyłączenia do atmega8 zasilania, resetu oraz programatora 

ISP. Kliknij w obrazek, żeby powiększyć.

 

Schemat przedstawia sposób przyłączenia diod LED, przycisków i buzzera do portów 

we/wy uC Kliknij w obrazek, żeby powiększyć.

Ja zestawiłem wszystkie części na małej płytce stykowej. 

Page 4 of 20

XYZ Hobby Robot-Kurs AVR-GCC, cz.2

17/04/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=2&pv

background image

 

Gotowy układ zestawiony na płytce stykowej.Kliknij w obrazek, żeby powiększyć.

Szkielet prostego programu dla avr-gcc

Wszystkie przykładowe programy z tej części kursy będą wyglądać 
podobnie, poniżej znajduje się szkielet prostego programu dla AVR-
GCC. 

/* Szkielet prostego programu dla avr-gcc */

 

 

#define F_CPU 1000000L 
#include <avr/io.h> 
#include <util/delay.h>                

 

int

 main(

void


  

/* Tutaj wpisujemy instrukcje naszego  programu */

 

  

for

(;;) 

  {    
  

/* Instrukcje można umieścić w nieskończonej pętli */

 

  } 

Szkielet prostego programu dla avr-gcc

Pierwsza linia to komentarz. 

/* Szkielet prostego programu dla avr-gcc */

 

Komentarze są pomijanie przez kompilator, zwykle objaśniają mniej 
oczywiste fragmenty kodu. GCC pozwala wstawiać komentarze na 
dwa sposoby. Pierwszy sposób to objęcie treści komentarza parą 
ograniczników: '/*' , '*/'; w ten sposób utworzony komentarz może 
zawierać wiele linii tekstu. 

/*  
    Komentarz blokowy, 
    może zawierać wiele  
    linii tekstu 
*/

Drugi sposób to umieszczenie przed treścią komentarza dwóch 
znaków slash '//' , tak utworzony komentarz rozciąga tylko do 
końca linii. 

Page 5 of 20

XYZ Hobby Robot-Kurs AVR-GCC, cz.2

17/04/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=2&pv

background image

// Komentarz liniowy

A czy GCC pozwala zakomentowywać inne komentarze ? Proszę to 
sprawdzić samemu :) 

Kolejne trzy linijki to polecenia preprocesora. 

#define F_CPU 1000000L 
#include <avr/io.h> 
#include <util/delay.h>

Polecenia preprocesora zaczynają się znakiem hash "#". 
Preprocesor języka C przeprowadza rozmaite operacje na tekście 
programu jeszcze przed rozpoczęciem "właściwej" kompilacji 
programu. Na przykład pierwsze z tych trzech poleceń zmienia w 
tekście programu wszystkie wystąpienia ciągu znaków "F_CPU" na 
"1000000L", jak opcja "replace" w edytorze teksu. Liczba ta jest 
częstotliwością taktowania mikrokontrolera podaną w Hz. Nowe 
układy atmega skonfigurowane są do pracy z wewnętrznym 
oscylatorem RC 1Mz, dla przykładowych programików z tej części 
kursu nie ma potrzeby tego zmieniać. Inne częstotliwości 
taktowania mikrokontrolera atmaga można wybrać programując 
odpowiednie fuse-bity, napiszę o tym w dalszej części kursu. Drugie 
polecenie dołącza (wkleja) do tekstu programu, w miejscu jego 
wystąpienia, zawartość pliku "include/avr/io.h"; podobnie trzecie 
polecenie dołącza zawartość pliku "include/util/delay.h" A co takiego 
zawierają te pliki? Są to pliki tekstowe, więc ich zawartość można 
przejrzeć w dowolnym edytorze tekstu. Plik "include/avr/io.h" 
zawiera definicje rejestrów, bitów i przerwań; zaś plik 
"include/util/delay.h" zawiera funkcje wstrzymujących działanie 
programu na zadany okres czasu. Temat preprocesora zostanie 
dalej w kursie szczegółowo omówiony. 

Następny fragment listingu to początek "głównej" funkcji programu. 

int

 main(

void


  

/* Tutaj wpisujemy instrukcje naszego programu */

 

 
}

Funkcja w języku C to najprościej mówiąc wydzielony z programu 
fragment kodu, wykonujący jakieś konkretne zadanie. 
Odpowiednikiem funkcji z C w innych językach programowania są 
procedura i podprogram. Każda funkcja posada własną nazwę, 
nadaną jej przy tworzeniu, posługując się nazwą funkcji można 
wielokrotnie, dowolnym miejscu programu, uruchamiać blok 
instrukcji zawarty w funkcji, tak jak by to była pojedyncza 
instrukcja. Programy podzielone są zwykle na wiele mniejszych 
funkcji, dzięki temu są bardziej czytelne i oszczędza się pamięć. 
Funkcje można tworzyć (definiować) samemu, a także korzysta się 
z gotowych funkcji z bibliotek dostarczonych wraz z kompilatorem. 
Każdy program w C musi zawierać funkcję o nazwie "main" (funkcja 
główna), ponieważ pierwsza instrukcja funkcji "main" jest 
jednocześnie pierwszą instrukcją całego programu. Przykładowe 
programy w tej części kursu będą zawierać jedynie funkcję "main", 
cały nasz kod będzie umieszczany w głównej funkcji, między 
nawiasami klamrowymi "{","}". 

Page 6 of 20

XYZ Hobby Robot-Kurs AVR-GCC, cz.2

17/04/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=2&pv

background image

W naszych prostych, przykładowych programach będzie można 
wyróżnić dwie sekcje, pierwsza to instrukcje wykonywane raz 
jeden, natychmiast po starcie programu, a druga sekcja to blok 
instrukcji wykonywany wielokrotnie w nieskończonej pętli. 
Nieskończoną pętle w programie można zbudować używając 
instrukcji "for" lub "while", w taki sposób, jak w przykładzie poniżej. 
W obu przypadkach instrukcje we wnętrzu pętli objęte są parą 
nawiasów klamrowych "{","}", podobnie jak zawartość całej funkcji 
głównej "main". Instrukcje tworzące pętle: "for" i "while" omówię 
szczegółowo w dalszej części kursu, ale już teraz będziemy je 
wykorzystywać w naszych przykładowych programach. 

int

 main(

void


  

/* Instrukcje wykonywane raz jeden po starcie programu */

 

 
  

/* Pętla nieskończona utworzona instrukcją "for" */

 

  

for

(;;) 

  {    
  

/* Instrukcje w nieskończonej pętli */

 

  } 
}

/* Pętla nieskończona utworzona instrukcją "while" */

 

  

while

(1) 

  {    
  

/* Instrukcje w nieskończonej pętli */

 

  }

Programujemy porty we/wy 

Jak pisałem, zaczniemy od naprawdę prostych przykładów. Pierwszy 
program będzie powodował miganie ośmiu diod LED przyłączonych 
do wyprowadzeń skojarzonych z portem D. Diody mają świecić się 
na przemian, raz parzyste, raz nieparzyste. Tak, jak pokazuje to 
animacja poniżej. 

/* przykład 2.1 "leds.c" */

 

/* 8 diod LED przłączonych do portu D */

 

/* ATmega 1MHz */

 

 

#define F_CPU 1000000L 
#include <avr/io.h> 
#include <util/delay.h> 

 

int

 main(

void

{    
   

/* Wszystkie linie portu D będą wyjściami */

 

   DDRD = 

0xFF

;  

/* 0xFF binarnie 1111 1111 */

 

 
   

/* Początek nieskończonej pętli */

 

   

while

(

1

   { 
      PORTD = 

0xaa

;    

/* 0xaa binarnie 1010 1010 */

 

      

/* opóźnienie 0.33 sek. */

 

      _delay_ms(

330

);  

       PORTD = 

0x55

;    

/* 0x55 binarnie 0101 0101 */

 

      

/* opóźnienie 0.33 sek. */

 

      _delay_ms(

330

); 

   } 

Listing 2.1

Page 7 of 20

XYZ Hobby Robot-Kurs AVR-GCC, cz.2

17/04/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=2&pv

background image

 

Efekt działania programu z listingu 2.1 

Pierwszą instrukcją (będę tłumaczył zaczynając od wnętrza funkcji 
głównej "main") ładujemy do rejestru DDRD wartość 0xff (postać 
szesnastkowa liczby), w ten sposób konfigurujemy wszystkie linie 
portu D jako wyjścia. 

/* Wszystkie linie portu D będą wyjściami */

 

   DDRD = 

0xFF

;  

/* 0xFF binarnie 1111 1111 */

Używając nazwy rejestru i operatora przypisania - znaku "=", 
można zapisać(odczytać) całą 8-bitową zawartość rejestru I/O, 
instrukcję przypisania należy zakończyć znakiem średnika, inaczej 
kompilacja zakończy się z błędem. Należy też pamiętać o dołączeniu 
do kodu plików z definicjami rejestrów I/O, wstawiając gdzieś na 
początku programu linię: 

#include <avr/io.h>

Inaczej kompilator nie rozpozna nazw rejestrów I/0 i przerwie 
kompilację wysyłając komunikat o wielu błędach. 

W tekście programu można umieszczać stałe liczby (tzw. literały) w 
postaci dziesiętnej, szesnastkowej oraz ósemkowej, natomiast 
standard języka C nie przewiduje możliwości zapisywania stałych w 
postaci dwójkowej. Postać szesnastkową liczby stałej tworzymy 
wstawiając przed liczbą parę znaków '0x' lub '0X' (np. 0xFF), stałe 
ósemkowe rozpoczynają się od cyfry "0" (np. 077), liczby w 
systemie dziesiątkowym wpisujemy zwyczajnie (np. 123). 

Kolejne dwie linie to początek nieskończonej pętli utowrzonej 
instrukcją "while". Wykonanie bloku instrukcji po "while(1)", 
objętych parą nawiasów klamrowych "{","}", będzie powtarzane, aż 
do momentu odłączenia zasilania lub resetu mikroprocesora 

/* Początek nieskończonej pętli */

 

while

(1) 

  {

Pierwsza instrukcja w pętli zapisuje do rejestru PORTD wartość 
0xAA ( binarnie 1010 1010), zaświecą się diody LED przyłączone do 
wyprowadzeń: PD1,PD3,PD5 i PD7; pozostałe diody będą 
wygaszone. 

      PORTD = 

0xaa

;    

/* 0xaa binarnie 1010 1010 */

Page 8 of 20

XYZ Hobby Robot-Kurs AVR-GCC, cz.2

17/04/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=2&pv

background image

Kolejna linijka wprowadza w tym miejscu programu opóźnienie ok 
0.33 sek. Na razie nie będę szczegółowo objaśniał tej instrukcji, bo 
nie jest dobrze tłumaczyć zbyt wielu rzeczach na raz. Będziemy 
wstawiać tę linię do naszych przykładów tam, gdzie potrzebne 
będzie opóźnienie, w kolejnej części kursu dokładnie wyjaśnię 
działanie tej instrukcji. Długość opóźnienia wpisujemy tam gdzie 
jest liczba 330, w tysięcznych częściach sekundy. 

     

/* opóźnienie 0.33 sek. */

 

       _delay_ms(

330

); 

W następnej linii do rejestru PORTD ładowana jest wartość 0x55
( dwójkowo 0101 0101), tym razem zaświecą się diody LED 
przyłączone do wyprowadzeń PD0,PD2,PD4 i PD6. I ponownie 
działanie programu zostanie wstrzymane na okres ok 0.33 sek. 

      PORTD = 

0x55

;    

/* 0x55 binarnie 0101 0101 */

 

      

/* opóźnienie 0.33 sek. */

 

      _delay_ms(

330

); 

Dalej, pierwszy nawias klamrowy kończy blok instrukcji 
wykonywanych w pętli, kolejny nawias oznacza koniec funkcji 
głównej "main". 

  } 

W pierwszym przykładzie zapisywana była cała zawartość rejestru 
PORTD, w kolejnym programie pokażę jak ustawiać i kasować 
poszczególne bity rejestrów. 

Zanim zacznę objaśniać program, to omówię operacje bitowe. 
Operacje bitowe działają na wartościach całkowitych i służą do 
manipulowania bitami. W języku C jest sześć operatorów 
bitowych:|,&,^,<<,>>,~. Operatory te są szczególnie użyteczne 
przy zabawie z bitami rejestrów, więc już teraz wyjaśnię ich 
działanie na przykładach. Oczywiście w przykładach liczby 
przedstawione są w postaci dwójkowej. 

operator "|" - bitowa alternatywa (OR) 
 
    0 1 0 1 0 1 0 1  

    0 0 1 1 0 0 1 1  

    0 1 1 1 0 1 1 1

operator "&" - bitowa koniunkcja (AND) 
 
    0 1 0 1 0 1 0 1 

    0 0 1 1 0 0 1 1  

    0 0 0 1 0 0 0 1

operator "^" - bitowa alternatywa wykluczająca (XOR) 
 
    0 1 0 1 0 1 0 1 

Page 9 of 20

XYZ Hobby Robot-Kurs AVR-GCC, cz.2

17/04/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=2&pv

background image

    0 0 1 1 0 0 1 1  

    0 1 1 0 0 1 1 0

operator "<<" - przesunięcie w lewo 
 1 0 0 1 1 0 0 1  << 3 =  1 1 0 0 1 0 0 0

operator ">>" - przesunięcie w prawo 
 1 0 0 1 1 0 0 1  >> 5 =  0 0 0 0 0 1 0 0

operator "~" - dopełnienie jedynkowe 
 ~1 0 0 1 1 0 0 1  =  0 1 1 0 0 1 1 0 

Drugi programik różni się niewiele od pierwszego, więc omówię 
tylko te linie gdzie, gdzie można znaleźć coś nowego. 

/* przykład 2.2 "leds2.c" */

 

/* 8 diod LED przłączonych do portu D */

 

/* ATmega 1MHz */

 

 

#define F_CPU 1000000L 
#include <avr/io.h> 
#include <util/delay.h> 

 

int

 main(

void


    

/* Wszystkie linie portu D będą wyjściami */

 

    DDRD  = 

0xFF

 
    

/* Początek nieskończonej pętli */

 

    

for

(;;) 

    { 
        PORTD = 

0x0f

;  

/* Ładuje do PORTD wartość 0x0f*/

 

        

/* opóźnienie 1 sek. */

 

         _delay_ms(

1000

); 

        PORTD |= 

0xf0

;  

/* ustawia bity nr. 4..7 */

 

         _delay_ms(

1000

); 

        PORTD &= 

0xaa

;  

/* zeruje bity nr. 0,2,4,6 */

 

         _delay_ms(

1000

); 

        PORTD ^= 

0x0f

;  

/* "odwraca" bity nr. 0..3 */

 

         _delay_ms(

1000

);     

        PORTD = 

0x00

        

/* opóźnienie 2 sek. */

 

         _delay_ms(

2000

);  

    } 
}

Listing 2.2

  

Efekt działania programu z listingu 2.2 

Page 10 of 20

XYZ Hobby Robot-Kurs AVR-GCC, cz.2

17/04/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=2&pv

background image

Wybrane bity rejestru można ustawić na wartość "1", bez zmiany 
wartości pozostałych bitów rejestru, używając operatora przypisania 
"|=", instrukcja przypisania kończy się średnikem. 

  PORTD |= 

0xf0

;  

/* ustawia bity nr. 0..3 */

 

Na zawartości rejestru PORTD wykonywana jest operacja bitowa OR 
z wartością stałej znajdującej się po prawej stronie operatora "|=", 
a następnie wynik operacji zapisywany jest do rejestru PORTD. 

PORTD    0 0 0 0 1 1 1 1 
            bitowe OR 
0xF0     1 1 1 1 0 0 0 0  
               = 
         1 1 1 1 1 1 1 1   -> PORTD 

W kodach źródłówych programów spotyka się następujący sposób 
ustawiania wybranych bitów rejestru. 

PORTC |= (1<<3)|(1<<5)|(1<<7);

W tym przykładzie, zostaną ustawione bity numer: 3,5,7 rejestru 
PORTC (bity w rejestrze numerowane są zaczynając od zera), 
pozostałe bity pozostaną niezmienione. Użyto operatorów bitowych: 
"|"- bitowe OR, "<<" - przesunięcie w lewo. 

 (1<<3)  = 0000 1000 
           bitowe OR 
 (1<<5)  = 0010 0000 
           bitowe OR 
 (1<<7)  = 1000 0000   
         ------------- 
           1010 1000

Wracamy do naszego programu. Wybrane bity rejestru można 
ustawiać na wartość "0", bez zmiany pozostałych bitów rejestru, 
używając instrukcji przypisania "&=" 

  PORTD &= 

0xaa

;  

/* zeruje bity nr. 0,2,4,6 */

 

Na zawartości rejestru PORTD wykonywana jest operacja bitowa 
AND z wartością stałej znajdującej się po prawej stronie operatora 
"&=", a następnie wynik operacji zapisywany jest do rejestru 
PORTD. 

PORTD    1 1 1 1 1 1 1 1 
            bitowe AND 
 
0xAA     1 0 1 0 1 0 1 0  
               = 
         1 0 1 0 1 0 1 0   -> PORTD 
 

Wybrane bity rejestru można "odwrócić", tzn. zmieniać wartość bitu 
"1" na "0", a "0" na "1", bez zmiany pozostałych bitów rejestru, 
używając instrukcji przypisania "^=" 

  PORTD ^= 

0x0f

;  

/* "odwraca" bity nr. 0..3 */

 

Page 11 of 20

XYZ Hobby Robot-Kurs AVR-GCC, cz.2

17/04/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=2&pv

background image

Na zawartości rejestru PORTD wykonywana jest operacja bitowa 
XOR z wartością stałej znajdującej się po prawej stronie operatora 
"^=", a następnie wynik operacji zapisywany jest do rejestru 
PORTD. 

PORTD    1 0 1 0 1 0 1 0 
            bitowe XOR 
0x0f     0 0 0 0 1 1 1 1  
               = 
 
         1 0 1 0 0 1 0 1   -> PORTD 

Trzeci przykładowy program pokazuje jak odczytać całą zawrtość 
rejestru

/* przykład 2.3 "leds3.c" */

 

/* 8 diod LED przłączonych do portu D */

 

/* 4 przyciski przyłączone do PC0..PC3 */

 

/* ATmega 1MHz */

 

 

#define F_CPU 1000000L 
#include <avr/io.h> 
#include <util/delay.h> 

 

int

 main(

void


    

/* Wszystkie linie portu D będą wyjściami */

 

 
    DDRD  = 

0xff

     
    

/* Linie portu C będą wejściami z podciągnięciem do VCC */

 

    DDRC  = 

0x00

 
    PORTC = 

0xff

;     

     
    

/* Początek nieskończonej pętli */

 

    

for

(;;) 

    { 
        

/* Przepisanie zawartości PINC do PORTD */

 

        PORTD = PINC; 
        

//PORTD = ~PINC & 0x0f; 

    } 
}

Listing 2.3

 

Efekt działania programu z listingu 2.3

Wpierw linie we/wy portu C zostają skonfigurowane jako wejścia 
podciągnięte do VCC, a linie portu D wyjścia. Dalej, w 

Page 12 of 20

XYZ Hobby Robot-Kurs AVR-GCC, cz.2

17/04/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=2&pv

background image

nieskończonej pętli, z użyciem operatora przypisania "=", zawartość 
rejestru PINC jest bezpośrednio przepisywana do rejestru PORTD. 

   

/* Przepisanie zawartości PINC do PORTD */

 

   PORTD = PINC;

Normalnie cztery pierwsze diody LED przyłączone do linii PD0..PD3 
będą się świecić, wciśnięcie któregoś z przycisków powoduje 
wygaszenie odpowiedniej diody LED. 

W kolejnych przykładach potrzebna będzie instrukcja "if-else" 
wykorzystywana do podejmowania decyzji. 

if

( wartość_logiczna PRAWDA/FAŁSZ ) 

 

/* Instrukcja wykonywana jeśli PRAWDA */

 

else

 

/* Instrukcja wykonywana jeśli FAŁSZ */

 

Część "else" , czyli instrukcje wykonywaną gdy FAŁSZ można 
pominąć. 

if

( wartość_logiczna PRAWDA/FAŁSZ ) 

/* Instrukcja wykonywana jeśli PRAWDA */

 

Obejmując fragment kodu parą klamrowych nawiasów "{","}" 
tworzymy tzw. blok instrukcji, blok w "if-else" jest traktowany jako 
pojedyncza instrukcja. 

if

( wartość_logiczna PRAWDA/FAŁSZ ) 

/* Blok instrukcji wykonywany jeśli PRAWDA */

 

else

 

/* Blok instrukcji wykonywany jeśli FAŁSZ */

 

Chcąc sprawdzić czy jeden lub grupa bitów jednocześnie w rejestrze 
I/0 ma wartość "1" można to zrobić z użyciem instrukcji "if" w 
następujący sposób: 

if

(REJESTR_I0 & 

MASKA


  

/* Blok instrukcji wykonywany jeśli warunek spełniony */

 

}

Gdzie "REJESTR_IO" to nazwa rejestru, a "MASKA" to stała wartość 
z ustawionymi tymi bitami, które potrzeba testować  
Przykłady: 

/* Jeśli bit nr. 3 rejestru PINC ma wartość "1" */

 

if

(PINC & 

0x08

){} 

 

/* Jeśli bity 0 lub 1 w PIND mają wartość "1" */

 

if

(PIND & 

0x03

){}

Na zawartości rejestru wykonywana jest operacja bitowa AND z 
wartością stałej stojącej po prawej stronie operatora "&". I jeśli 

Page 13 of 20

XYZ Hobby Robot-Kurs AVR-GCC, cz.2

17/04/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=2&pv

background image

wynik tej operacji będzie różnił się od zera, to całe wyrażenie w 
nawiasach okrągłych będzie uznane jako wartość logiczną 
"PRAWDA" i wykonane zostaną instrukcje objęte nawiasami 
klamrowymi po "if"; w przeciwnym przypadku instrukcje te zostaną 
pominięte. W języku C, jeżeli jakieś wyrażenie po obliczeniu jest 
różne od zera, to ma wartość logiczną "PRAWDA", a jeżeli 
wyrażenie jest równe zero, to ma wartość logiczną "FAŁSZ". 

A po co na wartości odczytanej z rejestru wykonywana jest bitową 
operację AND? Żeby przysłonić te bity rejestru, których wartości 
nas nie interesują. Przykład: 

Interesuje na stan bitu nr 3 rejestru  
 
REJESTR_I0   0 1 0 1  1 1 1 1 (0x5f) 

 

 

             bitowe AND 
             0 0 0 0  1 0 0 0 (0x08) 
----------------------------- 

 

    

          =  0 0 0 0  1 0 0 0 (0x08)   
 
 
REJESTR_I0   0 1 0 0  0 1 0 1 (0x45) 
             bitowe  AND 
             0 0 0 0  1 0 0 0 (0x08) 
----------------------------- 

 

    

          =  0 0 0 0  0 0 0 0 (0x00)

Jednak na schemacie przyciski przyłączono do portów we/wy uC w 
taki sposób, że przy wciśniętym przycisku odczytujemy z rejestru 
wartość bitu "0", a nie "1".Tak można sprawdzić czy jeden lub 
grupa bitów w rejestrze I/0 ma wartość "0": 

if

(!(REJESTR_IO & 

MASKA

)) 


  

/* Blok instrukcji wykonywany jeśli warunek spełniony */

 

}

Przykłady:

/* Jeśli bit nr. 0 rejestru PINC ma wartość "0" */

 

if

(!(PINC &

0x01

)){} 

/* Jeśli bit nr. 1 rejestru PINC ma wartość "0" */

 

if

(!(PINC & 

0x02

)){}  

/* Jeśli bit nr. 2 rejestru PINC ma wartość "0" */

 

if

(!(PINC & 

0x04

)){} 

/* Jeśli bit nr. 3 rejestru PINC ma wartość "0" */

 

if

(!(PINC & 

0x08

)){}

Tutaj bitowy iloczyn zawartości rejestru i stałej został dodatkowo 
wzięty w okrągłe nawiasy i wartość logiczna całości została 
zanegowana z użyciem operatora wykrzyknik "!". Znak wykrzyknik 
"!" jest w języku C logicznym operatorem negacji, zmienia wartość 
logiczną PRAWDA na FAŁSZ, A wartość FAŁSZ na PRAWDA. A jak 
pisałem przed chwilą, jeżeli jakieś wyrażenie po obliczeniu jest 
większe od zera to ma wartość logiczną "PRAWDA", a jeżeli równe 
zero to ma wartość logiczną "FAŁSZ". 

A jeśli potrzeba, aby blok instrukcji wykonywał się wielokrotnie w 
pętli, dopóki jeden lub grupa bitów jednocześnie w rejestrze I/0 ma 
wartość "1", to można to zrobić z użyciem instrukcji "while": 

while

(REJESTR_I0 & 

MASKA

Page 14 of 20

XYZ Hobby Robot-Kurs AVR-GCC, cz.2

17/04/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=2&pv

background image

  

/* Blok instrukcji wykonywany jeśli warunek spełniony */

 

}

A jeśli potrzeba, aby blok instrukcji wykonywał się wielokrotnie w 
pętli, dopóki jeden lub grupa bitów jednocześnie w rejestrze ma 
wartość "0", na przykład gdy wciśnięty jest przycisk: 

while

(!(REJESTR_IO & 

MASKA

)) 


  

/* Blok instrukcji wykonywany jeśli warunek spełniony */

 

}

Gdzie, jak poprzednio, "REJESTR_IO" to nazwa rejestru, a "MASKA" 
to stała wartość z ustawionymi tymi bitami, które potrzeba testować 

Instrukcje sterujące programem w języku C będą głównym 
tematem następnej, trzeciej części kursu. 

A to kolejny przykładowy program, działa w następujący sposób: 
Jeśli jest wciśnięty pierwszy przycisk (przyłączony do PC0), to 
święcą się diody LED przyłączone do PD4..PD7, pozostałe LED są 
zgaszone. Jeśli wciśnięty jest drugi przycisk ( przyłączony PC1), to 
świecą się cztery diody podłączone do PD0..PD3, pozostałe LED 
gasną. 

/* przykład 2.4 "leds4.c" */

 

/* 8 diod LED przłączonych do portu D */

 

/* 2 przyciski przyłączone do PC0,PC1 */

 

/* ATmega 1MHz */

 

 
 

#include <avr/io.h> 

 

int

 main(

void


  

/* Wszystkie linie portu D będą wyjściami */

 

  DDRD  = 

0xff

 
  

/* linie PC0,PC1 będą wejściami z podciągnięciem do VCC */

 

  DDRC  = 

0x00

  PORTC = 

0x03

 
  

/* Początek nieskończonej pętli */

 

  

while

(

1

  { 
    

/* Jeśli pierwszy przycisk wciśnięty */

 

    

if

(!(PINC & 

0x01

)) PORTD = 

0xf0

   
    

/* Jeśli drugi przycisk wciśnięty */

 

    

if

(!(PINC & 

0x02

)) PORTD = 

0x0f

  } 
}

Listing 2.4

Page 15 of 20

XYZ Hobby Robot-Kurs AVR-GCC, cz.2

17/04/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=2&pv

background image

 

Efekt działania programu z listingu 2.4 

Omówię tylko te fragmenty listingu, w których jest coś nowego. 

Linia programu poniżej testuje czy został wciśnięty przycisk 
przyłączony do PC0. Sprawdzane jest czy odpowiadający 
przyciskowi bit w rejestrze PINC ma wartość "0", jeśli tak to 
wykonuje się instrukcję wpisującą do rejestru PORTD wartość 0xf0. 

  

/* Jeśli pierwszy przycisk wciśnięty */

 

    

if

(!(PINC & 

0x01

)) PORTD = 

0xf0

;

Podobnie w kolejnej linii programu sprawdzany jest stan drugiego 
przycisku, podpietego do PC1; jeżeli jest wciśnięty, to do rejestru 
PORTD ładowana jest wartość 0x0f; 

  

/* Jeśli drugi przycisk wciśnięty */

 

    

if

(!(PINC & 

0x02

)) PORTD = 

0x0f

;

Odczytując stan przycisków podłączonych do portów uC napotyka 
się kłopotliwy efekt drgających styków przycisku. W momentach 
wciśnięcia i zwolnienia przycisku dostajemy na wejściu uC serie 
impulsów, co "źle" napisany program może błędnie zinterpretować 
jako wielokrotne wciśnięcie i zwolnienie przycisku. 

  

Drgania na stykach przycisku 

Prostym i skutecznym sposobem ominięcia problemu drgających 
styków przycisku jest przeczekanie drgań. Po wykryciu wciśnięcia 
lub zwolnienia przycisku program może wstrzymać dalsze działanie 
na okres nieco dłuższy, niż spodziewany czas zaniku drgań styków. 
Pokaże to na kolejnym przykładzie, listing poniżej. 

Page 16 of 20

XYZ Hobby Robot-Kurs AVR-GCC, cz.2

17/04/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=2&pv

background image

Kolejny program będzie zliczał każde wciśnięcie przycisku 
przyłączonego do PC0, a aktualny stan tego licznika będzie 
wyświetlany na ośmiu diodach LEd przyłączonych do portu D. 

/* przykład 2.5 "leds5.c" */

 

/* 8 diod LED przłączonych do portu D */

 

/* przycisk przyłączony do PC0 */

 

 
 
 
 
 

/* ATmega 1MHz */

 

 

#define F_CPU 1000000L 
#include <avr/io.h> 
#include <util/delay.h> 

 

int

 main(

void


  

/* Wszystkie linie portu D będą wyjściami */

 

  DDRD  = 

0xff

  

/* Linia PC0 będzie wejściem z podciągnięciem do VCC */

 

  DDRC  = 

0x00

  PORTC = 

0x01

  

/* Początek nieskończonej pętli */

 

  

while

(

1

  { 
    

/* Jeśli pierwszy przycisk wciśnięty */

 

    

if

(!(PINC & 

0x01

)) 

    { 
    

 /* Zwiększenie stanu licznika o 1 */

 

      PORTD +=

1

      

/* opóżnienie aż drgania na stykach ustaną */

 

       _delay_ms(

80

); 

      

/* oczekiwanie na zwolnienie przycisku */

 

      

while

(!(PINC & 

0x01

)) {} 

      

/* opóżnienie aż drgania na stykach ustaną */

 

       _delay_ms(

80

); 

    } 
  } 
}

Listing 2.5

 

Efekt działania programu z listingu 2.5

Tutaj, w pętli sprawdzany jest stan przycisku, jeśli jest wciśnięty, to 
wykonywany jest blok instrukcji po "if" objęty parą nawiasów 
klamrowych, w przeciwnym przypadku stan przycisku testowany 
jest ponownie. 

Page 17 of 20

XYZ Hobby Robot-Kurs AVR-GCC, cz.2

17/04/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=2&pv

background image

    

/* Jeśli pierwszy przycisk wciśnięty */

 

    

if

(!(PINC & 

0x01

)) 

    { 

To kolejna postać operatora przypisania "+=", jak można się 
domyśleć, do rejestru PORTD zapisywana jest aktualna jego 
zawartość powiększona o wartość stałej stojącej po prawej stronie 
operatora "+=", w tym przypadku o 1. 

      

/* Zwiększenie stanu licznika o 1 */

 

       PORTD +=

1

;

Wstrzymanie działania programu na spodziewany czas wygaśnięcia 
drgań styków przycisku, czas wstrzymania programu dobrałem 
doświadczalnie. 

      

/* Opóżnienie aż drgania na stykach ustaną */

 

       _delay_ms(

80

);

Po ustaniu drgań styków, w następnej instrukcji, program testuje 
czy przycisk został zwolniony. W tym miejscu program zostanie 
uwięziony w wewnętrznej (zagnieżdżonej) pętli, wykonanej też z 
pomocą "while", do momentu zwolnienia przycisku. Po "while()", 
między nawiasami klamrowymi, jest pusto, program w pętli będzi 
"robił nic", zatrzma się w tym miejscu do chwili zwolnienia 
przycisku.

      

/* oczekiwanie na zwolnienie przycisku */

 

      

while

(!(PINC & 

0x01

)) {}

Ponowne wstrzymanie działania programu, aby wygasły drgania 
styków przycisku. 

      

/* Opóżnienie aż drgania na stykach ustaną */

 

       _delay_ms(

80

);

Nawias klamrowy zamyka nieskończoną pętlę 

     }

I to już wszystko w tej części kursu. Na zadanie domowe proponuje 
uruchomić program z listingu poniżej i samodzielnie przanalizować 
jego działanie. Jeśli znajdzie się coś w tym kodzie, o czym nie było 
jeszcze mowy, to porszę poszukać odpowiedzi swojej książce do 
języka C bądź w Internecie. 

/* przykład 2.6 "leds6.c */

 

/* 8 diod LED przłączonych do portu D */

 

/* 2 przycisk przyłączone do PC0,PC1 */

 

/* Buzzer z generatorem przyłączony do PB1*/

 

/* ATmega 1MHz */

 

 

#define F_CPU 1000000L 
#include <avr/io.h> 
#include <util/delay.h> 

 

int

 main(

void


  

/* Wszystkie linie portu D będą wyjściami */

 

Page 18 of 20

XYZ Hobby Robot-Kurs AVR-GCC, cz.2

17/04/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=2&pv

background image

  DDRD = 

0xff

  

/* PB1 wyjście - buzzer z generatorem */

 

  DDRB = 

0x02

  

/* PC0,PC1 będą wejściami z podciągnięciem do VCC */

 

  DDRC  = 

0x00

  PORTC = 

0x03

   
  

/* Początek nieskończonej pętli */

 

  

while

(

1

  { 
    

/* Jeśli pierwszy przycisk wciśnięty */

 

    

if

(!(PINC & 

0x01

)) 

    { 
      

if

(!PORTD) 

      { 
      

/* Krótki sygnał dźwiękowy */

 

        PORTB |= 

0x02

        _delay_ms(

100

); 

        PORTB &= ~

0x02

      } 
      

else

 

       

/* Wygasza jedną diodę LED */

 

        PORTD >>= 

1

 
      

/* Opóżnienie, aż drgania na stykach przycisku ustaną */

 

       _delay_ms(

80

); 

      

/* Oczekiwanie na zwolnienie przycisku */

 

      

while

(!(PINC & 

0x01

)) {} 

      

/* Opóżnienie, aż drgania na stykach przycisku ustaną */

 

       _delay_ms(

80

); 

    } 
    

/* Jeśli drugi przycisk wciśnięty */

 

    

if

(!(PINC & 

0x02

)) 

    { 
      

if

(PORTD & 

0X80

      { 
      

/* Krótki sygnał dźwiękowy */

 

        PORTB |= 

0x02

        _delay_ms(

100

); 

        PORTB &= ~

0x02

      } 
      

else

 

      { 
       

/* Zapala jedną diodę LED */

 

        PORTD <<= 

1

        PORTD |= 

1

      } 
      

/* Opóźnienie, aż drgania na stykach przycisku ustaną */

 

      _delay_ms(

80

); 

      

/* Oczekiwanie na zwolnienie przycisku */

 

      

while

(!(PINC & 

0x02

)) {} 

      

/* Opóżnienie, aż drgania na stykach przycisku ustaną */

 

      _delay_ms(

80

); 

    } 
  } 
}

 

Efekt działania programu z listingu 2.6 

Page 19 of 20

XYZ Hobby Robot-Kurs AVR-GCC, cz.2

17/04/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=2&pv

background image

W następnej części kursu... 

Tematem kolejnej części kursu będą: zmienne i stałe liczbowe, 
operatory oraz instrukcje sterujące programem. Dalej będziemy się 
bawić diodami LED, brzęczykiem i przyciskami. 

Kurs AVR-GCC cz.3

 

 

Copyright © 2009-2010 ABXYZ - Wszelkie prawa zastrzeżone 

Page 20 of 20

XYZ Hobby Robot-Kurs AVR-GCC, cz.2

17/04/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=2&pv