5 prostych projektów AVR w C część 1

5 prostych projektów AVR w C część 1

Witam!

Ostatnio postanowiłem trochę odejść od programowania mikrokontrolerów w Arduino IDE i nauczyć się programować je w prawie czystym C / C++.

Ponieważ jednak najlepiej uczyć się przez doświadczenia więc stworzyłem kilka prostych projektów bazujących na mikrokontrolerze ATtiny 45.

Pisząc ten artykuł stwierdziłem, że mogę przy okazji wyjaśnić jak one działają :p więc krótki artykuł który zmierzałem napisać zmienił się w nieplanowany  tutorial z programowania mikrokontrolerów w C.

Lista potrzebnych rzeczy

Do zaprogramowania Attiny 45 i innych mikrokontrolerów z rodziny AVR będą potrzebne :

Oprogramowanie :

  • IDE do programowania : Ja użyłem darmowego Code::Blocks (16.01).
  • Kompilator : AVR-gcc.
  • Oprogramowania do obsługi programatora : avrdude.
  • Pakiet AVR-libc

Sprzęt :

  • Programator USBASP
  • Płytka stykowa
  • Kable stykowe
  • Multimetr

Części elektroniczne :

  • Attiny45 P-PU
  • 1 kondensator 100 uF
  • 2 kondensatory 100 nF
  • 1 rezystor 10 K
  • 4 rezystory 220 omów
  • 1 Tranzystor BD911
  • 1 Głośnik

Pierwsze kroki

Na początek należy zainstalować potrzebne oprogramowanie.

Proces instalacji zależy od posiadanego systemu operacyjnego. Instrukcje instalacji poszczególnych narzędzi są szeroko dostępne w internecie więc nie będę ich tutaj objaśniał. Na systemie Ubuntu który posiadam, instalacja sprowadza się do zainstalowania pakietów przez menager apt oraz codeblocks z PPA.

Następnym krokiem jest zbudowanie podstawowego układu umożliwiającego programowanie mikrokontrolera.

Schemat :

Schemat jest bardzo prosty.

Właściwie jest to sam mikrokontroler podłączony do programatora.

Pin reset jest podciągnięty do zasilania przez rezystor 10 K (Jest on na tyle duży, że nie powoduje problemów przy programowaniu).

Zasilanie natomiast jest filtrowane jednym kondensatorem 100 nF i jednym 100 uF.

Poniżej zamieszczam rozmieszczenie pinów w standardowym gnieździe programatora, jednak może ono być różne w zależności od programatora.

UWAGA! W przypadku niskich częstotliwości zegara mikrokontrolera należy włożyć do programatora zworkę SLOW SCK (niska częstotliwość zegara). 

Należy to również zrobić w przypadku pierwszego programowania mikrokontrolera. 

Teraz pora na test czy wszystko dobrze podłączyliśmy.

W tym celu wchodzimy w konsolę i wydajemy komendę

Jeśli w odpowiedzi otrzymamy :

To oznacza, że wszystko dobrze podłączyliśmy i można przejść dalej. Jeśli pokazało się coś innego to najprawdopodobniej coś nie gra z połączeniem między mikrokontrolerem a programatorem lub coś jest nie tak z konfiguracją programatora.

Fusebity

Fusebity to bity które odpowiadają za ustawienia mikrokontrolera. Są one niezwykle istotne a ich niewłaściwie ustawienie może doprowadzić do uceglenia mikrokontrolera.

Fusebity znajdują się w 3 bajtach. Te bajty to wysoki, niski i rozszerzony. 

Każdy z 8 bitów w danym bajcie odpowiada za dane ustawienie mikrokontrolera.

Tabele fusebitów wraz z ustawieniami fabrycznymi :

WAŻNA INFORMACJA: Dana funkcja jest aktywna gdy fusebit jest zaprogramowany co następuje gdy jego wartość to 0!!! a nie 1 jak podpowiada intuicja.

Role fusebitów :

W bajcie LOW mamy fusebity odpowiedzialne za zegar mikrokontrolera.

  • CKDIV8 – Gdy ustawiony powoduje podzielenie częstotliwości zegara przez 8.
  • CKOUT – Gdy ustawiony powoduje wyprowadzenie sygnału zegara na pinie PORTB4 (Pin nr3).
  • SUT1 – Bit odpowiedzialny za ustawianie czasu startu zegara.
  • SUT0 – Bit odpowiedzialny za ustawianie czasu startu zegara.
  • CKSEL3 – Bit odpowiedzialny za ustawianie źródła zegara.
  • CKSEL2 – Bit odpowiedzialny za ustawianie źródła zegara.
  • CKSEL1 – Bit odpowiedzialny za ustawianie źródła zegara.
  • CKSEL0 – Bit odpowiedzialny za ustawianie źródła zegara.

Więcej o ustawianiu zegara w następnym rozdziale.

W bajcie HIGH mamy fusebity odpowiedzialne za generalne ustawienia mikrokontrolera.

  • RSTDISBL – Gdy ustawiony wyłącza sprzętowy pin reset mikrokontrolera.
  • DWEN – Gdy ustawiony włącza sprzętowy pin do debugowania.
  • SPIEN – Włącza lub wyłącza możliwość ściągania programu z mikrokontrolera.
  • WDTON – Gdy ustawiony włącza Watchdoga na stałe (nie da się go wyłączyć programowo).
  • EESAVE – Gdy ustawiony zachowuje pamięć EEPROM podczas przeprogramowywania mikrokontrolera.
  • BODLEVEL2 – Bit odpowiedzialny za ustawianie resetu przy zbyt niskim napięciu zasilania.
  • BODLEVEL1 – Bit odpowiedzialny za ustawianie resetu przy zbyt niskim napięciu zasilania.
  • BODLEVEL0 – Bit odpowiedzialny za ustawianie resetu przy zbyt niskim napięciu zasilania.

W bajcie EXTENDED mamy fusebity odpowiedzialne za funkcje specjalne mikrokontrolera.

  • SELFPRGEN – Gdy ustawiony włącza funkcję samoprogramowania.

Ustawianie fusebitów :

Aby ustawić fusebity należy wydać komendę :

lfuse to niski bajt, hfuse to wysoki bajt, efuse to rozszerzony bajt.

W miejsce (wartość) należy wpisać poszczególne bity w danym bajcie zaczynając od 7 a kończąc na 0.

Można również zaprogramować jeden bajt nie ruszając pozostałych. Wystarczy wpisać tylko ten dany argument -U.

Ustawienia zegara ( ciąg dalszy fusebitów)

Jak wynika z dokumentacji do dyspozycji mamy aż 6 różnych źródeł sygnału zegarowego.

  • Zewnętrzny sygnał zegarowy –  Sygnał prostokątny podawany z zewnętrznego generatora. Sygnał musi być podany do pinu CLKI.
  • Zewnętrzny oscylator – Zewnętrzny oscylator kwarcowy lub ceramiczny.
  • PLL – Źródło zegara o wysokiej częstotliwości.
  • Wewnętrzny oscylator 8 MHz / 6.4 MHz.
  • Oscylator RC 128 kHz.
  • Zewnętrzny oscylator  32.768 kHz.

Źródło zegara ustawia się za pomocą bitów CKSEL0, CKSEL1, CKSEL2, CKSEL3.

Tabela przedstawiająca ustawienia źródła zegara w zależności od ustawień tych bitów :

Dwa kolejne bity odpowiedzialne za kontrolę zegara to SUT0 i SUT1. Są odpowiedzialne za kontrolę czasu uruchamiania zegara. Ma to na celu ustabilizowania się zegara przed rozpoczęciem pracy mikrokontrolera.

UWAGA! Bity w tabelkach są podane w kolejności od najwyższego numeru do najniższego numeru.

Zewnętrzne źródło zegarowe 

Aby wybrać zewnętrzne źródło zegarowe należy ustawić wszystkie bity CKSEL na 0. Sygnał zegarowy musi być podany do pinu CLKI. Stan niski sygnału powinien być równy GND a wysoki VCC.

Tabela ustawień bitów SUT0 i SUT1 dla zewnętrznego źródła zegara.

Notatka1 : BOD ( Brown Out Detection) to system który utrzymuje mikrokontroler w stanie resetu kiedy napięcie zasilania jest mniejsze niż być powinno. Można ustawić próg poniżej którego mikrokontroler zostanie wprowadzony w stan resetu, zapobiegnie to nieprawidłowej pracy układu w przypadku awarii zasilania. 

Notatka 2 : CK to cykle zegara systemowego.

Pierwsze ustawienie powoduje że podczas resetu mikrokontroler poczeka 14 cykli zegara po czym zacznie pracować. Po wyjściu z trybu uśpienia odstęp to zawsze 6 cykli zegara.

W drugim ustawieniu mikrokontroler poczeka 14 cykli zegara + 4 ms zanim zacznie pracować.

W trzecim ustawieniu mikrokontroler poczeka 14 cykli zegara +64 ms zanim zacznie pracować. Jest  to najbezpieczniejsze ustawienie i warto je ustawić o ile nie jest wymagany szybki start mikrokontrolera.

Opóźnienie to ma na celu ustabilizowania sygnału zegarowego przed uruchomieniem mikrokontrolera. Zapobiega to błędnej pracy mikrokontrolera.

Zewnętrzny oscylator

Przy zewnętrznym oscylatorze sprawa nieco się komplikuje. Dla różnych częstotliwości rezonatorów mamy bowiem różne ustawienia bitów CKSEL i różne zalecane wartości kondensatorów. Rezonator powienien być podłączony pomiędzy piny XTAL1 i XTAL2, kondensatory powinny być połączone pomiędzy tymi dwoma pinami a masą.

Tabelka :

I tak jak mówi notatka pod tabelką, pierwszy tryb powinien być używany tylko przy rezonatorze ceramicznym.

Tabelka z ustawieniami czasów uruchamiania zegara:

Najbezpieczniejsza opcja to opcja ostatnia, zajmuje jednak ona najwięcej czasu.

Zewnętrzny oscylator  32.768 kHz.

Kiedy potrzebne jest niskie zużycie energii a prędkość wykonywania operacji jest nieistotna, można rozważyć taktowanie mikrokontrolera z oscylatora 32.768 kHz (standardowy oscylator zegarowy).

Aby to zrobić należy ustawić fusebity CKSEL na 0110.

Tabelka z ustawieniami czasu startu zegara. Standardowo ostatnie ustawienie zapewnia największą stabilność zegara w momencie uruchomienia procesora.

Warto również wiedzieć, że nie są wymagane żadne zewnętrzne kondensatory ponieważ ustawienie fusebitów CKSEL automatycznie podłączy do pinów XTAL1 i XTAL2 kondensatory o wartościach :

Wewnętrzny oscylator RC 128 kHz.

Kolejną opcją jest taktowanie z oscylatora RC o częstotliwości 128 kHz.

Aby to zrobić należy ustawić bity CKSEL na 0100.

Tabelka ustawień czasu uruchamiania oscylatora :

Wewnętrzny oscylator 8 MHz / 6.4 MHz.

Kolejnym źródłem sygnału zegarowego jest wewnętrzny oscylator który może pracować na częstotliwości 8 MHz i 6.4 MHz.

Jednak jak informuje notatka pod tabelą, ustawienie częstotliwości 6.4 MHz spowoduje uruchomienie trybu kompatybilności z ATtiny15 co spowoduje podzielenie częstotliwości przez 4 w wyniku czego finalna częstotliwość będzie wynosiła 1.6 MHz.

Domyślnie ustawiona jest opcja 8 MHz ( ustawiony jest też bit CKDIV8 w wyniku czego domyślna częstotliwość pracy to 1 MHz).

W tym wypadku częstotliwość można wybrać ustawiając odpowiednie bity CKSEL :

Ustawienia czasu startu zegara dla tego oscylatora :

PLL – Źródło zegara o wysokiej częstotliwości.

PLL to układ który umożliwia przeskalowanie częstotliwości z danego generatora w górę lub w dół.

Dla domyślnego ustawienie PLL i ustawienia  CKSEL na : 0001, częstotliwość zegara wynosi 16 MHz.

Tabelka z czasem uruchamiania zegara :

Więcej informacji na temat ustawień zegara i innych rzeczy można również znaleźć w dokumentacji mikrokontrolera : http://www.atmel.com/images/atmel-2586-avr-8-bit-microcontroller-attiny25-attiny45-attiny85_datasheet.pdf

Dokładność zegarów na różnych ustawieniach.

Ponieważ jestem posiadaczem oscyloskopu, postanowiłem sprawdzić jaka jest stabilność i dokładność sygnału zegarowego przy różnych ustawieniach. W tym celu zaprogramowałem fusebit CKOUT wyprowadzając tym samym sygnał zegarowy na pin 3. Następnie podłączyłem sondę oscyloskopu do tego pinu i przeprowadziłem pomiary. Pomiary zostały przeprowadzone w temperaturze pokojowej i napięciu zasilania równemu 5.2V Dla każdego ustawienia zastosowałem najwyższy czas rozruchu zegara. Pomiary dotyczyły oscylatorów wewnętrznych. Warto również wspomnieć o tym, że wszystkie pomiary były dokonywane przy stałej temperaturze. Przy temperaturze innej niż pokojowa, odchyłki będą z pewnością większe.

Domyślne ustawienia.

W domyślnych ustawieniach mamy ustawiony mikrokontroler korzysta z oscylatora 8 MHz, sygnał jest dzielony na 8 więc w efekcie częstotliwość powinna wynosić 1 MHz.

W praktyce :

Przy domyślnych ustawieniach częstotliwość wynosi  ok 1.093 MHz i waha się ok +- 0.02 MHz.

Oscylator 8 MHz

W teorii częstotliwość powinna wynosić 8 MHz.

W praktyce :

Tutaj wynik jest trochę gorszy, częstotliwość wynosi ok 8.256 MHz i waha się ok +-0.05 MHz.

Oscylator 6.4 MHz ( 1.4 MHz dzięki trybowi kompatybilności z Attiny 15)

W teorii częstotliwość powinna wynosić 1.4 MHz.

W praktyce :

Częstotliwość w praktyce wynosi ok 1.704 MHz i waha się ok +- 0.001 MHz

Wewnętrzny oscylator RC 128 KHz.

Częstotliwość powinna wynosić 128 kHz.

W praktyce :

Częstotliwość w praktyce wynosi 136.095 KH i waha się ok +-0.08  KHz.

PLL – Źródło zegara o wysokiej częstotliwości w trybie 16 MHz

Częstotliwość powinna wynosić 16 MHz.

W praktyce :

W praktyce częstotliwość wynosi 16.217 MHz a zmiany to aż ok +- 0.2 MHz.


Tworzenie nowego projektu AVR c++ w code::blocks

Ostatnią rzeczą jaką musimy zrobić przed rozpoczęciem tworzenia projektów jest przygotowanie posiadanego IDE do kompilowania kodu dla mikrokontrolerów AVR.

W posiadanym przez mnie Code::blocks (16.01) należy najpierw utworzyć nowy projekt AVR.

W tym celu uruchamiamy program.

A następnie klikamy Create New Project.

Wybieramy AVR project i klikamy Go.

Teraz musimy wybrać lokalizację folderu projektu oraz jego nazwę.

Następnym krokiem jest wybranie kompilatora ( GNU GCC Compiler for AVR) oraz wybranie lokalizacji dla plików wynikowych kompilacji (lepiej nic tu nie zmieniać).

W ostatnim kroku musimy ustawić opcje projektu. Wybieramy posiadany mikrokontroler z listy, następnie musimy ustawić prędkość pracy procesora ( w Hz) i ustawić opcje dodatkowe. Z opcji dodatkowych na tym etapie wystarczy nam opcja “create hex files” która przetworzy pliki wynikowe kompilacji w plik .hex który będziemy mogli wgrać do pamięci mikrokontrolera.

Po zatwierdzeniu ustawień, po prawej stronie powinniśmy zobaczyć drzewo katalogów naszego projektu wraz z plikiem main.c . To właśnie w nim znajduje się główny kod każdego programu.

Jak widać domyślnie dołączony jest nagłówek io.h która podstawowe funkcje i definicje.  (Na przykład adresy rejestrów).

Na koniec musimy jeszcze ustawić kilka opcji kompilacji projektu.

W tym celu wchodzimy w Project -> Build options.

Na tym etapie powinienem wyjaśnić, że projekt można skompilować do dwóch “celów kompilacji” pierwszy z nich to debug który z reguły służy do kompilacji “na brudno”.  Drugi cel to Relase który w założeniu ma być kodem skompilowanym “na czysto”. Opcje kompilacji można ustawiać niezależnie dla obu celów.

Tak więc wchodzimy w opcje kompilacji w projektu i w obu celach kompilacji : zaznaczamy opcję Enable all common compiler warnings oraz opcję Enable extra compiler warnings. Te dwie opcje spowodują, że kompilator będzie wyświetlał ostrzeżenie w przypadku gdy coś w kodzie jest potencjalnie nie tak. Ostrzeżenia dotyczą błędów które nie powodują niespkompilowania kodu.

Dodatkowo w obu celach powinniśmy wybrać AVR CPU architecture deriviates na Attiny45.

Pozwoli to zoptymalizować program pod kątem konkretnego procesora.

Wgrywanie pliku .hex do mikrokontrolera 

Przy okazji konfiguracji IDE powinienem jeszcze wyjaśnić jak należy wgrywać skompilowany program do mikrokontrolera.

Kiedy skompilujemy kod, w lokalizaji bin -> (cel kompilacji ) powinien pojawić się plik z rozszerzeniem .hex ( i kilka innych plików zależnie od ustawień projektu).

Zawiera on skompilowany, gotowy do wgrania program.

Aby go wgrać za pomocą programatora należy skorzystać z AVRDUDE wydając komendę :

W odpowiedzi powinniśmy uzyskać :

Jeśli tak to znaczy, że nasz program właśnie został wgrany do pamięci mikrokontrolera.

Lista komend programu AVRDUDE jest dostępna po wpisaniu komendy : avrdude


Na tym kończy się przydługi wstęp.

Pora na właściwe projekty ! (:


1 Migająca dioda LED

Migająca dioda LED to jeden z najprostszych możliwych projektów na mikrokontrolerze AVR.

Schemat elektroniczny płytki do migania diodą LED :

Jak widać jest to po prostu schemat do programowania z małą modyfikacją : Do pinu 5 dołączona jest dioda LED.

Aby zaprogramować miganie diodą LED musimy wiedzieć 3 rzeczy :

  • Jak ustawić pin jako wyjście ?
  • Jak ustawić stan wysoki i niski na pinie mikrokontrolera.
  • Jak dodać opóźnienie czasowe.

Zacznijmy od pierwszego punktu.

W mikrokontrolerach wejścia i wyjścia są pogrupowane w tak zwane porty, w ATtiny25 / 45 / 85 jest tylko jeden port – PORTB zawierający 5 wejść/wyjść cyfrowych.

Do ustawiania czy dany pin pracuje jako wejście czy wyjście służy rejestr DDRB.

Jak widać ponieważ pinów cyfrowych jest 5, bity 7 i 6 nie są używane.

Każdy bit rejestru odpowiada jednemu pinowi np.: bit 5 pinowi odpowiadającemu PORTB5, bit 4 pinowi odpowiadającemu PORTB4 itd.

Każdy bit można ustawić na 0 albo 1. Jeśli bit odpowiadającemu pinowi jest ustawiony na wartość 1, wtedy pin jest skonfigurowany jako wyjście. Jeśli bit odpowiadający pinowi ma wartość 0 wtedy pin jest skonfigurowany jako wejście.

Ustawianie stanu rejestru w programie jest bardzo proste, wystarczy dodać do programu linijkę :

Ustawia ona stan rejestru DDRB na 00000001 co odpowiada wyjściu na pinie PORTB0.

Teraz program wygląda tak :

Na tym etapie mamy ustawiony pin PORTB0 jako wyjście.

Teraz pora ustawić stan pinu. Do tego służy rejestr PORTB.

Jeśli bit odpowiadającemu pinowi jest ustawiony na wartość 1 to wtedy na pinie jest stan wysoki. W przeciwnym wypadku, na pinie jest stan niski.

Ustawianie stanu bitów rejestru PORTB odbywa się podobnie jak w przypadku rejestru DDRB.

Kod wzbogacony o przełączanie stanu pinu PORTB0 naprzemiennie na stan wysoki i niski :

Jednak do pełnego migania diodą brakuje jeszcze jednego składnika – opóźnień.

Po wgraniu tego kodu do mikrokontrolera, dioda LED podłączona do pinu PORTB0 zapali się odrobinę, jednak zdecydowanie nie będzie migać.

Przyczyną jest fakt, że podany kod nie ma opóźnień więc dioda włącza się i wyłącza tak szybko jak pozwala na to czas wykonywania instrukcji. A więc z częstotliwością ok 418.8 kHz.

Do realizacji opóźnień służy biblioteka delay.h .

W dokumentacji biblioteki jest również informacja o tym, że należy zdefiniować szybkość procesora :

(wartość podana w Hz )

Jednak codeblocks automatycznie dodaje tą informację do kodu więc w tym wypadku nie jest to konieczne.

Do poprawnego działania biblioteki należy również dodać -ffreestanding do opcji kompilatora.

Aby to zrobić należy wejść w project->build options ->other compiler options

I dodać linijkę -ffreestanding.

Po wykonaniu tego kroku możemy już spokojnie używać funkcji tej biblioteki w naszym kodzie.

Biblioteka delay.h zawiera dwie funkcje :

  • _delay_ms();
  • _delay_us();

Obie funkcje czekają ilość czasu podaną w nawiasie. Obie funkcje przyjmują czas podany za pomocą zmiennej typu unsigned long (w najnowszej wersji AVRlibc).

Funkcja _delay_us() przyjmuje wartość podaną w mikrosekundach. Jak jest napisane w dokumentacji, maksymalna wielkość opóźnienia to 768 us podzielone przez częstotliwość pracy procesora w MHz. Po przekroczeniu tej wartości, automatycznie zostanie wywołana funkcja _delay_ms().

Funkcja _delay_ms() przyjmuje wartości w milisekundach. Maksymalna wartość opóźnienia to 262.14 ms podzielone przez częstotliwość pracy procesora w MHz.

Po przekroczeniu tej wartości rozdzielczość jest zmniejszana 10 razy. W tym trybie maksymalne opóźnienie to 4294967.295 ms/ na częstotliwość pracy zegara (W MHz).

UWAGA! Jak wynika z dokumentacji “the delay time must be an expression that is a known constant at compile-time “.

To znaczy, że jako argument obu tych funkcji nie można podać zmiennej tylko liczbę.

Dodatkowo do poprawnego działania tych funkcji należy włączyć optymalizację kodu (w project->build options (optimization) ). Najlepiej włączyć opcję Optimize generated code for speed. Jeśli natomiast brakuje nam miejsca, możemy włączyć opcję Optimize generated code for size.

Pora na finalny program do migania diodą :

Po wgraniu tego kodu dioda LED powinna mrugać z częstotliwością 1 raz na sekundę.

2 Miganie diodami led sterowane za pomocą przycisków

Schemat elektroniczny do tego projektu:

Podobnie jak projekt z migającą diodą, ten również bazuje na schemacie układu do programowania.

Z tym, że do pinów 5,6,7,2 są podłączone diody LED, natomiast do pinu 3, podłączony jest przycisk. Kondensator C3 ma za zadanie eliminować drgania styków.

Tym razem jednak cel będzie nieco bardziej skomplikowany. Zamiast jednej migającej diody, teraz są 4. Jest też przycisk sterujący. Ma on za zadanie kontrolować która dioda miga oraz czas migania. W założeniu ma działać to tak : Miga jedna dioda na raz. Krótkie naciśnięcie przycisku zmienia zmienia opóźnienie migania (Od 100 ms do 2000 ms w skoku co 100 ms). Długie naciśnięcie przełącza miganie między diodami LED.

Co będzie nam potrzebne do zaprogramowania tego projektu :

  • Ustawianie pinu jako wejście.
  • Odczytywanie stanu wejścia cyfrowego.
  • Operacje logiczne.
  • Przerwania.
  • Przerwania zegarowe.

Pierwszym krokiem jest ustawienie pinu PB4 jako wejście a następnie podłączenie go do zasilania  za pomocą wbudowanych rezystorów Pull Up.

Do tego służy wspomniany już w poprzednim projekcie rejestr DDRB.

Aby ustawić pin jako wejście należy ustawić odpowiadający mu bit rejestru na 0.

Jak wynika z dokumentacji. Ustawienie na takim pinie stanu wysokiego za pomocą rejestru PORTB skutkuje podłączeniem go do zasilania przez rezystory Pull Up zamiast bezpośrednio jak to ma miejsce w przypadku pinu ustawionego jako wyjście.

Oprócz pinu PB4 ustawionego jako wejście musimy również ustawić piny PB0, PB1, PB2 i PB3 jako wejścia.

Kod który to wszystko robi :

Teraz o tym jak odczytywać stan pinu.

Informacja o stanie pinów przechowywana jest w rejestrze PINB

Do odczytania poszczególnych bitów można użyć 2 makr z biblioteki io.h:

  • bit_is_set();
  • bit_is_clear();

Jako pierwszy argument w obu podajemy nazwę portu np. PINB, jako drugi argument numer bitu z jakiego chcemy odczytać dane.

funkcja bit_is_set() zwraca wartość 1 kiedy dany bit ma wartość 1. Funkcja bit_is_clear() zwraca wartość 1 kiedy dany bit ma wartość 0.

Przykład użycia :

Teraz pojawia się pewien problem, tworząc program będziemy musieli modyfikować stany pojedynczych LED  bez ruszania np. stanu pinu odpowiedzialnego za przycisk. Problem polega na tym, że dotychczasowa metoda : PORTB=0b….. wymaga podania jednocześnie wszystkich bitów rejestru. Okazuje się jednak, że możemy obejść ten problem stosując funkcje logiczne oraz operatory przesuwania bitów.

Na początek lista 4 podstawowych (najczęściej używanych) funkcji logicznych oraz ich symbolI w c :

  • AND – “&”
  • OR – “|”
  • NOT – “~”
  • XOR – “^”

Operator AND wykonuje operację iloczynu logicznego.

Przykład z użyciem dwóch rejestrów (8 bitów) :

01011110

01000111

– – – – – – – – – –

01000110

Jak widać każde (1 AND 1) daje 1, (1 AND 0) daje 0 , (0 AND 1) daje 0 i (0 AND 0) daje 0.

Operator OR wykonuje operację sumy logicznej.

Przykład z użyciem dwóch rejestrów :

01010001

01011000

– – – – – – – – – – –

01011001

Jak widać każde (1 OR 1) daje 1 (1 OR 0) daje 1, (0 OR 1) daje 1 i (0 OR 0) daje 0.

Funkcja NOT realizuje operację negacji.

Przykład z użyciem rejestru :

01001111

– – – – – – – – –

10110000

Jak widać każde (1 NOT) daje 0 i każde (0 NOT) daje 1.

Funkcja XOR realizuje operację alternatywy rozłącznej.

Przykład z użyciem dwóch rejestrów :

10110001

10001001

– – – – – – – –

00111000

Jak widać każde (1 XOR 1) daje 0, (1 XOR 0) daje 1, (0 XOR 1) daje 1 i (0 XOR 0) daje 0.

Operacje przesunięć działają tak:

Przesunięcia w prawo.

x>>y

Bit x zostaje przesunięty w prawo o y miejsc (licząc od lewego końca rejestru).

Przesunięcia w lewo.

Bit x zostaje przesunięty w lewo o y miejsc (licząc od prawo końca rejestru).

x<<y

Wykonywanie tych operacji w C++ wygląda tak:

AND

OR

XOR

NOT

Powyższe funkcje można wykorzystać do ustawiania stanu pojedynczych bitów rejestrów.

Na przykład :

Aby ustawić 1 na 2 bicie od prawej rejestru PORTB można wykorzystać funkcję OR oraz operator przesunięcie w lewo. Przykład :

(1<<1) daje w efekcie 00000010, bajt ten jest następnie zapisywany do PORTB z wykorzystaniem operacji OR. Co oznacza, że jeśli którykolwiek bit miał wartość 1 to po tej operacji nadal ją będzie miał ( 0 OR 1 to 1 ). Natomiast jeśli którykolwiek bit miał wartość 0 to również zostanie ona zachowana ( 0 OR 0 to 0). Zmianie ulegnie jedynie drugi bit od prawej (0 OR 1 to 1), oczywiście jeśli korespondujący bit w rejestrze PORTB będzie miał wartość 0, jeśli nie to pozostanie on taki sam ( 1 OR 1 to 1).

Jeśli natomiast chcielibyśmy ustawić ten sam bit na 0 to można do tego wykorzystać operację AND oraz NOT. Przykład :

Wynik operacji (1<<1) to nadal 00000010 jednak jest on poddawany operacji NOT więc wynik końcowy to 11111101, bajt ten jest następnie zapisywany do PORTB z wykorzystaniem operacji AND. Co oznacza, że jeśli któryś bit rejestru będzie miał wartość 1 to zostanie ona zachowana ( 1 AND 1 to 1), 0 również zostanie zachowane ( 0 AND 1 to 0 ). Zmianie ulegnie tylko 2 bit od prawej ( 1 AND 0 to 0) lub pozostanie taki sam jeśli jest zerem ( 0 AND 0 to 0 ).

Można również ustawić wiele bitów na raz z użyciem operacji OR. Przykład :

Wynik z (1<<1)|(1<<3)|(1<<5) to: 00101010.

Kolejna rzecz to przerwania.

Przerwanie to sygnał który powoduje, że procesor porzuca wykonywanie danego programu i na moment przeskakuje do fragmentu kodu powiązanego z danym przerwaniem (wektor przerwania). Sygnałem wywołującym przerwanie może być na przykład zmiana stanu pinu albo sygnał z wbudowanego timera. Lista dostępnych przerwań i nazw ich wektorów :

W tym projekcie będą potrzebne dwa rodzaje przerwań, przerwanie wywołane zmianą stanu pinu (do obsługi przycisku) oraz przerwanie zegarowe (do funkcji związanych z czasem).

Do odczytywania stanu pinu postanowiłem wykorzystać przerwanie PCINT0 (zmiana stanu pinu).

Natomiast do operacji związanych z zegarem postanowiłem wykorzystać wbudowany ośmiobitowy timer/licznik0 i komparator A.

Ale po kolei.

Pierwszą rzeczą jaką musimy zrobić jest ustawienie które piny mają wywoływać przerwanie.

Należy to zrobić za pomocą rejestru PCMSK :

Ustawienie 1 na danym bicie powoduje uruchomienie przerwań z danego pinu.

Numery przerwań odpowiadające poszczególnym pinom można znaleźć na schemacie wyprowadzeń mikrokontrolera :

Warto zauważyć, że poszczególne piny nie mają swoich własnych wektorów przerwań (wszystkie wywołują ten sam wektor PCINT) i to w roli użytkownika leży ustalenie z którego pinu pochodzi przerwanie.

Warto również zauważyć, że przerwania pozostają aktywne nawet jeśli pin jest ustawiony jako wyjście.

Następnym krokiem jest włączenie przerwań wywołanych zmianą pinu (PCINT) za pomocą rejestru GIMSK :

Ustawienie bitu  bitu 5 na 1 włącza i przerwania wywoływane zmianą stanu pinu.

Na koniec należy jeszcze włączyć przerwanie w ogóle za pomocą rejestru SREG :

Aby włączyć  przerwania  należy ustawić bit 7 na 1, aby wyłączyć przerwania należy ustawić bit 7 na 0.

Powinienem jeszcze powiedzieć o rejestrze GIFR który zawiera flagi przerwań.

Zawiera on dwie flagi przerwań

Bit 6 zawiera flagę przerwanie INT, natomiast bit 5 zawiera flagę przerwania PCINT. Kiedy przerwanie zostaje wygenerowanie odpowiednia flaga zmienia swój stan na 1, procesor przeskakuje do wektora przerwania po czym flaga zostaje znowu ustawiona na 0. Można jednak ręcznie wyczyścić flagę poprzez ustawienie jej wartości na 1 ( tak pisze w dokumentacji).

Teraz pora na przerwania zegarowe :

Tutaj sprawa robi się nieco skomplikowana (:

Generalnie zamierzamy wykorzystać Timer/licznik0 oraz comparator A.

Timer to takie urządzenie we wnętrzu mikrokontrolera które zlicza impulsy sygnału zegarowego. Aktualna ilość zliczonych impulsów jest przechowywana w liczniku timera. Komparator to urządzenie które porównuje wartość tego licznika z wartością zadaną a następnie kiedy obie wartości się zgadzają, wywołuje impuls.

Pierwszą rzeczą jaką musimy zrobić przed ustawieniem timera 1 jest jego zatrzymanie.

Należy to zrobić za pomocą rejestru GTCCR :

W pierwszym kroku należy ustawić timer w trybie synchronizacji ustawiając 1 na bicie 7 (TSM). W tym trybie timer jest zatrzymany co umożliwia bezpieczną konfigurację.

Następnie możemy ale nie musimy zresetować preskaler timera ustawiając jedynkę na bicie PSR0.

Krokiem następnym jest ustawienie komparatora A.

Jego ustawienia znajdują się w rejestrze TCCR0A :

Pierwszą rzeczą jest ustawienie czy sygnał z komparatora ma zmieniać stan pinu OC0A. Jest to możliwe ale ponieważ ten projekt nie wymaga tej opcji to należy ją wyłączyć. Tą opcję wykorzystałem w projekcie ” Grające Attiny”.

Kolejną rzeczą jest ustawienie trybu pracy timera. Może on być użyty do wielu rzeczy, np. do generowania sygnału PWM. Jednak w tym projekcie zostanie wykorzystany  tryb pracy CTC w którym wartość licznika zostaje wyzerowana w momencie wygenerowania sygnału przez komparator.

Za ustawianie trybu pracy odpowiadają bity WGM00 WGM01 i WGM02. Znajdują się one w rejestrach TCCR0A i TCCR0B.

Aby ustawić tryb CTC trzeba ustawić 1 na bicie WGM01 w rejestrze TCCR0A. Pozostałe bity domyślnie mają wartość 0 więc nie trzeba ich ustawiać.

Następnym krokiem jest ustawienie preskalera sygnału zegarowego dla timera.

Preskaler to takie urządzenie które zmienia częstotliwość sygnału zegarowego. Jest ona dzielona przez pewną liczbę.

Za ustawienie preskalera odpowiadają bity CS0, CS1 i CS2 w rejestrze TCCR0B :

Domyślna wartość to 000 co powoduje, że timer nie działa. Wartość ta jest również przywracana w razie ustawienia bitu PSR0 na 1 w rejestrze GTCCR.

Tabelka możliwych ustawień preskalera :

Istnieje również opcja dostarczenia sygnału zegarowego z zewnątrz przez pin T0. Ta funkcja działa nawet jeśli pin jest ustawiony jako wyjście.

Kolejna rzecz jaką musimy zrobić to ustawić wartość do której której komparator porównuje stan licznika timera (licznik ma 8 bitów i znajduje się w rejestrze TCNT0).

Wartość ta jest przechowywana w 8 bitowym rejestrze OCR0A.

Rejestr ten przechowuje liczbę do której jest porównywany stan licznika. Ponieważ liczba ta jest 8 bitowa więc można ustawić wartości od 0 do 255 (zmienna typu unsigned char).

Przedostatnią rzeczą jaką musimy zrobić jest włączenie przerwań z comparatora A za pomocą rejestru TIMSK.

Aby uruchomić przerwania z komparatora A, należy ustawić OCIE0A na 1.

Na końcu musimy jeszcze wyczyścić bit TSM w rejestrze GTCCR który ustawiliśmy na początku a następnie włączyć przerwania w ogóle za pomocą rejestru SREG (ostatnia rzecz po konfiguracji wszystkich przerwań).

I to wszystko (:

Pora na finalny program (opatrzony komentarzami) :

Na tym kończy się część pierwsza.

Artykuł ten stał się nieco przydługi, więc aby ułatwić edycję / czytanie, postanowiłem go rozbić na dwie części.

Link do części drugiej :

5 prostych projektów AVR w C część 2

Ocena: 5/5 (głosów: 11)

Podobne posty

14 komentarzy do “5 prostych projektów AVR w C część 1

  • Cześć, bardzo fajny artykuł. Wkradł Ci się błąd podczas dodawania OR-a. 1+1 nie daje 1 tylko 0 przeniesienia 1 na bit starszy.

    Odpowiedz
  • U mnie na Attiny 2313 A na pinach PB3 I PB4 dioda nie miga na poniższym kodzie z artykułu.
    #include // biblioteka io.h
    #include //biblioteka delay.h ( opóźnienie)
    int main(void)
    {
    DDRB=0b00000001;// ustawienie pinu jako wyjście
    while(1)
    {
    PORTB=0b00000001;// ustawienie stanu wysokiego na pinie
    _delay_ms(4000);// czekanie 4sekundy
    PORTB=0b00000000;// ustawienie stanu niskiego na pinie
    _delay_ms(4000);// czekanie 4 sekundy
    }
    return 0;
    }

    Odpowiedz
    • W którym miejscu? PORTB to rejestr całego portu B. I tak:
      PORTB=0b10000000 zapala diodę na PB7
      PORTB=0b01000000 zapala diodę na PB6

      PORTB=0b00000001 zapala diodę na PB0 (pomyliłem się wcześniej, PB0, nie . Ale to nie jest ani PB3 ani PB4).
      Jeżeli chcesz migać na zmianę diodami na PB3 i PB4 musisz zmienić 0b00000001 na 0b00010000 oraz 0b00000000 na 0b00001000

      Odpowiedz
    • A na jaki uC to potrzebujesz ? Druga sprawa jeśli podajesz wypełnienie 50% tzn że potrzebujesz tez PWM … to sprawa jest prosta ….. skonfigurować timer w tryb CTC…. w przerwaniach timera zrobic programowy pwm oraz wlasna funkcje odliczania czasu i to byłoby tyle :)
      Kiedyś już coś takiego robiłem bodajże na attiny2313, jak znajdę kod to wrzucę.

      Odpowiedz
  • Potrzebuję na Attiny 2313A,Atmega 8A ,z podłączonym kwarcem zegarkowym 32768 kHz.
    Czyli czysto sprzętowo, w CTC , PWM,nie da się osiągnąć 64/64 sekund ?.
    Może być w przedziale,od 60 /60sek do 70/70 sekund.
    Jak mam kod w j.C to jak mogę zrobić jego rozwinięcie w ASM Assembler,jakim programem w jaki sposób,czy AVR 5.1,4.18 da radę,jak ?
    Robiłam tymi programami translacje plików .HEX w j.C ,do uzyskania kodów języka Assembler.

    Odpowiedz

Odpowiedz

anuluj

Masz uwagi?